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
278pub 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(); 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 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 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}