diff --git a/.gitignore b/.gitignore index 3bb8c9d..36fbfa7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ aerc/accounts.conf # Bin bin/sway_tree +bin/.gitignore # Mpv mpv/watch_later diff --git a/bin/random_password.sh b/bin/random_password.sh deleted file mode 100755 index 859c79e..0000000 --- a/bin/random_password.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -print_help() { - cat < - -Examples: - $(basename "$0") 20 | wl-copy - $(basename "$0") 400 > key_file - -Special characters: /[;-]/ - -Length must be at least $MIN_PASSWORD_LENGTH. Passwords are generated until one of each a -lowercase, uppercase, digit, and special character are in the password. -Both special characters are required for passwords >=$LONG_PASSWORD_LENGTH characters long. -Really long passwords have at least 2 of each special character. -HELP -} - -shopt -s lastpipe -declare -ri LONG_PASSWORD_LENGTH=15 MIN_PASSWORD_LENGTH=6 -declare -gri LENGTH="$1" -declare password="" - -generate_password() { - dd if=/dev/urandom bs=3 count="$(( 10 * LENGTH ))" 2>/dev/null \ - | base64 -w0 \ - | tr '/' '-' \ - | tr '+' ';' \ - | cut -c "1-$LENGTH" \ - | read -r password -} - -contains_characterset() { - local p="$password" - if [[ $LENGTH -ge $(( 2*LONG_PASSWORD_LENGTH )) ]]; then - [[ $p =~ [A-Z] && $p =~ [a-z] && $p =~ [0-9] && $p =~ \;.+\; && $p =~ -.+- ]] - elif [[ $LENGTH -ge $LONG_PASSWORD_LENGTH ]]; then - [[ $p =~ [A-Z] && $p =~ [a-z] && $p =~ [0-9] && $p =~ \; && $p =~ - ]] - else - [[ $p =~ [A-Z] && $p =~ [a-z] && $p =~ [0-9] ]] && [[ $p =~ \; || $p =~ - ]] - fi -} - -if [[ "$1" =~ ^[0-9]+$ && $1 -ge $MIN_PASSWORD_LENGTH ]]; then - while ! contains_characterset; do - generate_password "$1" - done - - echo "$password" -else - print_help - exit 1 -fi diff --git a/bin/rewritten_in_rust/Cargo.toml b/bin/rewritten_in_rust/Cargo.toml index 6f66a18..756a232 100644 --- a/bin/rewritten_in_rust/Cargo.toml +++ b/bin/rewritten_in_rust/Cargo.toml @@ -18,8 +18,13 @@ path = "src/sway_tree.rs" name = "rename_for_unix" path = "src/rename_for_unix.rs" +[[bin]] +name = "random_password" +path = "src/random_password.rs" + [dependencies] -clap = { version = "4", features = ["derive"] } chrono = "0.4" +clap = { version = "4", features = ["derive"] } +rand = "0.8" regex = "1" termion = "2" diff --git a/bin/rewritten_in_rust/src/random_password.rs b/bin/rewritten_in_rust/src/random_password.rs new file mode 100644 index 0000000..c111aea --- /dev/null +++ b/bin/rewritten_in_rust/src/random_password.rs @@ -0,0 +1,116 @@ +use clap::Parser; +use rand::{RngCore, rngs::OsRng}; +use std::io::Write; +use std::process::{Command, Stdio}; + +#[derive(Parser, Debug)] +#[command(name = "random_password", author, version = "0.1.0")] +/// Generates a random password, that's likely to work for most signups +struct Args { + /// Copy to clipboard instead of printing to stdout + #[arg(short = 'c', long = "clipboard")] + is_clipboard: bool, + /// Length of the generated password + #[arg(value_name = "LENGTH")] + pass_length: u8, +} + +#[derive(Default)] +struct CharDistro { + uppercase: usize, + lowercase: usize, + numerical: usize, + special: usize, +} + +impl CharDistro { + pub fn from(s: &str) -> Self { + let mut d = Self::default(); + + for c in s.chars() { + if c.is_uppercase() { + d.uppercase += 1; + } else if c.is_lowercase() { + d.lowercase += 1; + } else if c.is_digit(10) { + d.numerical += 1; + } else { + d.special += 1; + } + } + + d + } + + pub fn all_nonzero(&self) -> bool { + self.uppercase > 0 && self.lowercase > 0 && self.numerical > 0 && self.special > 0 + } +} + +fn main() { + let args = Args::parse(); + let mut pass; + let mut trimmed; + + let mut key = [0_u8; 256]; + + loop { + OsRng.fill_bytes(&mut key); + pass = encode_to_password(&key); + trimmed = &pass[..args.pass_length as usize]; + + let distro = CharDistro::from(trimmed); + let is_enough_special = distro.special >= (args.pass_length / 10 + 1) as usize; + + if distro.all_nonzero() && is_enough_special && has_two_special(trimmed) { + break; + } + } + + if args.is_clipboard { + let pbcopy = Command::new("wl-copy") + .stdin(Stdio::piped()) + .spawn() + .expect("Failed to start wl-copy"); + + pbcopy.stdin.unwrap().write(trimmed.as_bytes()).unwrap(); + } else { + println!("{}", trimmed); + } +} + +fn encode_to_password(key: &[u8; 256]) -> String { + let mut pass = String::new(); + + for bits in key { + let c = bits & 0o77; + + if c <= 25 { + pass.push((c + 65) as char); + } else if c <= 51 { + pass.push((c + 97 - 26) as char); + } else if c <= 61 { + pass.push((c + 48 - 52) as char); + } else if c == 62 { + pass.push('-'); + } else { + pass.push(';'); + } + } + + pass +} + +fn has_two_special(s: &str) -> bool { + let mut special_1 = 'a'; + + for c in s.chars() { + if !c.is_ascii_alphanumeric() && special_1 == 'a'{ + special_1 = c; + } else if !c.is_ascii_alphanumeric() { + return true; + } + } + + false +}