diff --git a/Cargo.lock b/Cargo.lock index 98c1d85..e09b1ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,23 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "home" version = "0.5.4" @@ -16,8 +33,79 @@ name = "ipass" version = "0.1.0" dependencies = [ "home", + "rand", + "rpassword", ] +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rpassword" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +dependencies = [ + "libc", + "rtoolbox", + "winapi", +] + +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 2cb6d31..fccfb07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -home = "0.5.4" \ No newline at end of file +home = "0.5.4" +rpassword = "7.2" +rand = "0.8.5" \ No newline at end of file diff --git a/README.md b/README.md index f45e557..59b7a86 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# IPass \ No newline at end of file +# IPass diff --git a/ipass.bat b/ipass.bat new file mode 100644 index 0000000..7df1256 --- /dev/null +++ b/ipass.bat @@ -0,0 +1,4 @@ +@echo off +REM this is just a shortcut +cargo run --release %* +@echo on \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1e2d7f5..abf8626 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,58 +1,36 @@ use std::collections::HashMap; -use home::home_dir; -use std::fs; - -fn get_args() -> Vec { - std::env::args().collect() // [0] = file path; [n>0] = argument -} - -fn get_home_folder_str() -> String { - match home::home_dir() { - Some(path) => { - let p = path.to_str(); - match p { - Some(pa) => return pa.to_owned(), - None => return "".to_owned(), - } - }, - None => return "".to_owned(), - } -} - -fn get_ipass_folder() -> String { - let path = get_home_folder_str()+"/.IPass/"; - fs::create_dir_all(&path).unwrap(); - return path; -} +use rand::rngs::OsRng; +use rand::RngCore; +use std::io::Write; +mod utils; fn main() { - let args = get_args(); + let version = "0.1.0"; + println!("IPass v{}\n", version); - if args.len()<2 { - help_message() + let args = utils::get_args(); + + if args.len() < 2 { + help_message(&args); + return; } let mode: &str = &args[1].trim().to_lowercase().to_owned(); match mode { "list" => list(), - "add" => add(), - "get" => get(), - "edit" => edit(), - "remove" => remove(), - _ => help_message(), + "add" => add(&args), + "get" => get(&args), + "edit" => edit(&args), + "remove" => remove(&args), + "import" => import(&args), + "export" => export(&args), + "rename" => rename(&args), + _ => help_message(&args), } - - - } -fn help_message() { - - let version = "0.0.1"; - println!("IPass v{}\n", version); - - let args = get_args(); +fn help_message(args: &Vec) { let mut help_messages:HashMap = HashMap::new(); help_messages.insert( @@ -79,53 +57,183 @@ fn help_message() { "remove".to_string(), "removes an existing entry".to_string(), ); + help_messages.insert( + "rename".to_string(), + "renames an existing entry".to_string(), + ); - if args.len()<3 { - print!("The possible commands are: "); + if args.len() < 3 { + println!("You can use the following commands:"); for i in help_messages.keys() { - print!("\"{i}\" "); + println!("\"{i}\"{}- {}"," ".repeat(8-i.len()),help_messages[i]); } return; } - - println!("{} {}", &args[2], &help_messages[&args[2]]) } -fn get_pw() -> String { - let mut output: String = String::new(); - std::io::stdin().read_line(&mut output).expect("Failed to read line"); - return output.replace("\n", "").replace("\r",""); -} - fn list() { - todo!("List websites/names"); + let paths = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); + + for path in paths { + println!("Entry: \"{}\"", path.unwrap().file_name().into_string().unwrap().replace(".ipass", "")); + } } -fn add() { - todo!("Add password") - // create_entry(args[3],encrypt_pass(args[4])); +fn add(args: &Vec) { + if args.len() < 3 || args.len() > 4 { + println!("Incorrect usage of \"add\""); + return; + } + + let mut pw: String; + + if args.len() > 3 { + pw = args[3].to_owned(); + } else { + let char_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!\"§$%&/()=?´`²³{[]}\\,.-;:_><|+*#'".as_bytes(); + let mut chars_index: Vec = vec![0;20]; + OsRng.fill_bytes(&mut chars_index); + let mut chars = String::new(); + + for index in chars_index { + chars += std::str::from_utf8(&[char_set[(index%(char_set.len() as u8)) as usize]]).unwrap(); + } + + pw = chars; + + } + + println!("Adding password for {}",args[2]); + let succeeded = utils::create_entry(&args[2],&mut pw); + if !succeeded { + println!("You already have an entry with that name! Did you mean to use \"edit\"?"); + return; + } + println!("Added password for {}",args[2]) } -fn get() { - todo!("Get password") +fn get(args: &Vec) { + let name = &args[2]; + let filepath = &(utils::get_ipass_folder()+name+".ipass"); + if std::path::Path::new(filepath).exists() { + println!("Getting entry"); + println!("{}",utils::get_entry(filepath)); + } else { + println!("No such entry!"); + return; + } } -fn edit() { - todo!("Edit entry") +fn edit(args: &Vec) { + if args.len() < 3 { + println!("Invalid usage of \"edit\""); + return; + } + let filepath = &(utils::get_ipass_folder()+&args[2]+".ipass"); + if std::path::Path::new(filepath).exists() { + let output: String; + if args.len() != 4 { + output = rpassword::prompt_password("Please enter the new password: ").unwrap(); + } else { + output = args[3].clone(); + } + + let mut file = std::fs::File::create(format!("{}/{}.ipass",utils::get_ipass_folder(),args[2])).unwrap(); + file.write_all(output.replace("\n", "").replace("\r","").as_bytes()).unwrap(); + + println!("Changed Password of {}!", args[2]); + } else { + println!("No such file!"); + } } -fn remove() { - todo!("Removes entry") +fn rename(args: &Vec) { // prog ren old new + if args.len() < 4 { + println!("Invalid usage of \"rename\""); + return; + } + let filepath = &(utils::get_ipass_folder()+&args[2]+".ipass"); + if std::path::Path::new(filepath).exists() { + std::fs::rename(format!("{}/{}.ipass",utils::get_ipass_folder(),args[2]), format!("{}/{}.ipass",utils::get_ipass_folder(),args[3])).unwrap(); + println!("Renamed {} to {}", args[2], args[3]); + } else { + println!("No such file"); + } } -fn encrypt_pass(pass: &str) -> &str { - return pass; +fn remove(args: &Vec) { + let name = &args[2]; + let filepath = &(utils::get_ipass_folder()+name+".ipass"); + if std::path::Path::new(filepath).exists() { + if utils::prompt_answer(format!("Are you sure you want to delete {}? [y/N]", name)) == "y" { + std::fs::remove_file(filepath).unwrap(); + println!("Removed entry \"{}\"", name); + } else{ + println!("Operation cancelled!") + } + } else { + println!("No entry named \"{}\"", name); + return; + } } -/* -prog.exe list -> all saved entries -prog.exe add [Name] \n **in stars** {password} -prog.exe get [Name] -*/ \ No newline at end of file +fn import(args: &Vec) { + let mut location = utils::get_home_folder_str(); + if args.len() == 3 { + location = args[2].clone(); + } else { + if utils::prompt_answer(format!("No location specified, defaulting to {} continue? [Y/n] ", location.clone())) == "n" { + println!("Operation cancelled"); + return; + } + } + if std::path::Path::new(&(location.clone()+"/export.ipassx")).exists() { + let content = &mut std::fs::read_to_string(location.clone()+"/export.ipassx").expect("Should have been able to read the file"); + let lines = content.lines(); + let mut name = ""; + for i in lines { + if name == "" { + name = i; + continue; + } + + let mut file = std::fs::File::create(format!("{}/{}.ipass",utils::get_ipass_folder(), name)).unwrap(); + file.write_all(i.as_bytes()).unwrap(); + name = ""; + } + print!("Imported all entries!"); + } else { + println!("No such file found!"); + } + +} + +fn export(args: &Vec) { //TODO: compress data + let mut location = utils::get_home_folder_str(); + if args.len() == 3 { + location = args[2].clone(); + } else { + if utils::prompt_answer(format!("No location specified, defaulting to {} continue? [Y/n] ", location.clone())) == "n" { + println!("Operation cancelled"); + return; + } + } + + let mut collected_data: String = String::new(); + + let paths = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); + + for p in paths { + if let Ok(path) = p { + let content = &mut std::fs::read_to_string(utils::get_ipass_folder()+&path.file_name().into_string().unwrap()).expect("Should have been able to read the file"); + collected_data += format!("{}\n{}\n", path.file_name().into_string().unwrap().replace(".ipass", ""),content).as_str(); + } + } + + let mut file = std::fs::File::create(location.clone()+"/export.ipassx").unwrap(); + file.write_all(collected_data.as_bytes()).unwrap(); + + println!("Saved at: {}/export.ipassx", location); +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..2adc9ce --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,68 @@ +use std::io::Write; + + +pub fn get_args() -> Vec { + std::env::args().collect() // [0] = file path; [n>0] = argument +} + +fn encrypt_pass(pass: &mut String,_mpw: String) -> &mut String { + return pass; +} + +fn decrypt_pass(pass: &mut String, _mpw: String) -> &mut String { + return pass; +} + +pub fn get_home_folder_str() -> String { + match home::home_dir() { + Some(path) => { + let p = path.to_str(); + match p { + Some(pa) => return pa.to_owned(), + None => return "".to_owned(), + } + }, + None => return "".to_owned(), + } +} + +pub fn get_ipass_folder() -> String { + let path = get_home_folder_str()+"/.IPass/"; + std::fs::create_dir_all(&path).unwrap(); + return path; +} + +pub fn create_entry(name:&String, pw:&mut String) -> bool { + if std::path::Path::new(&(get_ipass_folder()+name+".ipass")).exists() { + return false; + } + edit_entry(name, pw); + return true; +} + +pub fn get_entry(filepath:&String) -> String { + let content = &mut std::fs::read_to_string(filepath).expect("Should have been able to read the file"); + let mpw = ask_for_pw(); + return decrypt_pass(content,mpw).to_owned(); +} + +pub fn edit_entry(name:&String,mut pw:&mut String) { + let mpw = ask_for_pw(); + pw = encrypt_pass(pw,mpw); + let mut file = std::fs::File::create(get_ipass_folder()+name+".ipass").unwrap(); + file.write_all(pw.as_bytes()).unwrap(); +} + +fn ask_for_pw() -> String { + let output = rpassword::prompt_password("Please enter the master password: ").unwrap(); + return output.replace("\n", "").replace("\r",""); +} + +pub fn prompt_answer(toprint: String) -> String { + print!("{toprint}"); + std::io::stdout().flush().unwrap(); + let mut choice = String::new(); + std::io::stdin().read_line(&mut choice).expect("Failed to read choice"); + + return choice.trim().to_lowercase(); +} \ No newline at end of file