diff --git a/CHANGELOG.md b/CHANGELOG.md index db09900..612117d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,14 @@ FIXME: Released on ??? +- **Keyring to store secrets** + - On both MacOS and Windows, the secret used to encrypt passwords in bookmarks it is now store in the OS secret vault. This provides much more security to store the password - Enhancements: - Added connection timeout to 30 seconds to SFTP/SCP clients and improved name lookup system. - Bugfix: - Solved index in explorer files list which was no more kept after 0.3.0 - SCP file transfer: fixed possible wrong file size when sending file, due to a possible incoherent size between the file explorer and the actual file size. +- Breaking changes: on **MacOS / Windows systems only**, the password you saved for bookmarks won't be working anymore if you have support for the keyring crate. Because of the migration to keyring, the previously used secret hasn't been migrated to the storage, instead a new secret will be used. To solve this, just save the bookmark again with the password. ## 0.3.0 diff --git a/Cargo.lock b/Cargo.lock index c1169a0..e3b522f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + [[package]] name = "aes-soft" version = "0.6.4" @@ -10,6 +21,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.15" @@ -205,16 +226,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.2", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.2" @@ -272,6 +309,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-mac" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +dependencies = [ + "generic-array 0.14.4", + "subtle", +] + +[[package]] +name = "dbus" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0c10ea61042b7555729ab0608727bbbb06ce709c11e6047cfa4e10f6d052d" +dependencies = [ + "libc", +] + [[package]] name = "debug-helper" version = "0.3.10" @@ -423,6 +479,26 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "hkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" +dependencies = [ + "digest 0.9.0", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -452,6 +528,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keyring" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bcd64f48199f69993c705fd2f76882e53969db93bc6345021bc8bb6462a9ffa" +dependencies = [ + "byteorder", + "secret-service", + "security-framework 0.4.4", + "winapi", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -594,8 +682,8 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", - "security-framework-sys", + "security-framework 2.0.0", + "security-framework-sys 2.0.0", "tempfile", ] @@ -608,6 +696,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -618,6 +740,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -908,6 +1053,35 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "secret-service" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d752040301c251d653aa740dec847e95767ce312cfc469bee85eb13cbf81d8a" +dependencies = [ + "aes", + "block-modes", + "dbus", + "hkdf", + "lazy_static", + "num", + "rand 0.7.3", + "sha2", +] + +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "core-foundation-sys 0.7.0", + "libc", + "security-framework-sys 0.4.3", +] + [[package]] name = "security-framework" version = "2.0.0" @@ -915,10 +1089,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" dependencies = [ "bitflags", - "core-foundation", - "core-foundation-sys", + "core-foundation 0.9.1", + "core-foundation-sys 0.8.2", + "libc", + "security-framework-sys 2.0.0", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +dependencies = [ + "core-foundation-sys 0.7.0", "libc", - "security-framework-sys", ] [[package]] @@ -927,7 +1111,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.2", "libc", ] @@ -1019,6 +1203,12 @@ dependencies = [ "parking_lot 0.10.2", ] +[[package]] +name = "subtle" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" + [[package]] name = "syn" version = "1.0.58" @@ -1058,6 +1248,7 @@ dependencies = [ "ftp4", "getopts", "hostname", + "keyring", "lazy_static", "magic-crypt", "rand 0.8.1", diff --git a/Cargo.toml b/Cargo.toml index 9e1578a..3182414 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,9 @@ whoami = "1.0.1" [target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies] users = "0.11.0" +[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies] +keyring = "0.10.1" + [[bin]] name = "termscp" path = "src/main.rs" diff --git a/README.md b/README.md index ab59bcc..1bf9150 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,12 @@ If you go to [gallery](#gallery-), there is a GIF showing how bookmarks work ### Are my passwords Safe 😈 Well, kinda. -As said before, bookmarks are saved in your configuration directory along with passwords. Passwords are obviously not plain text, they are encrypted with **AES-128**. Does this make them safe? Well, no, the key used to encrypt your passwords is generated at the first launch of termscp and stored on your drive. So it's still possible to retrieve the key to decrypt passwords. Luckily, the location of the key guarantees your key can't be read by users different from yours, but yeah, I still wouldn't save the password for a server exposed on the internet 😉. +As said before, bookmarks are saved in your configuration directory along with passwords. Passwords are obviously not plain text, they are encrypted with **AES-128**. Does this make them safe? Well, depends on your operating system: + +On Windows and MacOS the passwords are (if possible, but should be) in respectively the Windows Vault and the Keychain. This is actually super-safe and is directly managed by your operating system. + +On Linux and BSD, on the other hand the key used to encrypt your passwords is stored on your drive. So it's still possible to retrieve the key to decrypt passwords. Luckily, the location of the key guarantees your key can't be read by users different from yours, but yeah, I still wouldn't save the password for a server exposed on the internet 😉. +Actually [keyring-rs](https://github.com/hwchen/keyring-rs), supports Linux, but for different reasons I preferred not to make it available for this configuration. If you want to read more about my decision read [this issue](https://github.com/veeso/termscp/issues/2), while if you think this might have been implemented differently feel free to open an issue with your proposal. --- @@ -357,6 +362,7 @@ TermSCP is powered by these aweseome projects: - [bytesize](https://github.com/hyunsik/bytesize) - [crossterm](https://github.com/crossterm-rs/crossterm) - [edit](https://github.com/milkey-mouse/edit) +- [keyring-rs](https://github.com/hwchen/keyring-rs) - [rpassword](https://github.com/conradkleinespel/rpassword) - [rust-ftp](https://github.com/mattnenterprise/rust-ftp) - [ssh2-rs](https://github.com/alexcrichton/ssh2-rs) diff --git a/src/activity_manager.rs b/src/activity_manager.rs index d36b4c9..130381e 100644 --- a/src/activity_manager.rs +++ b/src/activity_manager.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -27,7 +27,7 @@ use std::path::PathBuf; // Deps use crate::filetransfer::FileTransferProtocol; -use crate::host::Localhost; +use crate::host::{HostError, Localhost}; use crate::ui::activities::{ auth_activity::AuthActivity, filetransfer_activity::FileTransferActivity, filetransfer_activity::FileTransferParams, setup_activity::SetupActivity, Activity, @@ -60,11 +60,11 @@ impl ActivityManager { /// ### new /// /// Initializes a new Activity Manager - pub fn new(local_dir: &PathBuf, interval: Duration) -> Result { + pub fn new(local_dir: &PathBuf, interval: Duration) -> Result { // Prepare Context let host: Localhost = match Localhost::new(local_dir.clone()) { Ok(h) => h, - Err(_) => return Err(()), + Err(e) => return Err(e), }; let ctx: Context = Context::new(host); Ok(ActivityManager { diff --git a/src/bookmarks/mod.rs b/src/bookmarks/mod.rs index 331d412..6171916 100644 --- a/src/bookmarks/mod.rs +++ b/src/bookmarks/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/bookmarks/serializer.rs b/src/bookmarks/serializer.rs index 4835f26..5204b2e 100644 --- a/src/bookmarks/serializer.rs +++ b/src/bookmarks/serializer.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/config/mod.rs b/src/config/mod.rs index 6178a51..05f23a8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/config/serializer.rs b/src/config/serializer.rs index e2d62a3..89334d1 100644 --- a/src/config/serializer.rs +++ b/src/config/serializer.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 7fa03b5..8c1d065 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -180,10 +180,12 @@ impl FtpFileTransfer { Err(_) => None, }; // Get filesize - let filesize: usize = match metadata.get(6).unwrap().as_str().parse::() { - Ok(sz) => sz, - Err(_) => 0, - }; + let filesize: usize = metadata + .get(6) + .unwrap() + .as_str() + .parse::() + .unwrap_or(0); let file_name: String = String::from(metadata.get(8).unwrap().as_str()); // Check if file_name is '.' or '..' if file_name.as_str() == "." || file_name.as_str() == ".." { @@ -270,10 +272,7 @@ impl FtpFileTransfer { true => 0, // If is directory, filesize is 0 false => match metadata.get(3) { // If is file, parse arg 3 - Some(val) => match val.as_str().parse::() { - Ok(sz) => sz, - Err(_) => 0, - }, + Some(val) => val.as_str().parse::().unwrap_or(0), None => 0, // Should not happen }, }; diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 92d5af5..3a561f4 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index 13f1a43..db2d2c2 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -167,10 +167,7 @@ impl ScpFileTransfer { Err(_) => None, }; // Get filesize - let filesize: usize = match metadata.get(6).unwrap().as_str().parse::() { - Ok(sz) => sz, - Err(_) => 0, - }; + let filesize: usize = metadata.get(6).unwrap().as_str().parse::().unwrap_or(0); // Get link and name let (file_name, symlink_path): (String, Option) = match is_symlink { true => self.get_name_and_link(metadata.get(8).unwrap().as_str()), diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 38e1edb..c7ce56d 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/fs/explorer/builder.rs b/src/fs/explorer/builder.rs index 2e16dcc..41ba028 100644 --- a/src/fs/explorer/builder.rs +++ b/src/fs/explorer/builder.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 73011e0..ffc8ea3 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -30,6 +30,7 @@ extern crate bitflags; // Locals use super::FsEntry; // Ext +use std::cmp::Reverse; use std::collections::VecDeque; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -238,16 +239,14 @@ impl FileExplorer { /// /// Sort files by creation time; the newest comes first fn sort_files_by_creation_time(&mut self) { - self.files - .sort_by(|a: &FsEntry, b: &FsEntry| b.get_creation_time().cmp(&a.get_creation_time())); + self.files.sort_by_key(|b: &FsEntry| Reverse(b.get_creation_time())); } /// ### sort_files_by_size /// /// Sort files by size fn sort_files_by_size(&mut self) { - self.files - .sort_by(|a: &FsEntry, b: &FsEntry| b.get_size().cmp(&a.get_size())); + self.files.sort_by_key(|b: &FsEntry| Reverse(b.get_size())); } /// ### sort_files_directories_first diff --git a/src/fs/mod.rs b/src/fs/mod.rs index ea718a9..96f14e8 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/host/mod.rs b/src/host/mod.rs index 2411991..9aec7c1 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/lib.rs b/src/lib.rs index d65280e..280e257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/main.rs b/src/main.rs index f7f8a9b..76d4580 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -167,8 +167,8 @@ fn main() { // Create activity manager (and context too) let mut manager: ActivityManager = match ActivityManager::new(&wrkdir, ticks) { Ok(m) => m, - Err(_) => { - eprintln!("Invalid directory '{}'", wrkdir.display()); + Err(err) => { + eprintln!("Could not start activity manager: {}", err); std::process::exit(255); } }; diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index e071dbc..3b7aac9 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -23,6 +23,12 @@ * */ +// Deps +extern crate whoami; +// Crate +#[cfg(any(target_os = "windows", target_os = "macos"))] +use super::keys::keyringstorage::KeyringStorage; +use super::keys::{filestorage::FileStorage, KeyStorage, KeyStorageError}; // Local use crate::bookmarks::serializer::BookmarkSerializer; use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts}; @@ -31,8 +37,7 @@ use crate::utils::crypto; use crate::utils::fmt::fmt_time; use crate::utils::random::random_alphanumeric_with_len; // Ext -use std::fs::{OpenOptions, Permissions}; -use std::io::{Read, Write}; +use std::fs::OpenOptions; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::string::ToString; @@ -53,23 +58,60 @@ impl BookmarksClient { /// /// Instantiates a new BookmarksClient /// Bookmarks file path must be provided - /// Key file must be provided + /// Storage path for file provider must be provided pub fn new( bookmarks_file: &Path, - key_file: &Path, + storage_path: &Path, recents_size: usize, ) -> Result { // Create default hosts let default_hosts: UserHosts = Default::default(); - // If key file doesn't exist, create key, otherwise read it - let key: String = match key_file.exists() { - true => match BookmarksClient::load_key(key_file) { - Ok(key) => key, - Err(err) => return Err(err), - }, - false => match BookmarksClient::generate_key(key_file) { - Ok(key) => key, - Err(err) => return Err(err), + // Make a key storage (windows / macos) + #[cfg(any(target_os = "windows", target_os = "macos"))] + let (key_storage, service_id): (Box, &str) = { + let username: String = whoami::username(); + let storage: KeyringStorage = KeyringStorage::new(username.as_str()); + // Check if keyring storage is supported + #[cfg(not(test))] + let app_name: &str = "termscp"; + #[cfg(test)] // NOTE: when running test, add -test + let app_name: &str = "termscp-test"; + match storage.is_supported() { + true => (Box::new(storage), app_name), + false => (Box::new(FileStorage::new(storage_path)), "bookmarks"), + } + }; + // Make a key storage (linux / unix) + #[cfg(any(target_os = "linux", target_os = "unix"))] + let (key_storage, service_id): (Box, &str) = { + #[cfg(not(test))] + let app_name: &str = "bookmarks"; + #[cfg(test)] // NOTE: when running test, add -test + let app_name: &str = "bookmarks-test"; + (Box::new(FileStorage::new(storage_path)), app_name) + }; + // Load key + let key: String = match key_storage.get_key(service_id) { + Ok(k) => k, + Err(e) => match e { + KeyStorageError::NoSuchKey => { + // If no such key, generate key and set it into the storage + let key: String = Self::generate_key(); + if let Err(e) = key_storage.set_key(service_id, key.as_str()) { + return Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + format!("Could not write key to storage: {}", e), + )); + } + // Return key + key + } + _ => { + return Err(SerializerError::new_ex( + SerializerErrorKind::IoError, + format!("Could not get key from storage: {}", e), + )) + } }, }; let mut client: BookmarksClient = BookmarksClient { @@ -276,36 +318,10 @@ impl BookmarksClient { /// ### generate_key /// - /// Generate a new AES key and write it to key file - fn generate_key(key_file: &Path) -> Result { + /// Generate a new AES key + fn generate_key() -> String { // Generate 256 bytes (2048 bits) key - let key: String = random_alphanumeric_with_len(256); - // Write file - match OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(key_file) - { - Ok(mut file) => { - // Write key to file - if let Err(err) = file.write_all(key.as_bytes()) { - return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, - err.to_string(), - )); - } - // Set file to readonly - let mut permissions: Permissions = file.metadata().unwrap().permissions(); - permissions.set_readonly(true); - let _ = file.set_permissions(permissions); - Ok(key) - } - Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::IoError, - err.to_string(), - )), - } + random_alphanumeric_with_len(256) } /// ### make_bookmark @@ -331,28 +347,6 @@ impl BookmarksClient { } } - /// ### load_key - /// - /// Load key from key_file - fn load_key(key_file: &Path) -> Result { - match OpenOptions::new().read(true).open(key_file) { - Ok(mut file) => { - let mut key: String = String::with_capacity(256); - match file.read_to_string(&mut key) { - Ok(_) => Ok(key), - Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::IoError, - err.to_string(), - )), - } - } - Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::IoError, - err.to_string(), - )), - } - } - /// ### encrypt_str /// /// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64 @@ -375,6 +369,7 @@ impl BookmarksClient { } #[cfg(test)] +#[cfg(not(target_os = "macos"))] // CI/CD blocks mod tests { use super::*; @@ -382,6 +377,7 @@ mod tests { use std::time::Duration; #[test] + fn test_system_bookmarks_new() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -397,6 +393,7 @@ mod tests { } #[test] + #[cfg(any(target_os = "unix", target_os = "linux"))] fn test_system_bookmarks_new_err() { assert!(BookmarksClient::new( Path::new("/tmp/oifoif/omar"), @@ -413,6 +410,7 @@ mod tests { } #[test] + fn test_system_bookmarks_new_from_existing() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -458,6 +456,7 @@ mod tests { } #[test] + fn test_system_bookmarks_manipulate_bookmarks() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -503,6 +502,7 @@ mod tests { #[test] #[should_panic] + fn test_system_bookmarks_bad_bookmark_name() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -521,6 +521,7 @@ mod tests { } #[test] + fn test_system_bookmarks_manipulate_recents() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -555,6 +556,7 @@ mod tests { } #[test] + fn test_system_bookmarks_dup_recent() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -579,6 +581,7 @@ mod tests { } #[test] + fn test_system_bookmarks_recents_more_than_limit() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -626,6 +629,7 @@ mod tests { #[test] #[should_panic] + fn test_system_bookmarks_add_bookmark_empty() { let tmp_dir: tempfile::TempDir = create_tmp_dir(); let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); @@ -646,10 +650,10 @@ mod tests { /// ### get_paths /// /// Get paths for configuration and key for bookmarks + fn get_paths(dir: &Path) -> (PathBuf, PathBuf) { - let mut k: PathBuf = PathBuf::from(dir); + let k: PathBuf = PathBuf::from(dir); let mut c: PathBuf = k.clone(); - k.push("bookmarks.key"); c.push("bookmarks.toml"); (c, k) } @@ -657,6 +661,7 @@ mod tests { /// ### create_tmp_dir /// /// Create temporary directory + fn create_tmp_dir() -> tempfile::TempDir { tempfile::TempDir::new().ok().unwrap() } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 16d034a..50a1061 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/system/environment.rs b/src/system/environment.rs index 29798b3..b9c7973 100644 --- a/src/system/environment.rs +++ b/src/system/environment.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -59,14 +59,12 @@ pub fn init_config_dir() -> Result, String> { /// ### get_bookmarks_paths /// /// Get paths for bookmarks client -/// Returns: path of bookmarks.toml and path of key -pub fn get_bookmarks_paths(config_dir: &Path) -> (PathBuf, PathBuf) { +/// Returns: path of bookmarks.toml +pub fn get_bookmarks_paths(config_dir: &Path) -> PathBuf { // Prepare paths let mut bookmarks_file: PathBuf = PathBuf::from(config_dir); bookmarks_file.push("bookmarks.toml"); - let mut key_file: PathBuf = PathBuf::from(config_dir); - key_file.push(".bookmarks.key"); // key file is hidden - (bookmarks_file, key_file) + bookmarks_file } /// ### get_config_paths @@ -123,10 +121,7 @@ mod tests { fn test_system_environment_get_bookmarks_paths() { assert_eq!( get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")), - ( - PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"), - PathBuf::from("/home/omar/.config/termscp/.bookmarks.key") - ) + PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"), ); } diff --git a/src/system/keys/filestorage.rs b/src/system/keys/filestorage.rs new file mode 100644 index 0000000..fe9e921 --- /dev/null +++ b/src/system/keys/filestorage.rs @@ -0,0 +1,163 @@ +//! ## FileStorage +//! +//! `filestorage` provides an implementation of the `KeyStorage` trait using a file + +/* +* +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "TermSCP" +* +* TermSCP is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* TermSCP is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with TermSCP. If not, see . +* +*/ + +// Local +use super::{KeyStorage, KeyStorageError}; +// Ext +use std::fs::{OpenOptions, Permissions}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +/// ## FileStorage +/// +/// File storage is an implementation o the `KeyStorage` which uses a file to store the key +pub struct FileStorage { + dir_path: PathBuf, +} + +impl FileStorage { + /// ### new + /// + /// Instantiates a new `FileStorage` + pub fn new(dir_path: &Path) -> Self { + FileStorage { + dir_path: PathBuf::from(dir_path), + } + } + + /// ### make_file_path + /// + /// Make file path for key file from `dir_path` and the application id + fn make_file_path(&self, storage_id: &str) -> PathBuf { + let mut p: PathBuf = self.dir_path.clone(); + let file_name = format!(".{}.key", storage_id); + p.push(file_name); + p + } +} + +impl KeyStorage for FileStorage { + /// ### get_key + /// + /// Retrieve key from the key storage. + /// The key might be acccess through an identifier, which identifies + /// the key in the storage + fn get_key(&self, storage_id: &str) -> Result { + let key_file: PathBuf = self.make_file_path(storage_id); + // Check if file exists + if !key_file.exists() { + return Err(KeyStorageError::NoSuchKey); + } + // Read key from file + match OpenOptions::new().read(true).open(key_file.as_path()) { + Ok(mut file) => { + let mut key: String = String::new(); + match file.read_to_string(&mut key) { + Ok(_) => Ok(key), + Err(_) => Err(KeyStorageError::ProviderError), + } + } + Err(_) => Err(KeyStorageError::ProviderError), + } + } + + /// ### set_key + /// + /// Set the key into the key storage + fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError> { + let key_file: PathBuf = self.make_file_path(storage_id); + // Write key + match OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(key_file.as_path()) + { + Ok(mut file) => { + // Write key to file + if file.write_all(key.as_bytes()).is_err() { + return Err(KeyStorageError::ProviderError); + } + // Set file to readonly + let mut permissions: Permissions = file.metadata().unwrap().permissions(); + permissions.set_readonly(true); + let _ = file.set_permissions(permissions); + Ok(()) + } + Err(_) => Err(KeyStorageError::ProviderError), + } + } + + /// is_supported + /// + /// Returns whether the key storage is supported on the host system + fn is_supported(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_system_keys_filestorage_make_dir() { + let storage: FileStorage = FileStorage::new(&Path::new("/tmp/")); + assert_eq!( + storage.make_file_path("bookmarks").as_path(), + Path::new("/tmp/.bookmarks.key") + ); + } + + #[test] + fn test_system_keys_filestorage_ok() { + let key_dir: tempfile::TempDir = + tempfile::TempDir::new().expect("Could not create tempdir"); + let storage: FileStorage = FileStorage::new(key_dir.path()); + // Supported + assert!(storage.is_supported()); + let app_name: &str = "termscp"; + let secret: &str = "Th15-15/My-Супер-Секрет"; + // Secret should not exist + assert_eq!( + storage.get_key(app_name).err().unwrap(), + KeyStorageError::NoSuchKey + ); + // Write secret + assert!(storage.set_key(app_name, secret).is_ok()); + // Get secret + assert_eq!(storage.get_key(app_name).ok().unwrap().as_str(), secret); + } + + #[test] + fn test_system_keys_filestorage_err() { + let bad_dir: &Path = Path::new("/piro/poro/pero/"); + let storage: FileStorage = FileStorage::new(bad_dir); + let app_name: &str = "termscp"; + let secret: &str = "Th15-15/My-Супер-Секрет"; + assert!(storage.set_key(app_name, secret).is_err()); + } +} diff --git a/src/system/keys/keyringstorage.rs b/src/system/keys/keyringstorage.rs new file mode 100644 index 0000000..0e3662d --- /dev/null +++ b/src/system/keys/keyringstorage.rs @@ -0,0 +1,129 @@ +//! ## KeyringStorage +//! +//! `keyringstorage` provides an implementation of the `KeyStorage` trait using the OS keyring + +/* +* +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "TermSCP" +* +* TermSCP is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* TermSCP is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with TermSCP. If not, see . +* +*/ + +// Deps +extern crate keyring; +// Local +use super::{KeyStorage, KeyStorageError}; +// Ext +use keyring::{Keyring, KeyringError}; + +/// ## KeyringStorage +/// +/// provides a `KeyStorage` implementation using the keyring crate +pub struct KeyringStorage { + username: String, +} + +impl KeyringStorage { + /// ### new + /// + /// Instantiates a new KeyringStorage + pub fn new(username: &str) -> Self { + KeyringStorage { + username: username.to_string(), + } + } +} + +impl KeyStorage for KeyringStorage { + /// ### get_key + /// + /// Retrieve key from the key storage. + /// The key might be acccess through an identifier, which identifies + /// the key in the storage + fn get_key(&self, storage_id: &str) -> Result { + let storage: Keyring = Keyring::new(storage_id, self.username.as_str()); + match storage.get_password() { + Ok(s) => Ok(s), + Err(e) => match e { + KeyringError::NoPasswordFound => Err(KeyStorageError::NoSuchKey), + #[cfg(target_os = "windows")] + KeyringError::WindowsVaultError => Err(KeyStorageError::NoSuchKey), + #[cfg(target_os = "macos")] + KeyringError::MacOsKeychainError(_) => Err(KeyStorageError::NoSuchKey), + _ => panic!("{}", e), + }, + } + } + + /// ### set_key + /// + /// Set the key into the key storage + fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError> { + let storage: Keyring = Keyring::new(storage_id, self.username.as_str()); + match storage.set_password(key) { + Ok(_) => Ok(()), + Err(_) => Err(KeyStorageError::ProviderError), + } + } + + /// is_supported + /// + /// Returns whether the key storage is supported on the host system + fn is_supported(&self) -> bool { + let dummy: String = String::from("dummy-service"); + let storage: Keyring = Keyring::new(dummy.as_str(), self.username.as_str()); + // Check what kind of error is returned + match storage.get_password() { + Ok(_) => true, + Err(err) => !matches!(err, KeyringError::NoBackendFound), + } + } +} + +#[cfg(test)] +mod tests { + + extern crate whoami; + use super::*; + + use whoami::username; + + #[test] + fn test_system_keys_keyringstorage() { + let username: String = username(); + let storage: KeyringStorage = KeyringStorage::new(username.as_str()); + assert!(storage.is_supported()); + let app_name: &str = "termscp-test2"; + let secret: &str = "Th15-15/My-Супер-Секрет"; + let kring: Keyring = Keyring::new(app_name, username.as_str()); + let _ = kring.delete_password(); + drop(kring); + // Secret should not exist + assert_eq!( + storage.get_key(app_name).err().unwrap(), + KeyStorageError::NoSuchKey + ); + // Write secret + assert!(storage.set_key(app_name, secret).is_ok()); + // Get secret + assert_eq!(storage.get_key(app_name).ok().unwrap().as_str(), secret); + + // Delete the key manually... + let kring: Keyring = Keyring::new(app_name, username.as_str()); + assert!(kring.delete_password().is_ok()); + } +} diff --git a/src/system/keys/mod.rs b/src/system/keys/mod.rs new file mode 100644 index 0000000..02c66b7 --- /dev/null +++ b/src/system/keys/mod.rs @@ -0,0 +1,90 @@ +//! ## KeyStorage +//! +//! `keystorage` provides the trait to manipulate to a KeyStorage + +/* +* +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "TermSCP" +* +* TermSCP is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* TermSCP is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with TermSCP. If not, see . +* +*/ + +// Storages +pub mod filestorage; +#[cfg(any(target_os = "windows", target_os = "macos"))] +pub mod keyringstorage; + +/// ## KeyStorageError +/// +/// defines the error type for the `KeyStorage` +#[derive(PartialEq, std::fmt::Debug)] +pub enum KeyStorageError { + //BadKey, + ProviderError, + NoSuchKey, +} + +impl std::fmt::Display for KeyStorageError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let err: String = String::from(match &self { + //KeyStorageError::BadKey => "Bad key syntax", + KeyStorageError::ProviderError => "Provider service error", + KeyStorageError::NoSuchKey => "No such key", + }); + write!(f, "{}", err) + } +} + +/// ## KeyStorage +/// +/// this traits provides the methods to communicate and interact with the key storage. +pub trait KeyStorage { + /// ### get_key + /// + /// Retrieve key from the key storage. + /// The key might be acccess through an identifier, which identifies + /// the key in the storage + fn get_key(&self, storage_id: &str) -> Result; + + /// ### set_key + /// + /// Set the key into the key storage + fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError>; + + /// is_supported + /// + /// Returns whether the key storage is supported on the host system + fn is_supported(&self) -> bool; +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_system_keys_mod_errors() { + assert_eq!( + format!("{}", KeyStorageError::ProviderError), + String::from("Provider service error") + ); + assert_eq!( + format!("{}", KeyStorageError::NoSuchKey), + String::from("No such key") + ); + } +} diff --git a/src/system/mod.rs b/src/system/mod.rs index b31a84c..371d408 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -27,4 +27,5 @@ pub mod bookmarks_client; pub mod config_client; pub mod environment; +pub(crate) mod keys; pub mod sshkey_storage; diff --git a/src/system/sshkey_storage.rs b/src/system/sshkey_storage.rs index 20eb8b2..cab222e 100644 --- a/src/system/sshkey_storage.rs +++ b/src/system/sshkey_storage.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/auth_activity/bookmarks.rs b/src/ui/activities/auth_activity/bookmarks.rs index f035b9d..0e2f99c 100644 --- a/src/ui/activities/auth_activity/bookmarks.rs +++ b/src/ui/activities/auth_activity/bookmarks.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * @@ -224,11 +224,15 @@ impl AuthActivity { match environment::init_config_dir() { Ok(path) => { // If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system. - if let Some(path) = path { - let (bookmarks_file, key_file): (PathBuf, PathBuf) = - environment::get_bookmarks_paths(path.as_path()); + if let Some(config_dir_path) = path { + let bookmarks_file: PathBuf = + environment::get_bookmarks_paths(config_dir_path.as_path()); // Initialize client - match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path(), 16) { + match BookmarksClient::new( + bookmarks_file.as_path(), + config_dir_path.as_path(), + 16, + ) { Ok(cli) => self.bookmarks_client = Some(cli), Err(err) => { self.popup = Some(Popup::Alert( @@ -236,7 +240,7 @@ impl AuthActivity { format!( "Could not initialize bookmarks (at \"{}\", \"{}\"): {}", bookmarks_file.display(), - key_file.display(), + config_dir_path.display(), err ), )) diff --git a/src/ui/activities/auth_activity/callbacks.rs b/src/ui/activities/auth_activity/callbacks.rs index 645f6ca..db4bf1e 100644 --- a/src/ui/activities/auth_activity/callbacks.rs +++ b/src/ui/activities/auth_activity/callbacks.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/auth_activity/input.rs b/src/ui/activities/auth_activity/input.rs index 6f9fb33..e939503 100644 --- a/src/ui/activities/auth_activity/input.rs +++ b/src/ui/activities/auth_activity/input.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/auth_activity/layout.rs b/src/ui/activities/auth_activity/layout.rs index 7368bc3..a779aa1 100644 --- a/src/ui/activities/auth_activity/layout.rs +++ b/src/ui/activities/auth_activity/layout.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index f1fa96c..95de2b9 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/filetransfer_activity/callbacks.rs b/src/ui/activities/filetransfer_activity/callbacks.rs index 702082c..7dbf8e6 100644 --- a/src/ui/activities/filetransfer_activity/callbacks.rs +++ b/src/ui/activities/filetransfer_activity/callbacks.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/filetransfer_activity/input.rs b/src/ui/activities/filetransfer_activity/input.rs index 23ecc83..8abc8fc 100644 --- a/src/ui/activities/filetransfer_activity/input.rs +++ b/src/ui/activities/filetransfer_activity/input.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/filetransfer_activity/layout.rs b/src/ui/activities/filetransfer_activity/layout.rs index 4cf3600..641a2f6 100644 --- a/src/ui/activities/filetransfer_activity/layout.rs +++ b/src/ui/activities/filetransfer_activity/layout.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/filetransfer_activity/misc.rs b/src/ui/activities/filetransfer_activity/misc.rs index 3c0c286..e9cb9dd 100644 --- a/src/ui/activities/filetransfer_activity/misc.rs +++ b/src/ui/activities/filetransfer_activity/misc.rs @@ -1,6 +1,6 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/filetransfer_activity/mod.rs b/src/ui/activities/filetransfer_activity/mod.rs index e6b9aeb..e011bb3 100644 --- a/src/ui/activities/filetransfer_activity/mod.rs +++ b/src/ui/activities/filetransfer_activity/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/filetransfer_activity/session.rs b/src/ui/activities/filetransfer_activity/session.rs index 9ef9660..ad702a1 100644 --- a/src/ui/activities/filetransfer_activity/session.rs +++ b/src/ui/activities/filetransfer_activity/session.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/mod.rs b/src/ui/activities/mod.rs index dc188ab..70bf591 100644 --- a/src/ui/activities/mod.rs +++ b/src/ui/activities/mod.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/setup_activity/callbacks.rs b/src/ui/activities/setup_activity/callbacks.rs index 98205ce..9074124 100644 --- a/src/ui/activities/setup_activity/callbacks.rs +++ b/src/ui/activities/setup_activity/callbacks.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/setup_activity/config.rs b/src/ui/activities/setup_activity/config.rs index 7867c79..fad56d8 100644 --- a/src/ui/activities/setup_activity/config.rs +++ b/src/ui/activities/setup_activity/config.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/setup_activity/input.rs b/src/ui/activities/setup_activity/input.rs index 38ab98d..cdd7894 100644 --- a/src/ui/activities/setup_activity/input.rs +++ b/src/ui/activities/setup_activity/input.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/setup_activity/layout.rs b/src/ui/activities/setup_activity/layout.rs index 45ef475..e951f75 100644 --- a/src/ui/activities/setup_activity/layout.rs +++ b/src/ui/activities/setup_activity/layout.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/setup_activity/misc.rs b/src/ui/activities/setup_activity/misc.rs index 695c015..025cc5c 100644 --- a/src/ui/activities/setup_activity/misc.rs +++ b/src/ui/activities/setup_activity/misc.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/activities/setup_activity/mod.rs b/src/ui/activities/setup_activity/mod.rs index 724d06c..57ef6ce 100644 --- a/src/ui/activities/setup_activity/mod.rs +++ b/src/ui/activities/setup_activity/mod.rs @@ -5,7 +5,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/context.rs b/src/ui/context.rs index df382f5..1e68941 100644 --- a/src/ui/context.rs +++ b/src/ui/context.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/input.rs b/src/ui/input.rs index bb7931b..87835b6 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 31da84e..0e8d9c8 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/utils/crypto.rs b/src/utils/crypto.rs index 68faa45..f3dcb08 100644 --- a/src/utils/crypto.rs +++ b/src/utils/crypto.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 939ee02..498f94b 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/utils/mod.rs b/src/utils/mod.rs index cb298e1..ea844e7 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 271f4aa..b2ca6e9 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" * diff --git a/src/utils/random.rs b/src/utils/random.rs index 335f0ae..8622b4c 100644 --- a/src/utils/random.rs +++ b/src/utils/random.rs @@ -4,7 +4,7 @@ /* * -* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com +* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com * * This file is part of "TermSCP" *