aboutsummaryrefslogtreecommitdiffstats
path: root/rust/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/main.rs')
-rw-r--r--rust/src/main.rs260
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("-")
+}