pwtool/
lib.rs

1use base32::*;
2use base64::prelude::*;
3use chrono::Local;
4use hmac::{Hmac, Mac};
5use kpdb::{CompositeKey, Database, Entry};
6use pbkdf2::pbkdf2_hmac;
7use pwhash::*;
8use rand::distributions::Alphanumeric;
9use rand::thread_rng;
10use rand::Rng;
11use sha1::{Digest, Sha1};
12use sha2::Sha256;
13use sha2::Sha512;
14use std::fs::File;
15use std::io::BufRead;
16use std::io::BufReader;
17use std::thread;
18use std::time::Duration;
19use std::time::SystemTime;
20use std::time::UNIX_EPOCH;
21
22#[derive(Clone)]
23pub enum TotpAlgo {
24    Sha1,
25    Sha256,
26    Sha512,
27}
28
29#[derive(Clone)]
30pub struct Config {
31    pub number: Option<u32>,
32    pub len: Option<u32>,
33    pub pw_type: Option<u32>,
34    pub word_list: Option<String>,
35    pub words: Option<Vec<String>>,
36    pub username: Option<String>,
37    pub database: Option<String>,
38    pub create_database: bool,
39    pub spaces: bool,
40    pub digest: Option<String>,
41    pub servername: Option<String>,
42    pub keepassdb: Option<String>,
43    pub keepassfetch: bool,
44    pub keepassphrase: Option<String>,
45    pub totp_key: Option<String>,
46    pub totp_step: Option<u64>,
47    pub totp_algo: TotpAlgo,
48    pub totp_seconds: Option<u64>,
49    pub password: Option<String>,
50    pub loopdelay: Option<u32>,
51    pub salt: Option<String>,
52    pub wpinst_fmt: bool,
53    pub wpuser_fmt: bool,
54}
55
56impl Config {
57    pub fn new() -> Config {
58        Config {
59            len: None,
60            pw_type: None,
61            number: None,
62            word_list: None,
63            words: None,
64            username: None,
65            database: None,
66            create_database: false,
67            spaces: true,
68            digest: None,
69            servername: None,
70            keepassdb: None,
71            keepassfetch: false,
72            keepassphrase: None,
73            totp_key: None,
74            totp_step: Some(30),
75            totp_seconds: None,
76            password: None,
77            totp_algo: TotpAlgo::Sha1,
78            loopdelay: None,
79            wpinst_fmt: false,
80            wpuser_fmt: false,
81            salt: None,
82        }
83    }
84
85    pub fn digest(&self) -> String {
86        if self.digest.is_none() {
87            return "sha256".to_string();
88        }
89        self.digest.as_ref().unwrap().to_string()
90    }
91
92    pub fn totp_seconds(&self) -> u64 {
93        if self.totp_seconds.is_some() {
94            self.totp_seconds.unwrap()
95        } else {
96            SystemTime::now()
97                .duration_since(UNIX_EPOCH)
98                .unwrap()
99                .as_secs()
100        }
101    }
102}
103
104impl Default for Config {
105    fn default() -> Self {
106        Self::new()
107    }
108}
109
110pub enum PwClass {
111    Num = 1 << 0,
112    Alpha = 1 << 1,
113    Ext = 1 << 2,
114    Lower = 1 << 3,
115    Upper = 1 << 4,
116}
117
118pub fn valid_word(s: &str, word_set: Option<u32>) -> bool {
119    if let Some(word_set) = word_set {
120        let mut valid = false;
121
122        if word_set & PwClass::Num as u32 != 0 {
123            for j in s.chars() {
124                if j.is_ascii_digit() {
125                    valid = true;
126                }
127            }
128        }
129
130        if word_set & PwClass::Lower as u32 != 0 {
131            for j in s.chars() {
132                if j.is_ascii_lowercase() {
133                    valid = true;
134                }
135            }
136        }
137
138        if word_set & PwClass::Upper as u32 != 0 {
139            for j in s.chars() {
140                if j.is_ascii_uppercase() {
141                    valid = true;
142                }
143            }
144        }
145
146        if word_set & PwClass::Ext as u32 != 0 {
147            for j in s.chars() {
148                if !j.is_ascii_digit() && !j.is_ascii_lowercase() && !j.is_ascii_uppercase() {
149                    valid = true;
150                }
151            }
152        }
153        return valid;
154    };
155
156    true
157}
158
159pub fn prng_string(c: &Config) -> String {
160    let mut rng = rand::thread_rng();
161
162    if c.word_list.is_some() {
163        let mut phrase = vec![];
164
165        let words_len = c.words.as_ref().unwrap().len();
166        if c.words.as_ref().unwrap().is_empty() {
167            eprintln!("no words to process");
168            std::process::exit(1);
169        }
170
171        for _j in 0..c.len.unwrap_or(15) {
172            phrase.push(
173                c.words.as_ref().unwrap()[rng.gen_range(0..words_len)]
174                    .clone()
175                    .to_string(),
176            );
177        }
178
179        let mut phrase = phrase.join(if c.spaces { " " } else { "" });
180
181        if c.pw_type.is_some() {
182            let set = c.pw_type.as_ref().unwrap();
183            if set & PwClass::Lower as u32 != 0 {
184                phrase = phrase.to_lowercase();
185            }
186
187            if set & PwClass::Upper as u32 != 0 {
188                phrase = phrase.to_uppercase();
189            }
190        }
191
192        return phrase;
193    }
194
195    let mut set = c.pw_type;
196    if set.is_none() {
197        set = Some(
198            PwClass::Num as u32
199                | PwClass::Alpha as u32
200                | PwClass::Lower as u32
201                | PwClass::Upper as u32,
202        );
203    }
204
205    let set = set.unwrap();
206    let mut chars = "".to_string();
207    if set & PwClass::Num as u32 != 0 {
208        chars += "0123456789";
209    };
210
211    if set & PwClass::Alpha as u32 != 0 {
212        chars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
213    };
214
215    if set & PwClass::Lower as u32 != 0 {
216        chars += "abcdefghijklmnopqrstuvwxyz";
217    };
218
219    if set & PwClass::Upper as u32 != 0 {
220        chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
221    };
222
223    if set & PwClass::Ext as u32 != 0 {
224        chars += r#"!"$%^&*()-={}[]:;@'<>,./\|"#;
225    };
226
227    let one_char = || chars.chars().nth(rng.gen_range(0..chars.len())).unwrap();
228    std::iter::repeat_with(one_char)
229        .take(c.len.unwrap_or(15) as usize)
230        .collect()
231}
232
233pub fn gen_salt_str(chars: usize) -> String {
234    let rng = thread_rng();
235    rng.sample_iter(&Alphanumeric)
236        .take(chars)
237        .map(|x| x as char)
238        .collect()
239}
240
241pub fn postgres_pass(pw: &str) -> String {
242    let salt_size = 16;
243    let iterations = 4096;
244
245    let salt = gen_salt_str(salt_size);
246    let mut key = [0u8; 32];
247    pbkdf2_hmac::<Sha256>(pw.as_bytes(), salt.as_bytes(), iterations, &mut key);
248
249    let mut client_key = Hmac::<Sha256>::new_from_slice(&key).unwrap();
250    client_key.update(b"Client Key");
251    let client_result = client_key.finalize();
252
253    let mut server_key = Hmac::<Sha256>::new_from_slice(&key).unwrap();
254    server_key.update(b"Server Key");
255    let server_result = server_key.finalize();
256
257    let mut stored_key = Sha256::new();
258    stored_key.update(client_result.clone().into_bytes());
259    let stored_key = stored_key.finalize();
260
261    let bstored_key = BASE64_STANDARD.encode(stored_key);
262    let bsalt = BASE64_STANDARD.encode(salt);
263    let bserver_key = BASE64_STANDARD.encode(server_result.clone().into_bytes());
264
265    format!("SCRAM-SHA-256${iterations}:{bsalt}${bstored_key}:{bserver_key}")
266}
267
268pub fn mysql_pass(pw: &str) -> String {
269    let mut h = Sha1::new();
270    h.update(pw);
271    let r = h.finalize();
272    let mut h = Sha1::new();
273    h.update(r);
274    let r = h.finalize();
275    format!("*{:x}", r).to_string()
276}
277
278// https://github.com/ansible-collections/community.mysql/issues/621#issuecomment-2051837825
279pub fn to64(mut v: i32, n: i32) -> String {
280    let mut index64: Vec<u8> = vec![];
281    index64.push(b'.');
282    index64.push(b'/');
283    for i in 48..58 {
284        index64.push(i);
285    }
286    for i in 65..91 {
287        index64.push(i);
288    }
289    for i in 97..123 {
290        index64.push(i);
291    }
292
293    let mut result = "".to_string(); // make a vec of u8
294    let mut nn = n;
295    while nn > 0 {
296        nn -= 1;
297        result.push(index64[v as usize & 0x3f] as char);
298        v >>= 6;
299    }
300
301    String::from_utf8_lossy(result.as_bytes()).to_string()
302}
303
304pub fn mysql_caching_sha2_pass_salt(pw: &str, salt: &str, iterations: i32) -> String {
305    let num_bytes = 32;
306
307    let key = pw.as_bytes();
308    let salt = salt.as_bytes();
309    let mut d1: Vec<u8> = vec![];
310    d1.extend(key);
311    d1.extend(salt);
312    d1.extend(key);
313
314    let mut stored_key = Sha256::new();
315    stored_key.update(d1);
316    let digest_b = stored_key.clone().finalize();
317
318    let mut tmp: Vec<u8> = vec![];
319    tmp.extend(key);
320    tmp.extend(salt);
321
322    for i in (0..=key.len()).rev().step_by(num_bytes) {
323        tmp.extend(if i > num_bytes {
324            digest_b.as_slice()
325        } else {
326            &digest_b[0..i]
327        });
328    }
329
330    let mut i = key.len();
331    while i > 0 {
332        tmp.extend(if i & 1 != 0 {
333            digest_b.as_slice()
334        } else {
335            key as &[u8]
336        });
337        i >>= 1;
338    }
339
340    let mut digest_a_hash = Sha256::new();
341    digest_a_hash.update(tmp);
342    let digest_a = digest_a_hash.clone().finalize();
343
344    let mut tmp: Vec<u8> = vec![];
345    for _ in 0..key.len() {
346        tmp.extend(key);
347    }
348
349    let mut digest_dp_hash = Sha256::new();
350    digest_dp_hash.update(tmp);
351    let digest_dp = digest_dp_hash.finalize();
352
353    let mut byte_sequence_p: Vec<u8> = vec![];
354    for i in (0..=key.len()).rev().step_by(num_bytes) {
355        byte_sequence_p.extend(if i > num_bytes {
356            digest_dp.as_slice()
357        } else {
358            &digest_dp[0..i]
359        });
360    }
361
362    let mut tmp: Vec<u8> = vec![];
363    let til = 16 + (digest_a[0] as i32);
364
365    for _ in 0..til {
366        tmp.extend(salt);
367    }
368
369    let mut digest_ds_hash = Sha256::new();
370    digest_ds_hash.update(tmp);
371    let digest_ds = digest_ds_hash.finalize();
372
373    let mut byte_sequence_s: Vec<u8> = vec![];
374    for i in (0..=salt.len()).rev().step_by(num_bytes) {
375        byte_sequence_s.extend(if i > num_bytes {
376            digest_ds.as_slice()
377        } else {
378            &digest_ds[0..i]
379        });
380    }
381
382    let mut digest_c = digest_a;
383
384    for i in 0..iterations * 1000 {
385        tmp = if i & 1 > 0 {
386            byte_sequence_p.clone()
387        } else {
388            (&digest_c as &[u8]).to_vec()
389        };
390        if i % 3 > 0 {
391            tmp.extend(byte_sequence_s.clone());
392        }
393        if i % 7 > 0 {
394            tmp.extend(byte_sequence_p.clone());
395        }
396        tmp.extend(if i & 1 > 0 {
397            digest_c.as_slice()
398        } else {
399            &byte_sequence_p as &[u8]
400        });
401
402        let mut digest_c_hash = Sha256::new();
403        digest_c_hash.update(tmp);
404        digest_c = digest_c_hash.finalize();
405    }
406
407    let inc1 = 10;
408    let inc2 = 21;
409    let m = 30;
410    let end = 0;
411
412    let mut i = 0;
413    let mut tmp = "".to_string();
414
415    loop {
416        tmp.push_str(&to64(
417            ((digest_c[i] as i32) << 16)
418                | (((digest_c[(i + inc1) % m]) as i32) << 8)
419                | (digest_c[(i + inc1 * 2) % m]) as i32,
420            4,
421        ));
422        i = (i + inc2) % m;
423
424        if i == end {
425            break;
426        }
427    }
428
429    tmp.push_str(&to64(
430        ((digest_c[31] as i32) << 8) | (digest_c[30] as i32),
431        3,
432    ));
433
434    tmp
435}
436
437pub fn mysql_caching_sha2_pass(pw: &str) -> String {
438    let salt_size = 20;
439    let salt = gen_salt_str(salt_size);
440    let count = 5;
441
442    let st = mysql_caching_sha2_pass_salt(pw, &salt, count);
443    format!(
444        "0x{}",
445        hex::encode(format!("$A${count:>03}${salt}{st}")).to_uppercase()
446    )
447}
448
449pub fn useradd_format(c: &Config) -> String {
450    let digest = c.digest();
451    format!("useradd -m -s /bin/bash -p '%{{{digest}}}' %{{username}}").to_string()
452}
453
454pub fn usermod_format(c: &Config) -> String {
455    let digest = c.digest();
456    format!("usermod -p '%{{{digest}}}' %{{username}}").to_string()
457}
458
459pub fn mysql_format() -> String {
460    "grant all privileges on %{database}.* to %{username}@'%' identified with mysql_native_password as '%{mysql}';".to_string()
461}
462
463pub fn mysql_user_format() -> String {
464    "create user %{username}@'%'; alter user %{username}@'%' identified with 'caching_sha2_password' as %{mysql_caching_sha2}; grant all privileges on %{database}.* to %{username}@'%';".to_string()
465}
466
467pub fn postgres_format() -> String {
468    "create user %{username} password '%{postgres}';".to_string()
469}
470
471pub fn htauth_format(c: &Config) -> String {
472    let digest = if c.digest.is_none() {
473        "md5".to_string()
474    } else {
475        c.digest()
476    };
477    format!("%{{username}}:%{{{digest}}}").to_string()
478}
479
480pub fn wpinst_format() -> String {
481    "wp core install --url=https://%{servername}/ --title=%{servername} --admin_email=%{username}@%{servername} --admin_user=%{username} --admin_password=%{password} --skip-email".to_string()
482}
483
484pub fn wpuser_format() -> String {
485    "update wp_users set user_pass = '%{bcrypt}' where user_login = '%{username}'".to_string()
486}
487
488pub fn wpconfig_format() -> String {
489    "wp config create --dbname=%{database} --dbuser=%{username} --dbpass=%{password}".to_string()
490}
491
492fn test_crypt_result(a: &Result<String>, c: &Config) -> String {
493    match a {
494        Ok(x) => x.to_string(),
495        Err(x) => {
496            eprintln!("Invalid crypt {}: {}", c.salt.as_ref().unwrap(), x);
497            std::process::exit(1);
498        }
499    }
500}
501
502fn base32_format(pw: String) -> String {
503    base32::encode(Alphabet::Rfc4648Lower { padding: false }, &pw.into_bytes())
504}
505
506#[allow(deprecated)]
507pub fn process_format_string(format_string: &mut String, c: &Config, pw: &str) -> String {
508    if format_string.contains("%{userfmt}") {
509        *format_string = format_string.replace("%{userfmt}", &useradd_format(c).to_string());
510    }
511    if format_string.contains("%{usermodfmt}") {
512        *format_string = format_string.replace("%{usermodfmt}", &usermod_format(c).to_string());
513    }
514    if format_string.contains("%{mysqlfmt}") {
515        *format_string = format_string.replace("%{mysqlfmt}", &mysql_format().to_string());
516    }
517    if format_string.contains("%{mysqluserfmt}") {
518        *format_string = format_string.replace("%{mysqluserfmt}", &mysql_user_format().to_string());
519    }
520    if format_string.contains("%{pgfmt}") {
521        *format_string = format_string.replace("%{pgfmt}", &postgres_format().to_string());
522    }
523    if format_string.contains("%{htauthfmt}") {
524        *format_string = format_string.replace("%{htauthfmt}", &htauth_format(c).to_string());
525    }
526    if format_string.contains("%{wpconfigfmt}") {
527        *format_string = format_string.replace("%{wpconfigfmt}", &wpconfig_format().to_string());
528    }
529    if format_string.contains("%{wpuserfmt}") {
530        *format_string = format_string.replace("%{wpuserfmt}", &wpuser_format().to_string());
531    }
532    if format_string.contains("%{wpinstfmt}") {
533        *format_string = format_string.replace("%{wpinstfmt}", &wpinst_format().to_string());
534    }
535
536    if format_string.contains("%{totpfmt}") {
537        *format_string = format_string.replace(
538            "%{totpfmt}",
539            &("%{totp} [%{totpprogress}]".to_owned()
540                + if c.username.is_some() {
541                    " %{username}"
542                } else {
543                    ""
544                }),
545        );
546    }
547
548    if format_string.contains("%{base32}") {
549        *format_string =
550            format_string.replace("%{base32}", &base32_format(pw.to_string()).to_string());
551    }
552
553    if format_string.contains("%{md5}") {
554        let crypt = if c.salt.is_some() {
555            test_crypt_result(
556                &md5_crypt::hash_with(c.salt.as_ref().unwrap().as_str(), pw),
557                c,
558            )
559        } else {
560            md5_crypt::hash(pw).unwrap().to_string()
561        };
562        *format_string = format_string.replace("%{md5}", &crypt);
563    }
564    if format_string.contains("%{bcrypt}") {
565        let crypt = if c.salt.is_some() {
566            test_crypt_result(&bcrypt::hash_with(c.salt.as_ref().unwrap().as_str(), pw), c)
567        } else {
568            bcrypt::hash(pw).unwrap().to_string()
569        };
570        *format_string = format_string.replace("%{bcrypt}", &crypt);
571    }
572    if format_string.contains("%{des}") {
573        let crypt = if c.salt.is_some() {
574            test_crypt_result(
575                &unix_crypt::hash_with(c.salt.as_ref().unwrap().as_str(), pw),
576                c,
577            )
578        } else {
579            unix_crypt::hash(pw).unwrap().to_string()
580        };
581        *format_string = format_string.replace("%{des}", &crypt);
582    }
583
584    if format_string.contains("%{sha1}") {
585        let crypt = if c.salt.is_some() {
586            test_crypt_result(
587                &sha1_crypt::hash_with(c.salt.as_ref().unwrap().as_str(), pw),
588                c,
589            )
590        } else {
591            sha1_crypt::hash(pw).unwrap().to_string()
592        };
593        *format_string = format_string.replace("%{sha1}", &crypt);
594    }
595    if format_string.contains("%{sha256}") {
596        let crypt = if c.salt.is_some() {
597            test_crypt_result(
598                &sha256_crypt::hash_with(c.salt.as_ref().unwrap().as_str(), pw),
599                c,
600            )
601        } else {
602            sha256_crypt::hash(pw).unwrap().to_string()
603        };
604        *format_string = format_string.replace("%{sha256}", &crypt);
605    }
606    if format_string.contains("%{sha512}") {
607        let crypt = if c.salt.is_some() {
608            test_crypt_result(
609                &sha512_crypt::hash_with(c.salt.as_ref().unwrap().as_str(), pw),
610                c,
611            )
612        } else {
613            sha512_crypt::hash(pw).unwrap().to_string()
614        };
615        *format_string = format_string.replace("%{sha512}", &crypt);
616    }
617    if format_string.contains("%{password}") {
618        *format_string = format_string.replace("%{password}", pw);
619    }
620    if format_string.contains("%{username}") && c.username.is_some() {
621        *format_string = format_string.replace("%{username}", c.username.as_ref().unwrap());
622    }
623    if format_string.contains("%{database}") && c.database.is_some() {
624        *format_string = format_string.replace("%{database}", c.database.as_ref().unwrap());
625    }
626
627    if format_string.contains("%{mysql}") {
628        *format_string = format_string.replace("%{mysql}", &mysql_pass(pw).to_string());
629    }
630    if format_string.contains("%{mysql_caching_sha2}") {
631        *format_string = format_string.replace(
632            "%{mysql_caching_sha2}",
633            &mysql_caching_sha2_pass(pw).to_string(),
634        );
635    }
636
637    if format_string.contains("%{postgres}") {
638        *format_string = format_string.replace("%{postgres}", &postgres_pass(pw));
639    }
640    if format_string.contains("%{username}") && c.username.is_some() {
641        *format_string = format_string.replace("%{username}", c.username.as_ref().unwrap());
642    }
643    if format_string.contains("%{database}") && c.database.is_some() {
644        *format_string = format_string.replace("%{database}", c.database.as_ref().unwrap());
645    }
646
647    if format_string.contains("%{mysql}") {
648        *format_string = format_string.replace("%{mysql}", &mysql_pass(pw));
649    }
650
651    if format_string.contains("%{servername}") && c.servername.is_some() {
652        *format_string = format_string.replace("%{servername}", c.servername.as_ref().unwrap());
653    }
654
655    if format_string.contains("%{totp}") && c.totp_key.is_some() {
656        *format_string = format_string.replace("%{totp}", &totp_string(c));
657    }
658
659    if format_string.contains("%{totpsecs}") && c.totp_key.is_some() {
660        *format_string = format_string.replace("%{totpsecs}", &totp_secs_remaining(c).to_string());
661    }
662
663    if format_string.contains("%{totpsecsmax}") && c.totp_key.is_some() {
664        *format_string = format_string.replace("%{totpsecsmax}", &c.totp_step.unwrap().to_string());
665    }
666
667    if format_string.contains("%{totpprogress}") && c.totp_key.is_some() {
668        *format_string = format_string.replace("%{totpprogress}", &totp_progress(c));
669    }
670
671    if format_string.contains("%{totpalgo}") && c.totp_key.is_some() {
672        *format_string = format_string.replace(
673            "%{totpalgo}",
674            match c.totp_algo {
675                TotpAlgo::Sha1 => "sha1",
676                TotpAlgo::Sha256 => "sha256",
677                TotpAlgo::Sha512 => "sha512",
678            },
679        );
680    }
681
682    *format_string = format_string.replace(r#"\n"#, "\n");
683
684    format_string.to_string()
685}
686
687pub fn create_pg_database(c: &Config) -> String {
688    if c.create_database {
689        let db = c.database.as_ref().unwrap_or(&"".to_string()).to_string();
690        let username = c.username.as_ref().unwrap_or(&"".to_string()).to_string();
691        return format!(" create database {db}; alter database {db} owner to {username}; ")
692            .to_string();
693    }
694    "".to_string()
695}
696
697pub fn create_mysql_database(c: &Config) -> String {
698    if c.create_database {
699        return format!(
700            "create database {}; ",
701            c.database.as_ref().unwrap_or(&"".to_string())
702        )
703        .to_string();
704    }
705    "".to_string()
706}
707
708pub fn create_nginx_vhost() -> String {
709    let vhost = r#"
710server {
711    listen 80;
712    listen [::]:80;
713    server_name %{servername} www.%{servername};
714    return 301 https://$host$request_uri;
715}
716
717server {
718    listen 443 ssl;
719    listen [::]:443 ssl;
720
721    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
722    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
723    server_name %{servername} www.%{servername};
724
725    access_log /var/log/nginx/%{servername}_access.log;
726    error_log /var/log/nginx/%{servername}_error.log;
727
728    include /etc/nginx/snippets/snakeoil.conf;
729
730    location / {
731    }
732}
733"#;
734    vhost.to_string()
735}
736
737pub fn create_apache_vhost() -> String {
738    let vhost = r#"
739<VirtualHost *:80>
740    ServerName %{servername}
741    ServerAlias www.%{servername}
742
743    DocumentRoot /home/%{username}/public_html
744
745    ErrorLog ${APACHE_LOG_DIR}/%{servername}_error.log
746    CustomLog ${APACHE_LOG_DIR}/%{servername}_access.log combined
747
748    <Directory "/home/%{username}/public_html">
749        Require all granted
750        AllowOverride All
751    </Directory>
752</VirtualHost>
753"#;
754    vhost.to_string()
755}
756
757pub fn totp_string(c: &Config) -> String {
758    fn to_bytes(n: u64) -> [u8; 8] {
759        let mask: u64 = 0xff;
760        let mut bytes: [u8; 8] = [0; 8];
761        (0..8).for_each(|i| bytes[7 - i] = (mask & (n >> (i * 8))) as u8);
762        bytes
763    }
764
765    fn totp_maker(c: &Config, pw: &[u8]) -> String {
766        let mut hash: Vec<u8> = vec![];
767
768        match c.totp_algo {
769            TotpAlgo::Sha1 => {
770                let mut mac = Hmac::<Sha1>::new_from_slice(pw).unwrap();
771                Hmac::<Sha1>::update(&mut mac, &to_bytes(c.totp_seconds() / c.totp_step.unwrap()));
772                hash.extend_from_slice(&mac.finalize().into_bytes());
773            }
774            TotpAlgo::Sha256 => {
775                let mut mac = Hmac::<Sha256>::new_from_slice(pw).unwrap();
776                Hmac::<Sha256>::update(
777                    &mut mac,
778                    &to_bytes(c.totp_seconds() / c.totp_step.unwrap()),
779                );
780                hash.extend_from_slice(&mac.finalize().into_bytes());
781            }
782            TotpAlgo::Sha512 => {
783                let mut mac = Hmac::<Sha512>::new_from_slice(pw).unwrap();
784                Hmac::<Sha512>::update(
785                    &mut mac,
786                    &to_bytes(c.totp_seconds() / c.totp_step.unwrap()),
787                );
788                hash.extend_from_slice(&mac.finalize().into_bytes());
789            }
790        }
791
792        let offset: usize = (hash.last().unwrap() & 0xf) as usize;
793        let binary: u64 = (((hash[offset] & 0x7f) as u64) << 24)
794            | ((hash[offset + 1] as u64) << 16)
795            | ((hash[offset + 2] as u64) << 8)
796            | (hash[offset + 3] as u64);
797
798        format!(
799            "{:01$}",
800            binary % (10_u64.pow(c.len.unwrap_or(6))),
801            c.len.unwrap_or(6) as usize
802        )
803    }
804
805    let s = c
806        .totp_key
807        .as_ref()
808        .unwrap()
809        .trim()
810        .to_lowercase()
811        .to_string();
812    let password: &[u8] = &match base32::decode(Alphabet::Rfc4648Lower { padding: false }, &s) {
813        Some(x) => x,
814        None => {
815            eprintln!("Cannot base32 convert {}", &s);
816            std::process::exit(1);
817        }
818    };
819
820    totp_maker(c, password)
821}
822
823pub fn totp_secs_remaining(c: &Config) -> u64 {
824    c.totp_step.unwrap() - c.totp_seconds() % c.totp_step.unwrap()
825}
826
827pub fn totp_progress_bar(c: &Config) -> String {
828    let width: usize = 30;
829    let percent: usize = width / (c.totp_step.unwrap() as usize);
830    let remaining: usize = (c.totp_step.unwrap() as usize)
831        - (c.totp_seconds() as usize) % (c.totp_step.unwrap() as usize);
832    let progress = percent * remaining;
833    let padding = width - progress;
834
835    format!(
836        "{empty:#<progress$}{empty: ^padding$}",
837        empty = "",
838        progress = progress,
839        padding = padding,
840    )
841}
842
843pub fn totp_progress(c: &Config) -> String {
844    let mut ret = "".to_string();
845
846    ret.push_str(&urgent_colour(totp_secs_remaining(c) as i32));
847    ret.push_str(&totp_progress_bar(c));
848    ret.push_str(&normal_colour());
849
850    ret.to_string()
851}
852
853pub fn set_wordlist(c: &mut Config) {
854    if c.word_list.is_some() {
855        let word_list = c.word_list.as_ref().unwrap();
856        if c.words.is_none() {
857            let f = File::open(word_list);
858            if f.is_err() {
859                eprintln!("Cannot open {}: {}", word_list, f.err().unwrap());
860                std::process::exit(1);
861            }
862
863            let f = f.unwrap();
864
865            let mut line = String::new();
866            let mut br = BufReader::new(f);
867            let mut wv: Vec<String> = vec![];
868            loop {
869                line.clear();
870                let l = br.read_line(&mut line);
871                match l {
872                    Ok(i) => {
873                        if i == 0 {
874                            break;
875                        }
876                    }
877                    Err(_) => {
878                        break;
879                    }
880                }
881
882                // skip words with apostrophes etc
883
884                if line.find('\'').is_some() {
885                    continue;
886                }
887
888                let s = line.clone().trim().to_string();
889                if s.is_empty() {
890                    continue;
891                }
892
893                if !valid_word(&s, c.pw_type) {
894                    continue;
895                }
896
897                wv.push(s);
898            }
899            c.words = Some(wv);
900        }
901    }
902}
903
904pub fn new_db(c: &Config) {
905    let key = CompositeKey::from_password(c.keepassphrase.as_ref().unwrap());
906
907    let path = c.keepassdb.as_ref().unwrap();
908
909    if !std::path::Path::exists(std::path::Path::new(path)) {
910        let mut db_file = File::create(path).unwrap();
911        let mut db = Database::new(&key);
912        db.db_type = kpdb::DbType::Kdb2;
913        db.history_max_items = 100;
914        if let Err(x) = db.save(&mut db_file) {
915            eprintln!("Cannot create {}: {}", path, x);
916            std::process::exit(1);
917        }
918    }
919}
920
921pub fn keepass_item_name(c: &Config) -> String {
922    let mut item: Vec<String> = vec![];
923    if let Some(i) = &c.servername {
924        item.push(i.to_string());
925    }
926    if let Some(i) = &c.database {
927        item.push(i.to_string());
928    }
929    item.join("-")
930}
931
932pub fn get_keepass_pw(c: &mut Config) {
933    if let Some(keepass_path) = &c.keepassdb {
934        if c.keepassphrase.is_none()
935            && !std::path::Path::exists(std::path::Path::new(&keepass_path))
936        {
937            eprintln!("Cannot open {}", keepass_path);
938            std::process::exit(1);
939        }
940    }
941
942    let key = CompositeKey::from_password(c.keepassphrase.as_ref().unwrap());
943
944    let path = c.keepassdb.as_ref().unwrap();
945    let mut db_file = File::open(path).unwrap_or_else(|_| panic!("Cannot open {}", path));
946    let mut db =
947        Database::open(&mut db_file, &key).unwrap_or_else(|_| panic!("Cannot read {}", path));
948
949    let item = keepass_item_name(c);
950
951    for e in db.root_group.entries.iter_mut() {
952        if let Some(title) = e.title() {
953            if title == item {
954                if let Some(pw) = e.password() {
955                    c.password = Some(pw.to_string());
956                    return;
957                }
958            }
959        }
960    }
961
962    eprintln!("Cannot find password in {}", path);
963    std::process::exit(1);
964}
965
966pub fn update_keepass(c: &Config, pw: &str, item: &str) {
967    let key = CompositeKey::from_password(c.keepassphrase.as_ref().unwrap());
968
969    let path = c.keepassdb.as_ref().unwrap();
970    let mut db_file = File::open(path).unwrap_or_else(|_| panic!("Cannot open {}", path));
971    let mut db =
972        Database::open(&mut db_file, &key).unwrap_or_else(|_| panic!("Cannot read {}", path));
973
974    let mut found = false;
975    for e in db.root_group.entries.iter_mut() {
976        if c.username.is_some()
977            && e.username().is_some()
978            && c.username.as_ref().unwrap() != e.username().as_ref().unwrap()
979        {
980            continue;
981        }
982
983        if let Some(title) = e.title() {
984            if title == item {
985                e.history.push(e.clone());
986
987                e.set_title(item);
988                if let Some(user) = &c.username {
989                    e.set_username(user);
990                }
991                e.set_password(pw);
992                e.last_modified = Local::now().into();
993                found = true;
994            }
995        }
996    }
997
998    if !found {
999        let mut entry = Entry::new();
1000        entry.set_title(item);
1001        if let Some(user) = &c.username {
1002            entry.set_username(user);
1003        }
1004
1005        entry.set_password(pw);
1006        db.root_group.add_entry(entry);
1007    }
1008
1009    let mut db_file = File::create(path).unwrap();
1010    let _ = db.save(&mut db_file);
1011}
1012
1013pub fn update_keepassdb(c: &mut Config, pw: &str) {
1014    if c.keepassfetch {
1015        return;
1016    }
1017
1018    if let Some(keepass_path) = &c.keepassdb {
1019        if c.keepassphrase.is_none()
1020            && !std::path::Path::exists(std::path::Path::new(&keepass_path))
1021        {
1022            let passphrase = prng_string(c);
1023            c.keepassphrase = Some(passphrase);
1024            println!(
1025                "Using {} as the passphrase for {} as it does not exist",
1026                &c.keepassphrase.as_ref().unwrap(),
1027                keepass_path
1028            );
1029        }
1030
1031        if c.keepassphrase.is_some() && c.keepassdb.is_some() {
1032            new_db(c);
1033
1034            update_keepass(c, pw, &keepass_item_name(c));
1035        }
1036    }
1037}
1038
1039#[allow(deprecated)]
1040pub fn pw_loop(matches: &getopts::Matches, c: &mut Config) {
1041    if c.keepassfetch {
1042        get_keepass_pw(c);
1043    }
1044
1045    let pw = (if c.password.is_some() {
1046        c.password.as_ref().unwrap().to_string()
1047    } else {
1048        (*prng_string(c)).to_string()
1049    })
1050    .to_string();
1051    let mut donefmtopt = false;
1052
1053    if matches.opt_present("userfmt") {
1054        let mut format_string = "%{userfmt} # %{password}\n".to_string();
1055        format_string = process_format_string(&mut format_string, c, &pw);
1056        print!("{}", format_string);
1057        donefmtopt = true;
1058    }
1059
1060    if matches.opt_present("usermodfmt") {
1061        let mut format_string = "%{usermodfmt} # %{password}\n".to_string();
1062        format_string = process_format_string(&mut format_string, c, &pw);
1063        print!("{}", format_string);
1064        donefmtopt = true;
1065    }
1066
1067    if matches.opt_present("pgfmt") {
1068        let mut format_string =
1069            format!("%{{pgfmt}}{} -- # %{{password}}\n", create_pg_database(c)).to_string();
1070        format_string = process_format_string(&mut format_string, c, &pw);
1071        print!("{}", format_string);
1072        donefmtopt = true;
1073    }
1074
1075    if matches.opt_present("mysqlfmt") {
1076        let mut format_string = format!(
1077            "{}%{{mysqlfmt}} -- # %{{password}}\n",
1078            create_mysql_database(c)
1079        )
1080        .to_string();
1081        format_string = process_format_string(&mut format_string, c, &pw);
1082        print!("{}", format_string);
1083        donefmtopt = true;
1084    }
1085
1086    if matches.opt_present("mysqluserfmt") {
1087        let mut format_string = format!(
1088            "{}%{{mysqluserfmt}} -- # %{{password}}\n",
1089            create_mysql_database(c)
1090        )
1091        .to_string();
1092        format_string = process_format_string(&mut format_string, c, &pw);
1093        print!("{}", format_string);
1094        donefmtopt = true;
1095    }
1096
1097    if matches.opt_present("wpconfigfmt") {
1098        let mut format_string = "%{wpconfigfmt} # %{password}\n".to_string();
1099        format_string = process_format_string(&mut format_string, c, &pw);
1100        print!("{}", format_string);
1101        donefmtopt = true;
1102    }
1103
1104    if matches.opt_present("wpinstfmt") {
1105        let mut format_string = "%{wpinstfmt} # %{password}\n".to_string();
1106        format_string = process_format_string(&mut format_string, c, &pw);
1107        print!("{}", format_string);
1108        donefmtopt = true;
1109    }
1110
1111    if matches.opt_present("wpuserfmt") {
1112        let mut format_string = "%{wpuserfmt} -- %{password}\n".to_string();
1113        format_string = process_format_string(&mut format_string, c, &pw);
1114        print!("{}", format_string);
1115        donefmtopt = true;
1116    }
1117
1118    if matches.opt_present("htauthfmt") {
1119        let mut format_string = "# %{password}\n%{htauthfmt}\n".to_string();
1120        format_string = process_format_string(&mut format_string, c, &pw);
1121        print!("{}", format_string);
1122        donefmtopt = true;
1123    }
1124
1125    if matches.opt_present("nginxfmt") {
1126        let mut format_string = create_nginx_vhost();
1127        format_string = process_format_string(&mut format_string, c, &pw);
1128        print!("{}", format_string);
1129        donefmtopt = true;
1130    }
1131    if matches.opt_present("apachefmt") {
1132        let mut format_string = create_apache_vhost();
1133        format_string = process_format_string(&mut format_string, c, &pw);
1134        print!("{}", format_string);
1135        donefmtopt = true;
1136    }
1137
1138    if matches.opt_present("totpfmt") {
1139        let mut format_string = "%{totpfmt}\n".to_string();
1140        format_string = process_format_string(&mut format_string, c, &pw);
1141        print!("{}", format_string);
1142        donefmtopt = true;
1143    }
1144
1145    if matches.opt_present("format") {
1146        let mut format_string = matches.opt_str("format").unwrap().to_string();
1147
1148        if format_string.trim() == "useradd" {
1149            format_string = "%{userfmt} # %{password}\n".to_string();
1150        }
1151        if format_string.trim() == "usermod" {
1152            format_string = "%{usermodfmt} # %{password}\n".to_string();
1153        }
1154        if format_string.trim() == "mysql" {
1155            format_string = "%{mysqlfmt} -- %{password}\n".to_string();
1156        }
1157        if format_string.trim() == "pg" {
1158            format_string = "%{pgfmt} -- %{password}\n".to_string();
1159        }
1160        if format_string.trim() == "wpuserfmt" {
1161            format_string = "%{wpuserfmt} -- %{password}\n".to_string();
1162        }
1163        if format_string.trim() == "wpinstfmt" {
1164            format_string = "%{wpinstfmt} # %{password}\n".to_string();
1165        }
1166        if format_string.trim() == "htauth" {
1167            format_string = "# %{password}\n%{htauthfmt}\n".to_string();
1168        }
1169
1170        format_string = process_format_string(&mut format_string, c, &pw);
1171
1172        print!("{}", format_string);
1173        update_keepassdb(c, &pw);
1174        return;
1175    }
1176
1177    if donefmtopt {
1178        update_keepassdb(c, &pw);
1179        return;
1180    }
1181
1182    if matches.opt_present("des") {
1183        let mut format_string = "%{des} # %{password}\n".to_string();
1184        format_string = process_format_string(&mut format_string, c, &pw);
1185        print!("{}", format_string);
1186        donefmtopt = true;
1187    }
1188
1189    if matches.opt_present("md5") {
1190        let mut format_string = "%{md5} # %{password}\n".to_string();
1191        format_string = process_format_string(&mut format_string, c, &pw);
1192        print!("{}", format_string);
1193        donefmtopt = true;
1194    }
1195
1196    if matches.opt_present("sha1") {
1197        let mut format_string = "%{sha1} # %{password}\n".to_string();
1198        format_string = process_format_string(&mut format_string, c, &pw);
1199        print!("{}", format_string);
1200        donefmtopt = true;
1201    }
1202
1203    if matches.opt_present("sha256") {
1204        let mut format_string = "%{sha256} # %{password}\n".to_string();
1205        format_string = process_format_string(&mut format_string, c, &pw);
1206        print!("{}", format_string);
1207        donefmtopt = true;
1208    }
1209
1210    if matches.opt_present("sha512") {
1211        let mut format_string = "%{sha512} # %{password}\n".to_string();
1212        format_string = process_format_string(&mut format_string, c, &pw);
1213        print!("{}", format_string);
1214        donefmtopt = true;
1215    }
1216
1217    if matches.opt_present("bcrypt") {
1218        let mut format_string = "%{bcrypt} # %{password}\n".to_string();
1219        format_string = process_format_string(&mut format_string, c, &pw);
1220        print!("{}", format_string);
1221        donefmtopt = true;
1222    }
1223
1224    if donefmtopt {
1225        update_keepassdb(c, &pw);
1226        return;
1227    }
1228
1229    let mut outputs = vec![];
1230    let max_counter = if c.number.is_some() { 2 } else { 8 };
1231    for _ in 1..max_counter {
1232        let pw = (if c.password.is_some() {
1233            c.password.as_ref().unwrap().to_string()
1234        } else {
1235            (*prng_string(c)).to_string()
1236        })
1237        .to_string();
1238
1239        outputs.push(pw.clone());
1240        update_keepassdb(c, &pw);
1241        if c.word_list.is_some() {
1242            break;
1243        }
1244    }
1245    println!("{}", outputs.join(" "));
1246}
1247
1248fn parse_totp_item(c: &mut Config, items: Vec<&str>) {
1249    match items[0].to_lowercase().as_str() {
1250        "key" | "totp" => {
1251            c.totp_key = Some(items[1].to_string());
1252        }
1253        "name" | "username" => {
1254            c.username = Some(items[1].to_string());
1255        }
1256        "step" => {
1257            c.totp_step = match items[1].parse::<u32>() {
1258                Ok(l) => Some(l.into()),
1259                Err(_) => {
1260                    eprintln!("cannot convert {} to number", items[1]);
1261                    std::process::exit(1);
1262                }
1263            }
1264        }
1265        "algo" => {
1266            c.totp_algo = match items[1].to_lowercase().as_str() {
1267                "sha1" => TotpAlgo::Sha1,
1268                "sha256" => TotpAlgo::Sha256,
1269                "sha512" => TotpAlgo::Sha512,
1270                _ => {
1271                    eprintln!("cannot convert {} to a TOTP algorithm", items[1]);
1272                    std::process::exit(1);
1273                }
1274            }
1275        }
1276        "digits" => {
1277            c.len = match items[1].parse::<u32>() {
1278                Ok(l) => Some(l),
1279                Err(_) => {
1280                    eprintln!("cannot convert {} to number", items[1]);
1281                    std::process::exit(1);
1282                }
1283            }
1284        }
1285        "seconds" => {
1286            c.totp_seconds = match items[1].parse::<u32>() {
1287                Ok(l) => Some(l.into()),
1288                Err(_) => {
1289                    eprintln!("cannot convert {} to number", items[1]);
1290                    std::process::exit(1);
1291                }
1292            }
1293        }
1294        _ => {
1295            eprintln!("key {} isn't valid", items[0]);
1296            std::process::exit(1);
1297        }
1298    }
1299}
1300
1301pub fn totp_mode(matches: &getopts::Matches) -> bool {
1302    if matches.opt_present("totpfmt") {
1303        return true;
1304    }
1305
1306    if matches.opt_present("totp") {
1307        return true;
1308    }
1309
1310    false
1311}
1312
1313pub fn main_loop(matches: &getopts::Matches, c: &mut Config) {
1314    for _ in 0..c.number.unwrap_or(20) {
1315        if c.totp_key.is_some() && totp_mode(matches) {
1316            // step=30,hash=sha1,secret=sock; ;
1317            let totp_arr: Vec<_> = c.totp_key.as_ref().unwrap().split(";").collect();
1318
1319            for i in totp_arr {
1320                let i = i.trim();
1321                if i.is_empty() {
1322                    continue;
1323                }
1324
1325                let c: &mut Config = &mut c.clone();
1326                c.totp_key = Some(i.to_string());
1327
1328                let parts: Vec<_> = i.split(",").collect();
1329                for p in parts {
1330                    let p = p.trim();
1331                    if p.is_empty() {
1332                        continue;
1333                    }
1334
1335                    let items: Vec<_> = p.splitn(2, "=").collect();
1336                    if items.len() < 2 {
1337                        continue;
1338                    }
1339
1340                    parse_totp_item(c, items);
1341                }
1342                pw_loop(matches, c);
1343            }
1344            continue;
1345        }
1346        pw_loop(matches, c);
1347    }
1348}
1349
1350pub fn sleep(millis: u64) {
1351    let duration = Duration::from_millis(millis);
1352    thread::sleep(duration);
1353}
1354
1355pub fn urgent_colour(remaining: i32) -> String {
1356    if remaining < 6 {
1357        "\x1b[0;31m".to_string()
1358    } else {
1359        "\x1b[0;32m".to_string()
1360    }
1361}
1362
1363pub fn normal_colour() -> String {
1364    "\x1b[0m".to_string()
1365}