diff --git a/.gitignore b/.gitignore index 5aba510..43befc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target comp.bat comp.sh -report.json \ No newline at end of file +report.json +*.ipass +*.ipassx \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d0dda63..e4a112f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,7 +193,7 @@ dependencies = [ [[package]] name = "ipass" -version = "0.3.1" +version = "0.4.0" dependencies = [ "aes-gcm", "brotli", diff --git a/Cargo.toml b/Cargo.toml index 588eb30..81d43df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ipass" -version = "0.3.1" +version = "0.4.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -19,4 +19,4 @@ opt-level = 'z' # Optimize for size lto = true # Enable link-time optimization codegen-units = 1 # Reduce number of codegen units to increase optimizations panic = 'abort' # Abort on panic -strip = true # Strip symbols from binary; remove pdb +# strip = true # Strip symbols from binary; remove pdb diff --git a/build.bat b/build.bat index 7f1b114..17d5be7 100644 --- a/build.bat +++ b/build.bat @@ -1 +1,3 @@ -cargo build --release \ No newline at end of file +@echo off +cargo build -r -j -10 +@echo on \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 297d71b..20c1c13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,10 @@ +#![warn(missing_docs, single_use_lifetimes, unused_lifetimes, trivial_casts, trivial_numeric_casts)] +#![forbid(unsafe_code)] + +//! IPass Password Manager + use std::collections::HashMap; -use std::io::{Read, Write}; +use std::io::Write; use rand::rngs::OsRng; use rand::RngCore; mod utils; @@ -16,6 +21,19 @@ fn main() { } let mode: &str = &args[1].trim().to_lowercase().to_owned(); + + let sync_file_loc = utils::get_home_folder_str()+"/.sync.ipass"; + let sync_file_path = std::path::Path::new(&sync_file_loc); + + let sync_enabled = sync_file_path.clone().exists(); + let sync_loc: String; + + if sync_enabled { + sync_loc = std::fs::read_to_string(sync_file_path).expect("Should have been able to read the sync file"); + utils::import_file(&sync_loc); + } else { + sync_loc = "".to_string(); //sync is disabled, no location needed + } match mode { "list" => list(), @@ -28,9 +46,14 @@ fn main() { "export" => export(&args), "rename" => rename(&args), "version" => version_help(version), + "sync" => sync(&args), + "isync" => isync(&args), "clear" => clear(), _ => help_message(&args), } + if sync_enabled { + utils::export_file(&sync_loc); + } } fn version_help(version: &str) { @@ -90,6 +113,14 @@ fn help_message(args: &Vec) { "clear".to_string(), "clears all entries".to_string(), ); + help_messages.insert( + "sync".to_string(), + "automatically sync your data with a specified file".to_string(), + ); + help_messages.insert( + "isync".to_string(), + "not fully implemented yet; ignore this for now | Syncs the database to IPass servers".to_string(), + ); if args.len() < 3 { println!("You can use the following commands:"); @@ -103,7 +134,7 @@ fn help_message(args: &Vec) { } fn list() { - let mut paths = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); + let mut paths: std::fs::ReadDir = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); let mut has_entry:bool = false; @@ -123,6 +154,7 @@ fn list() { } fn add(args: &Vec) { + //! arguments: program add [name] {password} if args.len() < 4 || args.len() > 5 { println!("Incorrect usage of \"add\""); @@ -135,12 +167,12 @@ fn add(args: &Vec) { if args.len() > 4 { pw = username+";"+args[4].trim(); } else { - let alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!\"$%&/()=?{[]}\\,.-;:_><|+*#'"; - let alph_len = alphabet.chars().count(); + let alphabet: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!\"$%&/()=?{[]}\\,.-;:_><|+*#'"; + let alph_len: usize = alphabet.chars().count(); let char_set:Vec = alphabet.chars().collect(); let mut chars_index: Vec = vec![0;20]; OsRng.fill_bytes(&mut chars_index); - let mut chars = String::new(); + let mut chars: String = String::new(); for index in chars_index { // println!("{} - {} - {}",index,(index as usize)%(alph_len-1),alph_len); @@ -154,7 +186,7 @@ fn add(args: &Vec) { } println!("Adding password for {}",args[2]); - let succeeded = utils::create_entry(&args[2], pw); + let succeeded: bool = utils::create_entry(&args[2], pw); if !succeeded { println!("You already have an entry with that name! Did you mean to use \"edit\"?"); return; @@ -163,15 +195,16 @@ fn add(args: &Vec) { } fn get(args: &Vec) { + //! arguments: program get [name] if args.len() < 3 { println!("Invalid usage of \"get\""); return; } - let name = &args[2]; + let name: &String = &args[2]; let filepath = &(utils::get_ipass_folder()+name+".ipass"); if std::path::Path::new(filepath).exists() { println!("Getting entry"); - let entry = utils::get_entry(name); + let entry: String = utils::get_entry(name); let mut data = entry.split(";"); println!("Username: '{}' Password: '{}'",data.next().unwrap(),data.next().unwrap()); } else { @@ -180,7 +213,8 @@ fn get(args: &Vec) { } } -fn changepw(args: &Vec) { //rename func to changepw +fn changepw(args: &Vec) { + //! arguments: program changepw [name] {new_password} if args.len() < 3 { println!("Invalid usage of \"changepw\""); return; @@ -203,6 +237,7 @@ fn changepw(args: &Vec) { //rename func to changepw } fn changeuser(args: &Vec) { + //! arguments: program changeuser [name] {new_username} if args.len() < 3 { println!("Invalid usage of \"changeuser\""); return; @@ -224,7 +259,8 @@ fn changeuser(args: &Vec) { } } -fn rename(args: &Vec) { // prog ren old new +fn rename(args: &Vec) { + //! arguments: program rename [name] [new_name] if args.len() < 4 { println!("Invalid usage of \"rename\""); return; @@ -239,6 +275,7 @@ fn rename(args: &Vec) { // prog ren old new } fn remove(args: &Vec) { + //! arguments: program remove [name] if args.len() < 3 { println!("Invalid usage of \"remove\""); return; @@ -259,6 +296,7 @@ fn remove(args: &Vec) { } fn import(args: &Vec) { + //! arguments: program import {location} let mut location = utils::get_home_folder_str(); if args.len() == 3 { location = args[2].clone(); @@ -268,42 +306,7 @@ fn import(args: &Vec) { return; } } - if std::path::Path::new(&(location.clone()+"/export.ipassx")).exists() { - let mut reader = brotli::Decompressor::new( - std::fs::File::open(location.clone()+"/export.ipassx").unwrap(), - 4096, // buffer size - ); - let mut content: String = String::new(); - let mut buf = [0u8; 4096]; - loop { - match reader.read(&mut buf[..]) { - Err(e) => { - if let std::io::ErrorKind::Interrupted = e.kind() { - continue; - } - panic!("{}", e); - } - Ok(size) => { - if size == 0 { - break; - } - content += &std::str::from_utf8(&buf[..size]).unwrap(); - } - } - } - - 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 = ""; - } + if utils::import_file(&location) { println!("Imported all entries!"); } else { println!("No such file found!"); @@ -312,6 +315,7 @@ fn import(args: &Vec) { } fn export(args: &Vec) { + //! arguments: program export {location} let mut location = utils::get_home_folder_str(); if args.len() == 3 { location = args[2].clone(); @@ -321,37 +325,11 @@ fn export(args: &Vec) { 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(); - } - } - - - - if let Ok(file) = std::fs::File::create(location.clone()+"/export.ipassx") { - let mut writer = brotli::CompressorWriter::new( - file, - 4096, - 11, - 22); - - match writer.write_all(collected_data.as_bytes()) { - Err(e) => panic!("{}", e), - Ok(_) => {}, - } - + if utils::export_file(&location) { println!("Saved at: '{}/export.ipassx'", location); } else { - println!("Failed saving at '{}/export.ipassx' does it exist?",location) + println!("Failed saving at '{}/export.ipassx' does it exist?",location); } - } fn clear() { @@ -368,4 +346,83 @@ fn clear() { } } println!("Cleared all entries!"); +} + +fn sync(args: &Vec) { + //! arguments: program sync [on/off] {location if on} + let arg: String; + if args.len() < 3 { + println!("Invalid usage of \"sync\""); + return; + } else { + arg = args[2].to_lowercase(); + } + + match arg.as_str() { + "on" => { + let location: String; + + if args.len() < 4 { + location = utils::prompt_answer("No location specified, please provide the location of the file you want to sync: ".to_string()); + } else { + location = args[3].clone(); + } + + let mut sync_file = std::fs::File::create(utils::get_home_folder_str()+"/.sync.ipass").expect("could not open sync file"); + sync_file.write(location.as_bytes()).expect("could not write to sync file"); + + if !utils::export_file(&location) { + eprintln!("Test sync error, make sure you specified a valid folder!"); + } else { + println!("Sync is now Enabled!"); + println!("Sync file: {}",location); + } + + + + + }, + "off" => { + + let sync_file_loc = utils::get_home_folder_str()+"/.sync.ipass"; + let sync_file_path = std::path::Path::new(&sync_file_loc); + + let sync_enabled = sync_file_path.clone().exists(); + + if sync_enabled { + + std::fs::remove_file(utils::get_home_folder_str()+"/.sync.ipass").expect("could not disable sync, is it already disabled?"); + + println!("Sync is now Disabled!"); + } else { + println!("Sync is already disabled!"); + } + }, + _ => { + println!("Invalid argument, check \"help\" for help"); + } + } +} + +fn isync(args: &Vec) { + //! arguments: program isync [on/off] + if args.len() > 2 { + println!("Invalid usage of \"isync\""); + return; + } + let arg: String = args[2].clone().to_lowercase(); + + match arg.as_str() { + "on" => { + todo!("ISync"); + + // println!("ISync is now Enabled!"); + }, + "off" => { + println!("ISync is now Disabled!"); + }, + _ => { + println!("Invalid argument, check \"help\" for help"); + } + } } \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index 132dd7b..e7d962f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use std::io::Write; +use std::io::{Read, Write}; use aes_gcm::{ aead::{Aead, KeyInit}, Aes256Gcm, Nonce @@ -9,6 +9,79 @@ pub fn get_args() -> Vec { std::env::args().collect() // [0] = file path; [n>0] = argument } +pub fn import_file(location:&String) -> bool { + if std::path::Path::new(&(location.clone()+"/export.ipassx")).exists() { + let mut reader = brotli::Decompressor::new( + std::fs::File::open(location.clone()+"/export.ipassx").unwrap(), + 4096, // buffer size + ); + let mut content: String = String::new(); + let mut buf = [0u8; 4096]; + loop { + match reader.read(&mut buf[..]) { + Err(e) => { + if let std::io::ErrorKind::Interrupted = e.kind() { + continue; + } + panic!("{}", e); + } + Ok(size) => { + if size == 0 { + break; + } + content += &std::str::from_utf8(&buf[..size]).unwrap(); + } + } + } + + 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",get_ipass_folder(), name)).unwrap(); + file.write_all(i.as_bytes()).unwrap(); + name = ""; + } + return true; + } else { + return false; + } +} + +pub fn export_file(location:&String) -> bool { + let mut collected_data: String = String::new(); + + let paths = std::fs::read_dir(get_ipass_folder()).unwrap(); + + for p in paths { + if let Ok(path) = p { + let content = &mut std::fs::read_to_string(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(); + } + } + + if let Ok(file) = std::fs::File::create(location.clone()+"/export.ipassx") { + let mut writer = brotli::CompressorWriter::new( + file, + 4096, + 11, + 22); + + match writer.write_all(collected_data.as_bytes()) { + Err(e) => panic!("{}", e), + Ok(_) => {}, + } + + return true; + } else { + return false; + } +} + fn vecu8_to_string(vec: Vec) -> String { let mut do_print_warning = false; let mut out: String = String::new();