519 lines
21 KiB
Rust
519 lines
21 KiB
Rust
use regex::Regex;
|
|
use sha1::{Digest, Sha1};
|
|
use std::cmp::min;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::fs;
|
|
use std::io::{BufRead, BufReader};
|
|
use std::io::{BufWriter, Write};
|
|
|
|
use crate::parser::command_parser;
|
|
use crate::password::fix_password_recursion;
|
|
use crate::password::{Name, Password, PasswordRef};
|
|
use crate::repl::LKEval;
|
|
use crate::structs::{LKOut, Radix, CORRECT_FILE, DUMP_FILE};
|
|
use crate::utils::editor::password;
|
|
use crate::utils::{call_cmd_with_input, get_cmd_args_from_command, get_copy_command_from_env, rnd};
|
|
|
|
impl<'a> LKEval<'a> {
|
|
pub fn get_password(&self, name: &String) -> Option<PasswordRef> {
|
|
match self.state.lock().borrow().ls.get(name) {
|
|
Some(pwd) => Some(pwd.clone()),
|
|
None => match self.state.lock().borrow().db.get(name) {
|
|
Some(pwd) => Some(pwd.clone()),
|
|
None => None,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn read_master(&self, out: &LKOut, pwd: PasswordRef, read: bool) -> Option<String> {
|
|
if read {
|
|
match self.read_master(&out, pwd.clone(), false) {
|
|
Some(p) => {
|
|
return Some(p);
|
|
}
|
|
None => (),
|
|
}
|
|
}
|
|
let parent = match &pwd.lock().borrow().parent {
|
|
Some(p) => p.lock().borrow().name.to_string(),
|
|
None => "/".to_string(),
|
|
};
|
|
let secret = match self.state.lock().borrow().secrets.get(&parent) {
|
|
Some(p) => Some(p.clone()),
|
|
None => None,
|
|
};
|
|
match (pwd.lock().borrow().parent.clone(), secret) {
|
|
(_, Some(s)) => Some(s.to_string()),
|
|
(None, None) => {
|
|
if read {
|
|
match (self.read_password)("Master: ".to_string()) {
|
|
Ok(password) => {
|
|
let name = "/".to_string();
|
|
self.cmd_correct(&out, &name, true, Some(password.clone()));
|
|
self.state.lock().borrow_mut().secrets.insert(name, password.clone());
|
|
Some(password)
|
|
}
|
|
Err(_) => None,
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
(Some(pn), None) => {
|
|
let password = if read {
|
|
(self.read_password)(format!("Password for {}: ", pn.lock().borrow().name)).ok()
|
|
} else {
|
|
None
|
|
};
|
|
if password.is_some() && password.as_ref().unwrap().len() > 0 {
|
|
let name = pn.lock().borrow().name.to_string();
|
|
self.cmd_correct(&out, &name, true, Some(password.as_ref().unwrap().clone()));
|
|
self.state.lock().borrow_mut().secrets.insert(name, password.as_ref().unwrap().clone());
|
|
password
|
|
} else {
|
|
match self.read_master(&out, pn.clone(), read) {
|
|
Some(master) => {
|
|
let password = pn.lock().borrow().encode(master.as_str());
|
|
let name = pn.lock().borrow().name.to_string();
|
|
self.cmd_correct(&out, &name, true, Some(password.to_string()));
|
|
self.state.lock().borrow_mut().secrets.insert(name, password.clone());
|
|
Some(password)
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn cmd_add(&self, out: &LKOut, name: &PasswordRef) {
|
|
let state_cell = self.state.lock();
|
|
let mut state = state_cell.borrow_mut();
|
|
let mut fix = false;
|
|
{
|
|
let pwname = &name.lock().borrow().name.to_string();
|
|
if state.db.get(pwname).is_some() {
|
|
out.e(format!("error: password {} already exist", pwname));
|
|
} else {
|
|
state.db.insert(pwname.to_string(), name.clone());
|
|
fix = true;
|
|
}
|
|
}
|
|
if fix {
|
|
state.fix_hierarchy();
|
|
}
|
|
}
|
|
|
|
pub fn cmd_keep(&self, out: &LKOut, name: &Name) {
|
|
let pwd = match self.state.lock().borrow().ls.get(name) {
|
|
Some(pwd) => pwd.clone(),
|
|
None => {
|
|
out.e(format!("error: {} not found", name));
|
|
return;
|
|
}
|
|
};
|
|
self.cmd_add(&out, &pwd);
|
|
}
|
|
|
|
pub fn cmd_mv(&self, out: &LKOut, name: &String, folder: &String) {
|
|
match self.get_password(name) {
|
|
Some(pwd) => {
|
|
if folder == "/" {
|
|
pwd.lock().borrow_mut().parent = None
|
|
} else {
|
|
match self.get_password(folder) {
|
|
Some(fld) => {
|
|
pwd.lock().borrow_mut().parent = Some(fld.clone());
|
|
fix_password_recursion(pwd.clone());
|
|
}
|
|
None => out.e(format!("error: folder {} not found", folder)),
|
|
}
|
|
}
|
|
}
|
|
None => out.e(format!("error: password with name {} not found", name)),
|
|
}
|
|
}
|
|
|
|
pub fn cmd_pass(&self, out: &LKOut, name: &String, pass: &Option<String>) {
|
|
match self.get_password(name) {
|
|
Some(p) => {
|
|
let pwd = match pass {
|
|
Some(pp) => pp.to_string(),
|
|
None => (self.read_password)(format!("Password for {}: ", p.lock().borrow().name)).unwrap(),
|
|
};
|
|
self.cmd_correct(&out, &p.lock().borrow().name, true, Some(pwd.clone()));
|
|
self.state.lock().borrow_mut().secrets.insert(p.lock().borrow().name.to_string(), pwd);
|
|
}
|
|
None => {
|
|
if name == "/" {
|
|
let pwd = match pass {
|
|
Some(pp) => pp.to_string(),
|
|
None => (self.read_password)("Master: ".to_string()).unwrap(),
|
|
};
|
|
self.cmd_correct(&out, &"/".to_string(), true, Some(pwd.clone()));
|
|
self.state.lock().borrow_mut().secrets.insert("/".to_string(), pwd);
|
|
} else {
|
|
out.e(format!("error: password with name {} not found", name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn cmd_comment(&self, out: &LKOut, name: &String, comment: &Option<String>) {
|
|
match self.get_password(name) {
|
|
Some(pwd) => {
|
|
pwd.lock().borrow_mut().comment = match comment {
|
|
Some(c) => Some(c.to_string()),
|
|
None => None,
|
|
}
|
|
}
|
|
None => out.e("error: password not found".to_string()),
|
|
}
|
|
}
|
|
|
|
pub fn cmd_enc(&self, out: &LKOut, name: &String) -> Option<(String, String)> {
|
|
let root_folder = "/".to_string();
|
|
let (name, pass) = if name == "/" && self.state.lock().borrow().secrets.contains_key(&root_folder) {
|
|
(root_folder.to_string(), self.state.lock().borrow().secrets.get(&root_folder).unwrap().to_string())
|
|
} else {
|
|
let pwd = match self.get_password(name) {
|
|
Some(p) => p.clone(),
|
|
None => {
|
|
out.e(format!("error: name {} not found", name));
|
|
return None;
|
|
}
|
|
};
|
|
let name = pwd.lock().borrow().name.to_string();
|
|
if self.state.lock().borrow().secrets.contains_key(&name) {
|
|
(name.clone(), self.state.lock().borrow().secrets.get(&name).unwrap().to_string())
|
|
} else {
|
|
match self.read_master(&out, pwd.clone(), true) {
|
|
Some(sec) => (name.clone(), pwd.lock().borrow().encode(sec.as_str())),
|
|
None => {
|
|
out.e(format!("error: master for {} not found", name));
|
|
return None;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
if out.active() {
|
|
out.o(pass.clone());
|
|
self.cmd_correct(&out, &name, true, Some(pass.clone()));
|
|
}
|
|
Some((name, pass))
|
|
}
|
|
|
|
pub fn cmd_pb(&self, out: &LKOut, command: &String) {
|
|
match command_parser::cmd(command) {
|
|
Ok(cmd) => {
|
|
let print = LKEval::new(cmd, self.state.clone(), self.read_password).eval();
|
|
let data = print.out.data();
|
|
print.out.copy_err(&out);
|
|
if data.len() > 0 {
|
|
let (copy_command, copy_cmd_args) = get_copy_command_from_env();
|
|
match call_cmd_with_input(©_command, ©_cmd_args, &data) {
|
|
Ok(s) if s.len() > 0 => {
|
|
out.o(format!(
|
|
"Copied output with the command {}, and got following output:",
|
|
copy_command
|
|
));
|
|
out.o(s.trim().to_string());
|
|
}
|
|
Ok(_) => out.o(format!("Copied output with command {}", copy_command)),
|
|
Err(e) => out.e(format!("error: failed to copy: {}", e.to_string())),
|
|
};
|
|
}
|
|
}
|
|
Err(e) => out.e(format!("error: faild to parse command {}: {}", command, e.to_string())),
|
|
};
|
|
}
|
|
|
|
pub fn cmd_source(&self, out: &LKOut, source: &String) -> bool {
|
|
let script = if source.trim().ends_with("|") {
|
|
let (cmd, args) = match get_cmd_args_from_command(source.trim().trim_end_matches('|')) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
out.e(format!("error: failed to parse command {:?}: {}", source, e.to_string()));
|
|
return false;
|
|
}
|
|
};
|
|
match call_cmd_with_input(&cmd, &args, "") {
|
|
Ok(o) => o,
|
|
Err(e) => {
|
|
out.e(format!("error: failed to execute command {}: {}", cmd, e.to_string()));
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
let script = shellexpand::full(source).unwrap().into_owned();
|
|
match std::fs::read_to_string(script) {
|
|
Ok(script) => script,
|
|
Err(e) => {
|
|
out.e(format!("error: failed to read file {}: {}", source, e.to_string()));
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
match command_parser::script(&script) {
|
|
Ok(cmd_list) => {
|
|
for cmd in cmd_list {
|
|
let print = LKEval::new(cmd, self.state.clone(), password).eval();
|
|
print.out.copy(&out);
|
|
if print.quit {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
out.e(format!("error: {}", e.to_string()));
|
|
}
|
|
};
|
|
false
|
|
}
|
|
|
|
pub fn cmd_dump(&self, out: &LKOut, script: &Option<String>) {
|
|
let script = match script {
|
|
Some(p) => p,
|
|
None => DUMP_FILE.to_str().unwrap(),
|
|
};
|
|
let script = shellexpand::full(script).unwrap().into_owned();
|
|
fn save_dump(data: &HashMap<Name, PasswordRef>, script: &String) -> std::io::Result<()> {
|
|
let file = fs::File::create(script)?;
|
|
let mut writer = BufWriter::new(file);
|
|
let mut vals = data.values().map(|v| v.clone()).collect::<Vec<PasswordRef>>();
|
|
vals.sort_by(|a, b| a.lock().borrow().name.cmp(&b.lock().borrow().name));
|
|
for pwd in vals {
|
|
writeln!(writer, "add {}", pwd.lock().borrow().to_string())?
|
|
}
|
|
Ok(())
|
|
}
|
|
if script.trim().starts_with("|") {
|
|
let (cmd, args) = match get_cmd_args_from_command(script.trim().trim_start_matches('|')) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
out.e(format!("error: failed to parse command {:?}: {}", script, e.to_string()));
|
|
return;
|
|
}
|
|
};
|
|
let data = self
|
|
.state
|
|
.lock()
|
|
.borrow()
|
|
.db
|
|
.values()
|
|
.map(|v| format!("add {}", v.lock().borrow().to_string()))
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
let output = match call_cmd_with_input(&cmd, &args, data.as_str()) {
|
|
Ok(o) => o,
|
|
Err(e) => {
|
|
out.e(format!("error: failed to execute command {}: {}", cmd, e.to_string()));
|
|
return;
|
|
}
|
|
};
|
|
if output.len() > 0 {
|
|
out.e(format!("Passwords saved to command {} and got following output:", cmd));
|
|
out.o(output);
|
|
} else {
|
|
out.o(format!("Passwords saved to command {}", cmd));
|
|
}
|
|
} else if script.trim() == "-" {
|
|
let mut vals = (&self.state.lock().borrow().db).values().map(|v| v.clone()).collect::<Vec<PasswordRef>>();
|
|
vals.sort_by(|a, b| a.lock().borrow().name.cmp(&b.lock().borrow().name));
|
|
for pwd in vals {
|
|
out.o(format!("add {}", pwd.lock().borrow().to_string()))
|
|
}
|
|
} else {
|
|
match save_dump(&self.state.lock().borrow().db, &script) {
|
|
Ok(()) => out.o(format!("Passwords saved to file {}", script)),
|
|
Err(e) => out.e(format!("error: failed to dump passswords to {}: {}", script, e.to_string())),
|
|
};
|
|
}
|
|
}
|
|
|
|
pub fn cmd_ls<F>(&self, out: &LKOut, filter: String, sort_by: F)
|
|
where
|
|
F: Fn(&PasswordRef, &PasswordRef) -> std::cmp::Ordering,
|
|
{
|
|
let re = match Regex::new(&filter) {
|
|
Ok(re) => re,
|
|
Err(e) => {
|
|
out.e(format!("error: failed to parse re: {:?}", e));
|
|
return;
|
|
}
|
|
};
|
|
let mut tmp: Vec<PasswordRef> = vec![];
|
|
for (_, name) in &self.state.lock().borrow().db {
|
|
if re.find(&name.lock().borrow().to_string()).is_some() {
|
|
tmp.push(name.clone());
|
|
} else if re.find(&name.lock().borrow().name).is_some() {
|
|
tmp.push(name.clone());
|
|
} else if name.lock().borrow().comment.is_some()
|
|
&& re.find(&name.lock().borrow().comment.as_ref().unwrap()).is_some()
|
|
{
|
|
tmp.push(name.clone());
|
|
}
|
|
}
|
|
tmp.sort_by(|a, b| a.lock().borrow().name.cmp(&b.lock().borrow().name));
|
|
tmp.sort_by(sort_by);
|
|
self.state.lock().borrow_mut().ls.clear();
|
|
let mut counter = 1;
|
|
for pwd in tmp {
|
|
let key = Radix::new(counter, 36).unwrap().to_string();
|
|
counter += 1;
|
|
self.state.lock().borrow_mut().ls.insert(key.clone(), pwd.clone());
|
|
out.o(format!("{:>3} {}", key, pwd.lock().borrow().to_string()));
|
|
}
|
|
}
|
|
|
|
pub fn cmd_correct(&self, out: &LKOut, name: &String, correct: bool, check: Option<String>) {
|
|
let (check, pwd) = match check {
|
|
Some(p) => (true, Some((name.to_string(), p))),
|
|
None => (
|
|
false,
|
|
self.cmd_enc(
|
|
&LKOut::from_lkout(
|
|
None,
|
|
match &out.err {
|
|
Some(e) => Some(e.clone()),
|
|
None => None,
|
|
},
|
|
),
|
|
&name,
|
|
),
|
|
),
|
|
};
|
|
let (name, pwd) = match pwd {
|
|
Some(v) => v,
|
|
None => return,
|
|
};
|
|
fn load_lines() -> std::io::Result<HashSet<String>> {
|
|
let file = fs::File::open(CORRECT_FILE.to_str().unwrap())?;
|
|
let reader = BufReader::new(file);
|
|
let mut lines = HashSet::new();
|
|
for line in reader.lines() {
|
|
lines.insert(line?.trim().to_owned());
|
|
}
|
|
Ok(lines)
|
|
}
|
|
let mut data = match load_lines() {
|
|
Ok(d) => d,
|
|
Err(_) => HashSet::new(),
|
|
};
|
|
let mut sha1 = Sha1::new();
|
|
sha1.update(name.to_string());
|
|
sha1.update(&pwd);
|
|
let encpwd = format!("{:x}", sha1.finalize());
|
|
if check {
|
|
if data.contains(&encpwd) {
|
|
return;
|
|
}
|
|
out.e(format!("warning: password {} is not marked as correct", name));
|
|
return;
|
|
}
|
|
if correct {
|
|
if data.contains(&encpwd) {
|
|
return;
|
|
}
|
|
data.insert(encpwd);
|
|
} else {
|
|
if !data.contains(&encpwd) {
|
|
return;
|
|
}
|
|
data.remove(&encpwd);
|
|
}
|
|
fn save_lines(data: &HashSet<String>) -> std::io::Result<()> {
|
|
let file = fs::File::create(CORRECT_FILE.to_str().unwrap())?;
|
|
let mut writer = BufWriter::new(file);
|
|
for entry in data {
|
|
writeln!(writer, "{}", entry)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
match save_lines(&data) {
|
|
Ok(()) => out.o(format!(
|
|
"Hash of the password {} {} {}",
|
|
name,
|
|
if correct { "remembered to" } else { "removed from" },
|
|
CORRECT_FILE.to_str().unwrap()
|
|
)),
|
|
Err(e) => out.e(format!("error: failed to write: {}", e.to_string())),
|
|
};
|
|
}
|
|
|
|
pub fn cmd_gen(&self, out: &LKOut, num: &u32, name: &PasswordRef) {
|
|
lazy_static! {
|
|
static ref RE: Regex = Regex::new(r"^.+?(G+|X+)$").unwrap();
|
|
}
|
|
let num: usize = (*num).try_into().unwrap();
|
|
let pwd = name.lock();
|
|
let mut genpwds: Vec<PasswordRef> = Vec::new();
|
|
match RE.captures(pwd.borrow().name.as_ref()) {
|
|
Some(caps) => {
|
|
let gen = &caps[1];
|
|
if gen.starts_with("G") {
|
|
let name = pwd.borrow().name.trim_end_matches('G').to_string();
|
|
for num in 1..10_u32.pow(gen.len().try_into().unwrap()) {
|
|
let npwd = Password::from_password_ref(&pwd.borrow());
|
|
npwd.lock().borrow_mut().name = format!("{}{}", name, num).to_string();
|
|
genpwds.push(npwd);
|
|
}
|
|
} else {
|
|
let name = pwd.borrow().name.trim_end_matches('X').to_string();
|
|
let num = rnd::range(1, 10_u32.pow(gen.len().try_into().unwrap()));
|
|
let npwd = Password::from_password_ref(&pwd.borrow());
|
|
npwd.lock().borrow_mut().name = format!("{}{}", name, num).to_string();
|
|
genpwds.push(npwd);
|
|
}
|
|
}
|
|
None => {
|
|
let npwd = Password::from_password_ref(&pwd.borrow());
|
|
genpwds.push(npwd);
|
|
}
|
|
}
|
|
self.state.lock().borrow_mut().ls.clear();
|
|
let mut counter = 1;
|
|
let mut lspwds: Vec<(PasswordRef, String)> = Vec::new();
|
|
for num in 0..genpwds.len() {
|
|
let pwd = genpwds[num].clone();
|
|
let key = Radix::new(counter, 36).unwrap().to_string();
|
|
counter += 1;
|
|
self.state.lock().borrow_mut().ls.insert(key.to_string(), pwd.clone());
|
|
lspwds.push((pwd, key));
|
|
}
|
|
self.state.lock().borrow().fix_hierarchy();
|
|
let mut err = match &out.err {
|
|
Some(e) => Some(e.clone()),
|
|
None => None,
|
|
};
|
|
let mut encpwds: Vec<(PasswordRef, String)> = Vec::new();
|
|
for (pwd, key) in lspwds {
|
|
let pass = match self.cmd_enc(&LKOut::from_lkout(None, err), &key) {
|
|
Some((name, pass)) => {
|
|
if name != pwd.lock().borrow().name {
|
|
panic!("INTERNAL_ERROR: wrong name found: {} != {}", name, pwd.lock().borrow().name);
|
|
};
|
|
pass
|
|
}
|
|
None => {
|
|
out.e(format!("error: failed to encrypt password"));
|
|
return;
|
|
}
|
|
};
|
|
err = None;
|
|
encpwds.push((pwd.clone(), pass));
|
|
}
|
|
encpwds.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
|
|
self.state.lock().borrow_mut().ls.clear();
|
|
let mut counter = 1;
|
|
out.o(format!("{:>3} {:>36} {:>4} {}", "", "Password", "Len", "Name"));
|
|
for num in (encpwds.len() - min(genpwds.len(), num))..encpwds.len() {
|
|
let (pwd, pass) = (encpwds[num].0.clone(), encpwds[num].1.to_string());
|
|
let key = Radix::new(counter, 36).unwrap().to_string();
|
|
counter += 1;
|
|
self.state.lock().borrow_mut().ls.insert(key.clone(), pwd.clone());
|
|
out.o(format!("{:>3} {:>36} {:>4} {}", key, pass, pass.len(), pwd.lock().borrow().to_string()));
|
|
}
|
|
}
|
|
}
|