diff --git a/src/parser.rs b/src/parser.rs index 4a8b653..0fa0890 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -21,6 +21,8 @@ peg::parser! { / enc_cmd() / pass_cmd() / unpass_cmd() + / correct_cmd() + / uncorrect_cmd() / noop_cmd() / comment_cmd() ) { c } @@ -85,6 +87,8 @@ peg::parser! { rule error_cmd() -> Command<'input> = "error" _ e:$(([' '..='~'])+) { Command::Error(LKErr::Error(e)) } rule mv_cmd() -> Command<'input> = "mv" _ name:word() _ folder:word() { Command::Mv(name, folder) } rule pass_cmd() -> Command<'input> = "pass" _ name:word() { Command::Pass(name) } + rule correct_cmd() -> Command<'input> = "correct" _ name:word() { Command::Correct(name) } + rule uncorrect_cmd() -> Command<'input> = "uncorrect" _ name:word() { Command::Uncorrect(name) } rule unpass_cmd() -> Command<'input> = "unpass" _ name:word() { Command::UnPass(name) } rule enc_cmd() -> Command<'input> = "enc" _ name:word() { Command::Enc(name) } rule rm_cmd() -> Command<'input> = "rm" _ name:word() { Command::Rm(name) } diff --git a/src/repl.rs b/src/repl.rs index fa39e7e..a219e28 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -3,11 +3,16 @@ use rpassword::prompt_password; use rustyline::error::ReadlineError; use rustyline::Editor; use std::{cell::RefCell, rc::Rc}; +use std::io::{Write, BufWriter}; +use std::io::{BufRead, BufReader}; +use std::fs; +use std::collections::HashSet; +use sha1::{Digest, Sha1}; use crate::lk::LK; use crate::parser::command_parser; use crate::password::{fix_password_recursion, PasswordRef}; -use crate::structs::{Command, LKErr, Radix, HISTORY_FILE}; +use crate::structs::{Command, LKErr, Radix, HISTORY_FILE, CORRECT_FILE}; use crate::utils::{ call_cmd_with_input, get_copy_command_from_env, get_cmd_args_from_command }; #[derive(Debug)] @@ -156,7 +161,7 @@ impl<'a> LKEval<'a> { } } - fn cmd_enc(&self, out: &mut Vec, name: &String) { + fn cmd_enc(&self, out: Option<&mut Vec>, name: &String) -> Option { let root_folder = Rc::new("/".to_string()); let pass = if name == "/" && self.state.borrow().secrets.contains_key(&root_folder) { self.state.borrow().secrets.get(&root_folder).unwrap().to_string() @@ -164,8 +169,8 @@ impl<'a> LKEval<'a> { let pwd = match self.get_password(name) { Some(p) => p.clone(), None => { - out.push(format!("error: name {} not found", name)); - return; + if out.is_some() { out.unwrap().push(format!("error: name {} not found", name)) }; + return None; } }; let name = pwd.borrow().name.clone(); @@ -175,13 +180,14 @@ impl<'a> LKEval<'a> { match self.read_master(pwd.clone(), true) { Some(sec) => pwd.borrow().encode(sec.as_str()), None => { - out.push(format!("error: master for {} not found", pwd.borrow().name)); - return; + if out.is_some() { out.unwrap().push(format!("error: master for {} not found", pwd.borrow().name)) }; + return None; } } } }; - out.push(pass); + if out.is_some() { out.unwrap().push(pass.clone()) }; + Some(pass) } fn cmd_pb(&self, out: &mut Vec, command: &String) { @@ -259,6 +265,45 @@ impl<'a> LKEval<'a> { } } + fn cmd_correct(&self, out: &mut Vec, name: &String, correct: bool) { + let pwd = match self.cmd_enc(None, &name) { Some(v) => v, None => return }; + fn load_lines() -> std::io::Result> { + 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(pwd); + let encpwd = format!("{:x}", sha1.finalize()); + if correct { + if data.contains(&encpwd) { return; } + data.insert(encpwd); + } else { + if !data.contains(&encpwd) { return; } + data.remove(&encpwd); + } + fn save_lines(data: &HashSet) -> 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.push(format!("Hash of the password {} {}", if correct { "remembered to" } else { "removed from" },CORRECT_FILE.to_str().unwrap())), + Err(err) => out.push(format!("error: failed to write: {}", err.to_string())), + }; + } + pub fn eval(&self) -> LKPrint { let mut out: Vec = vec![]; let mut quit: bool = false; @@ -293,7 +338,7 @@ impl<'a> LKEval<'a> { } None => out.push("error: password not found".to_string()), }, - Command::Enc(name) => self.cmd_enc(&mut out, name), + Command::Enc(name) => { self.cmd_enc(Some(&mut out), name); }, Command::PasteBuffer(command) => self.cmd_pb(&mut out, command), Command::Source(script) => self.cmd_source(&mut out, script), Command::Pass(name) => match self.get_password(name) { @@ -318,6 +363,8 @@ impl<'a> LKEval<'a> { Some(_) => out.push(format!("Removed saved password for {}", name)), None => out.push(format!("error: saved password for {} not found", name)), } + Command::Correct(name) => self.cmd_correct(&mut out, name, true), + Command::Uncorrect(name) => self.cmd_correct(&mut out, name, false), Command::Noop => (), Command::Help => { out.push("HELP".to_string()); diff --git a/src/structs.rs b/src/structs.rs index a6650cd..6e0c3f4 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -16,6 +16,12 @@ lazy_static! { _ => home_dir().unwrap().join(".lesskeyrc").into_boxed_path(), } }; + pub static ref CORRECT_FILE: Box = { + match std::env::var("LESSKEY_INIT") { + Ok(v) => Path::new(shellexpand::full(&v).unwrap().into_owned().as_str()).to_path_buf().into_boxed_path(), + _ => home_dir().unwrap().join(".lesskey_correct").into_boxed_path(), + } + }; } #[derive(thiserror::Error, Debug, PartialEq)] @@ -37,6 +43,8 @@ pub enum Command<'a> { Enc(Name), Pass(Name), UnPass(Name), + Correct(Name), + Uncorrect(Name), PasteBuffer(String), Source(String), Comment(Name, Comment),