diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 912ad3e..ea3f8d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,7 +84,7 @@ jobs: - name: add user run: addgroup ubuntu && adduser --shell /bin/ash --disabled-password --home /home/ubuntu --ingroup ubuntu ubuntu && echo "ubuntu:password" | chpasswd - name: config ssh - run: ssh-keygen -A && sed -i -E "s|(AuthorizedKeysFile).*|\1 %h/.ssh/authorized_keys|g" /etc/ssh/sshd_config && echo "HostKeyAlgorithms=+ssh-rsa" >> /etc/ssh/sshd_config && echo "PubkeyAcceptedAlgorithms=+ssh-rsa" >> /etc/ssh/sshd_config && echo "KexAlgorithms=+diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" >> /etc/ssh/sshd_config && sed -i -E "s/#?(ChallengeResponseAuthentication|PasswordAuthentication).*/\1 yes/g" /etc/ssh/sshd_config + run: ssh-keygen -A && sed -i -E "s|(AuthorizedKeysFile).*|\1 %h/.ssh/authorized_keys|g" /etc/ssh/sshd_config && echo "HostKeyAlgorithms=+ssh-rsa" >> /etc/ssh/sshd_config && echo "PubkeyAcceptedAlgorithms=+ssh-rsa" >> /etc/ssh/sshd_config && echo "KexAlgorithms=+diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" >> /etc/ssh/sshd_config && echo "Ciphers=+aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc" >> /etc/ssh/sshd_config && sed -i -E "s/#?(ChallengeResponseAuthentication|PasswordAuthentication).*/\1 yes/g" /etc/ssh/sshd_config - name: create .ssh run: mkdir -p /home/ubuntu/.ssh && umask 066; touch /home/ubuntu/.ssh/authorized_keys - name: generate rsa files diff --git a/.gitignore b/.gitignore index 6e818ee..5d32368 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .idea target .gitignore +.vscode # Generated by Cargo # will have compiled files and executables /target/ diff --git a/Cargo.toml b/Cargo.toml index abd9586..1506de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-rs" -version = "0.3.3" +version = "0.4.0" edition = "2021" authors = [ "Gao Xiang Kang <1148118271@qq.com>", @@ -14,42 +14,58 @@ repository = "https://github.com/1148118271/ssh-rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -dangerous-algorithms = ["dangerous-rsa-sha1", "dangerous-dh-group1-sha1"] -dangerous-rsa-sha1 = ["sha1"] -dangerous-dh-group1-sha1 = [] +deprecated-algorithms = [ + "deprecated-rsa-sha1", + "deprecated-dh-group1-sha1", + "deprecated-aes-cbc", + "deprecated-des-cbc", + ] +deprecated-rsa-sha1 = ["dep:sha1"] +deprecated-dh-group1-sha1 = ["dep:sha1"] +deprecated-aes-cbc = ["dep:cbc", "dep:cipher"] +deprecated-des-cbc = ["dep:cbc", "dep:cipher", "dep:des"] +scp = ["dep:filetime"] + +[lib] +name = "ssh" +path = "src/lib.rs" [dependencies] -log = "0.4" -rand = "0.8" -num-bigint = { version = "0.4", features = ["rand"] } +## error +thiserror = "^1.0" + +## log +tracing = { version = "^0.1", features = ["log"] } + +## string enum strum = "0.25" strum_macros = "0.25" + +## algorithm +rand = "0.8" +num-bigint = { version = "0.4", features = ["rand"] } # the crate rsa has removed the internal hash implement from 0.7.0 sha1 = { version = "0.10.5", default-features = false, features = ["oid"], optional = true } sha2 = { version = "0.10.6", default-features = false, features = ["oid"]} rsa = "0.9" aes = "0.8" ctr = "0.9" +des = { version = "0.8", optional = true } +cbc = { version = "0.1", optional = true } +cipher = { version = "0.4", optional = true } ssh-key = { version = "0.6", features = ["rsa", "ed25519", "alloc"]} signature = "2.1" ring = "0.16" -filetime = "0.2" -# async -# [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -# tokio = { version = "^1", features = ["full"] } +## utils +filetime = { version = "0.2", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } -# tokio = { version = "^1", features = [ -# "sync", -# "macros", -# "io-util", -# "rt", -# "time" -# ]} + [dev-dependencies] +tracing-subscriber = { version = "^0.3" } paste = "1" diff --git a/README.md b/README.md index d779556..a38eba3 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,9 @@ or [PR](https://github.com/1148118271/ssh-rs/pulls) . ### 1. Password: ```rust -use ssh_rs::ssh; +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; let mut session = ssh::create_session() .username("ubuntu") .password("password") @@ -67,7 +69,9 @@ let mut session = ssh::create_session() // and end with // -----END RSA PRIVATE KEY----- / -----END OPENSSH PRIVATE KEY----- // simply generated by `ssh-keygen -t rsa -m PEM -b 4096` -use ssh_rs::ssh; +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; let mut session = ssh::create_session() .username("ubuntu") .private_key_path("./id_rsa") @@ -82,7 +86,9 @@ let mut session = ssh::create_session() // -----BEGIN RSA PRIVATE KEY----- / -----BEGIN OPENSSH PRIVATE KEY----- // and end with // -----END RSA PRIVATE KEY----- / -----END OPENSSH PRIVATE KEY----- -use ssh_rs::ssh; +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; let mut session = ssh::create_session() .username("ubuntu") .private_key("rsa_string") @@ -95,7 +101,9 @@ let mut session = ssh::create_session() * According to the implementation of OpenSSH, it will try public key first and fallback to password. So both of them can be provided. ```Rust -use ssh_rs::ssh; +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; let mut session = ssh::create_session() .username("username") .password("password") @@ -106,16 +114,22 @@ let mut session = ssh::create_session() ## Enable global logging: -* There are two APIs to enable logs, basicly `enable_log()` will set the log level to `INFO`, and `debug()` will set it to `Debug` - -* But you can implement your own logger as well. +* This crate now uses the `log` compatible `tracing` for logging functionality ```rust -use ssh_rs::ssh; +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; // this will generate some basic event logs -ssh::enable_log(); -// this will generate verbose logs -ssh::debug() +// a builder for `FmtSubscriber`. +let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + +tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); ``` ## Set timeout: @@ -123,7 +137,9 @@ ssh::debug() * Only global timeouts per r/w are currently supported. ```rust -use ssh_rs::ssh; +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; ssh::debug(); let _listener = TcpListener::bind("127.0.0.1:7777").unwrap(); @@ -159,14 +175,14 @@ match ssh::create_session() * `ecdh-sha2-nistp256` * `diffie-hellman-group14-sha256` * `diffie-hellman-group14-sha1` -* `diffie-hellman-group1-sha1` (behind feature "dangerous-dh-group1-sha1") +* `diffie-hellman-group1-sha1` (behind feature "deprecated-dh-group1-sha1") ### 2. Server host key algorithms * `ssh-ed25519` * `rsa-sha2-256` * `rsa-sha2-512` -* `rsa-sha` (behind feature "dangerous-rsa-sha1") +* `rsa-sha` (behind feature "deprecated-rsa-sha1") ### 3. Encryption algorithms (client to server) @@ -179,6 +195,10 @@ match ssh::create_session() * `aes128-ctr` * `aes192-ctr` * `aes256-ctr` +* `aes128-cbc` (behind feature "deprecated-aes-cbc") +* `aes192-cbc` (behind feature "deprecated-aes-cbc") +* `aes256-cbc` (behind feature "deprecated-aes-cbc") +* `3des-cbc` (behind feature "deprecated-des-cbc") ### 5. Mac algorithms (client to server) diff --git a/README_ZH.md b/README_ZH.md index d4cf3f4..ee6e48c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -51,7 +51,19 @@ fn main() { ### 启用全局日志: ```rust - ssh::debug(); +use ssh; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; +// this will generate some basic event logs +// a builder for `FmtSubscriber`. +let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + +tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); ``` ### 设置超时时间: @@ -82,7 +94,7 @@ ssh::create_session().timeout(Some(std::time::Duration::from_secs(5))); * `ssh-ed25519` * `rsa-sha2-512` * `rsa-sha2-256` -* `rsa-sha` (features = ["dangerous-rsa-sha1"]) +* `rsa-sha` (features = ["deprecated-rsa-sha1"]) #### 3. 加密算法(客户端到服务端) @@ -90,6 +102,11 @@ ssh::create_session().timeout(Some(std::time::Duration::from_secs(5))); * `aes128-ctr` * `aes192-ctr` * `aes256-ctr` +* `aes128-cbc` (features = ["deprecated-aes-cbc"]) +* `aes192-cbc` (features = ["deprecated-aes-cbc"]) +* `aes256-cbc` (features = ["deprecated-aes-cbc"]) +* `3des-cbc` (features = ["deprecated-des-cbc"]) + #### 4. 加密算法(服务端到客户端) diff --git a/build.rs b/build.rs index fe99e20..8727018 100644 --- a/build.rs +++ b/build.rs @@ -15,15 +15,15 @@ fn main() { println!("cargo:warning= current version: {current_version} last version: {last_version}"); - // 替换lib.rs + // lib.rs if replace_lib(¤t_version, &last_version).is_err() { return; } - // 替换constant.rs + // constant.rs if replace_constant(¤t_version, &last_version).is_err() { return; } - // 替换Cargo.toml + // Cargo.toml let _ = replace_cargo(¤t_version, &last_version); } diff --git a/build_check.sh b/build_check.sh index e30e664..906e845 100644 --- a/build_check.sh +++ b/build_check.sh @@ -29,5 +29,5 @@ echo done echo echo echo cargo test -cargo test > /dev/null +cargo test --all-features -- --test-threads 1 > /dev/null echo done \ No newline at end of file diff --git a/changelog b/changelog index f25ec4c..82174e2 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,13 @@ +v0.4.0 (2023-09-16) + 1. remove chinese comments + 2. add RFC links + 3. remove the self-implemented log, using tracing instead + 4. move scp related function behind feature `scp' + 5. re-implement the ssh-error to derive thiserror crate + 6. rename the dangerous-related features to deprecated-* + 7. add aes-128/192/256-cbc encryption modes (behind feature deprecated-aes-cbc) + 7. add 3des-cbc encryption modes (behind feature deprecated-des-cbc) + v0.3.3 (2023-09-10) 1. fix hang when tcp connects to a non-existent host 2. refactor aes_ctr file @@ -8,7 +18,7 @@ v0.3.3 (2023-09-10) v0.3.2 (2023-01-10) 1. fix some error with hmac2 - 2. add aes-192-crt,aes-256-ctr + 2. add aes-192-crt, aes-256-ctr v0.3.1 (2022-12-07) fix some issues diff --git a/examples/bio/Cargo.toml b/examples/bio/Cargo.toml index 8a4f135..16fe691 100644 --- a/examples/bio/Cargo.toml +++ b/examples/bio/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path = "../../" } \ No newline at end of file +ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } diff --git a/examples/bio/src/main.rs b/examples/bio/src/main.rs index b61daf8..cfb280d 100644 --- a/examples/bio/src/main.rs +++ b/examples/bio/src/main.rs @@ -1,8 +1,18 @@ -use ssh_rs::ssh; + use std::net::{TcpStream, ToSocketAddrs}; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let bio = MyProxy::new("127.0.0.1:22"); diff --git a/examples/customized_algorithms/Cargo.toml b/examples/customized_algorithms/Cargo.toml index 76da4af..22b9c27 100644 --- a/examples/customized_algorithms/Cargo.toml +++ b/examples/customized_algorithms/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path="../../", features = ["dangerous-algorithms"]} \ No newline at end of file +ssh-rs = { path="../../", features = ["deprecated-algorithms"]} +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } diff --git a/examples/customized_algorithms/src/main.rs b/examples/customized_algorithms/src/main.rs index d5bb248..4f6d1d0 100644 --- a/examples/customized_algorithms/src/main.rs +++ b/examples/customized_algorithms/src/main.rs @@ -1,8 +1,17 @@ -use ssh_rs::algorithm; -use ssh_rs::ssh; +use ssh::algorithm; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let mut session = ssh::create_session_without_default() .username("ubuntu") diff --git a/examples/exec/Cargo.toml b/examples/exec/Cargo.toml index 9d5a8a0..a9bef66 100644 --- a/examples/exec/Cargo.toml +++ b/examples/exec/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path = "../../" } \ No newline at end of file +ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } \ No newline at end of file diff --git a/examples/exec/src/main.rs b/examples/exec/src/main.rs index e6f97ba..b82b2d0 100644 --- a/examples/exec/src/main.rs +++ b/examples/exec/src/main.rs @@ -1,7 +1,17 @@ -use ssh_rs::ssh; + +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let mut session = ssh::create_session() .username("ubuntu") @@ -14,5 +24,8 @@ fn main() { let vec: Vec = exec.send_command("ls -all").unwrap(); println!("{}", String::from_utf8(vec).unwrap()); // Close session. + let exec = session.open_exec().unwrap(); + let vec: Vec = exec.send_command("no_command").unwrap(); + println!("{}", String::from_utf8(vec).unwrap()); session.close(); } diff --git a/examples/exec_backend/Cargo.toml b/examples/exec_backend/Cargo.toml index 0bef84f..e6e66f4 100644 --- a/examples/exec_backend/Cargo.toml +++ b/examples/exec_backend/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path = "../../" } \ No newline at end of file +ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } \ No newline at end of file diff --git a/examples/exec_backend/src/main.rs b/examples/exec_backend/src/main.rs index 5f9752d..9be9fc4 100644 --- a/examples/exec_backend/src/main.rs +++ b/examples/exec_backend/src/main.rs @@ -1,7 +1,17 @@ -use ssh_rs::ssh; + +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let mut session = ssh::create_session() .username("ubuntu") diff --git a/examples/scp/Cargo.toml b/examples/scp/Cargo.toml index c2a244e..22f8daa 100644 --- a/examples/scp/Cargo.toml +++ b/examples/scp/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path = "../../" } +ssh-rs = { path = "../../", features = ["scp"] } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } diff --git a/examples/scp/src/main.rs b/examples/scp/src/main.rs index b9c05b2..22265ef 100644 --- a/examples/scp/src/main.rs +++ b/examples/scp/src/main.rs @@ -1,6 +1,18 @@ -use ssh_rs::ssh; + +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + let mut session = ssh::create_session() .username("ubuntu") .password("password") diff --git a/examples/scp_backend/Cargo.toml b/examples/scp_backend/Cargo.toml index 6352a65..43bfd79 100644 --- a/examples/scp_backend/Cargo.toml +++ b/examples/scp_backend/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path = "../../" } +ssh-rs = { path = "../../", features = ["scp"] } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } diff --git a/examples/scp_backend/src/main.rs b/examples/scp_backend/src/main.rs index 76b8879..5140a64 100644 --- a/examples/scp_backend/src/main.rs +++ b/examples/scp_backend/src/main.rs @@ -1,7 +1,17 @@ -use ssh_rs::ssh; + +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let mut session = ssh::create_session() .username("ubuntu") .password("password") diff --git a/examples/shell/Cargo.toml b/examples/shell/Cargo.toml index 8e17841..7d47359 100644 --- a/examples/shell/Cargo.toml +++ b/examples/shell/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } diff --git a/examples/shell/src/main.rs b/examples/shell/src/main.rs index b695686..c2d310d 100644 --- a/examples/shell/src/main.rs +++ b/examples/shell/src/main.rs @@ -1,8 +1,19 @@ -use ssh_rs::{ssh, LocalShell, SshErrorKind}; +use ssh::{self, LocalShell, SshError}; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; + use std::time::Duration; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let mut session = ssh::create_session() .username("ubuntu") @@ -32,7 +43,7 @@ fn run_shell(shell: &mut LocalShell) { match shell.read() { Ok(out) => print!("{}", String::from_utf8(out).unwrap()), Err(e) => { - if let SshErrorKind::Timeout = e.kind() { + if let SshError::TimeoutError = e { break; } else { panic!("{}", e.to_string()) diff --git a/examples/shell_backend/Cargo.toml b/examples/shell_backend/Cargo.toml index 16ffffa..e867656 100644 --- a/examples/shell_backend/Cargo.toml +++ b/examples/shell_backend/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } diff --git a/examples/shell_backend/src/main.rs b/examples/shell_backend/src/main.rs index 933c827..41f5fa9 100644 --- a/examples/shell_backend/src/main.rs +++ b/examples/shell_backend/src/main.rs @@ -1,9 +1,19 @@ -use ssh_rs::{ssh, ShellBrocker}; +use ssh::{self, ShellBrocker}; use std::thread::sleep; use std::time::Duration; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let mut session = ssh::create_session() .username("ubuntu") diff --git a/examples/shell_interactive/Cargo.toml b/examples/shell_interactive/Cargo.toml index c0b1bd5..b7e8ac2 100644 --- a/examples/shell_interactive/Cargo.toml +++ b/examples/shell_interactive/Cargo.toml @@ -7,5 +7,7 @@ edition = "2021" [dependencies] ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } # nix = "0.25.0" mio = { version="0.8.5", features = ["os-poll", "net", "os-ext"]} \ No newline at end of file diff --git a/examples/shell_interactive/src/main.rs b/examples/shell_interactive/src/main.rs index a40e809..84539d2 100644 --- a/examples/shell_interactive/src/main.rs +++ b/examples/shell_interactive/src/main.rs @@ -1,11 +1,13 @@ #[cfg(unix)] use mio::unix::SourceFd; -use ssh_rs::ssh; + use std::fs::File; #[cfg(unix)] use std::os::unix::io::FromRawFd; -use std::{cell::RefCell, rc::Rc}; use std::time::Duration; +use std::{cell::RefCell, rc::Rc}; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; use mio::net::TcpStream; use mio::{event::Source, Events, Interest, Poll, Token}; @@ -22,7 +24,15 @@ fn main() { fn main() { use std::{io::Read, os::unix::prelude::AsRawFd}; - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let tcp = TcpStream::connect("127.0.0.1:22".parse().unwrap()).unwrap(); let mut session = ssh::create_session() .username("ubuntu") diff --git a/examples/timeout/Cargo.toml b/examples/timeout/Cargo.toml index 9a40b20..b749571 100644 --- a/examples/timeout/Cargo.toml +++ b/examples/timeout/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ssh-rs = { path = "../../" } \ No newline at end of file +ssh-rs = { path = "../../" } +tracing = { version = "^0.1", features = ["log"] } +tracing-subscriber = { version = "^0.3" } \ No newline at end of file diff --git a/examples/timeout/src/main.rs b/examples/timeout/src/main.rs index a7a401c..42ca035 100644 --- a/examples/timeout/src/main.rs +++ b/examples/timeout/src/main.rs @@ -1,9 +1,19 @@ -use ssh_rs::ssh; + use std::net::TcpListener; use std::time::Duration; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; fn main() { - ssh::enable_log(); + // a builder for `FmtSubscriber`. + let subscriber = FmtSubscriber::builder() + // all spans/events with a level higher than INFO (e.g, info, warn, etc.) + // will be written to stdout. + .with_max_level(Level::INFO) + // completes the builder. + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let _listener = TcpListener::bind("127.0.0.1:7777").unwrap(); match ssh::create_session() diff --git a/src/algorithm/encryption/aes_cbc.rs b/src/algorithm/encryption/aes_cbc.rs new file mode 100644 index 0000000..3a9dd1d --- /dev/null +++ b/src/algorithm/encryption/aes_cbc.rs @@ -0,0 +1,153 @@ +use crate::{ + algorithm::{hash::Hash, mac::Mac}, + SshError, SshResult, +}; +use aes::{ + cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit}, + Aes128, Aes192, Aes256, +}; +use cipher::generic_array::GenericArray; + +use super::Encryption; + +const CBC128_KEY_SIZE: usize = 16; +const CBC192_KEY_SIZE: usize = 24; +const CBC256_KEY_SIZE: usize = 32; +const IV_SIZE: usize = 16; +const BLOCK_SIZE: usize = 16; + +struct Extend { + // hmac + mac: Box, + ik_c_s: Vec, + ik_s_c: Vec, +} + +impl Extend { + fn from(mac: Box, ik_c_s: Vec, ik_s_c: Vec) -> Self { + Extend { + mac, + ik_c_s, + ik_s_c, + } + } +} + +macro_rules! crate_aes_cbc { + ($name: ident, $alg: ident, $key_size: expr) => { + pub(super) struct $name { + pub(super) client_key: cbc::Encryptor<$alg>, + pub(super) server_key: cbc::Decryptor<$alg>, + extend: Extend, + } + + impl Encryption for $name { + fn bsize(&self) -> usize { + BLOCK_SIZE + } + + fn iv_size(&self) -> usize { + IV_SIZE + } + + fn new(hash: Hash, mac: Box) -> Self + where + Self: Sized, + { + let (ck, sk) = hash.mix_ek($key_size); + let mut ckey = [0u8; $key_size]; + let mut skey = [0u8; $key_size]; + ckey.clone_from_slice(&ck[..$key_size]); + skey.clone_from_slice(&sk[..$key_size]); + + let mut civ = [0u8; IV_SIZE]; + let mut siv = [0u8; IV_SIZE]; + civ.clone_from_slice(&hash.iv_c_s[..IV_SIZE]); + siv.clone_from_slice(&hash.iv_s_c[..IV_SIZE]); + + let c = cbc::Encryptor::<$alg>::new(&ckey.into(), &civ.into()); + let r = cbc::Decryptor::<$alg>::new(&skey.into(), &siv.into()); + // hmac + let (ik_c_s, ik_s_c) = hash.mix_ik(mac.bsize()); + $name { + client_key: c, + server_key: r, + extend: Extend::from(mac, ik_c_s, ik_s_c), + } + } + + fn encrypt(&mut self, client_sequence_num: u32, buf: &mut Vec) { + let len = buf.len(); + let tag = self + .extend + .mac + .sign(&self.extend.ik_c_s, client_sequence_num, buf); + let mut idx = 0; + while idx < len { + let mut block = GenericArray::clone_from_slice(&buf[idx..idx + BLOCK_SIZE]); + self.client_key.encrypt_block_mut(&mut block); + buf[idx..idx + BLOCK_SIZE].clone_from_slice(&block); + + idx += BLOCK_SIZE; + } + buf.extend(tag.as_ref()) + } + + fn decrypt( + &mut self, + server_sequence_number: u32, + buf: &mut [u8], + ) -> SshResult> { + let pl = self.packet_len(server_sequence_number, buf); + let data = &mut buf[..(pl + self.extend.mac.bsize())]; + let (d, m) = data.split_at_mut(pl); + + let len = d.len(); + let mut idx = 0; + while idx < len { + let mut block = GenericArray::clone_from_slice(&d[idx..idx + BLOCK_SIZE]); + self.server_key.decrypt_block_mut(&mut block); + d[idx..idx + BLOCK_SIZE].clone_from_slice(&block); + + idx += BLOCK_SIZE; + } + + let tag = self + .extend + .mac + .sign(&self.extend.ik_s_c, server_sequence_number, d); + let t = tag.as_ref(); + if m != t { + return Err(SshError::EncryptionError( + "Failed to decrypt the server traffic".to_owned(), + )); + } + Ok(d.to_vec()) + } + + fn packet_len(&mut self, _: u32, buf: &[u8]) -> usize { + let mut block = GenericArray::clone_from_slice(&buf[..BLOCK_SIZE]); + self.server_key.clone().decrypt_block_mut(&mut block); + let packet_len = u32::from_be_bytes(block[..4].try_into().unwrap()); + (packet_len + 4) as usize + } + + fn data_len(&mut self, server_sequence_number: u32, buf: &[u8]) -> usize { + let pl = self.packet_len(server_sequence_number, buf); + let bsize = self.extend.mac.bsize(); + pl + bsize + } + + fn no_pad(&self) -> bool { + false + } + } + }; +} + +// aes-128-cbc +crate_aes_cbc!(Cbc128, Aes128, CBC128_KEY_SIZE); +// aes-192-cbc +crate_aes_cbc!(Cbc192, Aes192, CBC192_KEY_SIZE); +// aes-256-cbc +crate_aes_cbc!(Cbc256, Aes256, CBC256_KEY_SIZE); diff --git a/src/algorithm/encryption/aes_ctr.rs b/src/algorithm/encryption/aes_ctr.rs index e2d9976..b3fd350 100644 --- a/src/algorithm/encryption/aes_ctr.rs +++ b/src/algorithm/encryption/aes_ctr.rs @@ -10,10 +10,11 @@ type Aes128Ctr64BE = ctr::Ctr64BE; type Aes192Ctr64BE = ctr::Ctr64BE; type Aes256Ctr64BE = ctr::Ctr64BE; -const CTR128_BLOCK_SIZE: usize = 16; -const CTR192_BLOCK_SIZE: usize = 24; -const CTR256_BLOCK_SIZE: usize = 32; +const CTR128_KEY_SIZE: usize = 16; +const CTR192_KEY_SIZE: usize = 24; +const CTR256_KEY_SIZE: usize = 32; const IV_SIZE: usize = 16; +const BLOCK_SIZE: usize = 16; // extend data for data encryption struct Extend { @@ -32,8 +33,8 @@ impl Extend { } } -macro_rules! crate_aes { - ($name: ident, $alg: ident, $block_size: expr, $iv_size: expr) => { +macro_rules! crate_aes_ctr { + ($name: ident, $alg: ident, $key_size: expr) => { pub(super) struct $name { pub(super) client_key: $alg, pub(super) server_key: $alg, @@ -42,33 +43,28 @@ macro_rules! crate_aes { impl Encryption for $name { fn bsize(&self) -> usize { - $block_size + BLOCK_SIZE } fn iv_size(&self) -> usize { - $iv_size - } - - fn group_size(&self) -> usize { - $iv_size + IV_SIZE } fn new(hash: Hash, mac: Box) -> Self where Self: Sized, { - let (ck, sk) = hash.mix_ek($block_size); - let mut ckey = [0u8; $block_size]; - let mut skey = [0u8; $block_size]; - ckey.clone_from_slice(&ck[..$block_size]); - skey.clone_from_slice(&sk[..$block_size]); + let (ck, sk) = hash.mix_ek($key_size); + let mut ckey = [0u8; $key_size]; + let mut skey = [0u8; $key_size]; + ckey.clone_from_slice(&ck[..$key_size]); + skey.clone_from_slice(&sk[..$key_size]); - let mut civ = [0u8; $iv_size]; - let mut siv = [0u8; $iv_size]; - civ.clone_from_slice(&hash.iv_c_s[..$iv_size]); - siv.clone_from_slice(&hash.iv_s_c[..$iv_size]); + let mut civ = [0u8; IV_SIZE]; + let mut siv = [0u8; IV_SIZE]; + civ.clone_from_slice(&hash.iv_c_s[..IV_SIZE]); + siv.clone_from_slice(&hash.iv_s_c[..IV_SIZE]); - // TODO unwrap let c = $alg::new(&ckey.into(), &civ.into()); let r = $alg::new(&skey.into(), &siv.into()); // hmac @@ -81,11 +77,10 @@ macro_rules! crate_aes { } fn encrypt(&mut self, client_sequence_num: u32, buf: &mut Vec) { - let vec = buf.clone(); - let tag = - self.extend - .mac - .sign(&self.extend.ik_c_s, client_sequence_num, vec.as_slice()); + let tag = self + .extend + .mac + .sign(&self.extend.ik_c_s, client_sequence_num, buf); self.client_key.apply_keystream(buf); buf.extend(tag.as_ref()) } @@ -105,7 +100,9 @@ macro_rules! crate_aes { .sign(&self.extend.ik_s_c, server_sequence_number, d); let t = tag.as_ref(); if m != t { - return Err(SshError::from("encryption error.")); + return Err(SshError::EncryptionError( + "Failed to decrypt the server traffic".to_owned(), + )); } Ok(d.to_vec()) } @@ -117,9 +114,7 @@ macro_rules! crate_aes { self.server_key.apply_keystream(&mut r); let pos: usize = self.server_key.current_pos(); self.server_key.seek(pos - bsize); - let mut u32_bytes = [0_u8; 4]; - u32_bytes.clone_from_slice(&r[..4]); - let packet_len = u32::from_be_bytes(u32_bytes); + let packet_len = u32::from_be_bytes(r[..4].try_into().unwrap()); (packet_len + 4) as usize } @@ -129,7 +124,7 @@ macro_rules! crate_aes { pl + bsize } - fn is_cp(&self) -> bool { + fn no_pad(&self) -> bool { false } } @@ -137,8 +132,8 @@ macro_rules! crate_aes { } // aes-128-ctr -crate_aes!(Ctr128, Aes128Ctr64BE, CTR128_BLOCK_SIZE, IV_SIZE); +crate_aes_ctr!(Ctr128, Aes128Ctr64BE, CTR128_KEY_SIZE); // aes-192-ctr -crate_aes!(Ctr192, Aes192Ctr64BE, CTR192_BLOCK_SIZE, IV_SIZE); +crate_aes_ctr!(Ctr192, Aes192Ctr64BE, CTR192_KEY_SIZE); // aes-256-ctr -crate_aes!(Ctr256, Aes256Ctr64BE, CTR256_BLOCK_SIZE, IV_SIZE); +crate_aes_ctr!(Ctr256, Aes256Ctr64BE, CTR256_KEY_SIZE); diff --git a/src/algorithm/encryption/chacha20_poly1305_openssh.rs b/src/algorithm/encryption/chacha20_poly1305_openssh.rs index f394d42..2e0a75b 100644 --- a/src/algorithm/encryption/chacha20_poly1305_openssh.rs +++ b/src/algorithm/encryption/chacha20_poly1305_openssh.rs @@ -4,7 +4,10 @@ use crate::error::SshError; use crate::{algorithm::encryption::Encryption, error::SshResult}; use ring::aead::chacha20_poly1305_openssh::{OpeningKey, SealingKey}; -const BSIZE: usize = 64; +const KEY_SIZE: usize = 64; +const IV_SIZE: usize = 0; +const BLOCK_SIZE: usize = 0; +const MAC_SIZE: usize = 16; pub(super) struct ChaCha20Poly1305 { client_key: SealingKey, @@ -13,21 +16,17 @@ pub(super) struct ChaCha20Poly1305 { impl Encryption for ChaCha20Poly1305 { fn bsize(&self) -> usize { - 0 + BLOCK_SIZE } fn iv_size(&self) -> usize { - 0 - } - - fn group_size(&self) -> usize { - 64 + IV_SIZE } fn new(hash: Hash, _mac: Box) -> ChaCha20Poly1305 { - let (ck, sk) = hash.mix_ek(BSIZE); - let mut sealing_key = [0_u8; BSIZE]; - let mut opening_key = [0_u8; BSIZE]; + let (ck, sk) = hash.mix_ek(KEY_SIZE); + let mut sealing_key = [0_u8; KEY_SIZE]; + let mut opening_key = [0_u8; KEY_SIZE]; sealing_key.copy_from_slice(&ck); opening_key.copy_from_slice(&sk); @@ -38,7 +37,7 @@ impl Encryption for ChaCha20Poly1305 { } fn encrypt(&mut self, sequence_number: u32, buf: &mut Vec) { - let mut tag = [0_u8; 16]; + let mut tag = [0_u8; MAC_SIZE]; self.client_key .seal_in_place(sequence_number, buf, &mut tag); buf.append(&mut tag.to_vec()); @@ -53,11 +52,13 @@ impl Encryption for ChaCha20Poly1305 { .decrypt_packet_length(sequence_number, packet_len_slice); let packet_len = u32::from_be_bytes(packet_len_slice); let (buf, tag_) = buf.split_at_mut((packet_len + 4) as usize); - let mut tag = [0_u8; 16]; + let mut tag = [0_u8; MAC_SIZE]; tag.copy_from_slice(tag_); match self.server_key.open_in_place(sequence_number, buf, &tag) { Ok(result) => Ok([&packet_len_slice[..], result].concat()), - Err(_) => Err(SshError::from("encryption error.")), + Err(_) => Err(SshError::EncryptionError( + "Failed to decrypt the server traffic".to_owned(), + )), } } @@ -67,15 +68,15 @@ impl Encryption for ChaCha20Poly1305 { let packet_len_slice = self .server_key .decrypt_packet_length(sequence_number, packet_len_slice); - u32::from_be_bytes(packet_len_slice) as usize + u32::from_be_bytes(packet_len_slice) as usize + 4 } fn data_len(&mut self, sequence_number: u32, buf: &[u8]) -> usize { let packet_len = self.packet_len(sequence_number, buf); - packet_len + 4 + 16 + packet_len + MAC_SIZE } - fn is_cp(&self) -> bool { + fn no_pad(&self) -> bool { true } } diff --git a/src/algorithm/encryption/des_cbc.rs b/src/algorithm/encryption/des_cbc.rs new file mode 100644 index 0000000..2d15020 --- /dev/null +++ b/src/algorithm/encryption/des_cbc.rs @@ -0,0 +1,133 @@ +use crate::{ + algorithm::{hash::Hash, mac::Mac}, + SshError, SshResult, +}; +use cipher::{generic_array::GenericArray, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use des::TdesEde3 as des; + +use super::Encryption; + +const KEY_SIZE: usize = 24; +const IV_SIZE: usize = 8; +const BLOCK_SIZE: usize = 8; + +struct Extend { + // hmac + mac: Box, + ik_c_s: Vec, + ik_s_c: Vec, +} + +impl Extend { + fn from(mac: Box, ik_c_s: Vec, ik_s_c: Vec) -> Self { + Extend { + mac, + ik_c_s, + ik_s_c, + } + } +} + +pub(super) struct Cbc { + pub(super) client_key: cbc::Encryptor, + pub(super) server_key: cbc::Decryptor, + extend: Extend, +} + +impl Encryption for Cbc { + fn bsize(&self) -> usize { + BLOCK_SIZE + } + + fn iv_size(&self) -> usize { + IV_SIZE + } + + fn new(hash: Hash, mac: Box) -> Self + where + Self: Sized, + { + let (ck, sk) = hash.mix_ek(KEY_SIZE); + let mut ckey = [0u8; KEY_SIZE]; + let mut skey = [0u8; KEY_SIZE]; + ckey.clone_from_slice(&ck[..KEY_SIZE]); + skey.clone_from_slice(&sk[..KEY_SIZE]); + + let mut civ = [0u8; IV_SIZE]; + let mut siv = [0u8; IV_SIZE]; + civ.clone_from_slice(&hash.iv_c_s[..IV_SIZE]); + siv.clone_from_slice(&hash.iv_s_c[..IV_SIZE]); + + let c = cbc::Encryptor::::new(&ckey.into(), &civ.into()); + let r = cbc::Decryptor::::new(&skey.into(), &siv.into()); + // hmac + let (ik_c_s, ik_s_c) = hash.mix_ik(mac.bsize()); + Cbc { + client_key: c, + server_key: r, + extend: Extend::from(mac, ik_c_s, ik_s_c), + } + } + + fn encrypt(&mut self, client_sequence_num: u32, buf: &mut Vec) { + let len = buf.len(); + let tag = self + .extend + .mac + .sign(&self.extend.ik_c_s, client_sequence_num, buf); + let mut idx = 0; + while idx < len { + let mut block = GenericArray::clone_from_slice(&buf[idx..idx + BLOCK_SIZE]); + self.client_key.encrypt_block_mut(&mut block); + buf[idx..idx + BLOCK_SIZE].clone_from_slice(&block); + + idx += BLOCK_SIZE; + } + buf.extend(tag.as_ref()) + } + + fn decrypt(&mut self, server_sequence_number: u32, buf: &mut [u8]) -> SshResult> { + let pl = self.packet_len(server_sequence_number, buf); + let data = &mut buf[..(pl + self.extend.mac.bsize())]; + let (d, m) = data.split_at_mut(pl); + + let len = d.len(); + let mut idx = 0; + while idx < len { + let mut block = GenericArray::clone_from_slice(&d[idx..idx + BLOCK_SIZE]); + self.server_key.decrypt_block_mut(&mut block); + d[idx..idx + BLOCK_SIZE].clone_from_slice(&block); + + idx += BLOCK_SIZE; + } + + let tag = self + .extend + .mac + .sign(&self.extend.ik_s_c, server_sequence_number, d); + let t = tag.as_ref(); + if m != t { + return Err(SshError::EncryptionError( + "Failed to decrypt the server traffic".to_owned(), + )); + } + Ok(d.to_vec()) + } + + fn packet_len(&mut self, _: u32, buf: &[u8]) -> usize { + let mut block = GenericArray::clone_from_slice(&buf[..BLOCK_SIZE]); + self.server_key.clone().decrypt_block_mut(&mut block); + let packet_len = u32::from_be_bytes(block[..4].try_into().unwrap()); + (packet_len + 4) as usize + } + + fn data_len(&mut self, server_sequence_number: u32, buf: &[u8]) -> usize { + let pl = self.packet_len(server_sequence_number, buf); + let bsize = self.extend.mac.bsize(); + pl + bsize + } + + fn no_pad(&self) -> bool { + false + } +} diff --git a/src/algorithm/encryption/mod.rs b/src/algorithm/encryption/mod.rs index 9d43770..ecdd992 100644 --- a/src/algorithm/encryption/mod.rs +++ b/src/algorithm/encryption/mod.rs @@ -1,5 +1,9 @@ +#[cfg(feature = "deprecated-aes-cbc")] +mod aes_cbc; mod aes_ctr; mod chacha20_poly1305_openssh; +#[cfg(feature = "deprecated-des-cbc")] +mod des_cbc; use crate::algorithm::hash::Hash; use crate::algorithm::mac::Mac; @@ -7,18 +11,10 @@ use crate::SshResult; use super::{hash::HashCtx, mac::MacNone, Enc}; -/// # 加密算法 -/// 在密钥交互中将协商出一种加密算法和一个密钥。当加密生效时,每个数据包的数据包长度、填 -/// 充长度、有效载荷和填充域必须使用给定的算法加密。 -/// 所有从一个方向发送的数据包中的加密数据应被认为是一个数据流。例如,初始向量应从一个数 -/// 据包的结束传递到下一个数据包的开始。所有加密器应使用有效密钥长度为 128 位或以上的密 -/// 钥。 -/// 两个方向上的加密器必须独立运行。如果本地策略允许多种算法,系统实现必须允许独立选择每 -/// 个方向上的算法。但是,在实际使用中,推荐在两个方向上使用相同的算法。 +/// pub(crate) trait Encryption: Send + Sync { fn bsize(&self) -> usize; fn iv_size(&self) -> usize; - fn group_size(&self) -> usize; fn new(hash: Hash, mac: Box) -> Self where Self: Sized; @@ -26,7 +22,7 @@ pub(crate) trait Encryption: Send + Sync { fn decrypt(&mut self, sequence_number: u32, buf: &mut [u8]) -> SshResult>; fn packet_len(&mut self, sequence_number: u32, buf: &[u8]) -> usize; fn data_len(&mut self, sequence_number: u32, buf: &[u8]) -> usize; - fn is_cp(&self) -> bool; + fn no_pad(&self) -> bool; } pub(crate) fn from(s: &Enc, hash: Hash, mac: Box) -> Box { @@ -37,6 +33,14 @@ pub(crate) fn from(s: &Enc, hash: Hash, mac: Box) -> Box Box::new(aes_ctr::Ctr128::new(hash, mac)), Enc::Aes192Ctr => Box::new(aes_ctr::Ctr192::new(hash, mac)), Enc::Aes256Ctr => Box::new(aes_ctr::Ctr256::new(hash, mac)), + #[cfg(feature = "deprecated-aes-cbc")] + Enc::Aes128Cbc => Box::new(aes_cbc::Cbc128::new(hash, mac)), + #[cfg(feature = "deprecated-aes-cbc")] + Enc::Aes192Cbc => Box::new(aes_cbc::Cbc192::new(hash, mac)), + #[cfg(feature = "deprecated-aes-cbc")] + Enc::Aes256Cbc => Box::new(aes_cbc::Cbc256::new(hash, mac)), + #[cfg(feature = "deprecated-des-cbc")] + Enc::TripleDesCbc => Box::new(des_cbc::Cbc::new(hash, mac)), } } @@ -50,10 +54,6 @@ impl Encryption for EncryptionNone { 8 } - fn group_size(&self) -> usize { - 8 - } - fn new(_hash: Hash, _mac: Box) -> Self where Self: Sized, @@ -72,7 +72,7 @@ impl Encryption for EncryptionNone { fn data_len(&mut self, sequence_number: u32, buf: &[u8]) -> usize { self.packet_len(sequence_number, buf) + 4 } - fn is_cp(&self) -> bool { + fn no_pad(&self) -> bool { false } } diff --git a/src/algorithm/hash/hash.rs b/src/algorithm/hash/hash.rs index ffad05f..a4d94e0 100644 --- a/src/algorithm/hash/hash.rs +++ b/src/algorithm/hash/hash.rs @@ -3,36 +3,65 @@ use crate::algorithm::hash; use crate::algorithm::hash::HashType; use crate::constant; -/// 加密密钥必须是对一个已知值和 K 的 HASH 结果,方法如下: -/// ○ 客户端到服务器的初始 IV:HASH(K || H || "A" || session_id)(这里 K 为 mpint -/// 格式,"A"为 byte 格式,session_id 为原始数据(raw data)。"A"是单个字母 A, -/// ASCII 65)。 -/// ○ 服务器到客户端的初始 IV:HASH(K || H || "B" || session_id) -/// ○ 客户端到服务器的加密密钥:HASH(K || H || "C" || session_id) -/// ○ 服务器到客户端的加密密钥:HASH(K || H || "D" || session_id) -/// ○ 客户端到服务器的完整性密钥:HASH(K || H || "E" || session_id) -/// ○ 服务器到客户端的完整性密钥:HASH(K || H || "F" || session_id) -/// 密钥数据必须从哈希输出的开头开始取。即从哈希值的开头开始,取所需数量的字节。如果需要 -/// 的密钥长度超过 HASH 输出,则拼接 K、H 和当前的整个密钥并计算其 HASH,然后将 HASH 产 -/// 生的字节附加到密钥尾部。重复该过程,直到获得了足够的密钥材料;密钥从该值的开头开始取 -/// 换句话说: -/// K1 = HASH(K || H || X || session_id)(X 表示"A"等) -/// K2 = HASH(K || H || K1) -/// K3 = HASH(K || H || K1 || K2) -/// ... -/// key = K1 || K2 || K3 || ... -/// 如果 K 的熵比 HASH 的内状态(internal state)大小要大,则该过程将造成熵的丢失。 +/// +/// +/// The key exchange produces two values: a shared secret K, and an +/// exchange hash H. Encryption and authentication keys are derived from +/// these. The exchange hash H from the first key exchange is +/// additionally used as the session identifier, which is a unique +/// identifier for this connection. It is used by authentication methods +/// as a part of the data that is signed as a proof of possession of a +/// private key. Once computed, the session identifier is not changed, +/// even if keys are later re-exchanged. +/// Each key exchange method specifies a hash function that is used in +/// the key exchange. The same hash algorithm MUST be used in key +/// derivation. Here, we'll call it HASH. + +/// Encryption keys MUST be computed as HASH, of a known value and K, as +/// follows: + +/// o Initial IV client to server: HASH(K || H || "A" || session_id) +/// (Here K is encoded as mpint and "A" as byte and session_id as raw +/// data. "A" means the single character A, ASCII 65). + +/// o Initial IV server to client: HASH(K || H || "B" || session_id) + +/// o Encryption key client to server: HASH(K || H || "C" || session_id) + +/// o Encryption key server to client: HASH(K || H || "D" || session_id) + +/// o Integrity key client to server: HASH(K || H || "E" || session_id) + +/// o Integrity key server to client: HASH(K || H || "F" || session_id) + +/// Key data MUST be taken from the beginning of the hash output. As +/// many bytes as needed are taken from the beginning of the hash value. +/// If the key length needed is longer than the output of the HASH, the +/// key is extended by computing HASH of the concatenation of K and H and +/// the entire key so far, and appending the resulting bytes (as many as +/// HASH generates) to the key. This process is repeated until enough +/// key material is available; the key is taken from the beginning of +/// this value. In other words: + +/// K1 = HASH(K || H || X || session_id) (X is e.g., "A") +/// K2 = HASH(K || H || K1) +/// K3 = HASH(K || H || K1 || K2) +/// ... +/// key = K1 || K2 || K3 || ... + +/// This process will lose entropy if the amount of entropy in K is +/// larger than the internal state size of HASH. pub struct Hash { - /// 数据加密时只使用一次的随机数 number used once + /// reandom number used once pub iv_c_s: Vec, pub iv_s_c: Vec, - /// 数据加密的 key + /// key used for data exchange pub ek_c_s: Vec, pub ek_s_c: Vec, - /// Hmac时候用到的 key + /// key used for hmac pub ik_c_s: Vec, pub ik_s_c: Vec, diff --git a/src/algorithm/hash/hash_ctx.rs b/src/algorithm/hash/hash_ctx.rs index d8e3ddc..7337a61 100644 --- a/src/algorithm/hash/hash_ctx.rs +++ b/src/algorithm/hash/hash_ctx.rs @@ -1,48 +1,40 @@ use crate::model::Data; -/// 密钥交换产生两个值:一个共享秘密 K,以及一个交换哈希 H。加密和验证密钥来自它们。第一 -/// 次密钥交换的交换哈希 H 也被用作会话标识,它是一个对该连接的唯一标识。它是验证方法中 -/// 被签名(以证明拥有私钥)的数据的一部分。会话标识被计算出来后,即使后来重新交换了密钥, -/// 也不会改变。 +/// /// +/// The key exchange produces two values: a shared secret K, and an +/// exchange hash H. Encryption and authentication keys are derived from +/// these. The exchange hash H from the first key exchange is +/// additionally used as the session identifier, which is a unique +/// identifier for this connection. It is used by authentication methods +/// as a part of the data that is signed as a proof of possession of a +/// private key. Once computed, the session identifier is not changed, +/// even if keys are later re-exchanged. /// -/// H = hash algorithm(v_c | v_s | i_c | i_s | k_s | q_c | q_s | k) +/// H = hash(V_C || V_S || I_C || I_S || K_S || e || f || K) /// /// #[derive(Clone, Debug, Default)] pub struct HashCtx { - /// 一下数据如果有从数据包解析的数据 - /// 统一不包含数据包里面的 PacketLength PaddingLength PaddingString - - /// 数据统一转为 [bytes] - - /// 双方(客户端/服务端)的版本, - /// 数据长度 + 数据, 不包含 /r/n - /// 数据长度 [u32] - /// 数据 [str] + /// string V_C, the client's identification string (CR and LF excluded) pub v_c: Vec, + /// string V_S, the server's identification string (CR and LF excluded) pub v_s: Vec, - /// 双方(客户端/服务端)交换的算法, - /// 数据长度 + 数据 - /// 数据长度 [u32] - /// 数据(客户端密钥交换信息数组) [`[str, str ...]`] + /// string I_C, the payload of the client's SSH_MSG_KEXINIT pub i_c: Vec, + /// string I_S, the payload of the server's SSH_MSG_KEXINIT pub i_s: Vec, - /// 主机密钥 - /// 服务端发过来的host key 整体数据 + /// string K_S, the host key pub k_s: Vec, - /// 双方(客户端/服务端)的公钥, - /// 数据长度 + 数据 - /// 数据长度 [u32] - /// 数据(客户端密钥交换信息数组) [bytes] - pub q_c: Vec, - pub q_s: Vec, + /// mpint e, exchange value sent by the client + pub e: Vec, + /// mpint f, exchange value sent by the server + pub f: Vec, - /// 共享密钥 - /// 二进制补码 + 数据 + /// mpint K, the shared secret pub k: Vec, } @@ -54,8 +46,8 @@ impl HashCtx { i_c: vec![], i_s: vec![], k_s: vec![], - q_c: vec![], - q_s: vec![], + e: vec![], + f: vec![], k: vec![], } } @@ -85,15 +77,15 @@ impl HashCtx { data.put_u8s(ks); self.k_s = data.to_vec(); } - pub fn set_q_c(&mut self, qc: &[u8]) { + pub fn set_e(&mut self, qc: &[u8]) { let mut data = Data::new(); data.put_u8s(qc); - self.q_c = data.to_vec(); + self.e = data.to_vec(); } - pub fn set_q_s(&mut self, qs: &[u8]) { + pub fn set_f(&mut self, qs: &[u8]) { let mut data = Data::new(); data.put_u8s(qs); - self.q_s = data.to_vec(); + self.f = data.to_vec(); } pub fn set_k(&mut self, k: &[u8]) { let mut data = Data::new(); @@ -108,8 +100,8 @@ impl HashCtx { v.extend(&self.i_c); v.extend(&self.i_s); v.extend(&self.k_s); - v.extend(&self.q_c); - v.extend(&self.q_s); + v.extend(&self.e); + v.extend(&self.f); v.extend(&self.k); v } diff --git a/src/algorithm/hash/hash_type.rs b/src/algorithm/hash/hash_type.rs index aa654ec..eff485e 100644 --- a/src/algorithm/hash/hash_type.rs +++ b/src/algorithm/hash/hash_type.rs @@ -1,4 +1,5 @@ -/// 密钥交换对应的hash算法 +/// The hash type used during kex +/// this is determined by the kex alg #[derive(Copy, Clone)] pub enum HashType { None, diff --git a/src/algorithm/key_exchange/curve25519.rs b/src/algorithm/key_exchange/curve25519.rs index ba6a498..cd4e5f9 100644 --- a/src/algorithm/key_exchange/curve25519.rs +++ b/src/algorithm/key_exchange/curve25519.rs @@ -13,14 +13,14 @@ impl KeyExchange for CURVE25519 { let rng = ring::rand::SystemRandom::new(); let private_key = match EphemeralPrivateKey::generate(&X25519, &rng) { Ok(v) => v, - Err(_) => return Err(SshError::from("encryption error.")), + Err(e) => return Err(SshError::KexError(e.to_string())), }; match private_key.compute_public_key() { Ok(public_key) => Ok(CURVE25519 { private_key, public_key, }), - Err(_) => Err(SshError::from("encryption error.")), + Err(e) => Err(SshError::KexError(e.to_string())), } } diff --git a/src/algorithm/key_exchange/dh.rs b/src/algorithm/key_exchange/dh.rs index c131a30..be49b32 100644 --- a/src/algorithm/key_exchange/dh.rs +++ b/src/algorithm/key_exchange/dh.rs @@ -5,7 +5,7 @@ use crate::SshResult; use super::{HashType, KeyExchange}; -#[cfg(feature = "dangerous-dh-group1-sha1")] +#[cfg(feature = "deprecated-dh-group1-sha1")] const GROUP1: [u8; 128] = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0xf, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x2, 0x4e, 0x8, 0x8a, 0x67, 0xcc, 0x74, @@ -42,7 +42,7 @@ struct DhGroup { exp_size: u64, } -#[cfg(feature = "dangerous-dh-group1-sha1")] +#[cfg(feature = "deprecated-dh-group1-sha1")] const DH_GROUP1: DhGroup = DhGroup { prime: &GROUP1, generator: 2, @@ -108,7 +108,7 @@ macro_rules! create_dh_with_group { }; } -#[cfg(feature = "dangerous-dh-group1-sha1")] +#[cfg(feature = "deprecated-dh-group1-sha1")] create_dh_with_group!(DiffieHellmanGroup1Sha1, DH_GROUP1, HashType::SHA1); create_dh_with_group!(DiffieHellmanGroup14Sha1, DH_GROUP14, HashType::SHA1); create_dh_with_group!(DiffieHellmanGroup14Sha256, DH_GROUP14, HashType::SHA256); diff --git a/src/algorithm/key_exchange/ecdh_sha2_nistp256.rs b/src/algorithm/key_exchange/ecdh_sha2_nistp256.rs index c357063..9315037 100644 --- a/src/algorithm/key_exchange/ecdh_sha2_nistp256.rs +++ b/src/algorithm/key_exchange/ecdh_sha2_nistp256.rs @@ -13,14 +13,14 @@ impl KeyExchange for EcdhP256 { let rng = ring::rand::SystemRandom::new(); let private_key = match EphemeralPrivateKey::generate(&ECDH_P256, &rng) { Ok(v) => v, - Err(_) => return Err(SshError::from("encryption error.")), + Err(e) => return Err(SshError::KexError(e.to_string())), }; match private_key.compute_public_key() { Ok(public_key) => Ok(EcdhP256 { private_key, public_key, }), - Err(_) => Err(SshError::from("encryption error.")), + Err(e) => Err(SshError::KexError(e.to_string())), } } diff --git a/src/algorithm/key_exchange/mod.rs b/src/algorithm/key_exchange/mod.rs index 74ac872..d894d16 100644 --- a/src/algorithm/key_exchange/mod.rs +++ b/src/algorithm/key_exchange/mod.rs @@ -3,17 +3,16 @@ use crate::{SshError, SshResult}; use ring::agreement; use ring::agreement::{EphemeralPrivateKey, UnparsedPublicKey}; -/// # 密钥交换方法 -/// -/// 密钥交换方法规定如何生成用于加密和验证的一次性会话密钥,以及如何进行服务器验证。 +/// # Algorithms that used for key exchange /// +/// mod curve25519; mod dh; mod ecdh_sha2_nistp256; use super::Kex; use curve25519::CURVE25519; -#[cfg(feature = "dangerous-dh-group1-sha1")] +#[cfg(feature = "deprecated-dh-group1-sha1")] use dh::DiffieHellmanGroup1Sha1; use dh::{DiffieHellmanGroup14Sha1, DiffieHellmanGroup14Sha256}; use ecdh_sha2_nistp256::EcdhP256; @@ -38,7 +37,7 @@ pub(crate) fn agree_ephemeral>( |key_material| Ok(key_material.to_vec()), ) { Ok(o) => Ok(o), - Err(_) => Err(SshError::from("encryption error.")), + Err(e) => Err(SshError::KexError(e.to_string())), } } @@ -46,7 +45,7 @@ pub(crate) fn from(s: &Kex) -> SshResult> { match s { Kex::Curve25519Sha256 => Ok(Box::new(CURVE25519::new()?)), Kex::EcdhSha2Nistrp256 => Ok(Box::new(EcdhP256::new()?)), - #[cfg(feature = "dangerous-dh-group1-sha1")] + #[cfg(feature = "deprecated-dh-group1-sha1")] Kex::DiffieHellmanGroup1Sha1 => Ok(Box::new(DiffieHellmanGroup1Sha1::new()?)), Kex::DiffieHellmanGroup14Sha1 => Ok(Box::new(DiffieHellmanGroup14Sha1::new()?)), Kex::DiffieHellmanGroup14Sha256 => Ok(Box::new(DiffieHellmanGroup14Sha256::new()?)), diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index faaad8e..c6b06f6 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -19,6 +19,18 @@ pub enum Enc { Aes192Ctr, #[strum(serialize = "aes256-ctr")] Aes256Ctr, + #[cfg(feature = "deprecated-aes-cbc")] + #[strum(serialize = "aes128-cbc")] + Aes128Cbc, + #[cfg(feature = "deprecated-aes-cbc")] + #[strum(serialize = "aes192-cbc")] + Aes192Cbc, + #[cfg(feature = "deprecated-aes-cbc")] + #[strum(serialize = "aes256-cbc")] + Aes256Cbc, + #[cfg(feature = "deprecated-des-cbc")] + #[strum(serialize = "3des-cbc")] + TripleDesCbc, } /// key exchange algorithm @@ -28,7 +40,7 @@ pub enum Kex { Curve25519Sha256, #[strum(serialize = "ecdh-sha2-nistp256")] EcdhSha2Nistrp256, - #[cfg(feature = "dangerous-dh-group1-sha1")] + #[cfg(feature = "deprecated-dh-group1-sha1")] #[strum(serialize = "diffie-hellman-group1-sha1")] DiffieHellmanGroup1Sha1, #[strum(serialize = "diffie-hellman-group14-sha1")] @@ -42,7 +54,7 @@ pub enum Kex { pub enum PubKey { #[strum(serialize = "ssh-ed25519")] SshEd25519, - #[cfg(feature = "dangerous-rsa-sha1")] + #[cfg(feature = "deprecated-rsa-sha1")] #[strum(serialize = "ssh-rsa")] SshRsa, #[strum(serialize = "rsa-sha2-256")] diff --git a/src/algorithm/public_key/mod.rs b/src/algorithm/public_key/mod.rs index f67e17b..b706dec 100644 --- a/src/algorithm/public_key/mod.rs +++ b/src/algorithm/public_key/mod.rs @@ -3,15 +3,16 @@ use crate::SshError; mod ed25519; mod rsa; -#[cfg(feature = "dangerous-rsa-sha1")] +#[cfg(feature = "deprecated-rsa-sha1")] use self::rsa::RsaSha1; use self::rsa::RsaSha256; use self::rsa::RsaSha512; use super::PubKey; use ed25519::Ed25519; -/// # 公钥算法 -/// 主要用于对服务端签名的验证 +/// # Public Key Algorithms +/// +/// pub(crate) trait PublicKey: Send + Sync { fn new() -> Self @@ -23,7 +24,7 @@ pub(crate) trait PublicKey: Send + Sync { pub(crate) fn from(s: &PubKey) -> Box { match s { PubKey::SshEd25519 => Box::new(Ed25519::new()), - #[cfg(feature = "dangerous-rsa-sha1")] + #[cfg(feature = "deprecated-rsa-sha1")] PubKey::SshRsa => Box::new(RsaSha1::new()), PubKey::RsaSha2_256 => Box::new(RsaSha256::new()), PubKey::RsaSha2_512 => Box::new(RsaSha512::new()), diff --git a/src/algorithm/public_key/rsa.rs b/src/algorithm/public_key/rsa.rs index 5c901a1..06ebfc1 100644 --- a/src/algorithm/public_key/rsa.rs +++ b/src/algorithm/public_key/rsa.rs @@ -56,9 +56,9 @@ impl PubK for RsaSha512 { } } -#[cfg(feature = "dangerous-rsa-sha1")] +#[cfg(feature = "deprecated-rsa-sha1")] pub(super) struct RsaSha1; -#[cfg(feature = "dangerous-rsa-sha1")] +#[cfg(feature = "deprecated-rsa-sha1")] impl PubK for RsaSha1 { fn new() -> Self where diff --git a/src/channel/backend/channel.rs b/src/channel/backend/channel.rs index 3c3ed51..6e94878 100644 --- a/src/channel/backend/channel.rs +++ b/src/channel/backend/channel.rs @@ -6,13 +6,16 @@ use std::{ use crate::{ client::Client, - constant::ssh_msg_code, + constant::ssh_connection_code, error::{SshError, SshResult}, model::{BackendResp, BackendRqst, Data, FlowControl, Packet}, TerminalSize, }; +use tracing::*; -use super::{channel_exec::ExecBroker, channel_scp::ScpBroker, channel_shell::ShellBrocker}; +#[cfg(feature = "scp")] +use super::channel_scp::ScpBroker; +use super::{channel_exec::ExecBroker, channel_shell::ShellBrocker}; pub(crate) struct Channel { snd: Sender, @@ -63,7 +66,7 @@ impl Channel { // send it let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_DATA) + data.put_u8(ssh_connection_code::CHANNEL_DATA) .put_u32(self.server_channel_no) .put_u8s(&self.pending_send); @@ -85,7 +88,9 @@ impl Channel { if !self.is_close() { data.pack(client).write_stream(stream) } else { - Err(SshError::from("Send data on a closed channel")) + Err(SshError::GeneralError( + "Send data on a closed channel".to_owned(), + )) } } @@ -111,7 +116,7 @@ impl Channel { S: Read + Write, { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_WINDOW_ADJUST) + data.put_u8(ssh_connection_code::CHANNEL_WINDOW_ADJUST) .put_u32(self.server_channel_no) .put_u32(to_add); self.flow_control.on_send(to_add); @@ -136,13 +141,13 @@ impl Channel { } pub fn local_close(&mut self) -> SshResult<()> { - log::trace!("Channel {} send local close", self.client_channel_no); + trace!("Channel {} send local close", self.client_channel_no); self.local_close = true; Ok(()) } pub fn remote_close(&mut self) -> SshResult<()> { - log::trace!("Channel {} recv remote close", self.client_channel_no); + trace!("Channel {} recv remote close", self.client_channel_no); self.remote_close = true; if !self.local_close { self.snd.send(BackendResp::Close)?; @@ -167,7 +172,7 @@ impl Channel { impl Drop for Channel { fn drop(&mut self) { - log::info!("Channel {} closed", self.client_channel_no); + info!("Channel {} closed", self.client_channel_no); } } @@ -203,6 +208,7 @@ impl ChannelBroker { /// open a [ScpBroker] channel which can download/upload files/directories /// + #[cfg(feature = "scp")] pub fn scp(self) -> SshResult { Ok(ScpBroker::open(self)) } @@ -222,7 +228,7 @@ impl ChannelBroker { fn close_no_consue(&mut self) -> SshResult<()> { if !self.close { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_CLOSE) + data.put_u8(ssh_connection_code::CHANNEL_CLOSE) .put_u32(self.server_channel_no); self.close = true; self.snd @@ -242,11 +248,10 @@ impl ChannelBroker { .send(BackendRqst::Command(self.client_channel_no, data))?; if !self.close { match self.rcv.recv().unwrap() { - BackendResp::Ok(_) => log::trace!("{}: control command ok", self.client_channel_no), - BackendResp::Fail(msg) => log::error!( + BackendResp::Ok(_) => trace!("{}: control command ok", self.client_channel_no), + BackendResp::Fail(msg) => error!( "{}: channel error with reason {}", - self.client_channel_no, - msg + self.client_channel_no, msg ), _ => unreachable!(), } @@ -290,7 +295,9 @@ impl ChannelBroker { Ok(None) } } else { - Err(SshError::from("Read data on a closed channel")) + Err(SshError::GeneralError( + "Read data on a closed channel".to_owned(), + )) } } diff --git a/src/channel/backend/channel_exec.rs b/src/channel/backend/channel_exec.rs index f61b56f..69a62ee 100644 --- a/src/channel/backend/channel_exec.rs +++ b/src/channel/backend/channel_exec.rs @@ -1,5 +1,5 @@ use super::channel::ChannelBroker; -use crate::constant::{ssh_msg_code, ssh_str}; +use crate::constant::{ssh_connection_code, ssh_str}; use crate::error::SshResult; use crate::model::Data; use std::ops::{Deref, DerefMut}; @@ -16,9 +16,9 @@ impl ExecBroker { /// This method is non-block as it will not wait the result /// pub fn send_command(&self, command: &str) -> SshResult<()> { - log::debug!("Send command {}", command); + tracing::debug!("Send command {}", command); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::EXEC) .put_u8(true as u8) diff --git a/src/channel/backend/channel_scp.rs b/src/channel/backend/channel_scp.rs index 7b04fed..85466c7 100644 --- a/src/channel/backend/channel_scp.rs +++ b/src/channel/backend/channel_scp.rs @@ -6,7 +6,7 @@ use crate::{ util::file_time, }; use crate::{ - constant::{ssh_msg_code, ssh_str}, + constant::{ssh_connection_code, ssh_str}, util, }; use crate::{model::Data, util::check_path}; @@ -16,8 +16,10 @@ use std::{ io::{Read, Write}, ops::{Deref, DerefMut}, path::Path, + str::FromStr, time::SystemTime, }; +use tracing::*; pub struct ScpBroker(ChannelBroker, Option); @@ -28,7 +30,7 @@ impl ScpBroker { fn exec_scp(&mut self, command: &str) -> SshResult<()> { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::EXEC) .put_u8(true as u8) @@ -55,13 +57,13 @@ impl ScpBroker { fn get_end(&mut self) -> SshResult<()> { let vec = self.recv()?; if vec.is_empty() { - Err(SshError::from("read a closed channel")) + Err(SshError::ScpError("read a closed channel".to_owned())) } else { match vec[0] { scp::END => Ok(()), // error - scp::ERR | scp::FATAL_ERR => Err(SshError::from(util::from_utf8(vec)?)), - _ => Err(SshError::from("unknown error.")), + scp::ERR | scp::FATAL_ERR => Err(SshError::ScpError(String::from_utf8(vec)?)), + _ => Err(SshError::ScpError("unknown error.".to_owned())), } } } @@ -95,11 +97,10 @@ impl ScpBroker { let remote_path_str = remote_path.to_str().unwrap(); let local_path_str = local_path.to_str().unwrap(); - log::info!( + info!( "start to upload files, \ local [{}] files will be synchronized to the remote [{}] folder.", - local_path_str, - remote_path_str + local_path_str, remote_path_str ); self.exec_scp(self.command_init(remote_path_str, scp::SINK).as_str())?; @@ -108,27 +109,25 @@ impl ScpBroker { scp_file.local_path = local_path.to_path_buf(); self.file_all(&mut scp_file)?; - log::info!("files upload successful."); + info!("files upload successful."); self.0.close() } fn file_all(&mut self, scp_file: &mut ScpFile) -> SshResult<()> { - // 如果获取不到文件或者目录名的话,就不处理该数据 - // 如果文件不是有效的Unicode数据的话,也不处理 + // test if input file path valid scp_file.name = match scp_file.local_path.file_name() { None => return Ok(()), Some(name) => match name.to_str() { None => return Ok(()), - Some(name) => name.to_string(), + Some(name) => name.to_owned(), }, }; self.send_time(scp_file)?; if scp_file.local_path.is_dir() { - // 文件夹如果读取异常的话。就略过该文件夹 - // 详细的错误信息请查看 [std::fs::read_dir] 方法介绍 + // skip the read_dir errs if let Err(e) = fs::read_dir(scp_file.local_path.as_path()) { - log::error!("read dir error, error info: {}", e); + error!("read dir error, error info: {}", e); return Ok(()); } self.send_dir(scp_file)?; @@ -139,8 +138,8 @@ impl ScpBroker { self.file_all(scp_file)? } Err(e) => { - // 暂不处理 - log::error!("dir entry error, error info: {}", e); + // TODO + error!("dir entry error, error info: {}", e); } } } @@ -157,9 +156,8 @@ impl ScpBroker { fn send_file(&mut self, scp_file: &ScpFile) -> SshResult<()> { let mut file = match File::open(scp_file.local_path.as_path()) { Ok(f) => f, - // 文件打开异常,不影响后续操作 Err(e) => { - log::error!( + error!( "failed to open the folder, \ it is possible that the path does not exist, \ which does not affect subsequent operations. \ @@ -170,10 +168,9 @@ impl ScpBroker { } }; - log::debug!( + debug!( "name: [{}] size: [{}] type: [file] start upload.", - scp_file.name, - scp_file.size + scp_file.name, scp_file.size ); let cmd = format!( @@ -199,13 +196,13 @@ impl ScpBroker { } self.get_end()?; - log::debug!("file: [{}] upload completed.", scp_file.name); + debug!("file: [{}] upload completed.", scp_file.name); Ok(()) } fn send_dir(&mut self, scp_file: &ScpFile) -> SshResult<()> { - log::debug!( + debug!( "name: [{}] size: [0], type: [dir] start upload.", scp_file.name ); @@ -214,7 +211,7 @@ impl ScpBroker { self.send_bytes(cmd.as_bytes())?; self.get_end()?; - log::debug!("dir: [{}] upload completed.", scp_file.name); + debug!("dir: [{}] upload completed.", scp_file.name); Ok(()) } @@ -228,14 +225,12 @@ impl ScpBroker { fn get_time(&self, scp_file: &mut ScpFile) -> SshResult<()> { let metadata = scp_file.local_path.as_path().metadata()?; - // 最后修改时间 let modified_time = match metadata.modified() { Ok(t) => t, Err(_) => SystemTime::now(), }; let modified_time = util::sys_time_to_secs(modified_time)?; - // 最后访问时间 let accessed_time = match metadata.accessed() { Ok(t) => t, Err(_) => SystemTime::now(), @@ -274,11 +269,10 @@ impl ScpBroker { let local_path_str = local_path.to_str().unwrap(); let remote_path_str = remote_path.to_str().unwrap(); - log::info!( + info!( "start to download files, \ remote [{}] files will be synchronized to the local [{}] folder.", - remote_path_str, - local_path_str + remote_path_str, local_path_str ); self.exec_scp(self.command_init(remote_path_str, scp::SOURCE).as_str())?; @@ -308,7 +302,6 @@ impl ScpBroker { let code = &data[0]; match *code { scp::T => { - // 处理时间 let (modify_time, access_time) = file_time(data)?; scp_file.modify_time = modify_time; scp_file.access_time = access_time; @@ -325,15 +318,17 @@ impl ScpBroker { } }, // error - scp::ERR | scp::FATAL_ERR => return Err(SshError::from(util::from_utf8(data)?)), - _ => return Err(SshError::from("unknown error.")), + scp::ERR | scp::FATAL_ERR => { + return Err(SshError::ScpError(String::from_utf8(data)?)) + } + _ => return Err(SshError::ScpError("unknown error.".to_owned())), } } Ok(()) } fn process_dir_d(&mut self, data: Vec, scp_file: &mut ScpFile) -> SshResult<()> { - let string = util::from_utf8(data)?; + let string = String::from_utf8(data)?; let dir_info = string.trim(); let split = dir_info.split(' ').collect::>(); match split.get(2) { @@ -342,7 +337,7 @@ impl ScpBroker { } scp_file.is_dir = true; let buf = scp_file.join(&scp_file.name); - log::debug!( + debug!( "name: [{}] size: [0], type: [dir] start download.", scp_file.name ); @@ -362,29 +357,23 @@ impl ScpBroker { self.sync_permissions(scp_file, file); } Err(e) => { - log::error!( - "failed to open the folder, \ - it is possible that the path does not exist, \ - which does not affect subsequent operations. \ - error info: {:?}, path: {:?}", - e, - scp_file.local_path.to_str() - ); - return Err(SshError::from(format!("file open error: {}", e))); + let err_msg = format!("file open error: {}", e); + error!(err_msg); + return Err(SshError::ScpError(err_msg)); } }; } - log::debug!("dir: [{}] download completed.", scp_file.name); + debug!("dir: [{}] download completed.", scp_file.name); Ok(()) } fn process_file_d(&mut self, data: Vec, scp_file: &mut ScpFile) -> SshResult<()> { - let string = util::from_utf8(data)?; + let string = String::from_utf8(data)?; let file_info = string.trim(); let split = file_info.split(' ').collect::>(); let size_str = *split.get(1).unwrap_or(&"0"); - let size = util::str_to_i64(size_str)?; + let size = i64::from_str(size_str)?; scp_file.size = size as u64; match split.get(2) { None => return Ok(()), @@ -395,10 +384,9 @@ impl ScpBroker { } fn save_file(&mut self, scp_file: &ScpFile) -> SshResult<()> { - log::debug!( + debug!( "name: [{}] size: [{}] type: [file] start download.", - scp_file.name, - scp_file.size + scp_file.name, scp_file.size ); let path = scp_file.join(&scp_file.name); if path.exists() { @@ -412,11 +400,9 @@ impl ScpBroker { { Ok(v) => v, Err(e) => { - log::error!("file processing error, error info: {}", e); - return Err(SshError::from(format!( - "{:?} file processing exception", - path - ))); + let err_msg = format!("file processing error, error info: {}", e); + error!(err_msg); + return Err(SshError::ScpError(err_msg)); } }; self.send_end()?; @@ -444,7 +430,7 @@ impl ScpBroker { #[cfg(any(target_os = "linux", target_os = "macos"))] self.sync_permissions(scp_file, file); - log::debug!("file: [{}] download completed.", scp_file.name); + debug!("file: [{}] download completed.", scp_file.name); Ok(()) } @@ -455,7 +441,7 @@ impl ScpBroker { if let Err(e) = filetime::set_file_times(scp_file.local_path.as_path(), access_time, modify_time) { - log::error!( + error!( "the file time synchronization is abnormal,\ which may be caused by the operating system,\ which does not affect subsequent operations.\ @@ -472,7 +458,7 @@ impl ScpBroker { if let Err(e) = filetime::set_file_times(scp_file.local_path.as_path(), access_time, modify_time) { - log::error!( + error!( "the file time synchronization is abnormal,\ which may be caused by the operating system,\ which does not affect subsequent operations.\ @@ -495,14 +481,14 @@ impl ScpBroker { .set_permissions(fs::Permissions::from_mode(mode)) .is_err() { - log::error!( + error!( "the operating system does not allow modification of file permissions, \ which does not affect subsequent operations." ); } } Err(v) => { - log::error!("Unknown error {}", v) + error!("Unknown error {}", v) } } } diff --git a/src/channel/backend/channel_shell.rs b/src/channel/backend/channel_shell.rs index bd075d8..f3e368e 100644 --- a/src/channel/backend/channel_shell.rs +++ b/src/channel/backend/channel_shell.rs @@ -1,5 +1,5 @@ use super::channel::ChannelBroker; -use crate::constant::{ssh_msg_code, ssh_str}; +use crate::constant::{ssh_connection_code, ssh_str}; use crate::error::SshResult; use crate::model::Data; use crate::TerminalSize; @@ -9,7 +9,7 @@ pub struct ShellBrocker(ChannelBroker); impl ShellBrocker { pub(crate) fn open(channel: ChannelBroker, tv: TerminalSize) -> SshResult { - // shell 形式需要一个伪终端 + // to open a shell channel, we need to request a pesudo-terminal let mut channel_shell = ShellBrocker(channel); channel_shell.request_pty(tv)?; channel_shell.get_shell()?; @@ -19,7 +19,7 @@ impl ShellBrocker { fn request_pty(&mut self, tv: TerminalSize) -> SshResult<()> { let tvs = tv.fetch(); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::PTY_REQ) .put_u8(true as u8) @@ -41,7 +41,7 @@ impl ShellBrocker { fn get_shell(&mut self) -> SshResult<()> { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::SHELL) .put_u8(true as u8); diff --git a/src/channel/backend/mod.rs b/src/channel/backend/mod.rs index c2dc827..a95cf02 100644 --- a/src/channel/backend/mod.rs +++ b/src/channel/backend/mod.rs @@ -1,10 +1,13 @@ mod channel; mod channel_exec; -mod channel_scp; mod channel_shell; pub(crate) use channel::Channel; pub use channel::ChannelBroker; pub use channel_exec::ExecBroker; -pub use channel_scp::ScpBroker; pub use channel_shell::ShellBrocker; + +#[cfg(feature = "scp")] +mod channel_scp; +#[cfg(feature = "scp")] +pub use channel_scp::ScpBroker; diff --git a/src/channel/local/channel.rs b/src/channel/local/channel.rs index 792c21f..c3648bd 100644 --- a/src/channel/local/channel.rs +++ b/src/channel/local/channel.rs @@ -1,16 +1,19 @@ use std::io::{Read, Write}; -use crate::model::TerminalSize; use crate::{ algorithm::Digest, client::Client, config::algorithm::AlgList, - constant::ssh_msg_code, + constant::ssh_connection_code, error::{SshError, SshResult}, model::{Data, FlowControl, Packet, RcMut, SecPacket}, }; +use crate::{constant::ssh_transport_code, model::TerminalSize}; +use tracing::*; -use super::{ChannelExec, ChannelScp, ChannelShell}; +#[cfg(feature = "scp")] +use super::ChannelScp; +use super::{ChannelExec, ChannelShell}; pub(super) enum ChannelRead { Data(Vec), @@ -55,14 +58,15 @@ where /// convert the raw channel to an [self::ChannelExec] /// pub fn exec(self) -> SshResult> { - log::info!("exec opened."); + info!("exec opened."); Ok(ChannelExec::open(self)) } /// convert the raw channel to an [self::ChannelScp] /// + #[cfg(feature = "scp")] pub fn scp(self) -> SshResult> { - log::info!("scp opened."); + info!("scp opened."); Ok(ChannelScp::open(self)) } @@ -71,14 +75,14 @@ where /// with `row` lines & `column` characters per one line /// pub fn shell(self, tv: TerminalSize) -> SshResult> { - log::info!("shell opened."); + info!("shell opened."); ChannelShell::open(self, tv) } /// close the channel gracefully, but donnot consume it /// pub fn close(&mut self) -> SshResult<()> { - log::info!("channel close."); + info!("channel close."); self.send_close()?; self.receive_close() } @@ -88,7 +92,7 @@ where return Ok(()); } let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_CLOSE) + data.put_u8(ssh_connection_code::CHANNEL_CLOSE) .put_u32(self.server_channel_no); self.local_close = true; self.send(data) @@ -118,7 +122,7 @@ where // send it let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_DATA) + data.put_u8(ssh_connection_code::CHANNEL_DATA) .put_u32(self.server_channel_no) .put_u8s(&buf); self.send(data)?; @@ -193,7 +197,7 @@ where fn handle_msg(&mut self, mut data: Data) -> SshResult { let message_code = data.get_u8(); match message_code { - x @ ssh_msg_code::SSH_MSG_KEXINIT => { + x @ ssh_transport_code::KEXINIT => { data.insert(0, message_code); let mut digest = Digest::new(); digest.hash_ctx.set_i_s(&data); @@ -205,7 +209,7 @@ where )?; Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_CHANNEL_DATA => { + x @ ssh_connection_code::CHANNEL_DATA => { let cc = data.get_u32(); if cc == self.client_channel_no { let mut data = data.get_u8s(); @@ -218,13 +222,13 @@ where } Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_CHANNEL_EXTENDED_DATA => { + x @ ssh_connection_code::CHANNEL_EXTENDED_DATA => { let cc = data.get_u32(); if cc == self.client_channel_no { let data_type_code = data.get_u32(); let mut data = data.get_u8s(); - log::debug!("Recv extended data with type {data_type_code}"); + debug!("Recv extended data with type {data_type_code}"); // flow_contrl self.flow_control.tune_on_recv(&mut data); @@ -234,33 +238,35 @@ where } Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_GLOBAL_REQUEST => { + x @ ssh_connection_code::GLOBAL_REQUEST => { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_REQUEST_FAILURE); + data.put_u8(ssh_connection_code::REQUEST_FAILURE); self.send(data)?; Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_CHANNEL_WINDOW_ADJUST => { + x @ ssh_connection_code::CHANNEL_WINDOW_ADJUST => { data.get_u32(); // to add let rws = data.get_u32(); self.recv_window_adjust(rws)?; Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_CHANNEL_EOF => { - log::debug!("Currently ignore message {}", x); + x @ ssh_connection_code::CHANNEL_EOF => { + debug!("Currently ignore message {}", x); Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_CHANNEL_REQUEST => { - log::debug!("Currently ignore message {}", x); + x @ ssh_connection_code::CHANNEL_REQUEST => { + debug!("Currently ignore message {}", x); Ok(ChannelRead::Code(x)) } - x @ ssh_msg_code::SSH_MSG_CHANNEL_SUCCESS => { - log::debug!("Currently ignore message {}", x); + x @ ssh_connection_code::CHANNEL_SUCCESS => { + debug!("Currently ignore message {}", x); Ok(ChannelRead::Code(x)) } - ssh_msg_code::SSH_MSG_CHANNEL_FAILURE => Err(SshError::from("channel failure.")), - x @ ssh_msg_code::SSH_MSG_CHANNEL_CLOSE => { + ssh_connection_code::CHANNEL_FAILURE => { + Err(SshError::GeneralError("channel failure.".to_owned())) + } + x @ ssh_connection_code::CHANNEL_CLOSE => { let cc = data.get_u32(); if cc == self.client_channel_no { self.remote_close = true; @@ -269,7 +275,7 @@ where Ok(ChannelRead::Code(x)) } x => { - log::debug!("Currently ignore message {}", x); + debug!("Currently ignore message {}", x); Ok(ChannelRead::Code(x)) } } @@ -277,7 +283,7 @@ where fn send_window_adjust(&mut self, to_add: u32) -> SshResult<()> { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_WINDOW_ADJUST) + data.put_u8(ssh_connection_code::CHANNEL_WINDOW_ADJUST) .put_u32(self.server_channel_no) .put_u32(to_add); self.flow_control.on_send(to_add); diff --git a/src/channel/local/channel_exec.rs b/src/channel/local/channel_exec.rs index ac0458e..c115388 100644 --- a/src/channel/local/channel_exec.rs +++ b/src/channel/local/channel_exec.rs @@ -1,5 +1,5 @@ use super::channel::Channel; -use crate::constant::{ssh_msg_code, ssh_str}; +use crate::constant::{ssh_connection_code, ssh_str}; use crate::error::SshResult; use crate::model::Data; use std::{ @@ -18,9 +18,9 @@ where } fn exec_command(&mut self, command: &str) -> SshResult<()> { - log::debug!("Send command {}", command); + tracing::debug!("Send command {}", command); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::EXEC) .put_u8(true as u8) diff --git a/src/channel/local/channel_scp.rs b/src/channel/local/channel_scp.rs index 7da2208..e12283a 100644 --- a/src/channel/local/channel_scp.rs +++ b/src/channel/local/channel_scp.rs @@ -6,7 +6,7 @@ use crate::{ util::{check_path, file_time}, }; use crate::{ - constant::{ssh_msg_code, ssh_str}, + constant::{ssh_connection_code, ssh_str}, error::SshError, }; use crate::{model::Data, util}; @@ -16,8 +16,10 @@ use std::{ io::{Read, Write}, ops::{Deref, DerefMut}, path::Path, + str::FromStr, time::SystemTime, }; +use tracing::*; pub struct ChannelScp(Channel); @@ -31,7 +33,7 @@ where fn exec_scp(&mut self, command: &str) -> SshResult<()> { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::EXEC) .put_u8(true as u8) @@ -60,8 +62,8 @@ where match vec[0] { scp::END => Ok(()), // error - scp::ERR | scp::FATAL_ERR => Err(SshError::from(util::from_utf8(vec)?)), - _ => Err(SshError::from("unknown error.")), + scp::ERR | scp::FATAL_ERR => Err(SshError::ScpError(String::from_utf8(vec)?)), + _ => Err(SshError::ScpError("unknown error.".to_owned())), } } @@ -97,11 +99,10 @@ where let remote_path_str = remote_path.to_str().unwrap(); let local_path_str = local_path.to_str().unwrap(); - log::info!( + info!( "start to upload files, \ local [{}] files will be synchronized to the remote [{}] folder.", - local_path_str, - remote_path_str + local_path_str, remote_path_str ); self.exec_scp(self.command_init(remote_path_str, scp::SINK).as_str())?; @@ -110,27 +111,25 @@ where scp_file.local_path = local_path.to_path_buf(); self.file_all(&mut scp_file)?; - log::info!("files upload successful."); + info!("files upload successful."); self.close() } fn file_all(&mut self, scp_file: &mut ScpFile) -> SshResult<()> { - // 如果获取不到文件或者目录名的话,就不处理该数据 - // 如果文件不是有效的Unicode数据的话,也不处理 + // test if input file path valid scp_file.name = match scp_file.local_path.file_name() { None => return Ok(()), Some(name) => match name.to_str() { None => return Ok(()), - Some(name) => name.to_string(), + Some(name) => name.to_owned(), }, }; self.send_time(scp_file)?; if scp_file.local_path.is_dir() { - // 文件夹如果读取异常的话。就略过该文件夹 - // 详细的错误信息请查看 [std::fs::read_dir] 方法介绍 + // skip the read_dir errs if let Err(e) = fs::read_dir(scp_file.local_path.as_path()) { - log::error!("read dir error, error info: {}", e); + error!("read dir error, error info: {}", e); return Ok(()); } self.send_dir(scp_file)?; @@ -141,8 +140,8 @@ where self.file_all(scp_file)? } Err(e) => { - // 暂不处理 - log::error!("dir entry error, error info: {}", e); + // TODO + error!("dir entry error, error info: {}", e); } } } @@ -159,9 +158,8 @@ where fn send_file(&mut self, scp_file: &ScpFile) -> SshResult<()> { let mut file = match File::open(scp_file.local_path.as_path()) { Ok(f) => f, - // 文件打开异常,不影响后续操作 Err(e) => { - log::error!( + error!( "failed to open the folder, \ it is possible that the path does not exist, \ which does not affect subsequent operations. \ @@ -172,10 +170,9 @@ where } }; - log::debug!( + debug!( "name: [{}] size: [{}] type: [file] start upload.", - scp_file.name, - scp_file.size + scp_file.name, scp_file.size ); let cmd = format!( @@ -200,13 +197,13 @@ where } self.get_end()?; - log::debug!("file: [{}] upload completed.", scp_file.name); + debug!("file: [{}] upload completed.", scp_file.name); Ok(()) } fn send_dir(&mut self, scp_file: &ScpFile) -> SshResult<()> { - log::debug!( + debug!( "name: [{}] size: [0], type: [dir] start upload.", scp_file.name ); @@ -215,7 +212,7 @@ where self.send_bytes(cmd.as_bytes())?; self.get_end()?; - log::debug!("dir: [{}] upload completed.", scp_file.name); + debug!("dir: [{}] upload completed.", scp_file.name); Ok(()) } @@ -229,14 +226,12 @@ where fn get_time(&self, scp_file: &mut ScpFile) -> SshResult<()> { let metadata = scp_file.local_path.as_path().metadata()?; - // 最后修改时间 let modified_time = match metadata.modified() { Ok(t) => t, Err(_) => SystemTime::now(), }; let modified_time = util::sys_time_to_secs(modified_time)?; - // 最后访问时间 let accessed_time = match metadata.accessed() { Ok(t) => t, Err(_) => SystemTime::now(), @@ -275,11 +270,10 @@ where let local_path_str = local_path.to_str().unwrap(); let remote_path_str = remote_path.to_str().unwrap(); - log::info!( + info!( "start to download files, \ remote [{}] files will be synchronized to the local [{}] folder.", - remote_path_str, - local_path_str + remote_path_str, local_path_str ); self.exec_scp(self.command_init(remote_path_str, scp::SOURCE).as_str())?; @@ -287,7 +281,7 @@ where scp_file.local_path = local_path.to_path_buf(); self.process_d(&mut scp_file, local_path)?; - log::info!("files download successful."); + info!("files download successful."); self.close() } @@ -302,7 +296,6 @@ where let code = &data[0]; match *code { scp::T => { - // 处理时间 let (modify_time, access_time) = file_time(data)?; scp_file.modify_time = modify_time; scp_file.access_time = access_time; @@ -319,15 +312,17 @@ where } }, // error - scp::ERR | scp::FATAL_ERR => return Err(SshError::from(util::from_utf8(data)?)), - _ => return Err(SshError::from("unknown error.")), + scp::ERR | scp::FATAL_ERR => { + return Err(SshError::ScpError(String::from_utf8(data)?)) + } + _ => return Err(SshError::ScpError("unknown error.".to_owned())), } } Ok(()) } fn process_dir_d(&mut self, data: Vec, scp_file: &mut ScpFile) -> SshResult<()> { - let string = util::from_utf8(data)?; + let string = String::from_utf8(data)?; let dir_info = string.trim(); let split = dir_info.split(' ').collect::>(); match split.get(2) { @@ -336,7 +331,7 @@ where } scp_file.is_dir = true; let buf = scp_file.join(&scp_file.name); - log::debug!( + debug!( "name: [{}] size: [0], type: [dir] start download.", scp_file.name ); @@ -356,29 +351,23 @@ where self.sync_permissions(scp_file, file); } Err(e) => { - log::error!( - "failed to open the folder, \ - it is possible that the path does not exist, \ - which does not affect subsequent operations. \ - error info: {:?}, path: {:?}", - e, - scp_file.local_path.to_str() - ); - return Err(SshError::from(format!("file open error: {}", e))); + let err_msg = format!("file open error: {}", e); + error!(err_msg); + return Err(SshError::ScpError(err_msg)); } }; } - log::debug!("dir: [{}] download completed.", scp_file.name); + debug!("dir: [{}] download completed.", scp_file.name); Ok(()) } fn process_file_d(&mut self, data: Vec, scp_file: &mut ScpFile) -> SshResult<()> { - let string = util::from_utf8(data)?; + let string = String::from_utf8(data)?; let file_info = string.trim(); let split = file_info.split(' ').collect::>(); let size_str = *split.get(1).unwrap_or(&"0"); - let size = util::str_to_i64(size_str)?; + let size = i64::from_str(size_str)?; scp_file.size = size as u64; match split.get(2) { None => return Ok(()), @@ -389,10 +378,9 @@ where } fn save_file(&mut self, scp_file: &mut ScpFile) -> SshResult<()> { - log::debug!( + debug!( "name: [{}] size: [{}] type: [file] start download.", - scp_file.name, - scp_file.size + scp_file.name, scp_file.size ); let path = scp_file.join(&scp_file.name); if path.exists() { @@ -406,11 +394,9 @@ where { Ok(v) => v, Err(e) => { - log::error!("file processing error, error info: {}", e); - return Err(SshError::from(format!( - "{:?} file processing exception", - path - ))); + let err_msg = format!("file open error: {}", e); + error!(err_msg); + return Err(SshError::ScpError(err_msg)); } }; self.send_end()?; @@ -438,7 +424,7 @@ where #[cfg(any(target_os = "linux", target_os = "macos"))] self.sync_permissions(scp_file, file); - log::debug!("file: [{}] download completed.", scp_file.name); + debug!("file: [{}] download completed.", scp_file.name); Ok(()) } @@ -449,7 +435,7 @@ where if let Err(e) = filetime::set_file_times(scp_file.local_path.as_path(), access_time, modify_time) { - log::error!( + error!( "the file time synchronization is abnormal,\ which may be caused by the operating system,\ which does not affect subsequent operations.\ @@ -466,7 +452,7 @@ where if let Err(e) = filetime::set_file_times(scp_file.local_path.as_path(), access_time, modify_time) { - log::error!( + error!( "the file time synchronization is abnormal,\ which may be caused by the operating system,\ which does not affect subsequent operations.\ @@ -489,14 +475,14 @@ where .set_permissions(fs::Permissions::from_mode(mode)) .is_err() { - log::error!( + error!( "the operating system does not allow modification of file permissions, \ which does not affect subsequent operations." ); } } Err(v) => { - log::error!("Unknown error {}", v) + error!("Unknown error {}", v) } } } diff --git a/src/channel/local/channel_shell.rs b/src/channel/local/channel_shell.rs index f1e6c0f..e759959 100644 --- a/src/channel/local/channel_shell.rs +++ b/src/channel/local/channel_shell.rs @@ -1,5 +1,5 @@ use super::channel::Channel; -use crate::constant::{ssh_msg_code, ssh_str}; +use crate::constant::{ssh_connection_code, ssh_str}; use crate::error::SshResult; use crate::model::{Data, TerminalSize}; use std::{ @@ -14,7 +14,7 @@ where S: Read + Write, { pub(crate) fn open(channel: Channel, tv: TerminalSize) -> SshResult { - // shell 形式需要一个伪终端 + // to open a shell channel, we need to request a pesudo-terminal let mut channel_shell = ChannelShell(channel); channel_shell.request_pty(tv)?; channel_shell.get_shell()?; @@ -25,7 +25,7 @@ where let tvs = tv.fetch(); println!("tvs {:?}", tvs); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::PTY_REQ) .put_u8(false as u8) @@ -47,7 +47,7 @@ where fn get_shell(&mut self) -> SshResult<()> { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_REQUEST) + data.put_u8(ssh_connection_code::CHANNEL_REQUEST) .put_u32(self.server_channel_no) .put_str(ssh_str::SHELL) .put_u8(false as u8); diff --git a/src/channel/local/mod.rs b/src/channel/local/mod.rs index 831df92..54cd355 100644 --- a/src/channel/local/mod.rs +++ b/src/channel/local/mod.rs @@ -1,9 +1,12 @@ mod channel; mod channel_exec; -mod channel_scp; mod channel_shell; pub(crate) use channel::Channel; pub use channel_exec::ChannelExec; -pub use channel_scp::ChannelScp; pub use channel_shell::ChannelShell; + +#[cfg(feature = "scp")] +mod channel_scp; +#[cfg(feature = "scp")] +pub use channel_scp::ChannelScp; diff --git a/src/channel/mod.rs b/src/channel/mod.rs index 814d5d5..81fe799 100644 --- a/src/channel/mod.rs +++ b/src/channel/mod.rs @@ -2,9 +2,13 @@ mod backend; mod local; pub(crate) use backend::Channel as BackendChannel; -pub use backend::{ChannelBroker, ExecBroker, ScpBroker, ShellBrocker}; +pub use backend::{ChannelBroker, ExecBroker, ShellBrocker}; pub(crate) use local::Channel as LocalChannel; pub use local::ChannelExec as LocalExec; -pub use local::ChannelScp as LocalScp; pub use local::ChannelShell as LocalShell; + +#[cfg(feature = "scp")] +pub use backend::ScpBroker; +#[cfg(feature = "scp")] +pub use local::ChannelScp as LocalScp; diff --git a/src/client/client_auth.rs b/src/client/client_auth.rs index 86aeb18..8c10bec 100644 --- a/src/client/client_auth.rs +++ b/src/client/client_auth.rs @@ -1,8 +1,9 @@ use std::io::{Read, Write}; +use tracing::*; use crate::{ algorithm::Digest, - constant::{ssh_msg_code, ssh_str}, + constant::{ssh_connection_code, ssh_str, ssh_transport_code, ssh_user_auth_code}, error::{SshError, SshResult}, model::{Data, Packet, SecPacket}, }; @@ -14,9 +15,9 @@ impl Client { where S: Read + Write, { - log::info!("Auth start"); + info!("Auth start"); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_SERVICE_REQUEST) + data.put_u8(ssh_transport_code::SERVICE_REQUEST) .put_str(ssh_str::SSH_USERAUTH); data.pack(self).write_stream(stream)?; @@ -25,7 +26,7 @@ impl Client { let mut data = Data::unpack(SecPacket::from_stream(stream, self)?)?; let message_code = data.get_u8(); match message_code { - ssh_msg_code::SSH_MSG_SERVICE_ACCEPT => { + ssh_transport_code::SERVICE_ACCEPT => { if self.config.auth.key_pair.is_none() { tried_public_key = true; // if no private key specified @@ -37,31 +38,31 @@ impl Client { self.public_key_authentication(stream)? } } - ssh_msg_code::SSH_MSG_USERAUTH_FAILURE => { + ssh_user_auth_code::FAILURE => { if !tried_public_key { - log::error!("user auth failure. (public key)"); - log::info!("fallback to password authentication"); + error!("user auth failure. (public key)"); + info!("fallback to password authentication"); tried_public_key = true; // keep the same with openssh // if the public key auth failed // try with password again self.password_authentication(stream)? } else { - log::error!("user auth failure. (password)"); - return Err(SshError::from("user auth failure.")); + error!("user auth failure. (password)"); + return Err(SshError::AuthError); } } - ssh_msg_code::SSH_MSG_USERAUTH_PK_OK => { - log::info!("user auth support this algorithm."); + ssh_user_auth_code::PK_OK => { + info!("user auth support this algorithm."); self.public_key_signature(stream, digest)? } - ssh_msg_code::SSH_MSG_USERAUTH_SUCCESS => { - log::info!("user auth successful."); + ssh_user_auth_code::SUCCESS => { + info!("user auth successful."); return Ok(()); } - ssh_msg_code::SSH_MSG_GLOBAL_REQUEST => { + ssh_connection_code::GLOBAL_REQUEST => { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_REQUEST_FAILURE); + data.put_u8(ssh_connection_code::REQUEST_FAILURE); data.pack(self).write_stream(stream)?; } _ => {} @@ -73,9 +74,9 @@ impl Client { where S: Write, { - log::info!("password authentication."); + info!("password authentication."); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_USERAUTH_REQUEST) + data.put_u8(ssh_user_auth_code::REQUEST) .put_str(self.config.auth.username.as_str()) .put_str(ssh_str::SSH_CONNECTION) .put_str(ssh_str::PASSWORD) @@ -91,12 +92,12 @@ impl Client { { let data = { let pubkey_alg = &self.negotiated.public_key[0]; - log::info!( + info!( "public key authentication. algorithm: {}", pubkey_alg.as_ref() ); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_USERAUTH_REQUEST) + data.put_u8(ssh_user_auth_code::REQUEST) .put_str(self.config.auth.username.as_str()) .put_str(ssh_str::SSH_CONNECTION) .put_str(ssh_str::PUBLIC_KEY) @@ -128,7 +129,7 @@ impl Client { let pubkey_alg = &self.negotiated.public_key[0]; let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_USERAUTH_REQUEST) + data.put_u8(ssh_user_auth_code::REQUEST) .put_str(self.config.auth.username.as_str()) .put_str(ssh_str::SSH_CONNECTION) .put_str(ssh_str::PUBLIC_KEY) diff --git a/src/client/client_kex.rs b/src/client/client_kex.rs index bac9de3..2166e30 100644 --- a/src/client/client_kex.rs +++ b/src/client/client_kex.rs @@ -8,12 +8,13 @@ use crate::{ Digest, }, client::Client, - config::{algorithm::AlgList, version::SshVersion}, - constant::ssh_msg_code, + config::algorithm::AlgList, + constant::ssh_transport_code, error::{SshError, SshResult}, model::{Data, Packet, SecPacket}, }; use std::io::{Read, Write}; +use tracing::*; impl Client { pub fn key_agreement( @@ -26,13 +27,11 @@ impl Client { S: Read + Write, { // initialize the hash context - if let SshVersion::V2(ref our, ref their) = self.config.ver { - digest.hash_ctx.set_v_c(our); - digest.hash_ctx.set_v_s(their); - } + digest.hash_ctx.set_v_c(&self.config.ver.client_ver); + digest.hash_ctx.set_v_s(&self.config.ver.server_ver); - log::info!("start for key negotiation."); - log::info!("send client algorithm list."); + info!("start for key negotiation."); + info!("send client algorithm list."); let algs = self.config.algs.clone(); let client_algs = algs.pack(self); @@ -81,7 +80,7 @@ impl Client { self.encryptor = encryption; digest.key_exchange = Some(key_exchange); - log::info!("key negotiation successful."); + info!("key negotiation successful."); Ok(()) } @@ -92,7 +91,7 @@ impl Client { S: Read + Write, { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_KEXDH_INIT) + data.put_u8(ssh_transport_code::KEXDH_INIT) .put_u8s(public_key); data.pack(self).write_stream(stream) } @@ -112,19 +111,20 @@ impl Client { let mut data = Data::unpack(SecPacket::from_stream(stream, self)?)?; let message_code = data.get_u8(); match message_code { - ssh_msg_code::SSH_MSG_KEXDH_REPLY => { - // 生成session_id并且获取signature + ssh_transport_code::KEXDH_REPLY => { + // Generate the session id, get the signature let sig = self.generate_signature(data, h, key_exchange)?; - // 验签 + // verify the signature session_id = hash::digest(&h.as_bytes(), key_exchange.get_hash_type()); let flag = public_key.verify_signature(&h.k_s, &session_id, &sig)?; if !flag { - log::error!("signature verification failure."); - return Err(SshError::from("signature verification failure.")); + let err_msg = "signature verification failure.".to_owned(); + error!(err_msg); + return Err(SshError::KexError(err_msg)); } - log::info!("signature verification success."); + info!("signature verification success."); } - ssh_msg_code::SSH_MSG_NEWKEYS => { + ssh_transport_code::NEWKEYS => { self.new_keys(stream)?; return Ok(session_id); } @@ -133,7 +133,7 @@ impl Client { } } - /// 生成签名 + /// get the signature fn generate_signature( &mut self, mut data: Data, @@ -142,10 +142,11 @@ impl Client { ) -> SshResult> { let ks = data.get_u8s(); h.set_k_s(&ks); - // TODO 未进行密钥指纹验证!! + // TODO: + // No fingerprint verification let qs = data.get_u8s(); - h.set_q_c(key_exchange.get_public_key()); - h.set_q_s(&qs); + h.set_e(key_exchange.get_public_key()); + h.set_f(&qs); let vec = key_exchange.get_shared_secret(qs)?; h.set_k(&vec); let h = data.get_u8s(); @@ -155,14 +156,14 @@ impl Client { Ok(signature) } - /// SSH_MSG_NEWKEYS 代表密钥交换完成 + /// NEWKEYS indicates that kex is done fn new_keys(&mut self, stream: &mut S) -> SshResult<()> where S: Write, { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_NEWKEYS); - log::info!("send new keys"); + data.put_u8(ssh_transport_code::NEWKEYS); + info!("send new keys"); data.pack(self).write_stream(stream) } } diff --git a/src/config/algorithm.rs b/src/config/algorithm.rs index 6305f0e..fa49581 100644 --- a/src/config/algorithm.rs +++ b/src/config/algorithm.rs @@ -3,11 +3,12 @@ use std::{ ops::{Deref, DerefMut}, str::FromStr, }; +use tracing::*; use crate::{ algorithm::{Compress, Enc, Kex, Mac, PubKey}, client::Client, - constant::ssh_msg_code, + constant::ssh_transport_code, error::{SshError, SshResult}, model::{Data, Packet, SecPacket}, util, @@ -133,14 +134,14 @@ impl AlgList { fn from(mut data: Data) -> SshResult { data.get_u8(); - // 跳过16位cookie + // skip the 16-bit cookie data.skip(16); let mut server_algorithm = Self::new(); macro_rules! try_convert { ($hint: literal, $field: ident) => { let alg_string = util::vec_u8_to_string(data.get_u8s(), ",")?; - log::info!("server {}: {:?}", $hint, alg_string); + info!("server {}: {:?}", $hint, alg_string); server_algorithm.$field = alg_string.try_into()?; }; } @@ -152,7 +153,7 @@ impl AlgList { try_convert!("s2c mac", s_mac); try_convert!("c2s compression", c_compress); try_convert!("s2c compression", s_compress); - log::debug!("converted server algorithms: [{:?}]", server_algorithm); + debug!("converted server algorithms: [{:?}]", server_algorithm); Ok(server_algorithm) } @@ -169,15 +170,14 @@ impl AlgList { } }) .ok_or_else(|| { - log::error!( + let err_msg = format!( "Key_agreement: the {} fails to match, \ - algorithms supported by the server: {},\ - algorithms supported by the client: {}", - $err_hint, - $their.$field, - $our.$field + algorithms supported by the server: {},\ + algorithms supported by the client: {}", + $err_hint, $their.$field, $our.$field ); - SshError::from("key exchange error.") + error!(err_msg); + SshError::KexError(err_msg) }) }; } @@ -209,7 +209,7 @@ impl AlgList { s_compress: vec![*s_compress].into(), }; - log::info!("matched algorithms [{:?}]", negotiated); + info!("matched algorithms [{:?}]", negotiated); Ok(negotiated) } @@ -230,9 +230,9 @@ impl AlgList { impl<'a> Packet<'a> for AlgList { fn pack(self, client: &'a mut Client) -> crate::model::SecPacket<'a> { - log::info!("client algorithms: [{:?}]", self); + info!("client algorithms: [{:?}]", self); let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_KEXINIT); + data.put_u8(ssh_transport_code::KEXINIT); data.extend(util::cookie()); data.extend(self.as_i()); data.put_str("") @@ -248,7 +248,7 @@ impl<'a> Packet<'a> for AlgList { Self: Sized, { let data = pkt.into_inner(); - assert_eq!(data[0], ssh_msg_code::SSH_MSG_KEXINIT); + assert_eq!(data[0], ssh_transport_code::KEXINIT); AlgList::from(data) } } diff --git a/src/config/auth.rs b/src/config/auth.rs index 8314258..cf4f4d9 100644 --- a/src/config/auth.rs +++ b/src/config/auth.rs @@ -33,16 +33,18 @@ impl KeyPair { ssh_key::Algorithm::Rsa { hash: _hash } => (KeyType::SshRsa, key_str), ssh_key::Algorithm::Ed25519 => (KeyType::SshEd25519, key_str), x => { - return Err(SshError::from(format!( + return Err(SshError::SshPubKeyError(format!( "Currently don't support the key file type {}", x ))) } }, - Err(e) => return Err(SshError::from(e.to_string())), + Err(e) => return Err(SshError::SshPubKeyError(e.to_string())), } } else { - return Err(SshError::from("Unable to detect the pulic key type")); + return Err(SshError::SshPubKeyError( + "Unable to detect the pulic key type".to_owned(), + )); }; // then store it @@ -101,7 +103,7 @@ impl KeyPair { Pkcs1v15Sign::new::(), ring::digest::digest(&ring::digest::SHA256, sd), ), - #[cfg(feature = "dangerous-rsa-sha1")] + #[cfg(feature = "deprecated-rsa-sha1")] PubKey::SshRsa => ( Pkcs1v15Sign::new::(), ring::digest::digest(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, sd), @@ -205,10 +207,7 @@ impl AuthInfo { where P: AsRef, { - let mut file = match File::open(p) { - Ok(file) => file, - Err(e) => return Err(SshError::from(e.to_string())), - }; + let mut file = File::open(p)?; let mut prks = String::new(); file.read_to_string(&mut prks)?; diff --git a/src/config/version.rs b/src/config/version.rs index 0969f06..0e0f328 100644 --- a/src/config/version.rs +++ b/src/config/version.rs @@ -1,5 +1,6 @@ use std::io::{Read, Write}; use std::time::Duration; +use tracing::*; use crate::{ constant::{self, CLIENT_VERSION}, @@ -7,15 +8,19 @@ use crate::{ model::Timeout, }; -type OurVer = String; -type ServerVer = String; +#[derive(Debug, Clone)] +pub(crate) struct SshVersion { + pub client_ver: String, + pub server_ver: String, +} -#[derive(Debug, Clone, Default)] -pub(crate) enum SshVersion { - V1, - V2(OurVer, ServerVer), - #[default] - Unknown, +impl Default for SshVersion { + fn default() -> Self { + Self { + client_ver: CLIENT_VERSION.to_owned(), + server_ver: String::new(), + } + } } fn read_version(stream: &mut S, tm: Option) -> SshResult> @@ -45,45 +50,42 @@ where } impl SshVersion { - pub fn from(stream: &mut S, timeout: Option) -> SshResult + pub fn read_server_version( + &mut self, + stream: &mut S, + timeout: Option, + ) -> SshResult<()> where S: Read, { let buf = read_version(stream, timeout)?; - let from_utf8 = crate::util::from_utf8(buf)?; + let from_utf8 = String::from_utf8(buf)?; let version_str = from_utf8.trim(); - log::info!("server version: [{}]", version_str); + info!("server version: [{}]", version_str); - if version_str.contains("SSH-2.0") { - Ok(SshVersion::V2( - CLIENT_VERSION.to_string(), - version_str.to_string(), - )) - } else if version_str.contains("SSH-1.0") { - Ok(SshVersion::V1) - } else { - Ok(SshVersion::Unknown) - } + self.server_ver = version_str.to_owned(); + Ok(()) } - pub fn write(stream: &mut S) -> SshResult<()> + pub fn send_our_version(&self, stream: &mut S) -> SshResult<()> where S: Write, { - log::info!("client version: [{}]", CLIENT_VERSION); - let ver_string = format!("{}\r\n", CLIENT_VERSION); + info!("client version: [{}]", self.client_ver); + let ver_string = format!("{}\r\n", self.client_ver); let _ = stream.write(ver_string.as_bytes())?; Ok(()) } pub fn validate(&self) -> SshResult<()> { - if let SshVersion::V2(_, _) = self { - log::info!("version negotiation was successful."); + if self.server_ver.contains("SSH-2.0") { Ok(()) } else { - let err_msg = "error in version negotiation, version mismatch."; - log::error!("{}", err_msg); - Err(SshError::from(err_msg)) + error!("error in version negotiation, version mismatch."); + Err(SshError::VersionDismatchError { + our: constant::CLIENT_VERSION.to_owned(), + their: self.server_ver.clone(), + }) } } } diff --git a/src/constant.rs b/src/constant.rs index 525984e..fcbcc6e 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -1,147 +1,162 @@ -/// 客户端版本 -pub(crate) const CLIENT_VERSION: &str = "SSH-2.0-SSH_RS-0.3.3"; +/// The client version +pub(crate) const CLIENT_VERSION: &str = "SSH-2.0-SSH_RS-0.4.0"; pub(crate) const SSH_MAGIC: &[u8] = b"SSH-"; -/// ssh通讯时用到的常量字符串 +/// The constant strings that used for ssh communication #[allow(dead_code)] pub(crate) mod ssh_str { - /// 准备认证 + /// Pre-auth msg pub const SSH_USERAUTH: &str = "ssh-userauth"; - /// 开始认证 + /// Authenticate msg pub const SSH_CONNECTION: &str = "ssh-connection"; - /// 公钥验证方式 + /// Authenticate with public key pub const PUBLIC_KEY: &str = "publickey"; - /// 密码认证方式 + /// Authenticate with password pub const PASSWORD: &str = "password"; - /// 打开一个会话 + /// Session level msg pub const SESSION: &str = "session"; - /// 启动一个命令解释程序 + /// Open a Shell pub const SHELL: &str = "shell"; - /// 执行一个命令 + /// Execute a command pub const EXEC: &str = "exec"; - /// 执行文件传输 + /// SCP pub const SCP: &str = "scp"; - /// 请求一个伪终端 + /// Request a pesudo-terminal pub const PTY_REQ: &str = "pty-req"; - /// 伪终端的样式 + /// The xterm style that used for the pty pub const XTERM_VAR: &str = "xterm-256color"; } #[allow(dead_code)] pub(crate) mod permission { - /// 文件夹默认权限 + /// The default permission for directories pub const DIR: &str = "775"; - /// 文件默认权限 + /// The default permission for files pub const FILE: &str = "664"; } -/// scp 操作时用到的常量 +/// Some constants that used when scp +#[cfg(feature = "scp")] #[allow(dead_code)] pub(crate) mod scp { - // scp 参数常量 - /// 意味着当前机器上的scp,将本地文件传输到另一个scp上 + /// Scp from our to the remote pub const SOURCE: &str = "-f"; - /// 意味着当前机器上的scp,即将收到另一个scp传输过来的文件 + /// Scp from the remote to our pub const SINK: &str = "-t"; - /// 递归复制整个目录 + /// Recursive scp for a dir pub const RECURSIVE: &str = "-r"; - /// 详细方式显示输出 + /// Show details pub const VERBOSE: &str = "-v"; - /// 保留原文件的修改时间,访问时间和访问权限 + /// Keep the modification, access time and permission the same with the origin pub const PRESERVE_TIMES: &str = "-p"; - /// 不显示传输进度条 + /// Show not progress bar pub const QUIET: &str = "-q"; - /// 限定用户所能使用的带宽 + /// Limit the bandwidth usage pub const LIMIT: &str = "-l"; - // scp传输时的状态常量 - /// 代表当前接收的数据是文件的最后修改时间和最后访问时间 + /// Indicate the modification, access time of the file we recieve /// "T1647767946 0 1647767946 0\n"; pub const T: u8 = b'T'; - /// 代表当前接收的数据是文件夹 + /// Indicate that we are recieving a directory /// "D0775 0 dirName\n" pub const D: u8 = b'D'; - /// 代表当前接收的数据是文件 + /// Indicate that we are recieving a file /// "C0664 200 fileName.js\n" pub const C: u8 = b'C'; - /// 代表当前文件夹传输结束,需要返回上层文件夹 + /// Indicate that current directory is done /// "D\n" pub const E: u8 = b'E'; - /// 代表结束当前操作 + /// The end flag of current operation // '\0' pub const END: u8 = 0; - /// scp操作异常 + /// Exceptions occur pub const ERR: u8 = 1; - /// scp操作比较严重的异常 + /// Exceptions that cannot recover pub const FATAL_ERR: u8 = 2; } -/// 一些默认大小 #[allow(dead_code)] pub(crate) mod size { pub const FILE_CHUNK: usize = 30000; - /// 最大数据包大小 + /// The max size of one packet pub const BUF_SIZE: usize = 32768; - /// 默认客户端的窗口大小 + /// The default window size of the flow-control pub const LOCAL_WINDOW_SIZE: u32 = 2097152; } -/// ssh 消息码 +/// #[allow(dead_code)] -pub(crate) mod ssh_msg_code { - pub const SSH_MSG_DISCONNECT: u8 = 1; - pub const SSH_MSG_IGNORE: u8 = 2; - pub const SSH_MSG_UNIMPLEMENTED: u8 = 3; - pub const SSH_MSG_DEBUG: u8 = 4; - pub const SSH_MSG_SERVICE_REQUEST: u8 = 5; - pub const SSH_MSG_SERVICE_ACCEPT: u8 = 6; - pub const SSH_MSG_KEXINIT: u8 = 20; - pub const SSH_MSG_NEWKEYS: u8 = 21; - pub const SSH_MSG_KEXDH_INIT: u8 = 30; - pub const SSH_MSG_KEXDH_REPLY: u8 = 31; - pub const SSH_MSG_USERAUTH_REQUEST: u8 = 50; - pub const SSH_MSG_USERAUTH_FAILURE: u8 = 51; - pub const SSH_MSG_USERAUTH_SUCCESS: u8 = 52; - pub const SSH_MSG_USERAUTH_PK_OK: u8 = 60; - pub const SSH_MSG_GLOBAL_REQUEST: u8 = 80; - pub const SSH_MSG_REQUEST_SUCCESS: u8 = 81; - pub const SSH_MSG_REQUEST_FAILURE: u8 = 82; - pub const SSH_MSG_CHANNEL_OPEN: u8 = 90; - pub const SSH_MSG_CHANNEL_OPEN_CONFIRMATION: u8 = 91; - pub const SSH_MSG_CHANNEL_OPEN_FAILURE: u8 = 92; - pub const SSH_MSG_CHANNEL_WINDOW_ADJUST: u8 = 93; - pub const SSH_MSG_CHANNEL_DATA: u8 = 94; - pub const SSH_MSG_CHANNEL_EXTENDED_DATA: u8 = 95; - pub const SSH_MSG_CHANNEL_EOF: u8 = 96; - pub const SSH_MSG_CHANNEL_CLOSE: u8 = 97; - pub const SSH_MSG_CHANNEL_REQUEST: u8 = 98; - pub const SSH_MSG_CHANNEL_SUCCESS: u8 = 99; - pub const SSH_MSG_CHANNEL_FAILURE: u8 = 100; +pub(crate) mod ssh_connection_code { + pub const GLOBAL_REQUEST: u8 = 80; + pub const REQUEST_SUCCESS: u8 = 81; + pub const REQUEST_FAILURE: u8 = 82; + pub const CHANNEL_OPEN: u8 = 90; + pub const CHANNEL_OPEN_CONFIRMATION: u8 = 91; + pub const CHANNEL_OPEN_FAILURE: u8 = 92; + pub const CHANNEL_WINDOW_ADJUST: u8 = 93; + pub const CHANNEL_DATA: u8 = 94; + pub const CHANNEL_EXTENDED_DATA: u8 = 95; + pub const CHANNEL_EOF: u8 = 96; + pub const CHANNEL_CLOSE: u8 = 97; + pub const CHANNEL_REQUEST: u8 = 98; + pub const CHANNEL_SUCCESS: u8 = 99; + pub const CHANNEL_FAILURE: u8 = 100; +} + +/// +#[allow(dead_code)] +pub(crate) mod ssh_channel_fail_code { + pub const ADMINISTRATIVELY_PROHIBITED: u32 = 1; + pub const CONNECT_FAILED: u32 = 2; + pub const UNKNOWN_CHANNEL_TYPE: u32 = 3; + pub const RESOURCE_SHORTAGE: u32 = 4; +} + +/// +#[allow(dead_code)] +pub(crate) mod ssh_transport_code { + pub const DISCONNECT: u8 = 1; + pub const IGNORE: u8 = 2; + pub const UNIMPLEMENTED: u8 = 3; + pub const DEBUG: u8 = 4; + pub const SERVICE_REQUEST: u8 = 5; + pub const SERVICE_ACCEPT: u8 = 6; + pub const KEXINIT: u8 = 20; + pub const NEWKEYS: u8 = 21; + pub const KEXDH_INIT: u8 = 30; + pub const KEXDH_REPLY: u8 = 31; +} - // 异常消息码 - pub const SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT: u8 = 1; - pub const SSH_DISCONNECT_PROTOCOL_ERROR: u8 = 2; - pub const SSH_DISCONNECT_KEY_EXCHANGE_FAILED: u8 = 3; - pub const SSH_DISCONNECT_RESERVED: u8 = 4; - pub const SSH_DISCONNECT_MAC_ERROR: u8 = 5; - pub const SSH_DISCONNECT_COMPRESSION_ERROR: u8 = 6; - pub const SSH_DISCONNECT_SERVICE_NOT_AVAILABLE: u8 = 7; - pub const SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED: u8 = 8; - pub const SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE: u8 = 9; - pub const SSH_DISCONNECT_CONNECTION_LOST: u8 = 10; - pub const SSH_DISCONNECT_BY_APPLICATION: u8 = 11; - pub const SSH_DISCONNECT_TOO_MANY_CONNECTIONS: u8 = 12; - pub const SSH_DISCONNECT_AUTH_CANCELLED_BY_USER: u8 = 13; - pub const SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE: u8 = 14; - pub const SSH_DISCONNECT_ILLEGAL_USER_NAME: u8 = 15; +/// +#[allow(dead_code)] +pub(crate) mod ssh_disconnection_code { + pub const HOST_NOT_ALLOWED_TO_CONNECT: u8 = 1; + pub const PROTOCOL_ERROR: u8 = 2; + pub const KEY_EXCHANGE_FAILED: u8 = 3; + pub const RESERVED: u8 = 4; + pub const MAC_ERROR: u8 = 5; + pub const COMPRESSION_ERROR: u8 = 6; + pub const SERVICE_NOT_AVAILABLE: u8 = 7; + pub const PROTOCOL_VERSION_NOT_SUPPORTED: u8 = 8; + pub const HOST_KEY_NOT_VERIFIABLE: u8 = 9; + pub const CONNECTION_LOST: u8 = 10; + pub const BY_APPLICATION: u8 = 11; + pub const TOO_MANY_CONNECTIONS: u8 = 12; + pub const AUTH_CANCELLED_BY_USER: u8 = 13; + pub const NO_MORE_AUTH_METHODS_AVAILABLE: u8 = 14; + pub const ILLEGAL_USER_NAME: u8 = 15; +} - // 通道连接失败码 SSH_MSG_CHANNEL_OPEN_FAILURE - pub const SSH_OPEN_ADMINISTRATIVELY_PROHIBITED: u32 = 1; - pub const SSH_OPEN_CONNECT_FAILED: u32 = 2; - pub const SSH_OPEN_UNKNOWN_CHANNEL_TYPE: u32 = 3; - pub const SSH_OPEN_RESOURCE_SHORTAGE: u32 = 4; +/// +#[allow(dead_code)] +pub(crate) mod ssh_user_auth_code { + pub const REQUEST: u8 = 50; + pub const FAILURE: u8 = 51; + pub const SUCCESS: u8 = 52; + pub const BANNER: u8 = 53; + pub const PK_OK: u8 = 60; } -/// 密钥交换后进行HASH时候需要的常量值 +/// The magic that used when doing hash after kex pub(crate) const ALPHABET: [u8; 6] = [b'A', b'B', b'C', b'D', b'E', b'F']; diff --git a/src/error.rs b/src/error.rs index 320fb5f..ad72a02 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,117 +1,54 @@ -use std::{ - error::Error, - fmt::{self, Debug, Display, Formatter}, - io, - sync::mpsc::{RecvError, SendError}, -}; +use std::sync::mpsc::{RecvError, SendError}; -pub type SshResult = Result; - -pub struct SshError { - inner: SshErrorKind, -} - -impl SshError { - pub fn kind(&self) -> &SshErrorKind { - &self.inner - } -} - -impl Debug for SshError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match &self.inner { - SshErrorKind::IoError(ie) => { - write!(f, r"IoError: {{ Kind({:?}), Message({}) }}", ie.kind(), ie) - } - _ => { - write!( - f, - r"Error: {{ Kind({:?}), Message({}) }}", - self.inner, self.inner - ) - } - } - } -} - -impl Display for SshError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match &self.inner { - SshErrorKind::IoError(ie) => { - write!(f, r"IoError: {{ Kind({:?}) }}", ie.kind()) - } - _ => { - write!(f, r"Error: {{ Kind({:?}) }}", self.inner) - } - } - } -} - -#[derive(Debug)] -pub enum SshErrorKind { - IoError(io::Error), - SshError(String), - SendError(String), - RecvError(String), - Timeout, -} - -impl fmt::Display for SshErrorKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match &self { - SshErrorKind::SshError(e) => write!(f, "{}", e), - SshErrorKind::IoError(v) => write!(f, "{}", v), - SshErrorKind::SendError(e) => write!(f, "{}", e), - SshErrorKind::RecvError(e) => write!(f, "{}", e), - SshErrorKind::Timeout => write!(f, "time out."), - } - } -} - -impl Error for SshError {} - -impl From for SshError { - fn from(kind: SshErrorKind) -> SshError { - SshError { inner: kind } - } -} +use thiserror::Error; -impl From<&str> for SshError { - fn from(kind: &str) -> SshError { - SshError { - inner: SshErrorKind::SshError(kind.to_string()), - } - } -} +pub type SshResult = Result; -impl From for SshError { - fn from(kind: String) -> SshError { - SshError { - inner: SshErrorKind::SshError(kind), - } - } +#[non_exhaustive] +#[derive(Debug, Error)] +pub enum SshError { + #[error("Version dismatch: {our} vs {their}")] + VersionDismatchError { our: String, their: String }, + #[error("Key exchange error: {0}")] + KexError(String), + #[error("Parse ssh key error: {0}")] + SshPubKeyError(String), + #[error("Auth error")] + AuthError, + #[error("Timeout")] + TimeoutError, + #[error(transparent)] + DataFormatError(#[from] std::string::FromUtf8Error), + #[error("Encryption error: {0}")] + EncryptionError(String), + #[cfg(feature = "scp")] + #[error(transparent)] + SystemTimeError(#[from] std::time::SystemTimeError), + #[cfg(feature = "scp")] + #[error(transparent)] + ParseIntError(#[from] std::num::ParseIntError), + #[cfg(feature = "scp")] + #[error("Invalid scp file path")] + InvalidScpFilePath, + #[cfg(feature = "scp")] + #[error("Scp error: {0}")] + ScpError(String), + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error("IPC error: {0}")] + IpcError(String), + #[error("Ssh Error: {0}")] + GeneralError(String), } -impl From for SshError { - fn from(kind: io::Error) -> Self { - SshError { - inner: SshErrorKind::IoError(io::Error::from(kind.kind())), - } +impl From for SshError { + fn from(value: RecvError) -> Self { + Self::IpcError(value.to_string()) } } impl From> for SshError { - fn from(e: SendError) -> Self { - Self { - inner: SshErrorKind::SendError(e.to_string()), - } - } -} - -impl From for SshError { - fn from(e: RecvError) -> Self { - Self { - inner: SshErrorKind::RecvError(e.to_string()), - } + fn from(value: SendError) -> Self { + Self::IpcError(value.to_string()) } } diff --git a/src/lib.rs b/src/lib.rs index 3307c6e..216f149 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,13 @@ //! Dependencies //! ```toml -//! ssh-rs = "0.3.3" +//! ssh-rs = "0.4.0" //! ``` //! //!Rust implementation of ssh2.0 client. //! //! Basic usage //! ```no_run -//! use ssh_rs::ssh; -//! -//! ssh::debug(); +//! use ssh; //! //! let mut session = ssh::create_session() //! .username("ubuntu") @@ -37,42 +35,24 @@ mod config; mod constant; mod model; mod session; -mod slog; mod util; pub mod error; pub use channel::*; -pub(crate) use error::SshError; -pub use error::{SshErrorKind, SshResult}; +pub use error::SshError; +pub use error::SshResult; pub use model::{TerminalSize, TerminalSizeType}; pub use session::{LocalSession, SessionBroker, SessionBuilder, SessionConnector}; -pub mod ssh { - use crate::{session::SessionBuilder, slog::Slog}; - - /// create a session via session builder w/ default configuration - /// - pub fn create_session() -> SessionBuilder { - SessionBuilder::new() - } - - /// create a session via session builder w/o default configuration - /// - pub fn create_session_without_default() -> SessionBuilder { - SessionBuilder::disable_default() - } - - /// set the global log level to `INFO` - /// - pub fn enable_log() { - Slog::default() - } +/// create a session via session builder w/ default configuration +/// +pub fn create_session() -> SessionBuilder { + SessionBuilder::new() +} - /// set the global log level to `TRACE` - /// - /// for diagnostic purposes only - pub fn debug() { - Slog::debug() - } +/// create a session via session builder w/o default configuration +/// +pub fn create_session_without_default() -> SessionBuilder { + SessionBuilder::disable_default() } diff --git a/src/model/data.rs b/src/model/data.rs index 2fde405..886f194 100644 --- a/src/model/data.rs +++ b/src/model/data.rs @@ -4,45 +4,98 @@ use crate::error::SshResult; use super::Packet; -/// **byte** -/// byte 标识任意一个 8 位值(8 位字节)。固定长度的数据有时被表示为一个字节数组,写 -/// 作 byte[[n]],其中 n 是数组中字节的数量。 +/// Data Type Representations Used in the SSH Protocols +/// + +/// byte +/// +/// A byte represents an arbitrary 8-bit value (octet). Fixed length +/// data is sometimes represented as an array of bytes, written +/// byte[n], where n is the number of bytes in the array. /// /// **boolean** -/// 一个布尔值作为一个字节存储。0 表示 FALSE,1 表示 TRUE。所有非零的值必须被解释为 -/// TRUE;但是,应用软件禁止储存除 0 和 1 以外的值。 +/// +/// A boolean value is stored as a single byte. The value 0 +/// represents FALSE, and the value 1 represents TRUE. All non-zero +/// values MUST be interpreted as TRUE; however, applications MUST NOT +/// store values other than 0 and 1. /// /// **uint32** -/// 表示一个 32 位无符号整数。按重要性降序(网络字节顺序)储存为 4 个字节。 +/// +/// Represents a 32-bit unsigned integer. Stored as four bytes in the +/// order of decreasing significance (network byte order). For +/// example: the value 699921578 (0x29b7f4aa) is stored as 29 b7 f4 +/// aa. /// /// **uint64** -/// 表示一个 64 位无符号整数。按重要性降序(网络字节顺序)储存为 8 个字节。 +/// +/// Represents a 64-bit unsigned integer. Stored as eight bytes in +/// the order of decreasing significance (network byte order). /// /// **string** -/// 任意长度二进制字符串。字符串用于装载任意二进制数据,包括空字符和 8 位字符。字符 -/// 串被储存为 1 个包含其长度(后续字节数量)的 uint32 以及 0(=空字符串)或作为字符 -/// 串的值的更多的字节。不使用终结符(空字符)。 -/// 字符串也被用来存储文本。在这种情况下,内部名称使用 US-ASCII,可能显示给用户的 -/// 文本使用 ISO-10646 UTF-8。终结符(空字符)一般不应被保存在字符串中。例如, -/// US-ASCII 字符串”testing”被表示为 00 00 00 07 t e s t i n g。UTF-8 映射不 -/// 改变 US-ASCII 字符的编码。 +/// +/// Arbitrary length binary string. Strings are allowed to contain +/// arbitrary binary data, including null characters and 8-bit +/// characters. They are stored as a uint32 containing its length +/// (number of bytes that follow) and zero (= empty string) or more +/// bytes that are the value of the string. Terminating null +/// characters are not used. +/// +/// Strings are also used to store text. In that case, US-ASCII is +/// used for internal names, and ISO-10646 UTF-8 for text that might +/// be displayed to the user. The terminating null character SHOULD +/// NOT normally be stored in the string. For example: the US-ASCII +/// string "testing" is represented as 00 00 00 07 t e s t i n g. The +/// UTF-8 mapping does not alter the encoding of US-ASCII characters. /// /// **mpint** -/// 表示二进制补码(two’s complement)格式的多精度整数,存储为一个字符串,每字节 -/// 8 位,从高位到低位(MSB first)。负数的数据区的首字节的最高位(the most -/// significant bit)的值为 1。对于正数,如果最高位将被置为 1,则必须在前面加一个 -/// 值为 0 的字节。禁止包含值为 0 或 255 的非必要的前导字节(leading bytes)。零必 -/// 须被存储为具有 0 个字节的数据的字符串。 +/// +/// Represents multiple precision integers in two's complement format, +/// stored as a string, 8 bits per byte, MSB first. Negative numbers +/// have the value 1 as the most significant bit of the first byte of +/// the data partition. If the most significant bit would be set for +/// a positive number, the number MUST be preceded by a zero byte. +/// Unnecessary leading bytes with the value 0 or 255 MUST NOT be +/// included. The value zero MUST be stored as a string with zero +/// bytes of data. +/// +/// By convention, a number that is used in modular computations in +/// Z_n SHOULD be represented in the range 0 <= x < n. +/// +/// Examples: +/// +/// value (hex) representation (hex) +/// ----------- -------------------- +/// 0 00 00 00 00 +/// 9a378f9b2e332a7 00 00 00 08 09 a3 78 f9 b2 e3 32 a7 +/// 80 00 00 00 02 00 80 +/// -1234 00 00 00 02 ed cc +/// -deadbeef 00 00 00 05 ff 21 52 41 11 /// /// **name-list** -/// 一个包含逗号分隔的名称列表的字符串。名称列表表示为一个含有其长度(后续字节数量) -/// 的 uint32,加上一个包含 0 或多个逗号分隔的名称的列表。名称的长度禁止为 0,并且禁 -/// 止包含逗号(",")。由于这是一个名称列表,所有被包含的元素都是名称并且必须使用 -/// US-ASCII。上下文可能对名称有附加的限制。例如,名称列表中的名称可能必须是一系列 -/// 有效的算法标识,或一系列[[RFC3066]]语言标识。名称列表中名称的顺序可能有也可能没 -/// 有意义。这取决于使用列表时的上下文。对单个名称或整个列表都禁止使用终结字符(空 -/// 字符)。 /// +/// A string containing a comma-separated list of names. A name-list +/// is represented as a uint32 containing its length (number of bytes +/// that follow) followed by a comma-separated list of zero or more +/// names. A name MUST have a non-zero length, and it MUST NOT +/// contain a comma (","). As this is a list of names, all of the +/// elements contained are names and MUST be in US-ASCII. Context may +/// impose additional restrictions on the names. For example, the +/// names in a name-list may have to be a list of valid algorithm +/// identifiers (see Section 6 below), or a list of [RFC3066] language +/// tags. The order of the names in a name-list may or may not be +/// significant. Again, this depends on the context in which the list +/// is used. Terminating null characters MUST NOT be used, neither +/// for the individual names, nor for the list as a whole. +/// +/// Examples: +/// +/// value representation (hex) +/// ----- -------------------- +/// (), the empty name-list 00 00 00 00 +/// ("zlib") 00 00 00 04 7a 6c 69 62 +/// ("zlib,none") 00 00 00 09 7a 6c 69 62 2c 6e 6f 6e 65 + #[derive(Debug, Clone)] pub(crate) struct Data(Vec); @@ -64,21 +117,20 @@ impl Data { Data(v) } - // 无符号字节 8位 + // write uint8 pub fn put_u8(&mut self, v: u8) -> &mut Self { self.0.push(v); self } - // 32位无符号整型 + // write uint32 pub fn put_u32(&mut self, v: u32) -> &mut Self { let vec = v.to_be_bytes().to_vec(); self.0.extend(&vec); self } - // 字符串型数据 - // 需要计算字符串长度 + // write string pub fn put_str(&mut self, str: &str) -> &mut Self { let v = str.as_bytes(); self.put_u32(v.len() as u32); @@ -86,20 +138,14 @@ impl Data { self } - // 字节数组 - // 需要计算数组长度 + // write [bytes] pub fn put_u8s(&mut self, v: &[u8]) -> &mut Self { self.put_u32(v.len() as u32); self.0.extend(v); self } - // 表示二进制补码(two’s complement)格式的多精度整数 - // 存储为一个字符串,每字节8 位,从高位到低位(MSB first)。 - // 负数的数据区的首字节的最高位(the most significant bit)的值为 1。 - // 对于正数,如果最高位将被置为 1,则必须在前面加一个值为 0 的字节。 - // 禁止包含值为 0 或 255 的非必要的前导字节(leading bytes)。 - // 零必须被存储为具有 0 个字节的数据的字符串。 + // write mpint pub fn put_mpint(&mut self, v: &[u8]) -> Vec { let mut result: Vec = Vec::new(); // 0x80 = 128 @@ -110,23 +156,23 @@ impl Data { self.put_u8s(&result).to_vec() } - // 跳过多少位数据 + // skip `size` pub fn skip(&mut self, size: usize) { self.0.drain(..size); } - // 获取字节 + // get uint8 pub fn get_u8(&mut self) -> u8 { self.0.remove(0) } - // 获取32位无符号整型 + // get uint32 pub fn get_u32(&mut self) -> u32 { let u32_buf = self.0.drain(..4).collect::>(); u32::from_be_bytes(u32_buf.try_into().unwrap()) } - // 获取字节数组 + // get [bytes] pub fn get_u8s(&mut self) -> Vec { let len = self.get_u32() as usize; let bytes = self.0.drain(..len).collect::>(); diff --git a/src/model/flow_control.rs b/src/model/flow_control.rs index 8f1ab34..eba9143 100644 --- a/src/model/flow_control.rs +++ b/src/model/flow_control.rs @@ -3,9 +3,7 @@ use crate::constant::size::LOCAL_WINDOW_SIZE; use crate::constant::size; pub(crate) struct FlowControl { - /// 本地窗口大小 local_window: u32, - /// 远程窗口大小 remote_window: u32, } @@ -24,7 +22,7 @@ impl FlowControl { self.local_window -= recv_len; } else { let drop_len = recv_len - self.local_window; - log::debug!("Recv more than expected, drop len {}", drop_len); + tracing::debug!("Recv more than expected, drop len {}", drop_len); buf.truncate(self.local_window as usize); self.local_window = 0; } diff --git a/src/model/mod.rs b/src/model/mod.rs index 2b84575..9c830fd 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -2,12 +2,14 @@ mod backend_msg; mod data; mod flow_control; mod packet; -mod scp_file; mod sequence; mod terminal; mod timeout; mod u32iter; +#[cfg(feature = "scp")] +mod scp_file; + use std::{ cell::RefCell, rc::Rc, @@ -20,10 +22,12 @@ pub(crate) use backend_msg::*; pub(crate) use data::Data; pub(crate) use flow_control::FlowControl; pub(crate) use packet::{Packet, SecPacket}; -pub(crate) use scp_file::ScpFile; pub(crate) use sequence::Sequence; pub(crate) use timeout::Timeout; pub(crate) use u32iter::U32Iter; +#[cfg(feature = "scp")] +pub(crate) use scp_file::ScpFile; + pub(crate) type RcMut = Rc>; pub(crate) type ArcMut = Arc>; diff --git a/src/model/packet.rs b/src/model/packet.rs index 59b904b..1e4b8d2 100644 --- a/src/model/packet.rs +++ b/src/model/packet.rs @@ -6,40 +6,48 @@ use crate::{client::Client, model::Data}; use super::timeout::Timeout; -/// ## 数据包整体结构 +/// ## Binary Packet Protocol /// -/// ### uint32 `packet_length` +/// /// -/// ### byte `padding_length` +/// uint32 `packet_length` /// -/// ### byte[[n1]] `payload`; n1 = packet_length - padding_length - 1 +/// byte `padding_length` /// -/// ### byte[[n2]] `random padding`; n2 = padding_length +/// byte[[n1]] `payload`; n1 = packet_length - padding_length - 1 /// -/// ### byte[[m]] `mac` (Message Authentication Code - MAC); m = mac_length +/// byte[[n2]] `random padding`; n2 = padding_length +/// +/// byte[[m]] `mac` (Message Authentication Code - MAC); m = mac_length /// /// --- /// /// **packet_length** -/// 以字节为单位的`数据包长度`,不包括`mac`或`packet_length`域自身。 +/// The length of the packet in bytes, not including 'mac' or the 'packet_length' field itself. /// /// /// **padding_length** -/// `random padding`的长度(字节)。 +/// Length of 'random padding' (bytes). /// /// /// **payload** -/// 数据包中有用的内容。如果已经协商了压缩,该域是压缩的。初始时,压缩必须为"none"。 +/// The useful contents of the packet. If compression has been negotiated, this field is compressed. +/// Initially, compression MUST be "none". /// /// /// **random padding** -/// 任意长度的填充,使(packet_length || padding_length || payload || random padding)的总长度是加密分组长度或 8 中较大者的倍数。 -/// 最少必须有 4 字节的填充。 -/// 填充应包含随机字节。填充的最大长度为 255 字节。 -/// +/// Arbitrary-length padding, such that the total length of +/// (packet_length || padding_length || payload || random padding) +/// is a multiple of the cipher block size or 8, whichever is +/// larger. There MUST be at least four bytes of padding. The +/// padding SHOULD consist of random bytes. The maximum amount of +/// padding is 255 bytes. + /// /// **mac** -/// 消息验证码。如果已经协商了消息验证,该域包含 MAC。初始时,MAC 算法必须是"none"。 +/// Message Authentication Code. If message authentication has +/// been negotiated, this field contains the MAC bytes. Initially, +/// the MAC algorithm MUST be "none".。 fn read_with_timeout(stream: &mut S, tm: Option, buf: &mut [u8]) -> SshResult<()> where @@ -132,25 +140,28 @@ pub(crate) struct SecPacket<'a> { } impl<'a> SecPacket<'a> { + fn get_align(bsize: usize) -> i32 { + let bsize = bsize as i32; + if bsize > 8 { + bsize + } else { + 8 + } + } + pub fn write_stream(self, stream: &mut S) -> SshResult<()> where S: Write, { let tm = self.client.get_timeout(); let payload_len = self.payload.len() as u32; - let group_size = self.client.get_encryptor().group_size() as i32; let pad_len = { - let mut pad = payload_len as i32; - if self.client.get_encryptor().is_cp() { - pad += 1; - } else { - pad += 5 + let mut pad = payload_len as i32 + 1; + let block_size = Self::get_align(self.client.get_encryptor().bsize()); + if !self.client.get_encryptor().no_pad() { + pad += 4 } - pad = (-pad) & (group_size - 1); - if pad < group_size { - pad += group_size; - } - pad as u32 + (((-pad) & (block_size - 1)) + block_size) as u32 } as u8; let packet_len = 1 + pad_len as u32 + payload_len; let mut buf = vec![]; @@ -168,14 +179,7 @@ impl<'a> SecPacket<'a> { S: Read, { let tm = client.get_timeout(); - let bsize = { - let bsize = client.get_encryptor().bsize(); - if bsize > 8 { - bsize - } else { - 8 - } - }; + let bsize = Self::get_align(client.get_encryptor().bsize()) as usize; // read the first block let mut first_block = vec![0; bsize]; @@ -208,14 +212,7 @@ impl<'a> SecPacket<'a> { S: Read, { let tm = client.get_timeout(); - let bsize = { - let bsize = client.get_encryptor().bsize(); - if bsize > 8 { - bsize - } else { - 8 - } - }; + let bsize = Self::get_align(client.get_encryptor().bsize()) as usize; // read the first block let mut first_block = vec![0; bsize]; diff --git a/src/model/timeout.rs b/src/model/timeout.rs index 7b880a3..537f5e4 100644 --- a/src/model/timeout.rs +++ b/src/model/timeout.rs @@ -1,5 +1,4 @@ -use crate::error::SshErrorKind; -use crate::{slog::log, SshError, SshResult}; +use crate::{SshError, SshResult}; use std::time::{Duration, Instant}; pub(crate) struct Timeout { @@ -18,8 +17,8 @@ impl Timeout { pub fn test(&self) -> SshResult<()> { if let Some(t) = self.timeout { if self.instant.elapsed() > t { - log::error!("time out."); - Err(SshError::from(SshErrorKind::Timeout)) + tracing::error!("time out."); + Err(SshError::TimeoutError) } else { Ok(()) } diff --git a/src/session/mod.rs b/src/session/mod.rs index abf24f5..29a0e55 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -4,6 +4,7 @@ mod session_local; pub use session_broker::SessionBroker; pub use session_local::LocalSession; +use tracing::*; use std::{ io::{Read, Write}, @@ -15,7 +16,7 @@ use std::{ use crate::{ algorithm::{Compress, Digest, Enc, Kex, Mac, PubKey}, client::Client, - config::{algorithm::AlgList, version::SshVersion, Config}, + config::{algorithm::AlgList, Config}, error::SshResult, model::{Packet, SecPacket}, }; @@ -48,15 +49,15 @@ where } .connect(), SessionState::Version(mut config, mut stream) => { - log::info!("start for version negotiation."); + info!("start for version negotiation."); // Receive the server version - let version = SshVersion::from(&mut stream, config.timeout)?; + config + .ver + .read_server_version(&mut stream, config.timeout)?; // Version validate - version.validate()?; + config.ver.validate()?; // Send Client version - SshVersion::write(&mut stream)?; - // Store the version info - config.ver = version; + config.ver.send_our_version(&mut stream)?; // from now on // each step of the interaction is subject to the ssh constraints on the packet @@ -161,7 +162,7 @@ impl SessionBuilder { { match self.config.auth.private_key(private_key) { Ok(_) => (), - Err(e) => log::error!( + Err(e) => error!( "Parse private key from string: {}, will fallback to password authentication", e ), @@ -175,7 +176,7 @@ impl SessionBuilder { { match self.config.auth.private_key_path(key_path) { Ok(_) => (), - Err(e) => log::error!( + Err(e) => error!( "Parse private key from file: {}, will fallback to password authentication", e ), diff --git a/src/session/session_broker.rs b/src/session/session_broker.rs index 08dbd32..2732f87 100644 --- a/src/session/session_broker.rs +++ b/src/session/session_broker.rs @@ -8,19 +8,22 @@ use std::{ thread::spawn, }; -use log::info; +use tracing::*; use crate::{ algorithm::Digest, channel::{BackendChannel, ExecBroker}, client::Client, config::algorithm::AlgList, - constant::{size, ssh_msg_code, ssh_str}, + constant::{size, ssh_channel_fail_code, ssh_connection_code, ssh_str, ssh_transport_code}, error::{SshError, SshResult}, model::{ArcMut, BackendResp, BackendRqst, Data, Packet, SecPacket, U32Iter}, - ChannelBroker, ScpBroker, ShellBrocker, TerminalSize, + ChannelBroker, ShellBrocker, TerminalSize, }; +#[cfg(feature = "scp")] +use crate::ScpBroker; + pub struct SessionBroker { channel_num: ArcMut, snd: Sender, @@ -34,7 +37,7 @@ impl SessionBroker { let (rqst_snd, rqst_rcv) = mpsc::channel(); spawn(move || { if let Err(e) = client_loop(client, stream, rqst_rcv) { - log::error!("Error {} occurred when running backend task", e.to_string()) + error!("Error {:?} occurred when running backend task", e) } }); Self { @@ -46,7 +49,7 @@ impl SessionBroker { /// close the backend session and consume the session broker itself /// pub fn close(self) { - log::info!("Client close"); + info!("Client close"); drop(self) } @@ -59,6 +62,7 @@ impl SessionBroker { /// open a [ScpBroker] channel which can download/upload files/directories /// + #[cfg(feature = "scp")] pub fn open_scp(&mut self) -> SshResult { let channel = self.open_channel()?; channel.scp() @@ -70,7 +74,7 @@ impl SessionBroker { self.open_shell_terminal(TerminalSize::from(80, 24)) } - /// open a [LocalShell] channel + /// open a [ShellBrocker] channel /// /// custom terminal dimensions /// @@ -89,7 +93,7 @@ impl SessionBroker { // open channel request let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_OPEN) + data.put_u8(ssh_connection_code::CHANNEL_OPEN) .put_str(ssh_str::SESSION) .put_u32(client_id) .put_u32(size::LOCAL_WINDOW_SIZE) @@ -107,7 +111,7 @@ impl SessionBroker { resp_recv, self.snd.clone(), )), - BackendResp::Fail(msg) => Err(SshError::from(msg)), + BackendResp::Fail(msg) => Err(SshError::GeneralError(msg)), _ => unreachable!(), }, Err(e) => Err(e.into()), @@ -132,7 +136,7 @@ where } else if let Ok(rqst) = try_recv { match rqst { BackendRqst::OpenChannel(id, data, sender) => { - log::info!("try open channel {}.", id); + info!("try open channel {}.", id); data.pack(&mut client).write_stream(&mut stream)?; @@ -142,17 +146,17 @@ where BackendRqst::Data(id, data) => { let channel = channels.get_mut(&id).unwrap(); - log::trace!("Channel {} send {} data", id, data.len()); + trace!("Channel {} send {} data", id, data.len()); channel.send_data(data, &mut client, &mut stream)?; } BackendRqst::Command(id, data) => { let channel = channels.get_mut(&id).unwrap(); - log::trace!("Channel {} send control data", id); + trace!("Channel {} send control data", id); channel.send(data, &mut client, &mut stream)?; } BackendRqst::CloseChannel(id, data) => { - log::info!("try close channel {}.", id); + info!("try close channel {}.", id); let channel = channels.get_mut(&id).unwrap(); channel.send(data, &mut client, &mut stream)?; @@ -170,7 +174,7 @@ where match message_code { // Successfully open a channel - ssh_msg_code::SSH_MSG_CHANNEL_OPEN_CONFIRMATION => { + ssh_connection_code::CHANNEL_OPEN_CONFIRMATION => { let client_channel_no = data.get_u32(); let server_channel_no = data.get_u32(); let remote_window_size = data.get_u32(); @@ -195,14 +199,14 @@ where .is_none()) } /* - byte SSH_MSG_CHANNEL_OPEN_FAILURE + byte CHANNEL_OPEN_FAILURE uint32 recipient channel uint32 reason code - string description,ISO-10646 UTF-8 编码[RFC3629] + string description,ISO-10646 UTF-8 [RFC3629] string language tag,[RFC3066] */ // Fail to open a channel - ssh_msg_code::SSH_MSG_CHANNEL_OPEN_FAILURE => { + ssh_connection_code::CHANNEL_OPEN_FAILURE => { // client channel number let id = data.get_u32(); @@ -217,39 +221,39 @@ where data.get_u8s(); let err_msg = match code { - ssh_msg_code::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED => { - format!("SSH_OPEN_ADMINISTRATIVELY_PROHIBITED: {description}") + ssh_channel_fail_code::ADMINISTRATIVELY_PROHIBITED => { + format!("ADMINISTRATIVELY_PROHIBITED: {description}") } - ssh_msg_code::SSH_OPEN_CONNECT_FAILED => { - format!("SSH_OPEN_CONNECT_FAILED: {description}") + ssh_channel_fail_code::CONNECT_FAILED => { + format!("CONNECT_FAILED: {description}") } - ssh_msg_code::SSH_OPEN_UNKNOWN_CHANNEL_TYPE => { - format!("SSH_OPEN_UNKNOWN_CHANNEL_TYPE: {description}") + ssh_channel_fail_code::UNKNOWN_CHANNEL_TYPE => { + format!("UNKNOWN_CHANNEL_TYPE: {description}") } - ssh_msg_code::SSH_OPEN_RESOURCE_SHORTAGE => { - format!("SSH_OPEN_RESOURCE_SHORTAGE: {description}") + ssh_channel_fail_code::RESOURCE_SHORTAGE => { + format!("RESOURCE_SHORTAGE: {description}") } _ => description, }; sender.unwrap().send(BackendResp::Fail(err_msg))?; } - ssh_msg_code::SSH_MSG_KEXINIT => { + ssh_transport_code::KEXINIT => { data.insert(0, message_code); let mut digest = Digest::new(); digest.hash_ctx.set_i_s(&data); let server_algs = AlgList::unpack((data, &mut client).into())?; client.key_agreement(&mut stream, server_algs, &mut digest)?; } - ssh_msg_code::SSH_MSG_CHANNEL_DATA => { + ssh_connection_code::CHANNEL_DATA => { let id = data.get_u32(); - log::trace!("Channel {id} get {} data", data.len()); + trace!("Channel {id} get {} data", data.len()); let channel = channels.get_mut(&id).unwrap(); channel.recv(data, &mut client, &mut stream)?; } - ssh_msg_code::SSH_MSG_CHANNEL_EXTENDED_DATA => { + ssh_connection_code::CHANNEL_EXTENDED_DATA => { let id = data.get_u32(); let data_type = data.get_u32(); - log::trace!( + trace!( "Channel {id} get {} extended data, type {data_type}", data.len(), ); @@ -257,7 +261,7 @@ where channel.recv(data, &mut client, &mut stream)?; } // flow_control msg - ssh_msg_code::SSH_MSG_CHANNEL_WINDOW_ADJUST => { + ssh_connection_code::CHANNEL_WINDOW_ADJUST => { // client channel number let id = data.get_u32(); // to_add @@ -265,43 +269,43 @@ where let channel = channels.get_mut(&id).unwrap(); channel.recv_window_adjust(rws, &mut client, &mut stream)?; } - ssh_msg_code::SSH_MSG_CHANNEL_CLOSE => { + ssh_connection_code::CHANNEL_CLOSE => { let id = data.get_u32(); - log::info!("Channel {} recv close", id); + info!("Channel {} recv close", id); let channel = channels.get_mut(&id).unwrap(); channel.remote_close()?; if channel.is_close() { channels.remove(&id); } } - ssh_msg_code::SSH_MSG_GLOBAL_REQUEST => { + ssh_connection_code::GLOBAL_REQUEST => { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_REQUEST_FAILURE); + data.put_u8(ssh_connection_code::REQUEST_FAILURE); data.pack(&mut client).write_stream(&mut stream)?; continue; } - x @ ssh_msg_code::SSH_MSG_CHANNEL_EOF => { - log::debug!("Currently ignore message {}", x); + x @ ssh_connection_code::CHANNEL_EOF => { + debug!("Currently ignore message {}", x); } - x @ ssh_msg_code::SSH_MSG_CHANNEL_REQUEST => { - log::debug!("Currently ignore message {}", x); + x @ ssh_connection_code::CHANNEL_REQUEST => { + debug!("Currently ignore message {}", x); } - _x @ ssh_msg_code::SSH_MSG_CHANNEL_SUCCESS => { + _x @ ssh_connection_code::CHANNEL_SUCCESS => { let id = data.get_u32(); - log::trace!("Channel {} control success", id); + trace!("Channel {} control success", id); let channel = channels.get_mut(&id).unwrap(); channel.success()? } - ssh_msg_code::SSH_MSG_CHANNEL_FAILURE => { + ssh_connection_code::CHANNEL_FAILURE => { let id = data.get_u32(); - log::trace!("Channel {} control failed", id); + trace!("Channel {} control failed", id); let channel = channels.get_mut(&id).unwrap(); channel.failed()? } x => { - log::debug!("Currently ignore message {}", x); + debug!("Currently ignore message {}", x); } } } diff --git a/src/session/session_local.rs b/src/session/session_local.rs index d324913..6e5d11e 100644 --- a/src/session/session_local.rs +++ b/src/session/session_local.rs @@ -4,13 +4,16 @@ use std::{ rc::Rc, time::Duration, }; +use tracing::*; -use crate::model::TerminalSize; +#[cfg(feature = "scp")] +use crate::channel::LocalScp; use crate::{ - channel::{LocalChannel, LocalExec, LocalScp, LocalShell}, + channel::{LocalChannel, LocalExec, LocalShell}, client::Client, - constant::{size, ssh_msg_code, ssh_str}, + constant::{size, ssh_channel_fail_code, ssh_connection_code, ssh_str}, error::{SshError, SshResult}, + model::TerminalSize, model::{Data, Packet, RcMut, SecPacket, U32Iter}, }; @@ -38,7 +41,7 @@ where /// close the local session and consume it /// pub fn close(self) { - log::info!("Client close"); + info!("Client close"); drop(self) } @@ -58,6 +61,7 @@ where /// open a [LocalScp] channel which can download/upload files/directories /// + #[cfg(feature = "scp")] pub fn open_scp(&mut self) -> SshResult> { let channel = self.open_channel()?; channel.scp() @@ -87,7 +91,7 @@ where /// need call `.exec()`, `.shell()`, `.scp()` and so on to convert it to a specific channel /// pub fn open_channel(&mut self) -> SshResult> { - log::info!("channel opened."); + info!("channel opened."); let client_channel_no = self.channel_num.next().unwrap(); self.send_open_channel(client_channel_no)?; @@ -102,10 +106,10 @@ where )) } - // 本地请求远程打开通道 + // open channel request fn send_open_channel(&mut self, client_channel_no: u32) -> SshResult<()> { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_CHANNEL_OPEN) + data.put_u8(ssh_connection_code::CHANNEL_OPEN) .put_str(ssh_str::SESSION) .put_u32(client_channel_no) .put_u32(size::LOCAL_WINDOW_SIZE) @@ -114,7 +118,7 @@ where .write_stream(&mut *self.stream.borrow_mut()) } - // 远程回应是否可以打开通道 + // get the response of the channel request fn receive_open_channel(&mut self) -> SshResult<(u32, u32)> { loop { let mut data = Data::unpack(SecPacket::from_stream( @@ -124,62 +128,59 @@ where let message_code = data.get_u8(); match message_code { - // 打开请求通过 - ssh_msg_code::SSH_MSG_CHANNEL_OPEN_CONFIRMATION => { - // 接收方通道号 + // Successfully open a channel + ssh_connection_code::CHANNEL_OPEN_CONFIRMATION => { data.get_u32(); - // 发送方通道号 let server_channel_no = data.get_u32(); - // 远程初始窗口大小 let remote_window_size = data.get_u32(); - // 远程的最大数据包大小, 暂时不需要 + // remote packet size, currently don't need it data.get_u32(); return Ok((server_channel_no, remote_window_size)); } /* - byte SSH_MSG_CHANNEL_OPEN_FAILURE + byte CHANNEL_OPEN_FAILURE uint32 recipient channel uint32 reason code - string description,ISO-10646 UTF-8 编码[RFC3629] + string description,ISO-10646 UTF-8 [RFC3629] string language tag,[RFC3066] */ - // 打开请求拒绝 - ssh_msg_code::SSH_MSG_CHANNEL_OPEN_FAILURE => { + // Fail to open a channel + ssh_connection_code::CHANNEL_OPEN_FAILURE => { data.get_u32(); - // 失败原因码 + // error code let code = data.get_u32(); - // 消息详情 默认utf-8编码 + // error detail: By default is utf-8 let description = String::from_utf8(data.get_u8s()).unwrap_or_else(|_| String::from("error")); - // language tag 暂不处理, 应该是 en-US + // language tag, assume to be en-US data.get_u8s(); let err_msg = match code { - ssh_msg_code::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED => { - format!("SSH_OPEN_ADMINISTRATIVELY_PROHIBITED: {}", description) + ssh_channel_fail_code::ADMINISTRATIVELY_PROHIBITED => { + format!("ADMINISTRATIVELY_PROHIBITED: {}", description) } - ssh_msg_code::SSH_OPEN_CONNECT_FAILED => { - format!("SSH_OPEN_CONNECT_FAILED: {}", description) + ssh_channel_fail_code::CONNECT_FAILED => { + format!("CONNECT_FAILED: {}", description) } - ssh_msg_code::SSH_OPEN_UNKNOWN_CHANNEL_TYPE => { - format!("SSH_OPEN_UNKNOWN_CHANNEL_TYPE: {}", description) + ssh_channel_fail_code::UNKNOWN_CHANNEL_TYPE => { + format!("UNKNOWN_CHANNEL_TYPE: {}", description) } - ssh_msg_code::SSH_OPEN_RESOURCE_SHORTAGE => { - format!("SSH_OPEN_RESOURCE_SHORTAGE: {}", description) + ssh_channel_fail_code::RESOURCE_SHORTAGE => { + format!("RESOURCE_SHORTAGE: {}", description) } _ => description, }; - return Err(SshError::from(err_msg)); + return Err(SshError::GeneralError(err_msg)); } - ssh_msg_code::SSH_MSG_GLOBAL_REQUEST => { + ssh_connection_code::GLOBAL_REQUEST => { let mut data = Data::new(); - data.put_u8(ssh_msg_code::SSH_MSG_REQUEST_FAILURE); + data.put_u8(ssh_connection_code::REQUEST_FAILURE); data.pack(&mut self.client.borrow_mut()) .write_stream(&mut *self.stream.borrow_mut())?; continue; } x => { - log::debug!("Ignore ssh msg {}", x); + debug!("Ignore ssh msg {}", x); continue; } } diff --git a/src/slog.rs b/src/slog.rs deleted file mode 100644 index dc0ce40..0000000 --- a/src/slog.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub use log; - -use log::{LevelFilter, Log, Metadata, Record}; - -pub(crate) static SLOG: Slog = Slog; - -pub struct Slog; - -impl Slog { - fn init(level: LevelFilter) { - if let Err(e) = log::set_logger(&SLOG) { - log::error!( - "initialization log error, the error information is: {:?}", - e - ); - return; - } - log::set_max_level(level); - } - - pub fn default() { - Slog::init(LevelFilter::Info) - } - - pub fn debug() { - Slog::init(LevelFilter::Trace) - } -} - -impl Log for Slog { - fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() != LevelFilter::Off - } - - fn log(&self, record: &Record) { - if self.enabled(record.metadata()) { - println!("[SSH]-[{}]: {}", record.level(), record.args()); - } - } - - fn flush(&self) {} -} diff --git a/src/util.rs b/src/util.rs index 593e8a2..604f7d4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,32 +1,19 @@ -use crate::error::{SshError, SshResult}; -use crate::slog::log; +use crate::error::SshResult; use rand::rngs::OsRng; use rand::Rng; + +#[cfg(feature = "scp")] +use crate::error::SshError; +#[cfg(feature = "scp")] use std::{ path::Path, str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; -pub(crate) fn from_utf8(v: Vec) -> SshResult { - match String::from_utf8(v) { - Ok(v) => Ok(v), - Err(e) => { - let err_msg = format!("Byte to utf8 string error, error info: {:?}", e); - log::error!("{}", err_msg); - Err(SshError::from(err_msg)) - } - } -} - +#[cfg(feature = "scp")] pub(crate) fn sys_time_to_secs(time: SystemTime) -> SshResult { - match time.duration_since(UNIX_EPOCH) { - Ok(t) => Ok(t.as_secs()), - Err(e) => Err(SshError::from(format!( - "SystemTimeError difference: {:?}", - e.duration() - ))), - } + Ok(time.duration_since(UNIX_EPOCH)?.as_secs()) } // a random cookie @@ -36,29 +23,24 @@ pub(crate) fn cookie() -> Vec { } pub(crate) fn vec_u8_to_string(v: Vec, pat: &str) -> SshResult> { - let result = from_utf8(v)?; + let result = String::from_utf8(v)?; let r: Vec<&str> = result.split(pat).collect(); let mut vec = vec![]; for x in r { - vec.push(x.to_string()) + vec.push(x.to_owned()) } Ok(vec) } -pub(crate) fn str_to_i64(v: &str) -> SshResult { - match i64::from_str(v) { - Ok(v) => Ok(v), - Err(_) => Err(SshError::from("str to i64 error")), - } -} - +#[cfg(feature = "scp")] pub(crate) fn check_path(path: &Path) -> SshResult<()> { if path.to_str().is_none() { - return Err(SshError::from("invalid path.")); + return Err(SshError::InvalidScpFilePath); } Ok(()) } +#[cfg(feature = "scp")] pub(crate) fn file_time(v: Vec) -> SshResult<(i64, i64)> { let mut t = vec![]; for x in v { @@ -68,7 +50,7 @@ pub(crate) fn file_time(v: Vec) -> SshResult<(i64, i64)> { t.push(x) } let a = t.len() / 2; - let ct = from_utf8(t[..(a - 1)].to_vec())?; - let ut = from_utf8(t[a..(t.len() - 1)].to_vec())?; - Ok((str_to_i64(&ct)?, str_to_i64(&ut)?)) + let ct = String::from_utf8(t[..(a - 1)].to_vec())?; + let ut = String::from_utf8(t[a..(t.len() - 1)].to_vec())?; + Ok((i64::from_str(&ct)?, i64::from_str(&ut)?)) } diff --git a/tests/algorithms.rs b/tests/algorithms.rs index 5d2786b..fce1792 100644 --- a/tests/algorithms.rs +++ b/tests/algorithms.rs @@ -1,6 +1,6 @@ mod test { use paste::paste; - use ssh_rs::{algorithm, ssh}; + use ssh::algorithm; use std::env; macro_rules! env_getter { @@ -16,12 +16,12 @@ mod test { env_getter!(server, "127.0.0.1:22"); env_getter!(pem_rsa, "./rsa_old"); - #[cfg(feature = "dangerous-rsa-sha1")] + #[cfg(feature = "deprecated-rsa-sha1")] #[test] fn test_ssh_rsa() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::EcdhSha2Nistrp256) .add_pubkey_algorithms(algorithm::PubKey::SshRsa) .add_enc_algorithms(algorithm::Enc::Chacha20Poly1305Openssh) @@ -33,12 +33,12 @@ mod test { session.close(); } - #[cfg(feature = "dangerous-algorithms")] + #[cfg(feature = "deprecated-algorithms")] #[test] fn test_dh_group1() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup1Sha1) .add_pubkey_algorithms(algorithm::PubKey::SshRsa) .add_enc_algorithms(algorithm::Enc::Aes128Ctr) @@ -54,7 +54,7 @@ mod test { fn test_curve25519_sha256() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::Curve25519Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Aes128Ctr) @@ -70,7 +70,7 @@ mod test { fn test_ecdh_sha2_nistp256() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::EcdhSha2Nistrp256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Aes128Ctr) @@ -82,11 +82,12 @@ mod test { session.close(); } + #[cfg(feature = "deprecated-dh-group1-sha1")] #[test] fn test_dh_group14_sha1() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha1) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Aes128Ctr) @@ -102,7 +103,7 @@ mod test { fn test_dh_group14_sha256() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Aes128Ctr) @@ -115,10 +116,10 @@ mod test { } #[test] - fn test_aes192() { + fn test_aes192_ctr() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Aes192Ctr) @@ -131,10 +132,10 @@ mod test { } #[test] - fn test_aes256() { + fn test_aes256_ctr() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Aes256Ctr) @@ -146,11 +147,79 @@ mod test { session.close(); } + #[cfg(feature = "deprecated-aes-cbc")] + #[test] + fn test_aes128_cbc() { + let session = ssh::create_session_without_default() + .username(&get_username()) + .private_key_path(get_pem_rsa()) + .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) + .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) + .add_enc_algorithms(algorithm::Enc::Aes128Cbc) + .add_compress_algorithms(algorithm::Compress::None) + .add_mac_algortihms(algorithm::Mac::HmacSha1) + .connect(get_server()) + .unwrap() + .run_local(); + session.close(); + } + + #[cfg(feature = "deprecated-aes-cbc")] + #[test] + fn test_aes192_cbc() { + let session = ssh::create_session_without_default() + .username(&get_username()) + .private_key_path(get_pem_rsa()) + .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) + .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) + .add_enc_algorithms(algorithm::Enc::Aes192Cbc) + .add_compress_algorithms(algorithm::Compress::None) + .add_mac_algortihms(algorithm::Mac::HmacSha1) + .connect(get_server()) + .unwrap() + .run_local(); + session.close(); + } + + #[cfg(feature = "deprecated-aes-cbc")] + #[test] + fn test_aes256_cbc() { + let session = ssh::create_session_without_default() + .username(&get_username()) + .private_key_path(get_pem_rsa()) + .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) + .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) + .add_enc_algorithms(algorithm::Enc::Aes256Cbc) + .add_compress_algorithms(algorithm::Compress::None) + .add_mac_algortihms(algorithm::Mac::HmacSha1) + .connect(get_server()) + .unwrap() + .run_local(); + session.close(); + } + + #[cfg(feature = "deprecated-des-cbc")] + #[test] + fn test_3des_cbc() { + let session = ssh::create_session_without_default() + .username(&get_username()) + .private_key_path(get_pem_rsa()) + .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) + .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) + .add_enc_algorithms(algorithm::Enc::TripleDesCbc) + .add_compress_algorithms(algorithm::Compress::None) + .add_mac_algortihms(algorithm::Mac::HmacSha1) + .connect(get_server()) + .unwrap() + .run_local(); + session.close(); + } + #[test] fn test_chacha20_poly1305() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Chacha20Poly1305Openssh) @@ -166,7 +235,7 @@ mod test { fn test_hmac_sha256() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Chacha20Poly1305Openssh) @@ -182,7 +251,7 @@ mod test { fn test_hmac_sha512() { let session = ssh::create_session_without_default() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .add_kex_algorithms(algorithm::Kex::DiffieHellmanGroup14Sha256) .add_pubkey_algorithms(algorithm::PubKey::RsaSha2_256) .add_enc_algorithms(algorithm::Enc::Chacha20Poly1305Openssh) diff --git a/tests/connect.rs b/tests/connect.rs index 2fe0e8e..2487725 100644 --- a/tests/connect.rs +++ b/tests/connect.rs @@ -1,6 +1,6 @@ mod tests { use paste::paste; - use ssh_rs::ssh; + use std::env; macro_rules! env_getter { @@ -45,7 +45,7 @@ mod tests { fn test_rsa_old() { let session = ssh::create_session() .username(&get_username()) - .private_key_path(&get_pem_rsa()) + .private_key_path(get_pem_rsa()) .connect(get_server()) .unwrap() .run_local(); @@ -56,7 +56,7 @@ mod tests { fn test_rsa_new() { let session = ssh::create_session() .username(&get_username()) - .private_key_path(&get_openssh_rsa()) + .private_key_path(get_openssh_rsa()) .connect(get_server()) .unwrap() .run_local(); @@ -67,7 +67,7 @@ mod tests { fn test_ed25519() { let session = ssh::create_session() .username(&get_username()) - .private_key_path(&get_ed25519()) + .private_key_path(get_ed25519()) .connect(get_server()) .unwrap() .run_local(); diff --git a/tests/exec.rs b/tests/exec.rs index 5751697..ed54005 100644 --- a/tests/exec.rs +++ b/tests/exec.rs @@ -1,6 +1,6 @@ mod tests { use paste::paste; - use ssh_rs::ssh; + use std::env; macro_rules! env_getter { diff --git a/tests/scp.rs b/tests/scp.rs index 344e7e9..0d6618e 100644 --- a/tests/scp.rs +++ b/tests/scp.rs @@ -1,7 +1,7 @@ +#[cfg(feature = "scp")] mod test { use paste::paste; - use ssh_rs::ssh; - use ssh_rs::LocalSession; + use ssh::{self, LocalSession}; use std::env; fn remove_file(filename: &str) { diff --git a/version b/version index 87a0871..60a2d3e 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.3.3 \ No newline at end of file +0.4.0 \ No newline at end of file