kanidm_cli/system_config/
badlist.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use crate::common::OpType;
use crate::{handle_client_error, PwBadlistOpt};

// use std::thread;
use std::fs::File;
use std::io::Read;
use tokio::task;

const CHUNK_SIZE: usize = 1000;

impl PwBadlistOpt {
    pub fn debug(&self) -> bool {
        match self {
            PwBadlistOpt::Show(copt) => copt.debug,
            PwBadlistOpt::Upload { copt, .. } => copt.debug,
            PwBadlistOpt::Remove { copt, .. } => copt.debug,
        }
    }

    pub async fn exec(&self) {
        match self {
            PwBadlistOpt::Show(copt) => {
                let client = copt.to_client(OpType::Read).await;
                match client.system_password_badlist_get().await {
                    Ok(list) => {
                        for i in list {
                            println!("{}", i);
                        }
                        eprintln!("--");
                        eprintln!("Success");
                    }
                    Err(e) => crate::handle_client_error(e, copt.output_mode),
                }
            }
            PwBadlistOpt::Upload {
                copt,
                paths,
                dryrun,
            } => {
                info!("pre-processing - this may take a while ...");

                let mut pwset: Vec<String> = Vec::new();

                for f in paths.iter() {
                    let mut file = match File::open(f) {
                        Ok(v) => v,
                        Err(e) => {
                            debug!(?e);
                            info!("Skipping file -> {:?}", f);
                            continue;
                        }
                    };
                    let mut contents = String::new();
                    if let Err(e) = file.read_to_string(&mut contents) {
                        error!("{:?} -> {:?}", f, e);
                        continue;
                    }
                    let mut inner_pw: Vec<_> =
                        contents.as_str().lines().map(str::to_string).collect();
                    pwset.append(&mut inner_pw);
                }

                debug!("Deduplicating pre-set ...");
                pwset.sort_unstable();
                pwset.dedup();

                info!("Have {} unique passwords to process", pwset.len());

                // Break the list into chunks per thread availability
                // let par_count = thread::available_parallelism()
                //     .expect("Failed to determine available parallelism")
                //     .get();

                let task_handles: Vec<_> = pwset
                    .chunks(CHUNK_SIZE)
                    .map(|chunk| chunk.to_vec())
                    .map(|chunk| {
                        task::spawn_blocking(move || {
                            let x = chunk
                                .iter()
                                .filter(|v| {
                                    if v.len() < 10 {
                                        return false;
                                    }
                                    match zxcvbn::zxcvbn(v.as_str(), &[]) {
                                        Ok(r) => r.score() >= 4,
                                        Err(e) => {
                                            error!(
                                                "zxcvbn unable to process '{}' - {:?}",
                                                v.as_str(),
                                                e
                                            );
                                            error!("adding to badlist anyway ...");
                                            true
                                        }
                                    }
                                })
                                .map(|s| s.to_string())
                                .collect::<Vec<_>>();
                            eprint!(".");
                            x
                        })
                    })
                    .collect();

                let mut filt_pwset = Vec::with_capacity(pwset.len());

                for task_handle in task_handles {
                    let Ok(mut results) = task_handle.await else {
                        error!("Failed to join a worker thread, unable to proceed");
                        return;
                    };
                    filt_pwset.append(&mut results);
                }

                filt_pwset.sort_unstable();

                info!(
                    "{} passwords passed zxcvbn, uploading ...",
                    filt_pwset.len()
                );

                if *dryrun {
                    for pw in filt_pwset {
                        println!("{}", pw);
                    }
                } else {
                    let client = copt.to_client(OpType::Write).await;
                    match client.system_password_badlist_append(filt_pwset).await {
                        Ok(_) => println!("Success"),
                        Err(e) => handle_client_error(e, copt.output_mode),
                    }
                }
            } // End Upload
            PwBadlistOpt::Remove { copt, paths } => {
                let client = copt.to_client(OpType::Write).await;

                let mut pwset: Vec<String> = Vec::new();

                for f in paths.iter() {
                    let mut file = match File::open(f) {
                        Ok(v) => v,
                        Err(e) => {
                            debug!(?e);
                            info!("Skipping file -> {:?}", f);
                            continue;
                        }
                    };
                    let mut contents = String::new();
                    if let Err(e) = file.read_to_string(&mut contents) {
                        error!("{:?} -> {:?}", f, e);
                        continue;
                    }
                    let mut inner_pw: Vec<_> =
                        contents.as_str().lines().map(str::to_string).collect();
                    pwset.append(&mut inner_pw);
                }

                debug!("Deduplicating pre-set ...");
                pwset.sort_unstable();
                pwset.dedup();

                if pwset.is_empty() {
                    eprintln!("No entries to remove?");
                    return;
                }

                match client.system_password_badlist_remove(pwset).await {
                    Ok(_) => println!("Success"),
                    Err(e) => handle_client_error(e, copt.output_mode),
                }
            } // End Remove
        }
    }
}