1 use rand::seq::SliceRandom;
2 use std::path::{Path};
3 use std::fs::File;
4 use std::io;
5 use std::io::{BufRead, BufReader};
6 use clap::{Arg,App};
7 use regex::Regex;
8
9 fn no_word_file() -> io::Result<String> {
10
11 // Fall back to this if we cant find the word file
12 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGIJKLMNOPQRSTUVWXYZ0123456789!@$%^&*()-+".chars().collect();
13 let mut password: String = String::new();
14 for _x in 0..63 {
15 let random_char = chars.choose(&mut rand::thread_rng());
16 password.push_str(&random_char.unwrap().to_string());
17 }
18
19 Ok(password)
20
21 }
22
23 fn character_weave(passphrase: String) -> io::Result<String> {
24
25 /*
26 * a - @
27 * i - !
28 * o - 0
29 * s - $
30 * e - 3
31 */
32 let mut new_passphrase: String = String::new();
33 for c in passphrase.chars() {
34 // Probably a better way to do this as opposed to a giant if / elsif
35 if c.to_string() == "a" {
36 new_passphrase.push_str(&"@".to_string());
37 } else if c.to_string() == "i" {
38 new_passphrase.push_str(&"!".to_string());
39 } else if c.to_string() == "o" {
40 new_passphrase.push_str(&"0".to_string());
41 } else if c.to_string() == "s" {
42 new_passphrase.push_str(&"$".to_string());
43 } else if c.to_string() == "e" {
44 new_passphrase.push_str(&"3".to_string());
45 } else {
46 new_passphrase.push_str(&c.to_string());
47 }
48 }
49
50 Ok(new_passphrase)
51
52 }
53
54 // This is stupid TODO
55 fn print_password(password: String) {
56
57 println!("{}",password);
58
59 }
60
61 fn read_phrase_file(word_file: String) -> io::Result<Vec<String>> {
62
63 if ! Path::new(&word_file).exists() {
64 println!("Could not find {}, generating a password and exiting", word_file);
65 print_password(no_word_file().unwrap());
66 std::process::exit(1);
67 }
68
69 let words_fh = File::open(word_file)?;
70 let mut words = vec![];
71 let re = Regex::new(r"^([0-9]{5})\s(.*)$").unwrap();
72 for line in BufReader::new(words_fh).lines() {
73 for re_capture in re.captures_iter(&line.unwrap()) {
74 // println!("Capture 1: {}, Capture 2: {}", &re_capture[1], &re_capture[2]);
75 // Only push word for now, need to figure out multidimentional arrays
76 words.push(re_capture[2].to_string());
77 }
78 }
79
80 Ok(words)
81
82 }
83
84 fn main() {
85
86 // This variable is used as the default word file path, see Arg::with_name("words_file")
87 // This is probably wrong/bad, and limits the functionality to Linux only
88 let mut default_words_path = dirs::home_dir().unwrap().to_str().unwrap().to_string();
89 default_words_path.push_str(&"/.local/bin/eff_large_wordlist.txt".to_string());
90
91 let args = App::new("swmkp")
92 .version("0.1")
93 .about("My pw gen. Based on EFF passphrase guidelines\nhttps://www.eff.org/dice")
94 .arg(Arg::with_name("words-file")
95 .short("f")
96 .long("words-file")
97 .required(true)
98 .takes_value(true)
99 .default_value(&default_words_path)
100 .help("Path to passphrase file, expects file provided by EFF\nSee:\nhttps://www.eff.org/files/2016/07/18/eff_large_wordlist.txt\nhttps://eff.org/files/2016/09/08/eff_short_wordlist_1.txt\nhttps://eff.org/files/2016/09/08/eff_short_wordlist_2_0.txt\n"))
101 .arg(Arg::with_name("length")
102 .short("l")
103 .long("length")
104 .required(false)
105 .takes_value(true)
106 .default_value("5")
107 .help("How many words to use"))
108 .arg(Arg::with_name("delimiter")
109 .short("d")
110 .long("delimiter")
111 .required(false)
112 .takes_value(true)
113 .default_value("-")
114 .help("What word delimiter to use"))
115 .arg(Arg::with_name("with-characters")
116 .short("w")
117 .long("with-characters")
118 .required(false)
119 .takes_value(false)
120 .help("Replaces some letters with special characters"))
121 .arg(Arg::with_name("password")
122 .short("p")
123 .long("password")
124 .required(false)
125 .takes_value(false)
126 .help("Generate a 64 character password, as opposed to a passphrase\nFallback here if no passphrase file provided/found"))
127 .get_matches();
128
129
130 if args.is_present("password") {
131 print_password(no_word_file().unwrap());
132 std::process::exit(0);
133 }
134
135 let roll_count: usize = args.value_of("length").unwrap().parse().unwrap();
136 let delim: String = args.value_of("delimiter").unwrap().to_string();
137
138 let words = read_phrase_file(args.value_of("words-file").unwrap().to_string()).unwrap();
139
140 let mut passphrase: String = String::new();
141 for x in 0..roll_count {
142 let random_word = words.choose(&mut rand::thread_rng());
143 if x == ( roll_count - 1 ) {
144 passphrase.push_str(random_word.unwrap());
145 } else {
146 passphrase.push_str(random_word.unwrap());
147 passphrase.push_str(&delim);
148 }
149 }
150
151 if args.is_present("with-characters") {
152 let new_passphrase = character_weave(passphrase).unwrap();
153 println!("{}",new_passphrase);
154 } else {
155 println!("{}",passphrase);
156 }
157 }