-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathmain.rs
More file actions
185 lines (155 loc) · 5.94 KB
/
main.rs
File metadata and controls
185 lines (155 loc) · 5.94 KB
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
176
177
178
179
180
181
182
183
184
185
extern crate base64;
extern crate clap;
extern crate keepass;
extern crate rpassword;
extern crate termcolor;
pub mod diff;
pub mod stack;
use clap::Parser;
use diff::{group::Group, Diff, DiffDisplay};
use keepass::{error::DatabaseOpenError, Database, DatabaseKey};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use std::fs::File;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// Sets the first file
#[clap(name = "INPUT-A", index = 1)]
input_a: String,
/// Sets the second file
#[clap(name = "INPUT-B", index = 2)]
input_b: String,
/// Disables color output
#[clap(short = 'C', long = "no-color")]
no_color: bool,
/// Enables verbose output
#[clap(short = 'v', long)]
verbose: bool,
/// Enables verbose output
#[clap(short = 'm', long = "mask-passwords")]
mask_passwords: bool,
/// Sets the password for the first file (will be asked for if omitted)
#[clap(name = "password-a", long)]
password_a: Option<String>,
/// Sets the password for the second file (will be asked for if omitted)
#[clap(name = "password-b", long)]
password_b: Option<String>,
/// Sets the password for both files (if it's the same for both files)
#[clap(name = "passwords", long)]
passwords: Option<String>,
/// Asks for password only once, and tries to open both files with it
#[clap(name = "same-password", long)]
same_password: bool,
/// Sets no password for the first file (and will not ask for it)
#[clap(name = "no-password-a", long)]
no_password_a: bool,
/// Sets no password for the second file (and will not ask for it)
#[clap(name = "no-password-b", long)]
no_password_b: bool,
/// Sets no password for both files (and will not ask for both files)
#[clap(name = "no-passwords", long)]
no_passwords: bool,
/// Sets the key file for the first file
#[clap(name = "keyfile-a", long)]
keyfile_a: Option<String>,
/// Sets the key file for the second file
#[clap(name = "keyfile-b", long)]
keyfile_b: Option<String>,
/// Sets the same key file for both files (keyfile-a and keyfile-b would take precedence if set as well)
#[clap(name = "keyfiles", long)]
keyfiles: Option<String>,
}
fn main() -> Result<(), ()> {
let arguments = Args::parse();
let (file_a, file_b) = (&arguments.input_a, &arguments.input_b);
{
let pass_a = match (
arguments.password_a,
arguments.passwords.clone(),
arguments.same_password,
arguments.no_password_a,
arguments.no_passwords,
) {
(Some(password), _, _, _, _) => Some(password),
(_, Some(password), _, _, _) => Some(password),
(_, _, true, _, _) => prompt_password("Password for both files: "),
(_, _, _, true, _) => None,
(_, _, _, _, true) => None,
_ => prompt_password(format!("Password for file {}: ", file_a).as_str()),
};
let pass_b = match (
arguments.password_b,
arguments.passwords.clone(),
arguments.same_password,
arguments.no_password_b,
arguments.no_passwords,
) {
(Some(password), _, _, _, _) => Some(password),
(_, Some(password), _, _, _) => Some(password),
(_, _, true, _, _) => pass_a.clone(),
(_, _, _, true, _) => None,
(_, _, _, _, true) => None,
_ => prompt_password(format!("Password for file {}: ", file_b).as_str()),
};
let keyfile_a: Option<String> = arguments.keyfile_a.or(arguments.keyfiles.clone());
let keyfile_b: Option<String> = arguments.keyfile_b.or(arguments.keyfiles.clone());
let use_color: bool = !arguments.no_color;
let use_verbose: bool = arguments.verbose;
let mask_passwords: bool = arguments.mask_passwords;
let db_a = kdbx_to_group(file_a, pass_a, keyfile_a, use_verbose, mask_passwords)
.expect("Error opening database A");
let db_b = kdbx_to_group(file_b, pass_b, keyfile_b, use_verbose, mask_passwords)
.expect("Error opening database B");
let delta = db_a.diff(&db_b);
println!(
"{}",
DiffDisplay {
inner: delta,
path: stack::Stack::empty(),
use_color,
use_verbose,
mask_passwords,
}
);
}
Ok(())
}
fn prompt_password(prompt: &str) -> Option<String> {
rpassword::prompt_password(prompt)
.map(|s| if s.is_empty() { None } else { Some(s) })
.unwrap_or(None)
}
pub fn kdbx_to_group(
file: &str,
password: Option<String>,
keyfile_path: Option<String>,
use_verbose: bool,
mask_passwords: bool,
) -> Result<Group, DatabaseOpenError> {
let db_key = get_database_key(password, keyfile_path)?;
let db = Database::open(&mut File::open(file)?, db_key)?;
Ok(Group::from_keepass(&db.root, use_verbose, mask_passwords))
}
fn get_database_key(
password: Option<String>,
keyfile_path: Option<String>,
) -> Result<DatabaseKey, std::io::Error> {
let db_key = DatabaseKey::new();
let db_key = match password {
Some(pwd) => db_key.with_password(pwd.as_str()),
_ => db_key,
};
if let Some(path) = keyfile_path {
db_key.with_keyfile(&mut File::open(path)?)
} else {
Ok(db_key)
}
}
pub fn set_fg(color: Option<Color>) {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
stdout.set_color(ColorSpec::new().set_fg(color)).expect("Setting colors in your console failed. Please use the --no-color flag to disable colors if the error persists.");
}
pub fn reset_color() {
let mut stdout = StandardStream::stdout(ColorChoice::Always);
stdout.reset().expect("Resetting colors in your console failed. Please use the --no-color flag to disable colors if the error persists.");
}