mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Bookmarks client
This commit is contained in:
@@ -24,17 +24,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Deps
|
// Deps
|
||||||
|
extern crate magic_crypt;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
|
||||||
// Local
|
// Local
|
||||||
use crate::bookmarks::serializer::BookmarkSerializer;
|
use crate::bookmarks::serializer::BookmarkSerializer;
|
||||||
use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts};
|
use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts};
|
||||||
use crate::filetransfer::FileTransferProtocol;
|
use crate::filetransfer::FileTransferProtocol;
|
||||||
|
use crate::utils::time_to_str;
|
||||||
// Ext
|
// Ext
|
||||||
|
use magic_crypt::MagicCryptTrait;
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
use std::fs::{OpenOptions, Permissions};
|
use std::fs::{OpenOptions, Permissions};
|
||||||
use std::io::Write;
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
/// ## BookmarksClient
|
/// ## BookmarksClient
|
||||||
///
|
///
|
||||||
@@ -42,7 +46,7 @@ use std::path::{Path, PathBuf};
|
|||||||
pub struct BookmarksClient {
|
pub struct BookmarksClient {
|
||||||
pub hosts: UserHosts,
|
pub hosts: UserHosts,
|
||||||
bookmarks_file: PathBuf,
|
bookmarks_file: PathBuf,
|
||||||
key_file: PathBuf,
|
key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BookmarksClient {
|
impl BookmarksClient {
|
||||||
@@ -54,10 +58,21 @@ impl BookmarksClient {
|
|||||||
pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result<BookmarksClient, SerializerError> {
|
pub fn new(bookmarks_file: &Path, key_file: &Path) -> Result<BookmarksClient, SerializerError> {
|
||||||
// Create default hosts
|
// Create default hosts
|
||||||
let default_hosts: UserHosts = Default::default();
|
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),
|
||||||
|
},
|
||||||
|
};
|
||||||
let client: BookmarksClient = BookmarksClient {
|
let client: BookmarksClient = BookmarksClient {
|
||||||
hosts: default_hosts,
|
hosts: default_hosts,
|
||||||
bookmarks_file: PathBuf::from(bookmarks_file),
|
bookmarks_file: PathBuf::from(bookmarks_file),
|
||||||
key_file: PathBuf::from(key_file),
|
key,
|
||||||
};
|
};
|
||||||
// If bookmark file doesn't exist, initialize it
|
// If bookmark file doesn't exist, initialize it
|
||||||
if !bookmarks_file.exists() {
|
if !bookmarks_file.exists() {
|
||||||
@@ -65,12 +80,7 @@ impl BookmarksClient {
|
|||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If key file doesn't exist, create key
|
// Load key
|
||||||
if !key_file.exists() {
|
|
||||||
if let Err(err) = client.generate_key() {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +92,6 @@ impl BookmarksClient {
|
|||||||
key: &String,
|
key: &String,
|
||||||
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
|
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
|
||||||
let entry: &Bookmark = self.hosts.bookmarks.get(key)?;
|
let entry: &Bookmark = self.hosts.bookmarks.get(key)?;
|
||||||
// TODO: decrypt password
|
|
||||||
Some((
|
Some((
|
||||||
entry.address.clone(),
|
entry.address.clone(),
|
||||||
entry.port,
|
entry.port,
|
||||||
@@ -93,58 +102,91 @@ impl BookmarksClient {
|
|||||||
_ => FileTransferProtocol::Sftp,
|
_ => FileTransferProtocol::Sftp,
|
||||||
},
|
},
|
||||||
entry.username.clone(),
|
entry.username.clone(),
|
||||||
None,
|
match entry.password {
|
||||||
))
|
// Decrypted password if Some; if decryption fails return None
|
||||||
}
|
Some(pwd) => match self.decrypt_str(pwd.as_str()) {
|
||||||
|
Ok(decrypted_pwd) => Some(decrypted_pwd),
|
||||||
/// ### get_recent
|
Err(_) => None,
|
||||||
///
|
},
|
||||||
/// Get recent associated to key
|
None => None,
|
||||||
pub fn get_recent(
|
|
||||||
&self,
|
|
||||||
key: &String,
|
|
||||||
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
|
|
||||||
let entry: &Bookmark = self.hosts.recents.get(key)?;
|
|
||||||
// TODO: decrypt password
|
|
||||||
Some((
|
|
||||||
entry.address.clone(),
|
|
||||||
entry.port,
|
|
||||||
match entry.protocol.to_ascii_uppercase().as_str() {
|
|
||||||
"FTP" => FileTransferProtocol::Ftp(false),
|
|
||||||
"FTPS" => FileTransferProtocol::Ftp(true),
|
|
||||||
"SCP" => FileTransferProtocol::Scp,
|
|
||||||
_ => FileTransferProtocol::Sftp,
|
|
||||||
},
|
},
|
||||||
entry.username.clone(),
|
|
||||||
None,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### make_bookmark
|
/// ### add_recent
|
||||||
///
|
///
|
||||||
/// Make bookmark from credentials
|
/// Add a new recent to bookmarks
|
||||||
pub fn make_bookmark(
|
pub fn add_bookmark(
|
||||||
&self,
|
&mut self,
|
||||||
|
name: String,
|
||||||
addr: String,
|
addr: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
protocol: FileTransferProtocol,
|
protocol: FileTransferProtocol,
|
||||||
username: String,
|
username: String,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
) -> Bookmark {
|
) {
|
||||||
// TODO: crypt password
|
// Make bookmark
|
||||||
Bookmark {
|
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password);
|
||||||
address: addr,
|
self.hosts.bookmarks.insert(name, host);
|
||||||
port,
|
}
|
||||||
username,
|
|
||||||
protocol: match protocol {
|
/// ### get_recent
|
||||||
FileTransferProtocol::Ftp(secure) => match secure {
|
///
|
||||||
true => String::from("FTPS"),
|
/// Get recent associated to key
|
||||||
false => String::from("FTP"),
|
pub fn get_recent(&self, key: &String) -> Option<(String, u16, FileTransferProtocol, String)> {
|
||||||
},
|
// NOTE: password is not decrypted; recents will never have password
|
||||||
FileTransferProtocol::Scp => String::from("SCP"),
|
let entry: &Bookmark = self.hosts.recents.get(key)?;
|
||||||
FileTransferProtocol::Sftp => String::from("SFTP"),
|
Some((
|
||||||
|
entry.address.clone(),
|
||||||
|
entry.port,
|
||||||
|
match entry.protocol.to_ascii_uppercase().as_str() {
|
||||||
|
"FTP" => FileTransferProtocol::Ftp(false),
|
||||||
|
"FTPS" => FileTransferProtocol::Ftp(true),
|
||||||
|
"SCP" => FileTransferProtocol::Scp,
|
||||||
|
_ => FileTransferProtocol::Sftp,
|
||||||
},
|
},
|
||||||
|
entry.username.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### add_recent
|
||||||
|
///
|
||||||
|
/// Add a new recent to bookmarks
|
||||||
|
pub fn add_recent(
|
||||||
|
&mut self,
|
||||||
|
addr: String,
|
||||||
|
port: u16,
|
||||||
|
protocol: FileTransferProtocol,
|
||||||
|
username: String,
|
||||||
|
) {
|
||||||
|
// Make bookmark
|
||||||
|
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, None);
|
||||||
|
// Check if duplicated
|
||||||
|
for recent_host in self.hosts.recents.values() {
|
||||||
|
if *recent_host == host {
|
||||||
|
// Don't save duplicates
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// If hosts size is bigger than 16; pop last
|
||||||
|
if self.hosts.recents.len() >= 16 {
|
||||||
|
let mut keys: Vec<String> = Vec::with_capacity(self.hosts.recents.len());
|
||||||
|
for key in self.hosts.recents.keys() {
|
||||||
|
keys.push(key.clone());
|
||||||
|
}
|
||||||
|
// Sort keys; NOTE: most recent is the last element
|
||||||
|
keys.sort();
|
||||||
|
// Delete keys starting from the last one
|
||||||
|
for key in keys.iter() {
|
||||||
|
let _ = self.hosts.recents.remove(key);
|
||||||
|
// If length is < 16; break
|
||||||
|
if self.hosts.recents.len() < 16 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let name: String = time_to_str(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
|
||||||
|
self.hosts.recents.insert(name, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### write_bookmarks
|
/// ### write_bookmarks
|
||||||
@@ -172,7 +214,7 @@ impl BookmarksClient {
|
|||||||
/// ### generate_key
|
/// ### generate_key
|
||||||
///
|
///
|
||||||
/// Generate a new AES key and write it to key file
|
/// Generate a new AES key and write it to key file
|
||||||
fn generate_key(&self) -> Result<(), SerializerError> {
|
fn generate_key(key_file: &Path) -> Result<String, SerializerError> {
|
||||||
// Generate 256 bytes (2048 bits) key
|
// Generate 256 bytes (2048 bits) key
|
||||||
let key: String = rand::thread_rng()
|
let key: String = rand::thread_rng()
|
||||||
.sample_iter(Alphanumeric)
|
.sample_iter(Alphanumeric)
|
||||||
@@ -183,7 +225,7 @@ impl BookmarksClient {
|
|||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(self.key_file.as_path())
|
.open(key_file)
|
||||||
{
|
{
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
// Write key to file
|
// Write key to file
|
||||||
@@ -197,7 +239,7 @@ impl BookmarksClient {
|
|||||||
let mut permissions: Permissions = file.metadata().unwrap().permissions();
|
let mut permissions: Permissions = file.metadata().unwrap().permissions();
|
||||||
permissions.set_readonly(true);
|
permissions.set_readonly(true);
|
||||||
let _ = file.set_permissions(permissions);
|
let _ = file.set_permissions(permissions);
|
||||||
Ok(())
|
Ok(key)
|
||||||
}
|
}
|
||||||
Err(err) => Err(SerializerError::new_ex(
|
Err(err) => Err(SerializerError::new_ex(
|
||||||
SerializerErrorKind::IoError,
|
SerializerErrorKind::IoError,
|
||||||
@@ -205,4 +247,73 @@ impl BookmarksClient {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### make_bookmark
|
||||||
|
///
|
||||||
|
/// Make bookmark from credentials
|
||||||
|
fn make_bookmark(
|
||||||
|
&self,
|
||||||
|
addr: String,
|
||||||
|
port: u16,
|
||||||
|
protocol: FileTransferProtocol,
|
||||||
|
username: String,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Bookmark {
|
||||||
|
Bookmark {
|
||||||
|
address: addr,
|
||||||
|
port,
|
||||||
|
username,
|
||||||
|
protocol: match protocol {
|
||||||
|
FileTransferProtocol::Ftp(secure) => match secure {
|
||||||
|
true => String::from("FTPS"),
|
||||||
|
false => String::from("FTP"),
|
||||||
|
},
|
||||||
|
FileTransferProtocol::Scp => String::from("SCP"),
|
||||||
|
FileTransferProtocol::Sftp => String::from("SFTP"),
|
||||||
|
},
|
||||||
|
password: match password {
|
||||||
|
Some(p) => Some(self.encrypt_str(p.as_str())), // Encrypt password if provided
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### load_key
|
||||||
|
///
|
||||||
|
/// Load key from key_file
|
||||||
|
fn load_key(key_file: &Path) -> Result<String, SerializerError> {
|
||||||
|
match OpenOptions::new().read(true).open(key_file) {
|
||||||
|
Ok(mut file) => {
|
||||||
|
let mut key: String = String::with_capacity(256);
|
||||||
|
file.read_to_string(&mut key);
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
fn encrypt_str(&self, txt: &str) -> String {
|
||||||
|
let crypter = new_magic_crypt!(self.key.clone(), 128);
|
||||||
|
crypter.encrypt_str_to_base64(txt.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### decrypt_str
|
||||||
|
///
|
||||||
|
/// Decrypt provided string using AES-128
|
||||||
|
fn decrypt_str(&self, secret: &str) -> Result<String, SerializerError> {
|
||||||
|
let crypter = new_magic_crypt!(self.key.clone(), 128);
|
||||||
|
match crypter.decrypt_base64_to_string(secret.to_string()) {
|
||||||
|
Ok(txt) => Ok(txt),
|
||||||
|
Err(err) => Err(SerializerError::new_ex(
|
||||||
|
SerializerErrorKind::SyntaxError,
|
||||||
|
err.to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user