diff options
Diffstat (limited to 'rust/src/main.rs')
-rw-r--r-- | rust/src/main.rs | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/rust/src/main.rs b/rust/src/main.rs new file mode 100644 index 0000000..3873850 --- /dev/null +++ b/rust/src/main.rs @@ -0,0 +1,260 @@ +use clap::{value_parser, Arg, ArgAction, Command}; +use rand::{seq::SliceRandom, Rng}; +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::io::BufReader; +use std::path::Path; +use std::process; +use terminal_size::{terminal_size, Width}; + +const MAX: u8 = 255; +const RANGE_MAX: u8 = 42; +const RANGE_MIN: u8 = 8; +const PASS_WORDS: u8 = 8; + +const LOWER: &str = "abcdefghijklmnopqrstuvwxyz"; +const UPPER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const DIGIT: &str = "0123456789"; +const OTHER: &str = "!$%&#/()=?+-_,.;:<>[]{}|@*"; +const EXTRA: &str = "-_"; + +const DEF_LENGTH: u8 = 0; +const DEF_NUMBER: u8 = 10; +const DEF_WORDLIST: &str = "/usr/share/dict/words"; + +struct CommonPrintData<'a> { + length: u8, + alpha: &'a Vec<char>, + alnum: &'a Vec<char>, + printlen: u8, + col_width: u8, + col_num: u16, +} + +fn main() { + let lower = LOWER.chars().collect::<Vec<_>>(); + let upper = UPPER.chars().collect::<Vec<_>>(); + let digit = DIGIT.chars().collect::<Vec<_>>(); + let other = OTHER.chars().collect::<Vec<_>>(); + let extra = EXTRA.chars().collect::<Vec<_>>(); + let alpha = [&lower[..], &upper[..]].concat(); + let alnum = [&alpha[..], &digit[..]].concat(); + let every = [&alnum[..], &other[..]].concat(); + + let mut normal = [&alnum[..], &extra[..]].concat(); + let mut special = every; + + let mut length = DEF_LENGTH; + let mut number = DEF_NUMBER; + + let mut wordlist = DEF_WORDLIST.to_string(); + + // + // env + // Deal with env before cli-args, because cli-args take precedence. + // + let env_length = env::var("MAKEPASS_LENGTH").unwrap_or_default(); + let env_number = env::var("MAKEPASS_NUMBER").unwrap_or_default(); + let env_wordlist = env::var("MAKEPASS_WORDLIST").unwrap_or_default(); + let env_normal = env::var("MAKEPASS_NORMAL").unwrap_or_default(); + let env_special = env::var("MAKEPASS_SPECIAL").unwrap_or_default(); + + if !env_length.is_empty() { + length = env_length.parse().unwrap_or_else(|_| { + eprintln!( + "Error: MAKEPASS_LENGTH is not a valid number. Valid numbers are between 0 and 255" + ); + process::exit(1); + }); + } + if !env_number.is_empty() { + match env_number.parse::<u8>() { + Ok(n) if n >= 1 => number = n, + _ => { + eprintln!("Error: MAKEPASS_NUMBER is not a valid number. Valid numbers are between 1 and 255"); + process::exit(1); + } + } + } + if !env_wordlist.is_empty() { + wordlist = env_wordlist; + } + if !env_normal.is_empty() { + normal = env_number.chars().collect::<Vec<_>>(); + } + if !env_special.is_empty() { + special = env_special.chars().collect::<Vec<_>>(); + } + + // + // Args + // + + let opts = Command::new("makepass") + .arg( + Arg::new("length_flag") + .short('l') + .value_name("length") + .help(format!("Length of passwords (0..={MAX}). 0 = random.")) + .value_parser(value_parser!(u8).range(0..)), + ) + .arg( + Arg::new("number") + .short('n') + .help(format!("Number of passwords (1..={MAX}). [default: 10]")) + .value_parser(value_parser!(u8).range(1..)), + ) + .arg( + Arg::new("printlen") + .short('p') + .help("Print length") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("length") + .help(format!( + "Length of passwords (0..={MAX}). 0 = random length." + )) + .value_parser(value_parser!(u8).range(0..)), + ) + .get_matches(); + + match opts.get_one::<u8>("length_flag") { + Some(n) => length = *n, + _ => (), + } + match opts.get_one::<u8>("number") { + Some(n) => number = *n, + _ => (), + } + match opts.get_one::<u8>("length") { + Some(n) => length = *n, + _ => (), + } + let printbool = if opts.get_flag("printlen") { + true + } else { + false + }; + + // + // Other logic + // + let printlen: u8 = if printbool && length < 100 { + 3 + } else if printbool { + 4 + } else { + 0 + }; + + let size = terminal_size(); + let term_width: u16 = match size { + Some((Width(w), _)) => w, + None => 1, + }; + + let col_width: u8 = match length { + 0 => RANGE_MAX + 2, + _ => length + 2, + }; + + let col_num: u16 = match term_width / (col_width + printlen) as u16 { + 0 => 1, + n => n, + }; + + let data = CommonPrintData { + length: length, + alpha: &alpha, + alnum: &alnum, + printlen: printlen, + col_width: col_width, + col_num: col_num, + }; + + // + // Do the dirty! + // + + print_columns("Normal passwords", number, &normal, &data); + + println!(""); + + print_columns( + "Passwords with special characters", + number / 3 * 2 + 1, + &special, + &data, + ); + + if Path::new(&wordlist).exists() { + println!(""); + println!("Passphrases:"); + let words = read_file(&wordlist); + + for _ in 0..number / 2 { + println!("{}", passphrase(&words)); + } + } +} + +fn print_columns(title: &str, num: u8, chars: &Vec<char>, data: &CommonPrintData) { + let mut strings: Vec<String> = Vec::new(); + for _ in 0..num { + strings.push(randstring(data.length, chars, data.alpha, data.alnum)); + } + + println!("{}:", title); + + let mut i: u16 = 0; + for s in &strings { + i = i + 1; + + if data.printlen > 0 { + print!("{1:00$} ", (data.printlen - 1) as usize, s.len()); + } + print!("{1:<0$}", data.col_width as usize, s); + if (i % data.col_num == 0) || (i == num as u16 && i % data.col_num > 0) { + println!(""); + } + } +} + +fn randstring(len: u8, chars: &Vec<char>, alpha: &Vec<char>, alnum: &Vec<char>) -> String { + let mut rng = rand::thread_rng(); + let length = if len == 0 { + rng.gen_range(RANGE_MIN..RANGE_MAX) + } else { + len + }; + + let mut s = String::with_capacity(length as usize); + for i in 0..length { + if i == 0 { + s.push(*alpha.choose(&mut rng).unwrap()); + } else if i == length - 1 { + s.push(*alnum.choose(&mut rng).unwrap()); + } else { + s.push(*chars.choose(&mut rng).unwrap()); + } + } + s +} + +fn read_file(filename: &str) -> Vec<String> { + let file = File::open(filename).unwrap(); + let reader = BufReader::new(file); + + reader.lines().map(|l| l.unwrap()).collect() +} + +fn passphrase(wordlist: &Vec<String>) -> String { + let mut rng = rand::thread_rng(); + (0..PASS_WORDS) + .map(|_| wordlist.choose(&mut rng).unwrap()) + .cloned() + .collect::<Vec<_>>() + .join("-") +} |