Merge pull request #33 from veeso/logging

Logging
This commit is contained in:
Christian Visintin
2021-05-18 09:03:50 +02:00
committed by GitHub
34 changed files with 858 additions and 122 deletions
+5
View File
@@ -28,6 +28,11 @@ A clear and concise description of what you expected to happen.
- Protocol used - Protocol used
- Remote server version and name - Remote server version and name
## Log
Report the snippet of the log file containing the unexpected behaviour.
If there is any information you consider to be confidential, shadow it.
## Additional information ## Additional information
Add any other context about the problem here. Add any other context about the problem here.
+4
View File
@@ -33,6 +33,9 @@ Released on FIXME: ??
- Select a file with `<M>`, the file when selected will have a `*` prepended to its name - Select a file with `<M>`, the file when selected will have a `*` prepended to its name
- Select all files in the current directory with `<CTRL+A>` - Select all files in the current directory with `<CTRL+A>`
- Read more on manual: [Work on multiple files](docs/man.md#Work-on-multiple-files-) - Read more on manual: [Work on multiple files](docs/man.md#Work-on-multiple-files-)
- **Logging**:
- termscp now writes a log file, useful to debug and to contribute to fix issues.
- Read more on [manual](docs/man.md)
- **File transfer changes** - **File transfer changes**
- *SFTP* - *SFTP*
- Added **COPY** command to SFTP (Please note that Copy command is not supported by SFTP natively, so here it just uses the `cp` shell command as it does in SCP). - Added **COPY** command to SFTP (Please note that Copy command is not supported by SFTP natively, so here it just uses the `cp` shell command as it does in SCP).
@@ -46,6 +49,7 @@ Released on FIXME: ??
- when you change the protocol in the authentication form and the current port is standard (`< 1024`), the port will be automatically changed to default value for the selected protocol (e.g. current port: `123`, protocol is changes to `FTP`, port becomes `21`) - when you change the protocol in the authentication form and the current port is standard (`< 1024`), the port will be automatically changed to default value for the selected protocol (e.g. current port: `123`, protocol is changes to `FTP`, port becomes `21`)
- Bugfix: - Bugfix:
- Fixed wrong text wrap in log box - Fixed wrong text wrap in log box
- Fixed empty bookmark name causing termscp to crash
- Fixed error message not being shown after an upload failure - Fixed error message not being shown after an upload failure
- Fixed default protocol not being loaded from config - Fixed default protocol not being loaded from config
- [Issue 23](https://github.com/veeso/termscp/issues/23): Remove created file if transfer failed or was abrupted - [Issue 23](https://github.com/veeso/termscp/issues/23): Remove created file if transfer failed or was abrupted
+2 -1
View File
@@ -61,6 +61,7 @@ Don't set other labels to your issue, not even priority.
When you open a bug try to be the most precise as possible in describing your issue. I'm not saying you should always be that precise, since sometimes it's very easy for maintainers to understand what you're talking about. Just try to be reasonable to understand sometimes we might not know what you're talking about or we just don't have the technical knowledge you might think. When you open a bug try to be the most precise as possible in describing your issue. I'm not saying you should always be that precise, since sometimes it's very easy for maintainers to understand what you're talking about. Just try to be reasonable to understand sometimes we might not know what you're talking about or we just don't have the technical knowledge you might think.
Please always provide the environment you're working on and consider that we don't provide any support for older version of termscp, at least for those not classified as LTS (if we'll ever have them). Please always provide the environment you're working on and consider that we don't provide any support for older version of termscp, at least for those not classified as LTS (if we'll ever have them).
If you can, provide the log file or the snippet involving your issue. You can find in the [user manual](docs/man.md) the location of the log file.
Last but not least: the template I've written must be used. Full stop. Last but not least: the template I've written must be used. Full stop.
Maintainers will may add additional labels to your issue: Maintainers will may add additional labels to your issue:
@@ -68,7 +69,7 @@ Maintainers will may add additional labels to your issue:
- **duplicate**: the issue is duplicated; the reference to the related issue will be added to your description. Your issue will be closed. - **duplicate**: the issue is duplicated; the reference to the related issue will be added to your description. Your issue will be closed.
- **priority**: this must be fixed asap - **priority**: this must be fixed asap
- **sorcery**: it is not possible to find out what's causing your bug, nor is reproducible on our test environments. - **sorcery**: it is not possible to find out what's causing your bug, nor is reproducible on our test environments.
- **wontfix**: your bug has a very high ratio between the probability to encounter it and the difficult to fix it, or it just isn't a bug, but a feature. - **wontfix**: your bug has a very high ratio between the difficulty to fix it and the probability to encounter it, or it just isn't a bug, but a feature.
### Feature requests ### Feature requests
Generated
+31
View File
@@ -1230,6 +1230,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simplelog"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59d0fe306a0ced1c88a58042dc22fc2ddd000982c26d75f6aa09a394547c41e0"
dependencies = [
"chrono",
"log",
"termcolor",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.6.1" version = "1.6.1"
@@ -1291,6 +1302,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "termscp" name = "termscp"
version = "0.5.0" version = "0.5.0"
@@ -1307,6 +1327,7 @@ dependencies = [
"hostname", "hostname",
"keyring", "keyring",
"lazy_static", "lazy_static",
"log",
"magic-crypt", "magic-crypt",
"path-slash", "path-slash",
"pretty_assertions", "pretty_assertions",
@@ -1314,6 +1335,7 @@ dependencies = [
"regex", "regex",
"rpassword", "rpassword",
"serde", "serde",
"simplelog",
"ssh2", "ssh2",
"tempfile", "tempfile",
"textwrap", "textwrap",
@@ -1664,6 +1686,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
+2
View File
@@ -37,10 +37,12 @@ edit = "0.1.3"
getopts = "0.2.21" getopts = "0.2.21"
hostname = "0.3.1" hostname = "0.3.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.14"
magic-crypt = "3.1.7" magic-crypt = "3.1.7"
rand = "0.8.3" rand = "0.8.3"
regex = "1.5.4" regex = "1.5.4"
rpassword = "5.0.1" rpassword = "5.0.1"
simplelog = "0.10.0"
ssh2 = "0.9.0" ssh2 = "0.9.0"
tempfile = "3.1.0" tempfile = "3.1.0"
textwrap = "0.13.4" textwrap = "0.13.4"
+32
View File
@@ -15,6 +15,7 @@
- [File Explorer Format](#file-explorer-format) - [File Explorer Format](#file-explorer-format)
- [Text Editor ✏](#text-editor-) - [Text Editor ✏](#text-editor-)
- [How do I configure the text editor 🦥](#how-do-i-configure-the-text-editor-) - [How do I configure the text editor 🦥](#how-do-i-configure-the-text-editor-)
- [Logging 🩺](#logging-)
--- ---
@@ -25,6 +26,7 @@ termscp can be started with the following options:
`termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]` `termscp [options]... [protocol://user@address:port:wrkdir] [local-wrkdir]`
- `-P, --password <password>` if address is provided, password will be this argument - `-P, --password <password>` if address is provided, password will be this argument
- `-q, --quiet` Disable logging
- `-v, --version` Print version info - `-v, --version` Print version info
- `-h, --help` Print help page - `-h, --help` Print help page
@@ -266,3 +268,33 @@ Just a reminder: **you can edit only textual file**; binary files are not suppor
### How do I configure the text editor 🦥 ### How do I configure the text editor 🦥
Text editor is automatically found using this [awesome crate](https://github.com/milkey-mouse/edit), if you want to change the text editor to use, change it in termscp configuration. [Read more](#configuration-️) Text editor is automatically found using this [awesome crate](https://github.com/milkey-mouse/edit), if you want to change the text editor to use, change it in termscp configuration. [Read more](#configuration-️)
---
## Logging 🩺
termscp writes a log file for each session, which is written at
- `$HOME/.config/termscp/termscp.log` on Linux/BSD
- `$HOME/Library/Application Support/termscp/termscp.log` on MacOs
- `FOLDERID_RoamingAppData\termscp\termscp.log` on Windows
the log won't be rotated, but will just be truncated after each launch of termscp, so if you want to report an issue and you want to attach your log file, keep in mind to save the log file in a safe place before using termscp again.
The log file always reports in *trace* level, so it is kinda verbose.
I know you might have some questions regarding log files, so I made a kind of a Q/A:
> Is it possible to reduce verbosity?
No. The reason is quite simple: when an issue happens, you must be able to know what's causing it and the only way to do that, is to have the log file with the maximum verbosity level set.
> If trace level is set for logging, is the file going to reach a huge size?
Probably not, unless you never quit termscp, but I think that's likely to happne. A long session may produce up to 10MB of log files (I said a long session), but I think a normal session won't exceed 2MB.
> I don't want logging, can I turn it off?
Yes, you can. Just start termscp with `-q or --quiet` option. You can alias termscp to make it persistent. Remember that logging is used to diagnose issues, so since behind every open source project, there should always be this kind of mutual help, keeping log files might be your way to support the project 😉. I don't want you to feel guilty, but just to say.
> Is logging safe?
If you're concerned about security, the log file doesn't contain any plain password, so don't worry and exposes the same information the sibling file `bookmarks` reports.
+30 -5
View File
@@ -69,7 +69,10 @@ impl ActivityManager {
let (config_client, error): (Option<ConfigClient>, Option<String>) = let (config_client, error): (Option<ConfigClient>, Option<String>) =
match Self::init_config_client() { match Self::init_config_client() {
Ok(cli) => (Some(cli), None), Ok(cli) => (Some(cli), None),
Err(err) => (None, Some(err)), Err(err) => {
error!("Failed to initialize config client: {}", err);
(None, Some(err))
}
}; };
let ctx: Context = Context::new(config_client, error); let ctx: Context = Context::new(config_client, error);
Ok(ActivityManager { Ok(ActivityManager {
@@ -131,6 +134,7 @@ impl ActivityManager {
/// Returns when activity terminates. /// Returns when activity terminates.
/// Returns the next activity to run /// Returns the next activity to run
fn run_authentication(&mut self) -> Option<NextActivity> { fn run_authentication(&mut self) -> Option<NextActivity> {
info!("Starting AuthActivity...");
// Prepare activity // Prepare activity
let mut activity: AuthActivity = AuthActivity::default(); let mut activity: AuthActivity = AuthActivity::default();
// Prepare result // Prepare result
@@ -138,7 +142,10 @@ impl ActivityManager {
// Get context // Get context
let ctx: Context = match self.context.take() { let ctx: Context = match self.context.take() {
Some(ctx) => ctx, Some(ctx) => ctx,
None => return None, None => {
error!("Failed to start AuthActivity: context is None");
return None;
}
}; };
// Create activity // Create activity
activity.on_create(ctx); activity.on_create(ctx);
@@ -149,16 +156,19 @@ impl ActivityManager {
if let Some(exit_reason) = activity.will_umount() { if let Some(exit_reason) = activity.will_umount() {
match exit_reason { match exit_reason {
ExitReason::Quit => { ExitReason::Quit => {
info!("AuthActivity terminated due to 'Quit'");
result = None; result = None;
break; break;
} }
ExitReason::EnterSetup => { ExitReason::EnterSetup => {
// User requested activity // User requested activity
info!("AuthActivity terminated due to 'EnterSetup'");
result = Some(NextActivity::SetupActivity); result = Some(NextActivity::SetupActivity);
break; break;
} }
ExitReason::Connect => { ExitReason::Connect => {
// User submitted, set next activity // User submitted, set next activity
info!("AuthActivity terminated due to 'Connect'");
result = Some(NextActivity::FileTransfer); result = Some(NextActivity::FileTransfer);
break; break;
} }
@@ -170,6 +180,7 @@ impl ActivityManager {
} }
// Destroy activity // Destroy activity
self.context = activity.on_destroy(); self.context = activity.on_destroy();
info!("AuthActivity destroyed");
result result
} }
@@ -179,15 +190,22 @@ impl ActivityManager {
/// Returns when activity terminates. /// Returns when activity terminates.
/// Returns the next activity to run /// Returns the next activity to run
fn run_filetransfer(&mut self) -> Option<NextActivity> { fn run_filetransfer(&mut self) -> Option<NextActivity> {
info!("Starting FileTransferActivity");
// Get context // Get context
let mut ctx: Context = match self.context.take() { let mut ctx: Context = match self.context.take() {
Some(ctx) => ctx, Some(ctx) => ctx,
None => return None, None => {
error!("Failed to start FileTransferActivity: context is None");
return None;
}
}; };
// If ft params is None, return None // If ft params is None, return None
let ft_params: &FileTransferParams = match ctx.ft_params.as_ref() { let ft_params: &FileTransferParams = match ctx.ft_params.as_ref() {
Some(ft_params) => &ft_params, Some(ft_params) => &ft_params,
None => return None, None => {
error!("Failed to start FileTransferActivity: file transfer params is None");
return None;
}
}; };
// Prepare activity // Prepare activity
let protocol: FileTransferProtocol = ft_params.protocol; let protocol: FileTransferProtocol = ft_params.protocol;
@@ -195,6 +213,7 @@ impl ActivityManager {
Ok(host) => host, Ok(host) => host,
Err(err) => { Err(err) => {
// Set error in context // Set error in context
error!("Failed to initialize localhost: {}", err);
ctx.set_error(format!("Could not initialize localhost: {}", err)); ctx.set_error(format!("Could not initialize localhost: {}", err));
return None; return None;
} }
@@ -211,11 +230,13 @@ impl ActivityManager {
if let Some(exit_reason) = activity.will_umount() { if let Some(exit_reason) = activity.will_umount() {
match exit_reason { match exit_reason {
ExitReason::Quit => { ExitReason::Quit => {
info!("FileTransferActivity terminated due to 'Quit'");
result = None; result = None;
break; break;
} }
ExitReason::Disconnect => { ExitReason::Disconnect => {
// User disconnected, set next activity to authentication // User disconnected, set next activity to authentication
info!("FileTransferActivity terminated due to 'Authentication'");
result = Some(NextActivity::Authentication); result = Some(NextActivity::Authentication);
break; break;
} }
@@ -241,7 +262,10 @@ impl ActivityManager {
// Get context // Get context
let ctx: Context = match self.context.take() { let ctx: Context = match self.context.take() {
Some(ctx) => ctx, Some(ctx) => ctx,
None => return None, None => {
error!("Failed to start SetupActivity: context is None");
return None;
}
}; };
// Create activity // Create activity
activity.on_create(ctx); activity.on_create(ctx);
@@ -250,6 +274,7 @@ impl ActivityManager {
activity.on_draw(); activity.on_draw();
// Check if activity has terminated // Check if activity has terminated
if let Some(ExitReason::Quit) = activity.will_umount() { if let Some(ExitReason::Quit) = activity.will_umount() {
info!("SetupActivity terminated due to 'Quit'");
break; break;
} }
// Sleep for ticks // Sleep for ticks
+6 -1
View File
@@ -50,6 +50,7 @@ impl BookmarkSerializer {
)) ))
} }
}; };
trace!("Serialized new bookmarks data: {}", data);
// Write file // Write file
match writable.write_all(data.as_bytes()) { match writable.write_all(data.as_bytes()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -72,9 +73,13 @@ impl BookmarkSerializer {
err.to_string(), err.to_string(),
)); ));
} }
trace!("Read bookmarks from file: {}", data);
// Deserialize // Deserialize
match toml::de::from_str(data.as_str()) { match toml::de::from_str(data.as_str()) {
Ok(hosts) => Ok(hosts), Ok(bookmarks) => {
debug!("Read bookmarks from file {:?}", bookmarks);
Ok(bookmarks)
}
Err(err) => Err(SerializerError::new_ex( Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::SyntaxError, SerializerErrorKind::SyntaxError,
err.to_string(), err.to_string(),
+6 -1
View File
@@ -50,6 +50,7 @@ impl ConfigSerializer {
)) ))
} }
}; };
trace!("Serialized new configuration data: {}", data);
// Write file // Write file
match writable.write_all(data.as_bytes()) { match writable.write_all(data.as_bytes()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -72,9 +73,13 @@ impl ConfigSerializer {
err.to_string(), err.to_string(),
)); ));
} }
trace!("Read configuration from file: {}", data);
// Deserialize // Deserialize
match toml::de::from_str(data.as_str()) { match toml::de::from_str(data.as_str()) {
Ok(hosts) => Ok(hosts), Ok(config) => {
debug!("Read config from file {:?}", config);
Ok(config)
}
Err(err) => Err(SerializerError::new_ex( Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::SyntaxError, SerializerErrorKind::SyntaxError,
err.to_string(), err.to_string(),
+68 -3
View File
@@ -34,6 +34,7 @@ extern crate regex;
use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::fs::{FsDirectory, FsEntry, FsFile};
use crate::utils::fmt::{fmt_time, shadow_password};
use crate::utils::parser::{parse_datetime, parse_lstime}; use crate::utils::parser::{parse_datetime, parse_lstime};
// Includes // Includes
@@ -105,6 +106,7 @@ impl FtpFileTransfer {
lazy_static! { lazy_static! {
static ref LS_RE: Regex = Regex::new(r#"^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#).unwrap(); static ref LS_RE: Regex = Regex::new(r#"^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#).unwrap();
} }
debug!("Parsing LIST (UNIX) line: '{}'", line);
// Apply regex to result // Apply regex to result
match LS_RE.captures(line) { match LS_RE.captures(line) {
// String matches regex // String matches regex
@@ -182,6 +184,7 @@ impl FtpFileTransfer {
}; };
// Check if file_name is '.' or '..' // Check if file_name is '.' or '..'
if file_name.as_str() == "." || file_name.as_str() == ".." { if file_name.as_str() == "." || file_name.as_str() == ".." {
debug!("File name is {}; ignoring entry", file_name);
return Err(()); return Err(());
} }
// Get symlink // Get symlink
@@ -236,6 +239,19 @@ impl FtpFileTransfer {
.extension() .extension()
.map(|s| String::from(s.to_string_lossy())); .map(|s| String::from(s.to_string_lossy()));
// Return // Return
debug!("Follows LIST line '{}' attributes", line);
debug!("Is directory? {}", is_dir);
debug!("Is symlink? {}", is_symlink);
debug!("name: {}", file_name);
debug!("abs_path: {}", abs_path.display());
debug!("last_change_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("last_access_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("creation_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("symlink: {:?}", symlink);
debug!("user: {:?}", uid);
debug!("group: {:?}", gid);
debug!("unix_pex: {:?}", unix_pex);
debug!("---------------------------------------");
// Push to entries // Push to entries
Ok(match is_dir { Ok(match is_dir {
true => FsEntry::Directory(FsDirectory { true => FsEntry::Directory(FsDirectory {
@@ -287,6 +303,7 @@ impl FtpFileTransfer {
) )
.unwrap(); .unwrap();
} }
debug!("Parsing LIST (DOS) line: '{}'", line);
// Apply regex to result // Apply regex to result
match DOS_RE.captures(line) { match DOS_RE.captures(line) {
// String matches regex // String matches regex
@@ -324,6 +341,14 @@ impl FtpFileTransfer {
.as_path() .as_path()
.extension() .extension()
.map(|s| String::from(s.to_string_lossy())); .map(|s| String::from(s.to_string_lossy()));
debug!("Follows LIST line '{}' attributes", line);
debug!("Is directory? {}", is_dir);
debug!("name: {}", file_name);
debug!("abs_path: {}", abs_path.display());
debug!("last_change_time: {}", fmt_time(time, "%Y-%m-%dT%H:%M:%S"));
debug!("last_access_time: {}", fmt_time(time, "%Y-%m-%dT%H:%M:%S"));
debug!("creation_time: {}", fmt_time(time, "%Y-%m-%dT%H:%M:%S"));
debug!("---------------------------------------");
// Return entry // Return entry
Ok(match is_dir { Ok(match is_dir {
true => FsEntry::Directory(FsDirectory { true => FsEntry::Directory(FsDirectory {
@@ -382,17 +407,20 @@ impl FileTransfer for FtpFileTransfer {
password: Option<String>, password: Option<String>,
) -> Result<Option<String>, FileTransferError> { ) -> Result<Option<String>, FileTransferError> {
// Get stream // Get stream
info!("Connecting to {}:{}", address, port);
let mut stream: FtpStream = match FtpStream::connect(format!("{}:{}", address, port)) { let mut stream: FtpStream = match FtpStream::connect(format!("{}:{}", address, port)) {
Ok(stream) => stream, Ok(stream) => stream,
Err(err) => { Err(err) => {
error!("Failed to connect: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
err.to_string(), err.to_string(),
)) ));
} }
}; };
// If SSL, open secure session // If SSL, open secure session
if self.ftps { if self.ftps {
info!("Setting up TLS stream...");
let ctx = match TlsConnector::builder() let ctx = match TlsConnector::builder()
.danger_accept_invalid_certs(true) .danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true) .danger_accept_invalid_hostnames(true)
@@ -400,19 +428,21 @@ impl FileTransfer for FtpFileTransfer {
{ {
Ok(tls) => tls, Ok(tls) => tls,
Err(err) => { Err(err) => {
error!("Failed to setup TLS stream: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::SslError, FileTransferErrorType::SslError,
err.to_string(), err.to_string(),
)) ));
} }
}; };
stream = match stream.into_secure(ctx, address.as_str()) { stream = match stream.into_secure(ctx, address.as_str()) {
Ok(s) => s, Ok(s) => s,
Err(err) => { Err(err) => {
error!("Failed to setup TLS stream: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::SslError, FileTransferErrorType::SslError,
err.to_string(), err.to_string(),
)) ));
} }
}; };
} }
@@ -425,14 +455,22 @@ impl FileTransfer for FtpFileTransfer {
Some(pwd) => pwd, Some(pwd) => pwd,
None => String::new(), None => String::new(),
}; };
info!(
"Signin in with username: {}, password: {}",
username,
shadow_password(password.as_str())
);
if let Err(err) = stream.login(username.as_str(), password.as_str()) { if let Err(err) = stream.login(username.as_str(), password.as_str()) {
error!("Login failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::AuthenticationFailed, FileTransferErrorType::AuthenticationFailed,
err.to_string(), err.to_string(),
)); ));
} }
debug!("Setting transfer type to Binary");
// Initialize file type // Initialize file type
if let Err(err) = stream.transfer_type(FileType::Binary) { if let Err(err) = stream.transfer_type(FileType::Binary) {
error!("Failed to set transfer type to binary: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ProtocolError, FileTransferErrorType::ProtocolError,
err.to_string(), err.to_string(),
@@ -440,6 +478,7 @@ impl FileTransfer for FtpFileTransfer {
} }
// Set stream // Set stream
self.stream = Some(stream); self.stream = Some(stream);
info!("Connection successfully established");
// Return OK // Return OK
Ok(self.stream.as_ref().unwrap().get_welcome_msg()) Ok(self.stream.as_ref().unwrap().get_welcome_msg())
} }
@@ -449,6 +488,7 @@ impl FileTransfer for FtpFileTransfer {
/// Disconnect from the remote server /// Disconnect from the remote server
fn disconnect(&mut self) -> Result<(), FileTransferError> { fn disconnect(&mut self) -> Result<(), FileTransferError> {
info!("Disconnecting from FTP server...");
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.quit() { Some(stream) => match stream.quit() {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -475,6 +515,7 @@ impl FileTransfer for FtpFileTransfer {
/// Print working directory /// Print working directory
fn pwd(&mut self) -> Result<PathBuf, FileTransferError> { fn pwd(&mut self) -> Result<PathBuf, FileTransferError> {
info!("PWD");
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.pwd() { Some(stream) => match stream.pwd() {
Ok(path) => Ok(PathBuf::from(path.as_str())), Ok(path) => Ok(PathBuf::from(path.as_str())),
@@ -495,6 +536,7 @@ impl FileTransfer for FtpFileTransfer {
fn change_dir(&mut self, dir: &Path) -> Result<PathBuf, FileTransferError> { fn change_dir(&mut self, dir: &Path) -> Result<PathBuf, FileTransferError> {
let dir: PathBuf = Self::resolve(dir); let dir: PathBuf = Self::resolve(dir);
info!("Changing directory to {}", dir.display());
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.cwd(&dir.as_path().to_string_lossy()) { Some(stream) => match stream.cwd(&dir.as_path().to_string_lossy()) {
Ok(_) => Ok(dir), Ok(_) => Ok(dir),
@@ -514,6 +556,7 @@ impl FileTransfer for FtpFileTransfer {
/// Copy file to destination /// Copy file to destination
fn copy(&mut self, _src: &FsEntry, _dst: &Path) -> Result<(), FileTransferError> { fn copy(&mut self, _src: &FsEntry, _dst: &Path) -> Result<(), FileTransferError> {
// FTP doesn't support file copy // FTP doesn't support file copy
debug!("COPY issues (will fail, since unsupported)");
Err(FileTransferError::new( Err(FileTransferError::new(
FileTransferErrorType::UnsupportedFeature, FileTransferErrorType::UnsupportedFeature,
)) ))
@@ -525,9 +568,11 @@ impl FileTransfer for FtpFileTransfer {
fn list_dir(&mut self, path: &Path) -> Result<Vec<FsEntry>, FileTransferError> { fn list_dir(&mut self, path: &Path) -> Result<Vec<FsEntry>, FileTransferError> {
let dir: PathBuf = Self::resolve(path); let dir: PathBuf = Self::resolve(path);
info!("LIST dir {}", dir.display());
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.list(Some(&dir.as_path().to_string_lossy())) { Some(stream) => match stream.list(Some(&dir.as_path().to_string_lossy())) {
Ok(entries) => { Ok(entries) => {
debug!("Got {} lines in LIST result", entries.len());
// Prepare result // Prepare result
let mut result: Vec<FsEntry> = Vec::with_capacity(entries.len()); let mut result: Vec<FsEntry> = Vec::with_capacity(entries.len());
// Iterate over entries // Iterate over entries
@@ -536,6 +581,11 @@ impl FileTransfer for FtpFileTransfer {
result.push(file); result.push(file);
} }
} }
debug!(
"{} out of {} were valid entries",
result.len(),
entries.len()
);
Ok(result) Ok(result)
} }
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -554,6 +604,7 @@ impl FileTransfer for FtpFileTransfer {
/// Make directory /// Make directory
fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> { fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> {
let dir: PathBuf = Self::resolve(dir); let dir: PathBuf = Self::resolve(dir);
info!("MKDIR {}", dir.display());
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.mkdir(&dir.as_path().to_string_lossy()) { Some(stream) => match stream.mkdir(&dir.as_path().to_string_lossy()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -577,9 +628,11 @@ impl FileTransfer for FtpFileTransfer {
FileTransferErrorType::UninitializedSession, FileTransferErrorType::UninitializedSession,
)); ));
} }
info!("Removing entry {}", fsentry.get_abs_path().display());
match fsentry { match fsentry {
// Match fs entry... // Match fs entry...
FsEntry::File(file) => { FsEntry::File(file) => {
debug!("entry is a file; removing file");
// Remove file directly // Remove file directly
match self.stream.as_mut().unwrap().rm(file.name.as_ref()) { match self.stream.as_mut().unwrap().rm(file.name.as_ref()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -591,9 +644,11 @@ impl FileTransfer for FtpFileTransfer {
} }
FsEntry::Directory(dir) => { FsEntry::Directory(dir) => {
// Get directory files // Get directory files
debug!("Entry is a directory; iterating directory entries");
match self.list_dir(dir.abs_path.as_path()) { match self.list_dir(dir.abs_path.as_path()) {
Ok(files) => { Ok(files) => {
// Remove recursively files // Remove recursively files
debug!("Removing {} entries from directory...", files.len());
for file in files.iter() { for file in files.iter() {
if let Err(err) = self.remove(&file) { if let Err(err) = self.remove(&file) {
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
@@ -603,6 +658,7 @@ impl FileTransfer for FtpFileTransfer {
} }
} }
// Once all files in directory have been deleted, remove directory // Once all files in directory have been deleted, remove directory
debug!("Finally removing directory {}", dir.name);
match self.stream.as_mut().unwrap().rmdir(dir.name.as_str()) { match self.stream.as_mut().unwrap().rmdir(dir.name.as_str()) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -625,6 +681,11 @@ impl FileTransfer for FtpFileTransfer {
/// Rename file or a directory /// Rename file or a directory
fn rename(&mut self, file: &FsEntry, dst: &Path) -> Result<(), FileTransferError> { fn rename(&mut self, file: &FsEntry, dst: &Path) -> Result<(), FileTransferError> {
let dst: PathBuf = Self::resolve(dst); let dst: PathBuf = Self::resolve(dst);
info!(
"Renaming {} to {}",
file.get_abs_path().display(),
dst.display()
);
match &mut self.stream { match &mut self.stream {
Some(stream) => { Some(stream) => {
// Get name // Get name
@@ -691,6 +752,7 @@ impl FileTransfer for FtpFileTransfer {
file_name: &Path, file_name: &Path,
) -> Result<Box<dyn Write>, FileTransferError> { ) -> Result<Box<dyn Write>, FileTransferError> {
let file_name: PathBuf = Self::resolve(file_name); let file_name: PathBuf = Self::resolve(file_name);
info!("Sending file {}", file_name.display());
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.put_with_stream(&file_name.as_path().to_string_lossy()) { Some(stream) => match stream.put_with_stream(&file_name.as_path().to_string_lossy()) {
Ok(writer) => Ok(Box::new(writer)), // NOTE: don't use BufWriter here, since already returned by the library Ok(writer) => Ok(Box::new(writer)), // NOTE: don't use BufWriter here, since already returned by the library
@@ -710,6 +772,7 @@ impl FileTransfer for FtpFileTransfer {
/// Receive file from remote with provided name /// Receive file from remote with provided name
/// Returns file and its size /// Returns file and its size
fn recv_file(&mut self, file: &FsFile) -> Result<Box<dyn Read>, FileTransferError> { fn recv_file(&mut self, file: &FsFile) -> Result<Box<dyn Read>, FileTransferError> {
info!("Receiving file {}", file.abs_path.display());
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.get(&file.abs_path.as_path().to_string_lossy()) { Some(stream) => match stream.get(&file.abs_path.as_path().to_string_lossy()) {
Ok(reader) => Ok(Box::new(reader)), // NOTE: don't use BufReader here, since already returned by the library Ok(reader) => Ok(Box::new(reader)), // NOTE: don't use BufReader here, since already returned by the library
@@ -732,6 +795,7 @@ impl FileTransfer for FtpFileTransfer {
/// This is necessary for some protocols such as FTP. /// This is necessary for some protocols such as FTP.
/// You must call this method each time you want to finalize the write of the remote file. /// You must call this method each time you want to finalize the write of the remote file.
fn on_sent(&mut self, writable: Box<dyn Write>) -> Result<(), FileTransferError> { fn on_sent(&mut self, writable: Box<dyn Write>) -> Result<(), FileTransferError> {
info!("Finalizing put stream");
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.finalize_put_stream(writable) { Some(stream) => match stream.finalize_put_stream(writable) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@@ -754,6 +818,7 @@ impl FileTransfer for FtpFileTransfer {
/// This mighe be necessary for some protocols. /// This mighe be necessary for some protocols.
/// You must call this method each time you want to finalize the read of the remote file. /// You must call this method each time you want to finalize the read of the remote file.
fn on_recv(&mut self, readable: Box<dyn Read>) -> Result<(), FileTransferError> { fn on_recv(&mut self, readable: Box<dyn Read>) -> Result<(), FileTransferError> {
info!("Finalizing get");
match &mut self.stream { match &mut self.stream {
Some(stream) => match stream.finalize_get(readable) { Some(stream) => match stream.finalize_get(readable) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
+2 -2
View File
@@ -319,14 +319,14 @@ impl std::string::ToString for FileTransferProtocol {
} }
impl std::str::FromStr for FileTransferProtocol { impl std::str::FromStr for FileTransferProtocol {
type Err = (); type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_uppercase().as_str() { match s.to_ascii_uppercase().as_str() {
"FTP" => Ok(FileTransferProtocol::Ftp(false)), "FTP" => Ok(FileTransferProtocol::Ftp(false)),
"FTPS" => Ok(FileTransferProtocol::Ftp(true)), "FTPS" => Ok(FileTransferProtocol::Ftp(true)),
"SCP" => Ok(FileTransferProtocol::Scp), "SCP" => Ok(FileTransferProtocol::Scp),
"SFTP" => Ok(FileTransferProtocol::Sftp), "SFTP" => Ok(FileTransferProtocol::Sftp),
_ => Err(()), _ => Err(s.to_string()),
} }
} }
} }
+80 -2
View File
@@ -35,6 +35,7 @@ extern crate ssh2;
use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::fs::{FsDirectory, FsEntry, FsFile};
use crate::system::sshkey_storage::SshKeyStorage; use crate::system::sshkey_storage::SshKeyStorage;
use crate::utils::fmt::{fmt_time, shadow_password};
use crate::utils::parser::parse_lstime; use crate::utils::parser::parse_lstime;
// Includes // Includes
@@ -90,6 +91,7 @@ impl ScpFileTransfer {
lazy_static! { lazy_static! {
static ref LS_RE: Regex = Regex::new(r#"^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#).unwrap(); static ref LS_RE: Regex = Regex::new(r#"^([\-ld])([\-rwxs]{9})\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w{3}\s+\d{1,2}\s+(?:\d{1,2}:\d{1,2}|\d{4}))\s+(.+)$"#).unwrap();
} }
debug!("Parsing LS line: '{}'", line);
// Apply regex to result // Apply regex to result
match LS_RE.captures(line) { match LS_RE.captures(line) {
// String matches regex // String matches regex
@@ -167,6 +169,7 @@ impl ScpFileTransfer {
}; };
// Check if file_name is '.' or '..' // Check if file_name is '.' or '..'
if file_name.as_str() == "." || file_name.as_str() == ".." { if file_name.as_str() == "." || file_name.as_str() == ".." {
debug!("File name is {}; ignoring entry", file_name);
return Err(()); return Err(());
} }
// Get symlink; PATH mustn't be equal to filename // Get symlink; PATH mustn't be equal to filename
@@ -200,6 +203,19 @@ impl ScpFileTransfer {
.extension() .extension()
.map(|s| String::from(s.to_string_lossy())); .map(|s| String::from(s.to_string_lossy()));
// Return // Return
debug!("Follows LS line '{}' attributes", line);
debug!("Is directory? {}", is_dir);
debug!("Is symlink? {}", is_symlink);
debug!("name: {}", file_name);
debug!("abs_path: {}", abs_path.display());
debug!("last_change_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("last_access_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("creation_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("symlink: {:?}", symlink);
debug!("user: {:?}", uid);
debug!("group: {:?}", gid);
debug!("unix_pex: {:?}", unix_pex);
debug!("---------------------------------------");
// Push to entries // Push to entries
Ok(match is_dir { Ok(match is_dir {
true => FsEntry::Directory(FsDirectory { true => FsEntry::Directory(FsDirectory {
@@ -262,6 +278,7 @@ impl ScpFileTransfer {
fn perform_shell_cmd(&mut self, cmd: &str) -> Result<String, FileTransferError> { fn perform_shell_cmd(&mut self, cmd: &str) -> Result<String, FileTransferError> {
match self.session.as_mut() { match self.session.as_mut() {
Some(session) => { Some(session) => {
debug!("Running command: {}", cmd);
// Create channel // Create channel
let mut channel: Channel = match session.channel_session() { let mut channel: Channel = match session.channel_session() {
Ok(ch) => ch, Ok(ch) => ch,
@@ -285,6 +302,7 @@ impl ScpFileTransfer {
Ok(_) => { Ok(_) => {
// Wait close // Wait close
let _ = channel.wait_close(); let _ = channel.wait_close();
debug!("Command output: {}", output);
Ok(output) Ok(output)
} }
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -312,6 +330,7 @@ impl FileTransfer for ScpFileTransfer {
password: Option<String>, password: Option<String>,
) -> Result<Option<String>, FileTransferError> { ) -> Result<Option<String>, FileTransferError> {
// Setup tcp stream // Setup tcp stream
info!("Connecting to {}:{}", address, port);
let socket_addresses: Vec<SocketAddr> = let socket_addresses: Vec<SocketAddr> =
match format!("{}:{}", address, port).to_socket_addrs() { match format!("{}:{}", address, port).to_socket_addrs() {
Ok(s) => s.collect(), Ok(s) => s.collect(),
@@ -325,8 +344,10 @@ impl FileTransfer for ScpFileTransfer {
let mut tcp: Option<TcpStream> = None; let mut tcp: Option<TcpStream> = None;
// Try addresses // Try addresses
for socket_addr in socket_addresses.iter() { for socket_addr in socket_addresses.iter() {
debug!("Trying socket address {}", socket_addr);
match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) { match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) {
Ok(stream) => { Ok(stream) => {
debug!("{} succeded", socket_addr);
tcp = Some(stream); tcp = Some(stream);
break; break;
} }
@@ -337,26 +358,30 @@ impl FileTransfer for ScpFileTransfer {
let tcp: TcpStream = match tcp { let tcp: TcpStream = match tcp {
Some(t) => t, Some(t) => t,
None => { None => {
error!("No suitable socket address found; connection timeout");
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
String::from("Connection timeout"), String::from("Connection timeout"),
)) ));
} }
}; };
// Create session // Create session
let mut session: Session = match Session::new() { let mut session: Session = match Session::new() {
Ok(s) => s, Ok(s) => s,
Err(err) => { Err(err) => {
error!("Could not create session: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
err.to_string(), err.to_string(),
)) ));
} }
}; };
// Set TCP stream // Set TCP stream
session.set_tcp_stream(tcp); session.set_tcp_stream(tcp);
// Open connection // Open connection
debug!("Initializing handshake");
if let Err(err) = session.handshake() { if let Err(err) = session.handshake() {
error!("Handshake failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
err.to_string(), err.to_string(),
@@ -372,6 +397,11 @@ impl FileTransfer for ScpFileTransfer {
.resolve(address.as_str(), username.as_str()) .resolve(address.as_str(), username.as_str())
{ {
Some(rsa_key) => { Some(rsa_key) => {
debug!(
"Authenticating with user {} and RSA key {}",
username,
rsa_key.display()
);
// Authenticate with RSA key // Authenticate with RSA key
if let Err(err) = session.userauth_pubkey_file( if let Err(err) = session.userauth_pubkey_file(
username.as_str(), username.as_str(),
@@ -379,6 +409,7 @@ impl FileTransfer for ScpFileTransfer {
rsa_key.as_path(), rsa_key.as_path(),
password.as_deref(), password.as_deref(),
) { ) {
error!("Authentication failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::AuthenticationFailed, FileTransferErrorType::AuthenticationFailed,
err.to_string(), err.to_string(),
@@ -387,10 +418,16 @@ impl FileTransfer for ScpFileTransfer {
} }
None => { None => {
// Proceeed with username/password authentication // Proceeed with username/password authentication
debug!(
"Authenticating with username {} and password {}",
username,
shadow_password(password.as_deref().unwrap_or(""))
);
if let Err(err) = session.userauth_password( if let Err(err) = session.userauth_password(
username.as_str(), username.as_str(),
password.unwrap_or_else(|| String::from("")).as_str(), password.unwrap_or_else(|| String::from("")).as_str(),
) { ) {
error!("Authentication failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::AuthenticationFailed, FileTransferErrorType::AuthenticationFailed,
err.to_string(), err.to_string(),
@@ -400,13 +437,22 @@ impl FileTransfer for ScpFileTransfer {
} }
// Get banner // Get banner
let banner: Option<String> = session.banner().map(String::from); let banner: Option<String> = session.banner().map(String::from);
debug!(
"Connection established: {}",
banner.as_deref().unwrap_or("")
);
// Set session // Set session
self.session = Some(session); self.session = Some(session);
// Get working directory // Get working directory
debug!("Getting working directory...");
match self.perform_shell_cmd("pwd") { match self.perform_shell_cmd("pwd") {
Ok(output) => self.wrkdir = PathBuf::from(output.as_str().trim()), Ok(output) => self.wrkdir = PathBuf::from(output.as_str().trim()),
Err(err) => return Err(err), Err(err) => return Err(err),
} }
info!(
"Connection established; working directory: {}",
self.wrkdir.display()
);
Ok(banner) Ok(banner)
} }
@@ -414,6 +460,7 @@ impl FileTransfer for ScpFileTransfer {
/// ///
/// Disconnect from the remote server /// Disconnect from the remote server
fn disconnect(&mut self) -> Result<(), FileTransferError> { fn disconnect(&mut self) -> Result<(), FileTransferError> {
info!("Disconnecting from remote...");
match self.session.as_ref() { match self.session.as_ref() {
Some(session) => { Some(session) => {
// Disconnect (greet server with 'Mandi' as they do in Friuli) // Disconnect (greet server with 'Mandi' as they do in Friuli)
@@ -447,6 +494,7 @@ impl FileTransfer for ScpFileTransfer {
/// Print working directory /// Print working directory
fn pwd(&mut self) -> Result<PathBuf, FileTransferError> { fn pwd(&mut self) -> Result<PathBuf, FileTransferError> {
info!("PWD: {}", self.wrkdir.display());
match self.is_connected() { match self.is_connected() {
true => Ok(self.wrkdir.clone()), true => Ok(self.wrkdir.clone()),
false => Err(FileTransferError::new( false => Err(FileTransferError::new(
@@ -471,6 +519,7 @@ impl FileTransfer for ScpFileTransfer {
Self::resolve(p.as_path()) Self::resolve(p.as_path())
} }
}; };
info!("Changing working directory to {}", remote_path.display());
// Change directory // Change directory
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
p.as_path(), p.as_path(),
@@ -484,6 +533,7 @@ impl FileTransfer for ScpFileTransfer {
true => { true => {
// Set working directory // Set working directory
self.wrkdir = PathBuf::from(&output.as_str()[1..].trim()); self.wrkdir = PathBuf::from(&output.as_str()[1..].trim());
info!("Changed working directory to {}", self.wrkdir.display());
Ok(self.wrkdir.clone()) Ok(self.wrkdir.clone())
} }
false => Err(FileTransferError::new_ex( false => Err(FileTransferError::new_ex(
@@ -512,6 +562,11 @@ impl FileTransfer for ScpFileTransfer {
match self.is_connected() { match self.is_connected() {
true => { true => {
let dst: PathBuf = Self::resolve(dst); let dst: PathBuf = Self::resolve(dst);
info!(
"Copying {} to {}",
src.get_abs_path().display(),
dst.display()
);
// Run `cp -rf` // Run `cp -rf`
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
@@ -555,6 +610,7 @@ impl FileTransfer for ScpFileTransfer {
match self.is_connected() { match self.is_connected() {
true => { true => {
// Send ls -l to path // Send ls -l to path
info!("Getting file entries in {}", path.display());
let path: PathBuf = Self::resolve(path); let path: PathBuf = Self::resolve(path);
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
@@ -572,6 +628,11 @@ impl FileTransfer for ScpFileTransfer {
entries.push(entry); entries.push(entry);
} }
} }
info!(
"Found {} out of {} valid file entries",
entries.len(),
lines.len()
);
Ok(entries) Ok(entries)
} }
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -594,6 +655,7 @@ impl FileTransfer for ScpFileTransfer {
match self.is_connected() { match self.is_connected() {
true => { true => {
let dir: PathBuf = Self::resolve(dir); let dir: PathBuf = Self::resolve(dir);
info!("Making directory {}", dir.display());
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
// Mkdir dir && echo 0 // Mkdir dir && echo 0
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
@@ -632,6 +694,7 @@ impl FileTransfer for ScpFileTransfer {
true => { true => {
// Get path // Get path
let path: PathBuf = file.get_abs_path(); let path: PathBuf = file.get_abs_path();
info!("Removing file {}", path.display());
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
p.as_path(), p.as_path(),
@@ -669,6 +732,7 @@ impl FileTransfer for ScpFileTransfer {
// Get path // Get path
let dst: PathBuf = Self::resolve(dst); let dst: PathBuf = Self::resolve(dst);
let path: PathBuf = file.get_abs_path(); let path: PathBuf = file.get_abs_path();
info!("Renaming {} to {}", path.display(), dst.display());
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
p.as_path(), p.as_path(),
@@ -717,6 +781,7 @@ impl FileTransfer for ScpFileTransfer {
match self.is_connected() { match self.is_connected() {
true => { true => {
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
info!("Stat {}", path.display());
// make command; Directories require `-d` option // make command; Directories require `-d` option
let cmd: String = match path.to_string_lossy().ends_with('/') { let cmd: String = match path.to_string_lossy().ends_with('/') {
true => format!("ls -ld \"{}\"", path.display()), true => format!("ls -ld \"{}\"", path.display()),
@@ -760,6 +825,7 @@ impl FileTransfer for ScpFileTransfer {
match self.is_connected() { match self.is_connected() {
true => { true => {
let p: PathBuf = self.wrkdir.clone(); let p: PathBuf = self.wrkdir.clone();
info!("Executing command {}", cmd);
match self.perform_shell_cmd_with_path(p.as_path(), cmd) { match self.perform_shell_cmd_with_path(p.as_path(), cmd) {
Ok(output) => Ok(output), Ok(output) => Ok(output),
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -788,7 +854,13 @@ impl FileTransfer for ScpFileTransfer {
match self.session.as_ref() { match self.session.as_ref() {
Some(session) => { Some(session) => {
let file_name: PathBuf = Self::resolve(file_name); let file_name: PathBuf = Self::resolve(file_name);
info!(
"Sending file {} to {}",
local.abs_path.display(),
file_name.display()
);
// Set blocking to true // Set blocking to true
debug!("blocking channel...");
session.set_blocking(true); session.set_blocking(true);
// Calculate file mode // Calculate file mode
let mode: i32 = match local.unix_pex { let mode: i32 = match local.unix_pex {
@@ -818,6 +890,10 @@ impl FileTransfer for ScpFileTransfer {
Ok(metadata) => metadata.len(), Ok(metadata) => metadata.len(),
Err(_) => local.size as u64, // NOTE: fallback to fsentry size Err(_) => local.size as u64, // NOTE: fallback to fsentry size
}; };
debug!(
"File mode {:?}; mtime: {}, atime: {}; file size: {}",
mode, times.0, times.1, file_size
);
// Send file // Send file
match session.scp_send(file_name.as_path(), mode, file_size, Some(times)) { match session.scp_send(file_name.as_path(), mode, file_size, Some(times)) {
Ok(channel) => Ok(Box::new(BufWriter::with_capacity(65536, channel))), Ok(channel) => Ok(Box::new(BufWriter::with_capacity(65536, channel))),
@@ -840,7 +916,9 @@ impl FileTransfer for ScpFileTransfer {
fn recv_file(&mut self, file: &FsFile) -> Result<Box<dyn Read>, FileTransferError> { fn recv_file(&mut self, file: &FsFile) -> Result<Box<dyn Read>, FileTransferError> {
match self.session.as_ref() { match self.session.as_ref() {
Some(session) => { Some(session) => {
info!("Receiving file {}", file.abs_path.display());
// Set blocking to true // Set blocking to true
debug!("Set blocking...");
session.set_blocking(true); session.set_blocking(true);
match session.scp_recv(file.abs_path.as_path()) { match session.scp_recv(file.abs_path.as_path()) {
Ok(reader) => Ok(Box::new(BufReader::with_capacity(65536, reader.0))), Ok(reader) => Ok(Box::new(BufReader::with_capacity(65536, reader.0))),
+72 -4
View File
@@ -32,6 +32,7 @@ extern crate ssh2;
use super::{FileTransfer, FileTransferError, FileTransferErrorType}; use super::{FileTransfer, FileTransferError, FileTransferErrorType};
use crate::fs::{FsDirectory, FsEntry, FsFile}; use crate::fs::{FsDirectory, FsEntry, FsFile};
use crate::system::sshkey_storage::SshKeyStorage; use crate::system::sshkey_storage::SshKeyStorage;
use crate::utils::fmt::{fmt_time, shadow_password};
// Includes // Includes
use ssh2::{Channel, FileStat, OpenFlags, OpenType, Session, Sftp}; use ssh2::{Channel, FileStat, OpenFlags, OpenType, Session, Sftp};
@@ -159,6 +160,19 @@ impl SftpFileTransfer {
} }
false => None, false => None,
}; };
debug!("Follows {} attributes", path.display());
debug!("Is directory? {}", metadata.is_dir());
debug!("Is symlink? {}", is_symlink);
debug!("name: {}", file_name);
debug!("abs_path: {}", path.display());
debug!("last_change_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("last_access_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("creation_time: {}", fmt_time(mtime, "%Y-%m-%dT%H:%M:%S"));
debug!("symlink: {:?}", symlink);
debug!("user: {:?}", uid);
debug!("group: {:?}", gid);
debug!("unix_pex: {:?}", pex);
debug!("---------------------------------------");
// Is a directory? // Is a directory?
match metadata.is_dir() { match metadata.is_dir() {
true => FsEntry::Directory(FsDirectory { true => FsEntry::Directory(FsDirectory {
@@ -205,6 +219,7 @@ impl SftpFileTransfer {
match self.session.as_mut() { match self.session.as_mut() {
Some(session) => { Some(session) => {
// Create channel // Create channel
debug!("Running command: {}", cmd);
let mut channel: Channel = match session.channel_session() { let mut channel: Channel = match session.channel_session() {
Ok(ch) => ch, Ok(ch) => ch,
Err(err) => { Err(err) => {
@@ -227,6 +242,7 @@ impl SftpFileTransfer {
Ok(_) => { Ok(_) => {
// Wait close // Wait close
let _ = channel.wait_close(); let _ = channel.wait_close();
debug!("Command output: {}", output);
Ok(output) Ok(output)
} }
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -254,6 +270,7 @@ impl FileTransfer for SftpFileTransfer {
password: Option<String>, password: Option<String>,
) -> Result<Option<String>, FileTransferError> { ) -> Result<Option<String>, FileTransferError> {
// Setup tcp stream // Setup tcp stream
info!("Connecting to {}:{}", address, port);
let socket_addresses: Vec<SocketAddr> = let socket_addresses: Vec<SocketAddr> =
match format!("{}:{}", address, port).to_socket_addrs() { match format!("{}:{}", address, port).to_socket_addrs() {
Ok(s) => s.collect(), Ok(s) => s.collect(),
@@ -267,6 +284,7 @@ impl FileTransfer for SftpFileTransfer {
let mut tcp: Option<TcpStream> = None; let mut tcp: Option<TcpStream> = None;
// Try addresses // Try addresses
for socket_addr in socket_addresses.iter() { for socket_addr in socket_addresses.iter() {
debug!("Trying socket address {}", socket_addr);
match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) { match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) {
Ok(stream) => { Ok(stream) => {
tcp = Some(stream); tcp = Some(stream);
@@ -279,26 +297,30 @@ impl FileTransfer for SftpFileTransfer {
let tcp: TcpStream = match tcp { let tcp: TcpStream = match tcp {
Some(t) => t, Some(t) => t,
None => { None => {
error!("No suitable socket address found; connection timeout");
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
String::from("Connection timeout"), String::from("Connection timeout"),
)) ));
} }
}; };
// Create session // Create session
let mut session: Session = match Session::new() { let mut session: Session = match Session::new() {
Ok(s) => s, Ok(s) => s,
Err(err) => { Err(err) => {
error!("Could not create session: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
err.to_string(), err.to_string(),
)) ));
} }
}; };
// Set TCP stream // Set TCP stream
session.set_tcp_stream(tcp); session.set_tcp_stream(tcp);
// Open connection // Open connection
debug!("Initializing handshake");
if let Err(err) = session.handshake() { if let Err(err) = session.handshake() {
error!("Handshake failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ConnectionError, FileTransferErrorType::ConnectionError,
err.to_string(), err.to_string(),
@@ -314,6 +336,11 @@ impl FileTransfer for SftpFileTransfer {
.resolve(address.as_str(), username.as_str()) .resolve(address.as_str(), username.as_str())
{ {
Some(rsa_key) => { Some(rsa_key) => {
debug!(
"Authenticating with user {} and RSA key {}",
username,
rsa_key.display()
);
// Authenticate with RSA key // Authenticate with RSA key
if let Err(err) = session.userauth_pubkey_file( if let Err(err) = session.userauth_pubkey_file(
username.as_str(), username.as_str(),
@@ -321,6 +348,7 @@ impl FileTransfer for SftpFileTransfer {
rsa_key.as_path(), rsa_key.as_path(),
password.as_deref(), password.as_deref(),
) { ) {
error!("Authentication failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::AuthenticationFailed, FileTransferErrorType::AuthenticationFailed,
err.to_string(), err.to_string(),
@@ -329,10 +357,16 @@ impl FileTransfer for SftpFileTransfer {
} }
None => { None => {
// Proceeed with username/password authentication // Proceeed with username/password authentication
debug!(
"Authenticating with username {} and password {}",
username,
shadow_password(password.as_deref().unwrap_or(""))
);
if let Err(err) = session.userauth_password( if let Err(err) = session.userauth_password(
username.as_str(), username.as_str(),
password.unwrap_or_else(|| String::from("")).as_str(), password.unwrap_or_else(|| String::from("")).as_str(),
) { ) {
error!("Authentication failed: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::AuthenticationFailed, FileTransferErrorType::AuthenticationFailed,
err.to_string(), err.to_string(),
@@ -343,16 +377,19 @@ impl FileTransfer for SftpFileTransfer {
// Set blocking to true // Set blocking to true
session.set_blocking(true); session.set_blocking(true);
// Get Sftp client // Get Sftp client
debug!("Getting SFTP client...");
let sftp: Sftp = match session.sftp() { let sftp: Sftp = match session.sftp() {
Ok(s) => s, Ok(s) => s,
Err(err) => { Err(err) => {
error!("Could not get sftp client: {}", err);
return Err(FileTransferError::new_ex( return Err(FileTransferError::new_ex(
FileTransferErrorType::ProtocolError, FileTransferErrorType::ProtocolError,
err.to_string(), err.to_string(),
)) ));
} }
}; };
// Get working directory // Get working directory
debug!("Getting working directory...");
self.wrkdir = match sftp.realpath(PathBuf::from(".").as_path()) { self.wrkdir = match sftp.realpath(PathBuf::from(".").as_path()) {
Ok(p) => p, Ok(p) => p,
Err(err) => { Err(err) => {
@@ -367,6 +404,11 @@ impl FileTransfer for SftpFileTransfer {
self.session = Some(session); self.session = Some(session);
// Set sftp // Set sftp
self.sftp = Some(sftp); self.sftp = Some(sftp);
info!(
"Connection established: {}; working directory {}",
banner.as_deref().unwrap_or(""),
self.wrkdir.display()
);
Ok(banner) Ok(banner)
} }
@@ -374,6 +416,7 @@ impl FileTransfer for SftpFileTransfer {
/// ///
/// Disconnect from the remote server /// Disconnect from the remote server
fn disconnect(&mut self) -> Result<(), FileTransferError> { fn disconnect(&mut self) -> Result<(), FileTransferError> {
info!("Disconnecting from remote...");
match self.session.as_ref() { match self.session.as_ref() {
Some(session) => { Some(session) => {
// Disconnect (greet server with 'Mandi' as they do in Friuli) // Disconnect (greet server with 'Mandi' as they do in Friuli)
@@ -407,6 +450,7 @@ impl FileTransfer for SftpFileTransfer {
/// ///
/// Print working directory /// Print working directory
fn pwd(&mut self) -> Result<PathBuf, FileTransferError> { fn pwd(&mut self) -> Result<PathBuf, FileTransferError> {
info!("PWD: {}", self.wrkdir.display());
match self.sftp { match self.sftp {
Some(_) => Ok(self.wrkdir.clone()), Some(_) => Ok(self.wrkdir.clone()),
None => Err(FileTransferError::new( None => Err(FileTransferError::new(
@@ -426,6 +470,7 @@ impl FileTransfer for SftpFileTransfer {
Ok(p) => p, Ok(p) => p,
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
info!("Changed working directory to {}", self.wrkdir.display());
Ok(self.wrkdir.clone()) Ok(self.wrkdir.clone())
} }
None => Err(FileTransferError::new( None => Err(FileTransferError::new(
@@ -442,6 +487,11 @@ impl FileTransfer for SftpFileTransfer {
match self.is_connected() { match self.is_connected() {
true => { true => {
let dst: PathBuf = self.get_abs_path(dst); let dst: PathBuf = self.get_abs_path(dst);
info!(
"Copying {} to {}",
src.get_abs_path().display(),
dst.display()
);
// Run `cp -rf` // Run `cp -rf`
match self.perform_shell_cmd_with_path( match self.perform_shell_cmd_with_path(
format!( format!(
@@ -486,6 +536,7 @@ impl FileTransfer for SftpFileTransfer {
Ok(p) => p, Ok(p) => p,
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
info!("Getting file entries in {}", path.display());
// Get files // Get files
match sftp.readdir(dir.as_path()) { match sftp.readdir(dir.as_path()) {
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -517,6 +568,7 @@ impl FileTransfer for SftpFileTransfer {
Some(sftp) => { Some(sftp) => {
// Make directory // Make directory
let path: PathBuf = self.get_abs_path(PathBuf::from(dir).as_path()); let path: PathBuf = self.get_abs_path(PathBuf::from(dir).as_path());
info!("Making directory {}", path.display());
match sftp.mkdir(path.as_path(), 0o775) { match sftp.mkdir(path.as_path(), 0o775) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(err) => Err(FileTransferError::new_ex( Err(err) => Err(FileTransferError::new_ex(
@@ -541,6 +593,7 @@ impl FileTransfer for SftpFileTransfer {
)); ));
} }
// Match if file is a file or a directory // Match if file is a file or a directory
info!("Removing file {}", file.get_abs_path().display());
match file { match file {
FsEntry::File(f) => { FsEntry::File(f) => {
// Remove file // Remove file
@@ -554,6 +607,7 @@ impl FileTransfer for SftpFileTransfer {
} }
FsEntry::Directory(d) => { FsEntry::Directory(d) => {
// Remove recursively // Remove recursively
debug!("{} is a directory; removing all directory entries", d.name);
// Get directory files // Get directory files
let directory_content: Vec<FsEntry> = match self.list_dir(d.abs_path.as_path()) { let directory_content: Vec<FsEntry> = match self.list_dir(d.abs_path.as_path()) {
Ok(entries) => entries, Ok(entries) => entries,
@@ -585,6 +639,11 @@ impl FileTransfer for SftpFileTransfer {
FileTransferErrorType::UninitializedSession, FileTransferErrorType::UninitializedSession,
)), )),
Some(sftp) => { Some(sftp) => {
info!(
"Moving {} to {}",
file.get_abs_path().display(),
dst.display()
);
// Resolve destination path // Resolve destination path
let abs_dst: PathBuf = self.get_abs_path(dst); let abs_dst: PathBuf = self.get_abs_path(dst);
// Get abs path of entry // Get abs path of entry
@@ -611,6 +670,7 @@ impl FileTransfer for SftpFileTransfer {
Ok(p) => p, Ok(p) => p,
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
info!("Stat file {}", dir.display());
// Get file // Get file
match sftp.stat(dir.as_path()) { match sftp.stat(dir.as_path()) {
Ok(metadata) => Ok(self.make_fsentry(dir.as_path(), &metadata)), Ok(metadata) => Ok(self.make_fsentry(dir.as_path(), &metadata)),
@@ -630,6 +690,7 @@ impl FileTransfer for SftpFileTransfer {
/// ///
/// Execute a command on remote host /// Execute a command on remote host
fn exec(&mut self, cmd: &str) -> Result<String, FileTransferError> { fn exec(&mut self, cmd: &str) -> Result<String, FileTransferError> {
info!("Executing command {}", cmd);
match self.is_connected() { match self.is_connected() {
true => match self.perform_shell_cmd_with_path(cmd) { true => match self.perform_shell_cmd_with_path(cmd) {
Ok(output) => Ok(output), Ok(output) => Ok(output),
@@ -660,14 +721,20 @@ impl FileTransfer for SftpFileTransfer {
)), )),
Some(sftp) => { Some(sftp) => {
let remote_path: PathBuf = self.get_abs_path(file_name); let remote_path: PathBuf = self.get_abs_path(file_name);
info!(
"Sending file {} to {}",
local.abs_path.display(),
remote_path.display()
);
// Calculate file mode // Calculate file mode
let mode: i32 = match local.unix_pex { let mode: i32 = match local.unix_pex {
None => 0o644, None => 0o644,
Some((u, g, o)) => ((u as i32) << 6) + ((g as i32) << 3) + (o as i32), Some((u, g, o)) => ((u as i32) << 6) + ((g as i32) << 3) + (o as i32),
}; };
debug!("File mode {:?}", mode);
match sftp.open_mode( match sftp.open_mode(
remote_path.as_path(), remote_path.as_path(),
OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::APPEND | OpenFlags::TRUNCATE, OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNCATE,
mode, mode,
OpenType::File, OpenType::File,
) { ) {
@@ -695,6 +762,7 @@ impl FileTransfer for SftpFileTransfer {
Ok(p) => p, Ok(p) => p,
Err(err) => return Err(err), Err(err) => return Err(err),
}; };
info!("Receiving file {}", remote_path.display());
// Open remote file // Open remote file
match sftp.open(remote_path.as_path()) { match sftp.open(remote_path.as_path()) {
Ok(file) => Ok(Box::new(BufReader::with_capacity(65536, file))), Ok(file) => Ok(Box::new(BufReader::with_capacity(65536, file))),
+151 -61
View File
@@ -125,12 +125,17 @@ impl Localhost {
/// ///
/// Instantiates a new Localhost struct /// Instantiates a new Localhost struct
pub fn new(wrkdir: PathBuf) -> Result<Localhost, HostError> { pub fn new(wrkdir: PathBuf) -> Result<Localhost, HostError> {
debug!("Initializing localhost at {}", wrkdir.display());
let mut host: Localhost = Localhost { let mut host: Localhost = Localhost {
wrkdir, wrkdir,
files: Vec::new(), files: Vec::new(),
}; };
// Check if dir exists // Check if dir exists
if !host.file_exists(host.wrkdir.as_path()) { if !host.file_exists(host.wrkdir.as_path()) {
error!(
"Failed to initialize localhost: {} doesn't exist",
host.wrkdir.display()
);
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, None,
@@ -140,8 +145,15 @@ impl Localhost {
// Retrieve files for provided path // Retrieve files for provided path
host.files = match host.scan_dir(host.wrkdir.as_path()) { host.files = match host.scan_dir(host.wrkdir.as_path()) {
Ok(files) => files, Ok(files) => files,
Err(err) => return Err(err), Err(err) => {
error!(
"Failed to initialize localhost: could not scan wrkdir: {}",
err
);
return Err(err);
}
}; };
info!("Localhost initialized with success");
Ok(host) Ok(host)
} }
@@ -165,8 +177,10 @@ impl Localhost {
/// Change working directory with the new provided directory /// Change working directory with the new provided directory
pub fn change_wrkdir(&mut self, new_dir: &Path) -> Result<PathBuf, HostError> { pub fn change_wrkdir(&mut self, new_dir: &Path) -> Result<PathBuf, HostError> {
let new_dir: PathBuf = self.to_abs_path(new_dir); let new_dir: PathBuf = self.to_abs_path(new_dir);
info!("Changing localhost directory to {}...", new_dir.display());
// Check whether directory exists // Check whether directory exists
if !self.file_exists(new_dir.as_path()) { if !self.file_exists(new_dir.as_path()) {
error!("Could not change directory: No such file or directory");
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, None,
@@ -174,10 +188,11 @@ impl Localhost {
)); ));
} }
// Change directory // Change directory
if std::env::set_current_dir(new_dir.as_path()).is_err() { if let Err(err) = std::env::set_current_dir(new_dir.as_path()) {
error!("Could not enter directory: {}", err);
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, Some(err),
new_dir.as_path(), new_dir.as_path(),
)); ));
} }
@@ -189,11 +204,13 @@ impl Localhost {
self.files = match self.scan_dir(self.wrkdir.as_path()) { self.files = match self.scan_dir(self.wrkdir.as_path()) {
Ok(files) => files, Ok(files) => files,
Err(err) => { Err(err) => {
error!("Could not scan new directory: {}", err);
// Restore directory // Restore directory
self.wrkdir = prev_dir; self.wrkdir = prev_dir;
return Err(err); return Err(err);
} }
}; };
debug!("Changed directory to {}", self.wrkdir.display());
Ok(self.wrkdir.clone()) Ok(self.wrkdir.clone())
} }
@@ -210,6 +227,7 @@ impl Localhost {
/// ignex: don't report error if directory already exists /// ignex: don't report error if directory already exists
pub fn mkdir_ex(&mut self, dir_name: &Path, ignex: bool) -> Result<(), HostError> { pub fn mkdir_ex(&mut self, dir_name: &Path, ignex: bool) -> Result<(), HostError> {
let dir_path: PathBuf = self.to_abs_path(dir_name); let dir_path: PathBuf = self.to_abs_path(dir_name);
info!("Making directory {}", dir_path.display());
// If dir already exists, return Error // If dir already exists, return Error
if dir_path.exists() { if dir_path.exists() {
match ignex { match ignex {
@@ -229,13 +247,17 @@ impl Localhost {
if dir_name.is_relative() { if dir_name.is_relative() {
self.files = self.scan_dir(self.wrkdir.as_path())?; self.files = self.scan_dir(self.wrkdir.as_path())?;
} }
info!("Created directory {}", dir_path.display());
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::CouldNotCreateFile, error!("Could not make directory: {}", err);
Some(err), Err(HostError::new(
dir_path.as_path(), HostErrorType::CouldNotCreateFile,
)), Some(err),
dir_path.as_path(),
))
}
} }
} }
@@ -246,7 +268,9 @@ impl Localhost {
match entry { match entry {
FsEntry::Directory(dir) => { FsEntry::Directory(dir) => {
// If file doesn't exist; return error // If file doesn't exist; return error
debug!("Removing directory {}", dir.abs_path.display());
if !dir.abs_path.as_path().exists() { if !dir.abs_path.as_path().exists() {
error!("Directory doesn't exist");
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, None,
@@ -258,18 +282,24 @@ impl Localhost {
Ok(_) => { Ok(_) => {
// Update dir // Update dir
self.files = self.scan_dir(self.wrkdir.as_path())?; self.files = self.scan_dir(self.wrkdir.as_path())?;
info!("Removed directory {}", dir.abs_path.display());
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::DeleteFailed, error!("Could not remove directory: {}", err);
Some(err), Err(HostError::new(
dir.abs_path.as_path(), HostErrorType::DeleteFailed,
)), Some(err),
dir.abs_path.as_path(),
))
}
} }
} }
FsEntry::File(file) => { FsEntry::File(file) => {
// If file doesn't exist; return error // If file doesn't exist; return error
debug!("Removing file {}", file.abs_path.display());
if !file.abs_path.as_path().exists() { if !file.abs_path.as_path().exists() {
error!("File doesn't exist");
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, None,
@@ -281,13 +311,17 @@ impl Localhost {
Ok(_) => { Ok(_) => {
// Update dir // Update dir
self.files = self.scan_dir(self.wrkdir.as_path())?; self.files = self.scan_dir(self.wrkdir.as_path())?;
info!("Removed file {}", file.abs_path.display());
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::DeleteFailed, error!("Could not remove file: {}", err);
Some(err), Err(HostError::new(
file.abs_path.as_path(), HostErrorType::DeleteFailed,
)), Some(err),
file.abs_path.as_path(),
))
}
} }
} }
} }
@@ -302,13 +336,26 @@ impl Localhost {
Ok(_) => { Ok(_) => {
// Scan dir // Scan dir
self.files = self.scan_dir(self.wrkdir.as_path())?; self.files = self.scan_dir(self.wrkdir.as_path())?;
debug!(
"Moved file {} to {}",
entry.get_abs_path().display(),
dst_path.display()
);
Ok(()) Ok(())
} }
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::CouldNotCreateFile, error!(
Some(err), "Failed to move {} to {}: {}",
abs_path.as_path(), entry.get_abs_path().display(),
)), dst_path.display(),
err
);
Err(HostError::new(
HostErrorType::CouldNotCreateFile,
Some(err),
abs_path.as_path(),
))
}
} }
} }
@@ -318,6 +365,11 @@ impl Localhost {
pub fn copy(&mut self, entry: &FsEntry, dst: &Path) -> Result<(), HostError> { pub fn copy(&mut self, entry: &FsEntry, dst: &Path) -> Result<(), HostError> {
// Get absolute path of dest // Get absolute path of dest
let dst: PathBuf = self.to_abs_path(dst); let dst: PathBuf = self.to_abs_path(dst);
info!(
"Copying file {} to {}",
entry.get_abs_path().display(),
dst.display()
);
// Match entry // Match entry
match entry { match entry {
FsEntry::File(file) => { FsEntry::File(file) => {
@@ -333,16 +385,19 @@ impl Localhost {
}; };
// Copy entry path to dst path // Copy entry path to dst path
if let Err(err) = std::fs::copy(file.abs_path.as_path(), dst.as_path()) { if let Err(err) = std::fs::copy(file.abs_path.as_path(), dst.as_path()) {
error!("Failed to copy file: {}", err);
return Err(HostError::new( return Err(HostError::new(
HostErrorType::CouldNotCreateFile, HostErrorType::CouldNotCreateFile,
Some(err), Some(err),
file.abs_path.as_path(), file.abs_path.as_path(),
)); ));
} }
info!("File copied");
} }
FsEntry::Directory(dir) => { FsEntry::Directory(dir) => {
// If destination path doesn't exist, create destination // If destination path doesn't exist, create destination
if !dst.exists() { if !dst.exists() {
debug!("Directory {} doesn't exist; creating it", dst.display());
self.mkdir(dst.as_path())?; self.mkdir(dst.as_path())?;
} }
// Scan dir // Scan dir
@@ -386,15 +441,17 @@ impl Localhost {
/// Stat file and create a FsEntry /// Stat file and create a FsEntry
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> { pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> {
info!("Stating file {}", path.display());
let path: PathBuf = self.to_abs_path(path); let path: PathBuf = self.to_abs_path(path);
let attr: Metadata = match fs::metadata(path.as_path()) { let attr: Metadata = match fs::metadata(path.as_path()) {
Ok(metadata) => metadata, Ok(metadata) => metadata,
Err(err) => { Err(err) => {
error!("Could not read file metadata: {}", err);
return Err(HostError::new( return Err(HostError::new(
HostErrorType::FileNotAccessible, HostErrorType::FileNotAccessible,
Some(err), Some(err),
path.as_path(), path.as_path(),
)) ));
} }
}; };
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or("")); let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
@@ -454,14 +511,16 @@ impl Localhost {
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> { pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> {
let path: PathBuf = self.to_abs_path(path); let path: PathBuf = self.to_abs_path(path);
info!("Stating file {}", path.display());
let attr: Metadata = match fs::metadata(path.as_path()) { let attr: Metadata = match fs::metadata(path.as_path()) {
Ok(metadata) => metadata, Ok(metadata) => metadata,
Err(err) => { Err(err) => {
error!("Could not read file metadata: {}", err);
return Err(HostError::new( return Err(HostError::new(
HostErrorType::FileNotAccessible, HostErrorType::FileNotAccessible,
Some(err), Some(err),
path.as_path(), path.as_path(),
)) ));
} }
}; };
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or("")); let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
@@ -523,16 +582,23 @@ impl Localhost {
let args: Vec<&str> = cmd.split(' ').collect(); let args: Vec<&str> = cmd.split(' ').collect();
let cmd: &str = args.first().unwrap(); let cmd: &str = args.first().unwrap();
let argv: &[&str] = &args[1..]; let argv: &[&str] = &args[1..];
info!("Executing command: {} {:?}", cmd, argv);
match std::process::Command::new(cmd).args(argv).output() { match std::process::Command::new(cmd).args(argv).output() {
Ok(output) => match std::str::from_utf8(&output.stdout) { Ok(output) => match std::str::from_utf8(&output.stdout) {
Ok(s) => Ok(s.to_string()), Ok(s) => {
info!("Command output: {}", s);
Ok(s.to_string())
}
Err(_) => Ok(String::new()), Err(_) => Ok(String::new()),
}, },
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::ExecutionFailed, error!("Failed to run command: {}", err);
Some(err), Err(HostError::new(
self.wrkdir.as_path(), HostErrorType::ExecutionFailed,
)), Some(err),
self.wrkdir.as_path(),
))
}
} }
} }
@@ -548,19 +614,32 @@ impl Localhost {
let mut mpex = metadata.permissions(); let mut mpex = metadata.permissions();
mpex.set_mode(self.mode_to_u32(pex)); mpex.set_mode(self.mode_to_u32(pex));
match set_permissions(path.as_path(), mpex) { match set_permissions(path.as_path(), mpex) {
Ok(_) => Ok(()), Ok(_) => {
Err(err) => Err(HostError::new( info!("Changed mode for {} to {:?}", path.display(), pex);
HostErrorType::FileNotAccessible, Ok(())
Some(err), }
path.as_path(), Err(err) => {
)), error!("Could not change mode for file {}: {}", path.display(), err);
Err(HostError::new(
HostErrorType::FileNotAccessible,
Some(err),
path.as_path(),
))
}
} }
} }
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::FileNotAccessible, error!(
Some(err), "Chmod failed; could not read metadata for file {}: {}",
path.as_path(), path.display(),
)), err
);
Err(HostError::new(
HostErrorType::FileNotAccessible,
Some(err),
path.as_path(),
))
}
} }
} }
@@ -569,7 +648,9 @@ impl Localhost {
/// Open file for read /// Open file for read
pub fn open_file_read(&self, file: &Path) -> Result<File, HostError> { pub fn open_file_read(&self, file: &Path) -> Result<File, HostError> {
let file: PathBuf = self.to_abs_path(file); let file: PathBuf = self.to_abs_path(file);
info!("Opening file {} for read", file.display());
if !self.file_exists(file.as_path()) { if !self.file_exists(file.as_path()) {
error!("File doesn't exist!");
return Err(HostError::new( return Err(HostError::new(
HostErrorType::NoSuchFileOrDirectory, HostErrorType::NoSuchFileOrDirectory,
None, None,
@@ -583,11 +664,14 @@ impl Localhost {
.open(file.as_path()) .open(file.as_path())
{ {
Ok(f) => Ok(f), Ok(f) => Ok(f),
Err(err) => Err(HostError::new( Err(err) => {
HostErrorType::FileNotAccessible, error!("Could not open file for read: {}", err);
Some(err), Err(HostError::new(
file.as_path(), HostErrorType::FileNotAccessible,
)), Some(err),
file.as_path(),
))
}
} }
} }
@@ -596,6 +680,7 @@ impl Localhost {
/// Open file for write /// Open file for write
pub fn open_file_write(&self, file: &Path) -> Result<File, HostError> { pub fn open_file_write(&self, file: &Path) -> Result<File, HostError> {
let file: PathBuf = self.to_abs_path(file); let file: PathBuf = self.to_abs_path(file);
info!("Opening file {} for write", file.display());
match OpenOptions::new() match OpenOptions::new()
.create(true) .create(true)
.write(true) .write(true)
@@ -603,18 +688,21 @@ impl Localhost {
.open(file.as_path()) .open(file.as_path())
{ {
Ok(f) => Ok(f), Ok(f) => Ok(f),
Err(err) => match self.file_exists(file.as_path()) { Err(err) => {
true => Err(HostError::new( error!("Failed to open file: {}", err);
HostErrorType::ReadonlyFile, match self.file_exists(file.as_path()) {
Some(err), true => Err(HostError::new(
file.as_path(), HostErrorType::ReadonlyFile,
)), Some(err),
false => Err(HostError::new( file.as_path(),
HostErrorType::FileNotAccessible, )),
Some(err), false => Err(HostError::new(
file.as_path(), HostErrorType::FileNotAccessible,
)), Some(err),
}, file.as_path(),
)),
}
}
} }
} }
@@ -629,13 +717,15 @@ impl Localhost {
/// ///
/// Get content of the current directory as a list of fs entry /// Get content of the current directory as a list of fs entry
pub fn scan_dir(&self, dir: &Path) -> Result<Vec<FsEntry>, HostError> { pub fn scan_dir(&self, dir: &Path) -> Result<Vec<FsEntry>, HostError> {
info!("Reading directory {}", dir.display());
match std::fs::read_dir(dir) { match std::fs::read_dir(dir) {
Ok(e) => { Ok(e) => {
let mut fs_entries: Vec<FsEntry> = Vec::new(); let mut fs_entries: Vec<FsEntry> = Vec::new();
for entry in e.flatten() { for entry in e.flatten() {
// NOTE: 0.4.1, don't fail if stat for one file fails // NOTE: 0.4.1, don't fail if stat for one file fails
if let Ok(entry) = self.stat(entry.path().as_path()) { match self.stat(entry.path().as_path()) {
fs_entries.push(entry); Ok(entry) => fs_entries.push(entry),
Err(e) => error!("Failed to stat {}: {}", entry.path().display(), e),
} }
} }
Ok(fs_entries) Ok(fs_entries)
+2
View File
@@ -35,6 +35,8 @@ extern crate bitflags;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate log;
#[macro_use]
extern crate magic_crypt; extern crate magic_crypt;
pub mod activity_manager; pub mod activity_manager;
+23
View File
@@ -32,6 +32,8 @@ extern crate bitflags;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate log;
#[macro_use]
extern crate magic_crypt; extern crate magic_crypt;
extern crate rpassword; extern crate rpassword;
@@ -55,6 +57,7 @@ mod utils;
// namespaces // namespaces
use activity_manager::{ActivityManager, NextActivity}; use activity_manager::{ActivityManager, NextActivity};
use filetransfer::FileTransferProtocol; use filetransfer::FileTransferProtocol;
use system::logging;
/// ### print_usage /// ### print_usage
/// ///
@@ -79,10 +82,12 @@ fn main() {
let mut remote_wrkdir: Option<PathBuf> = None; let mut remote_wrkdir: Option<PathBuf> = None;
let mut protocol: FileTransferProtocol = FileTransferProtocol::Sftp; // Default protocol let mut protocol: FileTransferProtocol = FileTransferProtocol::Sftp; // Default protocol
let mut ticks: Duration = Duration::from_millis(10); let mut ticks: Duration = Duration::from_millis(10);
let mut log_enabled: bool = true;
//Process options //Process options
let mut opts = Options::new(); let mut opts = Options::new();
opts.optopt("P", "password", "Provide password from CLI", "<password>"); opts.optopt("P", "password", "Provide password from CLI", "<password>");
opts.optopt("T", "ticks", "Set UI ticks; default 10ms", "<ms>"); opts.optopt("T", "ticks", "Set UI ticks; default 10ms", "<ms>");
opts.optflag("q", "quiet", "Disable logging");
opts.optflag("v", "version", ""); opts.optflag("v", "version", "");
opts.optflag("h", "help", "Print this menu"); opts.optflag("h", "help", "Print this menu");
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
@@ -105,6 +110,10 @@ fn main() {
); );
std::process::exit(255); std::process::exit(255);
} }
// Logging
if matches.opt_present("q") {
log_enabled = false;
}
// Match password // Match password
if let Some(passwd) = matches.opt_str("P") { if let Some(passwd) = matches.opt_str("P") {
password = Some(passwd); password = Some(passwd);
@@ -155,9 +164,17 @@ fn main() {
Ok(dir) => dir, Ok(dir) => dir,
Err(_) => PathBuf::from("/"), Err(_) => PathBuf::from("/"),
}; };
// Setup logging
if log_enabled {
if let Err(err) = logging::init() {
eprintln!("Failed to initialize logging: {}", err);
}
}
info!("termscp {} started!", TERMSCP_VERSION);
// Initialize client if necessary // Initialize client if necessary
let mut start_activity: NextActivity = NextActivity::Authentication; let mut start_activity: NextActivity = NextActivity::Authentication;
if address.is_some() { if address.is_some() {
debug!("User has specified remote options: address: {:?}, port: {:?}, protocol: {:?}, user: {:?}, password: {}", address, port, protocol, username, utils::fmt::shadow_password(password.as_deref().unwrap_or("")));
if password.is_none() { if password.is_none() {
// Ask password if unspecified // Ask password if unspecified
password = match rpassword::read_password_from_tty(Some("Password: ")) { password = match rpassword::read_password_from_tty(Some("Password: ")) {
@@ -173,6 +190,10 @@ fn main() {
std::process::exit(255); std::process::exit(255);
} }
}; };
debug!(
"Read password from tty: {}",
utils::fmt::shadow_password(password.as_deref().unwrap_or(""))
);
} }
// In this case the first activity will be FileTransfer // In this case the first activity will be FileTransfer
start_activity = NextActivity::FileTransfer; start_activity = NextActivity::FileTransfer;
@@ -190,7 +211,9 @@ fn main() {
manager.set_filetransfer_params(address, port, protocol, username, password, remote_wrkdir); manager.set_filetransfer_params(address, port, protocol, username, password, remote_wrkdir);
} }
// Run // Run
info!("Starting activity manager...");
manager.run(start_activity); manager.run(start_activity);
info!("termscp terminated");
// Then return // Then return
std::process::exit(0); std::process::exit(0);
} }
+66 -15
View File
@@ -68,9 +68,11 @@ impl BookmarksClient {
) -> Result<BookmarksClient, SerializerError> { ) -> Result<BookmarksClient, SerializerError> {
// Create default hosts // Create default hosts
let default_hosts: UserHosts = Default::default(); let default_hosts: UserHosts = Default::default();
debug!("Setting up bookmarks client...");
// Make a key storage (windows / macos) // Make a key storage (windows / macos)
#[cfg(any(target_os = "windows", target_os = "macos"))] #[cfg(any(target_os = "windows", target_os = "macos"))]
let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = { let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
debug!("Setting up KeyStorage");
let username: String = whoami::username(); let username: String = whoami::username();
let storage: KeyringStorage = KeyringStorage::new(username.as_str()); let storage: KeyringStorage = KeyringStorage::new(username.as_str());
// Check if keyring storage is supported // Check if keyring storage is supported
@@ -79,8 +81,14 @@ impl BookmarksClient {
#[cfg(test)] // NOTE: when running test, add -test #[cfg(test)] // NOTE: when running test, add -test
let app_name: &str = "termscp-test"; let app_name: &str = "termscp-test";
match storage.is_supported() { match storage.is_supported() {
true => (Box::new(storage), app_name), true => {
false => (Box::new(FileStorage::new(storage_path)), "bookmarks"), debug!("Using KeyringStorage");
(Box::new(storage), app_name)
}
false => {
warn!("KeyringStorage is not supported; using FileStorage");
(Box::new(FileStorage::new(storage_path)), "bookmarks")
}
} }
}; };
// Make a key storage (linux / unix) // Make a key storage (linux / unix)
@@ -90,16 +98,22 @@ impl BookmarksClient {
let app_name: &str = "bookmarks"; let app_name: &str = "bookmarks";
#[cfg(test)] // NOTE: when running test, add -test #[cfg(test)] // NOTE: when running test, add -test
let app_name: &str = "bookmarks-test"; let app_name: &str = "bookmarks-test";
debug!("Using FileStorage");
(Box::new(FileStorage::new(storage_path)), app_name) (Box::new(FileStorage::new(storage_path)), app_name)
}; };
// Load key // Load key
let key: String = match key_storage.get_key(service_id) { let key: String = match key_storage.get_key(service_id) {
Ok(k) => k, Ok(k) => {
debug!("Key loaded with success");
k
}
Err(e) => match e { Err(e) => match e {
KeyStorageError::NoSuchKey => { KeyStorageError::NoSuchKey => {
// If no such key, generate key and set it into the storage // If no such key, generate key and set it into the storage
let key: String = Self::generate_key(); let key: String = Self::generate_key();
debug!("Key doesn't exist yet or could not be loaded; generated a new key");
if let Err(e) = key_storage.set_key(service_id, key.as_str()) { if let Err(e) = key_storage.set_key(service_id, key.as_str()) {
error!("Failed to set new key into storage: {}", e);
return Err(SerializerError::new_ex( return Err(SerializerError::new_ex(
SerializerErrorKind::IoError, SerializerErrorKind::IoError,
format!("Could not write key to storage: {}", e), format!("Could not write key to storage: {}", e),
@@ -109,10 +123,11 @@ impl BookmarksClient {
key key
} }
_ => { _ => {
error!("Failed to get key from storage: {}", e);
return Err(SerializerError::new_ex( return Err(SerializerError::new_ex(
SerializerErrorKind::IoError, SerializerErrorKind::IoError,
format!("Could not get key from storage: {}", e), format!("Could not get key from storage: {}", e),
)) ));
} }
}, },
}; };
@@ -124,15 +139,19 @@ impl BookmarksClient {
}; };
// If bookmark file doesn't exist, initialize it // If bookmark file doesn't exist, initialize it
if !bookmarks_file.exists() { if !bookmarks_file.exists() {
info!("Bookmarks file doesn't exist yet; creating it...");
if let Err(err) = client.write_bookmarks() { if let Err(err) = client.write_bookmarks() {
error!("Failed to create bookmarks file: {}", err);
return Err(err); return Err(err);
} }
} else { } else {
// Load bookmarks from file // Load bookmarks from file
if let Err(err) = client.read_bookmarks() { if let Err(err) = client.read_bookmarks() {
error!("Failed to load bookmarks: {}", err);
return Err(err); return Err(err);
} }
} }
info!("Bookmarks client initialized");
// Load key // Load key
Ok(client) Ok(client)
} }
@@ -152,19 +171,29 @@ impl BookmarksClient {
key: &str, key: &str,
) -> 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)?;
debug!("Getting bookmark {}", key);
Some(( Some((
entry.address.clone(), entry.address.clone(),
entry.port, entry.port,
match FileTransferProtocol::from_str(entry.protocol.as_str()) { match FileTransferProtocol::from_str(entry.protocol.as_str()) {
Ok(proto) => proto, Ok(proto) => proto,
Err(_) => FileTransferProtocol::Sftp, // Default Err(err) => {
error!(
"Found invalid protocol in bookmarks: {}; defaulting to SFTP",
err
);
FileTransferProtocol::Sftp // Default
}
}, },
entry.username.clone(), entry.username.clone(),
match &entry.password { match &entry.password {
// Decrypted password if Some; if decryption fails return None // Decrypted password if Some; if decryption fails return None
Some(pwd) => match self.decrypt_str(pwd.as_str()) { Some(pwd) => match self.decrypt_str(pwd.as_str()) {
Ok(decrypted_pwd) => Some(decrypted_pwd), Ok(decrypted_pwd) => Some(decrypted_pwd),
Err(_) => None, Err(err) => {
error!("Failed to decrypt password for bookmark: {}", err);
None
}
}, },
None => None, None => None,
}, },
@@ -184,9 +213,11 @@ impl BookmarksClient {
password: Option<String>, password: Option<String>,
) { ) {
if name.is_empty() { if name.is_empty() {
error!("Fatal error; bookmark name is empty");
panic!("Bookmark name can't be empty"); panic!("Bookmark name can't be empty");
} }
// Make bookmark // Make bookmark
info!("Added bookmark {} with address {}", name, addr);
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password); let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password);
self.hosts.bookmarks.insert(name, host); self.hosts.bookmarks.insert(name, host);
} }
@@ -196,6 +227,7 @@ impl BookmarksClient {
/// Delete entry from bookmarks /// Delete entry from bookmarks
pub fn del_bookmark(&mut self, name: &str) { pub fn del_bookmark(&mut self, name: &str) {
let _ = self.hosts.bookmarks.remove(name); let _ = self.hosts.bookmarks.remove(name);
info!("Removed bookmark {}", name);
} }
/// ### iter_recents /// ### iter_recents
/// ///
@@ -209,13 +241,20 @@ impl BookmarksClient {
/// Get recent associated to key /// Get recent associated to key
pub fn get_recent(&self, key: &str) -> Option<(String, u16, FileTransferProtocol, String)> { pub fn get_recent(&self, key: &str) -> Option<(String, u16, FileTransferProtocol, String)> {
// NOTE: password is not decrypted; recents will never have password // NOTE: password is not decrypted; recents will never have password
info!("Getting bookmark {}", key);
let entry: &Bookmark = self.hosts.recents.get(key)?; let entry: &Bookmark = self.hosts.recents.get(key)?;
Some(( Some((
entry.address.clone(), entry.address.clone(),
entry.port, entry.port,
match FileTransferProtocol::from_str(entry.protocol.as_str()) { match FileTransferProtocol::from_str(entry.protocol.as_str()) {
Ok(proto) => proto, Ok(proto) => proto,
Err(_) => FileTransferProtocol::Sftp, // Default Err(err) => {
error!(
"Found invalid protocol in bookmarks: {}; defaulting to SFTP",
err
);
FileTransferProtocol::Sftp // Default
}
}, },
entry.username.clone(), entry.username.clone(),
)) ))
@@ -236,6 +275,7 @@ impl BookmarksClient {
// Check if duplicated // Check if duplicated
for recent_host in self.hosts.recents.values() { for recent_host in self.hosts.recents.values() {
if *recent_host == host { if *recent_host == host {
debug!("Discarding recent since duplicated ({})", host.address);
// Don't save duplicates // Don't save duplicates
return; return;
} }
@@ -252,6 +292,7 @@ impl BookmarksClient {
// Delete keys starting from the last one // Delete keys starting from the last one
for key in keys.iter() { for key in keys.iter() {
let _ = self.hosts.recents.remove(key); let _ = self.hosts.recents.remove(key);
debug!("Removed recent bookmark {}", key);
// If length is < self.recents_size; break // If length is < self.recents_size; break
if self.hosts.recents.len() < self.recents_size { if self.hosts.recents.len() < self.recents_size {
break; break;
@@ -259,6 +300,7 @@ impl BookmarksClient {
} }
} }
let name: String = fmt_time(SystemTime::now(), "ISO%Y%m%dT%H%M%S"); let name: String = fmt_time(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
info!("Saved recent host {} ({})", name, host.address);
self.hosts.recents.insert(name, host); self.hosts.recents.insert(name, host);
} }
@@ -267,6 +309,7 @@ impl BookmarksClient {
/// Delete entry from recents /// Delete entry from recents
pub fn del_recent(&mut self, name: &str) { pub fn del_recent(&mut self, name: &str) {
let _ = self.hosts.recents.remove(name); let _ = self.hosts.recents.remove(name);
info!("Removed recent host {}", name);
} }
/// ### write_bookmarks /// ### write_bookmarks
@@ -274,6 +317,7 @@ impl BookmarksClient {
/// Write bookmarks to file /// Write bookmarks to file
pub fn write_bookmarks(&self) -> Result<(), SerializerError> { pub fn write_bookmarks(&self) -> Result<(), SerializerError> {
// Open file // Open file
debug!("Writing bookmarks");
match OpenOptions::new() match OpenOptions::new()
.create(true) .create(true)
.write(true) .write(true)
@@ -284,10 +328,13 @@ impl BookmarksClient {
let serializer: BookmarkSerializer = BookmarkSerializer {}; let serializer: BookmarkSerializer = BookmarkSerializer {};
serializer.serialize(Box::new(writer), &self.hosts) serializer.serialize(Box::new(writer), &self.hosts)
} }
Err(err) => Err(SerializerError::new_ex( Err(err) => {
SerializerErrorKind::IoError, error!("Failed to write bookmarks: {}", err);
err.to_string(), Err(SerializerError::new_ex(
)), SerializerErrorKind::IoError,
err.to_string(),
))
}
} }
} }
@@ -296,6 +343,7 @@ impl BookmarksClient {
/// Read bookmarks from file /// Read bookmarks from file
fn read_bookmarks(&mut self) -> Result<(), SerializerError> { fn read_bookmarks(&mut self) -> Result<(), SerializerError> {
// Open bookmarks file for read // Open bookmarks file for read
debug!("Reading bookmarks");
match OpenOptions::new() match OpenOptions::new()
.read(true) .read(true)
.open(self.bookmarks_file.as_path()) .open(self.bookmarks_file.as_path())
@@ -311,10 +359,13 @@ impl BookmarksClient {
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
Err(err) => Err(SerializerError::new_ex( Err(err) => {
SerializerErrorKind::IoError, error!("Failed to read bookmarks: {}", err);
err.to_string(), Err(SerializerError::new_ex(
)), SerializerErrorKind::IoError,
err.to_string(),
))
}
} }
} }
+33 -8
View File
@@ -58,6 +58,11 @@ impl ConfigClient {
pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result<ConfigClient, SerializerError> { pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result<ConfigClient, SerializerError> {
// Initialize a default configuration // Initialize a default configuration
let default_config: UserConfig = UserConfig::default(); let default_config: UserConfig = UserConfig::default();
info!(
"Setting up config client with config path {} and SSH key directory {}",
config_path.display(),
ssh_key_dir.display()
);
// Create client // Create client
let mut client: ConfigClient = ConfigClient { let mut client: ConfigClient = ConfigClient {
config: default_config, config: default_config,
@@ -67,6 +72,7 @@ impl ConfigClient {
// If ssh key directory doesn't exist, create it // If ssh key directory doesn't exist, create it
if !ssh_key_dir.exists() { if !ssh_key_dir.exists() {
if let Err(err) = create_dir(ssh_key_dir) { if let Err(err) = create_dir(ssh_key_dir) {
error!("Failed to create SSH key dir: {}", err);
return Err(SerializerError::new_ex( return Err(SerializerError::new_ex(
SerializerErrorKind::IoError, SerializerErrorKind::IoError,
format!( format!(
@@ -76,17 +82,22 @@ impl ConfigClient {
), ),
)); ));
} }
debug!("Created SSH key directory");
} }
// If Config file doesn't exist, create it // If Config file doesn't exist, create it
if !config_path.exists() { if !config_path.exists() {
if let Err(err) = client.write_config() { if let Err(err) = client.write_config() {
error!("Couldn't create configuration file: {}", err);
return Err(err); return Err(err);
} }
debug!("Config file didn't exist; created file");
} else { } else {
// otherwise Load configuration from file // otherwise Load configuration from file
if let Err(err) = client.read_config() { if let Err(err) = client.read_config() {
error!("Couldn't read configuration file: {}", err);
return Err(err); return Err(err);
} }
debug!("Read configuration file");
} }
Ok(client) Ok(client)
} }
@@ -230,12 +241,18 @@ impl ConfigClient {
p.push(format!("{}.key", host_name)); p.push(format!("{}.key", host_name));
p p
}; };
info!(
"Writing SSH file to {} for host {}",
ssh_key_path.display(),
host_name
);
// Write key to file // Write key to file
let mut f: File = match File::create(ssh_key_path.as_path()) { let mut f: File = match File::create(ssh_key_path.as_path()) {
Ok(f) => f, Ok(f) => f,
Err(err) => return Self::make_io_err(err), Err(err) => return Self::make_io_err(err),
}; };
if let Err(err) = f.write_all(ssh_key.as_bytes()) { if let Err(err) = f.write_all(ssh_key.as_bytes()) {
error!("Failed to write SSH key to file: {}", err);
return Self::make_io_err(err); return Self::make_io_err(err);
} }
// Add host to keys // Add host to keys
@@ -251,6 +268,7 @@ impl ConfigClient {
/// and also commits changes to configuration, to prevent incoerent data /// and also commits changes to configuration, to prevent incoerent data
pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> { pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> {
// Remove key from configuration and get key path // Remove key from configuration and get key path
info!("Removing key for {}@{}", host, username);
let key_path: PathBuf = match self let key_path: PathBuf = match self
.config .config
.remote .remote
@@ -262,6 +280,7 @@ impl ConfigClient {
}; };
// Remove file // Remove file
if let Err(err) = remove_file(key_path.as_path()) { if let Err(err) = remove_file(key_path.as_path()) {
error!("Failed to remove key file {}: {}", key_path.display(), err);
return Self::make_io_err(err); return Self::make_io_err(err);
} }
// Commit changes to configuration // Commit changes to configuration
@@ -310,10 +329,13 @@ impl ConfigClient {
let serializer: ConfigSerializer = ConfigSerializer {}; let serializer: ConfigSerializer = ConfigSerializer {};
serializer.serialize(Box::new(writer), &self.config) serializer.serialize(Box::new(writer), &self.config)
} }
Err(err) => Err(SerializerError::new_ex( Err(err) => {
SerializerErrorKind::IoError, error!("Failed to write configuration file: {}", err);
err.to_string(), Err(SerializerError::new_ex(
)), SerializerErrorKind::IoError,
err.to_string(),
))
}
} }
} }
@@ -337,10 +359,13 @@ impl ConfigClient {
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
Err(err) => Err(SerializerError::new_ex( Err(err) => {
SerializerErrorKind::IoError, error!("Failed to read configuration: {}", err);
err.to_string(), Err(SerializerError::new_ex(
)), SerializerErrorKind::IoError,
err.to_string(),
))
}
} }
} }
+17
View File
@@ -87,6 +87,15 @@ pub fn get_config_paths(config_dir: &Path) -> (PathBuf, PathBuf) {
(bookmarks_file, keys_dir) (bookmarks_file, keys_dir)
} }
/// ### get_log_paths
///
/// Returns the path for the supposed log file
pub fn get_log_paths(config_dir: &Path) -> PathBuf {
let mut log_file: PathBuf = PathBuf::from(config_dir);
log_file.push("termscp.log");
log_file
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@@ -143,4 +152,12 @@ mod tests {
) )
); );
} }
#[test]
fn test_system_environment_get_log_paths() {
assert_eq!(
get_log_paths(&Path::new("/home/omar/.config/termscp/")),
PathBuf::from("/home/omar/.config/termscp/termscp.log"),
);
}
} }
+72
View File
@@ -0,0 +1,72 @@
//! ## Logging
//!
//! `logging` is the module which initializes the logging system for termscp
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// locals
use crate::system::environment::{get_log_paths, init_config_dir};
use crate::utils::file::open_file;
// ext
use simplelog::{ConfigBuilder, LevelFilter, WriteLogger};
use std::fs::File;
use std::path::PathBuf;
/// ### init
///
/// Initialize logger
pub fn init() -> Result<(), String> {
// Init config dir
let config_dir: PathBuf = match init_config_dir() {
Ok(Some(p)) => p,
Ok(None) => {
return Err(String::from(
"This system doesn't seem to support CONFIG_DIR",
))
}
Err(err) => return Err(err),
};
let log_file_path: PathBuf = get_log_paths(config_dir.as_path());
// Open log file
let file: File = open_file(log_file_path.as_path(), true, true, false)
.map_err(|e| format!("Failed to open file {}: {}", log_file_path.display(), e))?;
// Prepare log config
let config = ConfigBuilder::new()
.set_time_format_str("%Y-%m-%dT%H:%M:%S%z")
.build();
// Make logger
WriteLogger::init(LevelFilter::Trace, config, file)
.map_err(|e| format!("Failed to initialize logger: {}", e))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_system_logging_setup() {
assert!(init().is_ok());
}
}
+1
View File
@@ -30,4 +30,5 @@ pub mod bookmarks_client;
pub mod config_client; pub mod config_client;
pub mod environment; pub mod environment;
pub(crate) mod keys; pub(crate) mod keys;
pub mod logging;
pub mod sshkey_storage; pub mod sshkey_storage;
+6 -1
View File
@@ -42,6 +42,7 @@ impl SshKeyStorage {
pub fn storage_from_config(cfg_client: &ConfigClient) -> Self { pub fn storage_from_config(cfg_client: &ConfigClient) -> Self {
let mut hosts: HashMap<String, PathBuf> = let mut hosts: HashMap<String, PathBuf> =
HashMap::with_capacity(cfg_client.iter_ssh_keys().count()); HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
debug!("Setting up SSH key storage");
// Iterate over keys // Iterate over keys
for key in cfg_client.iter_ssh_keys() { for key in cfg_client.iter_ssh_keys() {
match cfg_client.get_ssh_key(key) { match cfg_client.get_ssh_key(key) {
@@ -52,8 +53,12 @@ impl SshKeyStorage {
} }
None => continue, None => continue,
}, },
Err(_) => continue, Err(err) => {
error!("Failed to get SSH key for {}: {}", key, err);
continue;
}
} }
info!("Got SSH key for {}", key);
} }
// Return storage // Return storage
SshKeyStorage { hosts } SshKeyStorage { hosts }
+17 -3
View File
@@ -108,17 +108,24 @@ impl AuthActivity {
/// ///
/// If enabled in configuration, check for updates from Github /// If enabled in configuration, check for updates from Github
fn check_for_updates(&mut self) { fn check_for_updates(&mut self) {
debug!("Check for updates...");
// Check version only if unset in the store // Check version only if unset in the store
let ctx: &Context = self.context.as_ref().unwrap(); let ctx: &Context = self.context.as_ref().unwrap();
if !ctx.store.isset(STORE_KEY_LATEST_VERSION) { if !ctx.store.isset(STORE_KEY_LATEST_VERSION) {
debug!("Version is not set in storage");
let mut new_version: Option<String> = match ctx.config_client.as_ref() { let mut new_version: Option<String> = match ctx.config_client.as_ref() {
Some(client) => { Some(client) => {
if client.get_check_for_updates() { if client.get_check_for_updates() {
debug!("Check for updates is enabled");
// Send request // Send request
match git::check_for_updates(env!("CARGO_PKG_VERSION")) { match git::check_for_updates(env!("CARGO_PKG_VERSION")) {
Ok(version) => version, Ok(version) => {
info!("Latest version is: {:?}", version);
version
}
Err(err) => { Err(err) => {
// Report error // Report error
error!("Failed to get latest version: {}", err);
self.mount_error( self.mount_error(
format!("Could not check for new updates: {}", err).as_str(), format!("Could not check for new updates: {}", err).as_str(),
); );
@@ -127,6 +134,7 @@ impl AuthActivity {
} }
} }
} else { } else {
info!("Check for updates is disabled");
None None
} }
} }
@@ -149,6 +157,7 @@ impl Activity for AuthActivity {
/// `on_create` must initialize all the data structures used by the activity /// `on_create` must initialize all the data structures used by the activity
/// Context is taken from activity manager and will be released only when activity is destroyed /// Context is taken from activity manager and will be released only when activity is destroyed
fn on_create(&mut self, mut context: Context) { fn on_create(&mut self, mut context: Context) {
debug!("Initializing activity");
// Initialize file transfer params // Initialize file transfer params
context.ft_params = Some(FileTransferParams::default()); context.ft_params = Some(FileTransferParams::default());
// Set context // Set context
@@ -156,7 +165,9 @@ impl Activity for AuthActivity {
// Clear terminal // Clear terminal
self.context.as_mut().unwrap().clear_screen(); self.context.as_mut().unwrap().clear_screen();
// Put raw mode on enabled // Put raw mode on enabled
let _ = enable_raw_mode(); if let Err(err) = enable_raw_mode() {
error!("Failed to enter raw mode: {}", err);
}
// Init bookmarks client // Init bookmarks client
if self.bookmarks_client.is_none() { if self.bookmarks_client.is_none() {
self.init_bookmarks_client(); self.init_bookmarks_client();
@@ -169,6 +180,7 @@ impl Activity for AuthActivity {
self.check_for_updates(); self.check_for_updates();
// Initialize view // Initialize view
self.init(); self.init();
info!("Activity initialized");
} }
/// ### on_draw /// ### on_draw
@@ -213,7 +225,9 @@ impl Activity for AuthActivity {
/// This function finally releases the context /// This function finally releases the context
fn on_destroy(&mut self) -> Option<Context> { fn on_destroy(&mut self) -> Option<Context> {
// Disable raw mode // Disable raw mode
let _ = disable_raw_mode(); if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
self.context.as_ref()?; self.context.as_ref()?;
// Clear terminal and return // Clear terminal and return
match self.context.take() { match self.context.take() {
+3 -1
View File
@@ -271,7 +271,9 @@ impl AuthActivity {
Some(Payload::One(Value::Usize(0))) Some(Payload::One(Value::Usize(0)))
); );
// Save bookmark // Save bookmark
self.save_bookmark(bookmark_name, save_pwd); if !bookmark_name.is_empty() {
self.save_bookmark(bookmark_name, save_pwd);
}
// Umount popup // Umount popup
self.umount_bookmark_save_dialog(); self.umount_bookmark_save_dialog();
// Reload bookmarks // Reload bookmarks
+6
View File
@@ -36,6 +36,12 @@ impl FileTransferActivity {
/// ///
/// Add message to log events /// Add message to log events
pub(super) fn log(&mut self, level: LogLevel, msg: String) { pub(super) fn log(&mut self, level: LogLevel, msg: String) {
// Log to file
match level {
LogLevel::Error => error!("{}", msg),
LogLevel::Info => info!("{}", msg),
LogLevel::Warn => warn!("{}", msg),
}
// Create log record // Create log record
let record: LogRecord = LogRecord::new(level, msg); let record: LogRecord = LogRecord::new(level, msg);
//Check if history overflows the size //Check if history overflows the size
+16 -2
View File
@@ -273,25 +273,33 @@ impl Activity for FileTransferActivity {
/// `on_create` is the function which must be called to initialize the activity. /// `on_create` is the function which must be called to initialize the activity.
/// `on_create` must initialize all the data structures used by the activity /// `on_create` must initialize all the data structures used by the activity
fn on_create(&mut self, context: Context) { fn on_create(&mut self, context: Context) {
debug!("Initializing activity...");
// Set context // Set context
self.context = Some(context); self.context = Some(context);
// Clear terminal // Clear terminal
self.context.as_mut().unwrap().clear_screen(); self.context.as_mut().unwrap().clear_screen();
// Put raw mode on enabled // Put raw mode on enabled
let _ = enable_raw_mode(); if let Err(err) = enable_raw_mode() {
error!("Failed to enter raw mode: {}", err);
}
// Set working directory // Set working directory
let pwd: PathBuf = self.host.pwd(); let pwd: PathBuf = self.host.pwd();
// Get files at current wd // Get files at current wd
self.local_scan(pwd.as_path()); self.local_scan(pwd.as_path());
self.local_mut().wrkdir = pwd; self.local_mut().wrkdir = pwd;
debug!("Read working directory");
// Configure text editor // Configure text editor
self.setup_text_editor(); self.setup_text_editor();
debug!("Setup text editor");
// init view // init view
self.init(); self.init();
debug!("Initialized view");
// Verify error state from context // Verify error state from context
if let Some(err) = self.context.as_mut().unwrap().get_error() { if let Some(err) = self.context.as_mut().unwrap().get_error() {
error!("Fatal error on create: {}", err);
self.mount_fatal(&err); self.mount_fatal(&err);
} }
info!("Created FileTransferActivity");
} }
/// ### on_draw /// ### on_draw
@@ -308,6 +316,10 @@ impl Activity for FileTransferActivity {
// Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error) // Check if connected (popup must be None, otherwise would try reconnecting in loop in case of error)
if !self.client.is_connected() && self.view.get_props(COMPONENT_TEXT_FATAL).is_none() { if !self.client.is_connected() && self.view.get_props(COMPONENT_TEXT_FATAL).is_none() {
let params = self.context.as_ref().unwrap().ft_params.as_ref().unwrap(); let params = self.context.as_ref().unwrap().ft_params.as_ref().unwrap();
info!(
"Client is not connected to remote; connecting to {}:{}",
params.address, params.port
);
let msg: String = format!("Connecting to {}:{}...", params.address, params.port); let msg: String = format!("Connecting to {}:{}...", params.address, params.port);
// Set init state to connecting popup // Set init state to connecting popup
self.mount_wait(msg.as_str()); self.mount_wait(msg.as_str());
@@ -341,7 +353,9 @@ impl Activity for FileTransferActivity {
/// This function must be called once before terminating the activity. /// This function must be called once before terminating the activity.
fn on_destroy(&mut self) -> Option<Context> { fn on_destroy(&mut self) -> Option<Context> {
// Disable raw mode // Disable raw mode
let _ = disable_raw_mode(); if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
// Disconnect client // Disconnect client
if self.client.is_connected() { if self.client.is_connected() {
let _ = self.client.disconnect(); let _ = self.client.disconnect();
+3 -1
View File
@@ -783,7 +783,9 @@ impl FileTransferActivity {
} }
} }
// Put input mode back to normal // Put input mode back to normal
let _ = disable_raw_mode(); if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
// Leave alternate mode // Leave alternate mode
if let Some(ctx) = self.context.as_mut() { if let Some(ctx) = self.context.as_mut() {
ctx.leave_alternate_screen(); ctx.leave_alternate_screen();
+6 -2
View File
@@ -111,13 +111,17 @@ impl SetupActivity {
env::set_var("EDITOR", cli.get_text_editor()); env::set_var("EDITOR", cli.get_text_editor());
let placeholder: String = format!("# Type private SSH key for {}@{}\n", username, host); let placeholder: String = format!("# Type private SSH key for {}@{}\n", username, host);
// Put input mode back to normal // Put input mode back to normal
let _ = disable_raw_mode(); if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
// Leave alternate mode // Leave alternate mode
if let Some(ctx) = self.context.as_mut() { if let Some(ctx) = self.context.as_mut() {
ctx.leave_alternate_screen(); ctx.leave_alternate_screen();
} }
// Re-enable raw mode // Re-enable raw mode
let _ = enable_raw_mode(); if let Err(err) = enable_raw_mode() {
error!("Failed to enter raw mode: {}", err);
}
// Write key to file // Write key to file
match edit::edit(placeholder.as_bytes()) { match edit::edit(placeholder.as_bytes()) {
Ok(rsa_key) => { Ok(rsa_key) => {
+6 -2
View File
@@ -88,7 +88,9 @@ impl SetupActivity {
env::set_var("EDITOR", config_cli.get_text_editor()); env::set_var("EDITOR", config_cli.get_text_editor());
} }
// Prepare terminal // Prepare terminal
let _ = disable_raw_mode(); if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
// Leave alternate mode // Leave alternate mode
ctx.leave_alternate_screen(); ctx.leave_alternate_screen();
// Get result // Get result
@@ -121,7 +123,9 @@ impl SetupActivity {
// Enter alternate mode // Enter alternate mode
ctx.enter_alternate_screen(); ctx.enter_alternate_screen();
// Re-enable raw mode // Re-enable raw mode
let _ = enable_raw_mode(); if let Err(err) = enable_raw_mode() {
error!("Failed to enter raw mode: {}", err);
}
// Return result // Return result
result result
} }
+6 -2
View File
@@ -110,7 +110,9 @@ impl Activity for SetupActivity {
// Clear terminal // Clear terminal
self.context.as_mut().unwrap().clear_screen(); self.context.as_mut().unwrap().clear_screen();
// Put raw mode on enabled // Put raw mode on enabled
let _ = enable_raw_mode(); if let Err(err) = enable_raw_mode() {
error!("Failed to enter raw mode: {}", err);
}
// Init view // Init view
self.init_setup(); self.init_setup();
// Verify error state from context // Verify error state from context
@@ -161,7 +163,9 @@ impl Activity for SetupActivity {
/// This function finally releases the context /// This function finally releases the context
fn on_destroy(&mut self) -> Option<Context> { fn on_destroy(&mut self) -> Option<Context> {
// Disable raw mode // Disable raw mode
let _ = disable_raw_mode(); if let Err(err) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", err);
}
self.context.as_ref()?; self.context.as_ref()?;
// Clear terminal and return // Clear terminal and return
match self.context.take() { match self.context.take() {
+14 -5
View File
@@ -104,29 +104,38 @@ impl Context {
/// ///
/// Enter alternate screen (gui window) /// Enter alternate screen (gui window)
pub fn enter_alternate_screen(&mut self) { pub fn enter_alternate_screen(&mut self) {
let _ = execute!( match execute!(
self.terminal.backend_mut(), self.terminal.backend_mut(),
EnterAlternateScreen, EnterAlternateScreen,
DisableMouseCapture DisableMouseCapture
); ) {
Err(err) => error!("Failed to enter alternate screen: {}", err),
Ok(_) => info!("Entered alternate screen"),
}
} }
/// ### leave_alternate_screen /// ### leave_alternate_screen
/// ///
/// Go back to normal screen (gui window) /// Go back to normal screen (gui window)
pub fn leave_alternate_screen(&mut self) { pub fn leave_alternate_screen(&mut self) {
let _ = execute!( match execute!(
self.terminal.backend_mut(), self.terminal.backend_mut(),
LeaveAlternateScreen, LeaveAlternateScreen,
DisableMouseCapture DisableMouseCapture
); ) {
Err(err) => error!("Failed to leave alternate screen: {}", err),
Ok(_) => info!("Left alternate screen"),
}
} }
/// ### clear_screen /// ### clear_screen
/// ///
/// Clear terminal screen /// Clear terminal screen
pub fn clear_screen(&mut self) { pub fn clear_screen(&mut self) {
let _ = self.terminal.clear(); match self.terminal.clear() {
Err(err) => error!("Failed to clear screen: {}", err),
Ok(_) => info!("Cleared screen"),
}
} }
} }
+57
View File
@@ -0,0 +1,57 @@
//! ## File
//!
//! `file` is the module which exposes file related utilities
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::path::Path;
/// ### open_file
///
/// Open file provided as parameter
pub fn open_file<P>(filename: P, create: bool, write: bool, append: bool) -> io::Result<File>
where
P: AsRef<Path>,
{
OpenOptions::new()
.create(create)
.write(write)
.append(append)
.truncate(!append)
.open(filename)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_utils_file_open() {
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
assert!(open_file(tmpfile.path(), true, true, true).is_ok());
}
}
+12
View File
@@ -152,6 +152,13 @@ pub fn fmt_path_elide(p: &Path, width: usize) -> String {
} }
} }
/// ### shadow_password
///
/// Return a string with the same length of input string, but each character is replaced by '*'
pub fn shadow_password(s: &str) -> String {
(0..s.len()).map(|_| '*').collect()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@@ -219,4 +226,9 @@ mod tests {
let p: &Path = &Path::new("/develop/pippo/foo/bar"); let p: &Path = &Path::new("/develop/pippo/foo/bar");
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/.../foo/bar")); assert_eq!(fmt_path_elide(p, 16), String::from("/develop/.../foo/bar"));
} }
#[test]
fn test_utils_fmt_shadow_password() {
assert_eq!(shadow_password("foobar"), String::from("******"));
}
} }
+1
View File
@@ -27,6 +27,7 @@
*/ */
// modules // modules
pub mod crypto; pub mod crypto;
pub mod file;
pub mod fmt; pub mod fmt;
pub mod git; pub mod git;
pub mod parser; pub mod parser;