mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f2be65a6c | ||
|
|
a5a745e444 | ||
|
|
7ee142314e | ||
|
|
c412d98ec7 | ||
|
|
d7e5eacd79 | ||
|
|
367fb235f6 | ||
|
|
d68d63b978 | ||
|
|
23ca2baa8c | ||
|
|
ac02928e69 | ||
|
|
cb20589b01 | ||
|
|
1acbf89717 | ||
|
|
08d8a3621c | ||
|
|
0192b86422 | ||
|
|
0e4caaecfd | ||
|
|
215927d432 | ||
|
|
eee08bd623 | ||
|
|
76fdd9864c | ||
|
|
350443ec99 | ||
|
|
928fc1b450 | ||
|
|
03e1bf53d0 | ||
|
|
9330025d07 | ||
|
|
bf56a269e0 | ||
|
|
0393c1a850 | ||
|
|
69ece00ae2 | ||
|
|
97656536d4 | ||
|
|
94b78d85ef | ||
|
|
ba3a888d26 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,3 +18,6 @@
|
||||
*.rpm
|
||||
*.deb
|
||||
dist/pkgs/arch/*.tar.gz
|
||||
|
||||
# Macos
|
||||
.DS_Store
|
||||
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
- [Changelog](#changelog)
|
||||
- [0.3.1](#031)
|
||||
- [0.3.0](#030)
|
||||
- [0.2.0](#020)
|
||||
- [0.1.3](#013)
|
||||
@@ -10,6 +11,19 @@
|
||||
|
||||
---
|
||||
|
||||
## 0.3.1
|
||||
|
||||
Released on 18/01/2021
|
||||
|
||||
- **Keyring to store secrets**
|
||||
- On both MacOS and Windows, the secret used to encrypt passwords in bookmarks it is now store in the OS secret vault. This provides much more security to store the password
|
||||
- Enhancements:
|
||||
- Added connection timeout to 30 seconds to SFTP/SCP clients and improved name lookup system.
|
||||
- Bugfix:
|
||||
- Solved index in explorer files list which was no more kept after 0.3.0
|
||||
- SCP file transfer: fixed possible wrong file size when sending file, due to a possible incoherent size between the file explorer and the actual file size.
|
||||
- Breaking changes: on **MacOS / Windows systems only**, the password you saved for bookmarks won't be working anymore if you have support for the keyring crate. Because of the migration to keyring, the previously used secret hasn't been migrated to the storage, instead a new secret will be used. To solve this, just save the bookmark again with the password.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
Released on 10/01/2021
|
||||
|
||||
207
Cargo.lock
generated
207
Cargo.lock
generated
@@ -1,5 +1,16 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
|
||||
dependencies = [
|
||||
"aes-soft",
|
||||
"aesni",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-soft"
|
||||
version = "0.6.4"
|
||||
@@ -10,6 +21,16 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aesni"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.15"
|
||||
@@ -205,16 +226,32 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.7.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"core-foundation-sys 0.8.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.2"
|
||||
@@ -272,6 +309,25 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4a0c10ea61042b7555729ab0608727bbbb06ce709c11e6047cfa4e10f6d052d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-helper"
|
||||
version = "0.3.10"
|
||||
@@ -423,6 +479,26 @@ dependencies = [
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
@@ -452,6 +528,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bcd64f48199f69993c705fd2f76882e53969db93bc6345021bc8bb6462a9ffa"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"secret-service",
|
||||
"security-framework 0.4.4",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -594,8 +682,8 @@ dependencies = [
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"security-framework 2.0.0",
|
||||
"security-framework-sys 2.0.0",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
@@ -608,6 +696,40 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
@@ -618,6 +740,29 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
@@ -908,6 +1053,35 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d752040301c251d653aa740dec847e95767ce312cfc469bee85eb13cbf81d8a"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"block-modes",
|
||||
"dbus",
|
||||
"hkdf",
|
||||
"lazy_static",
|
||||
"num",
|
||||
"rand 0.7.3",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation 0.7.0",
|
||||
"core-foundation-sys 0.7.0",
|
||||
"libc",
|
||||
"security-framework-sys 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.0.0"
|
||||
@@ -915,10 +1089,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"core-foundation 0.9.1",
|
||||
"core-foundation-sys 0.8.2",
|
||||
"libc",
|
||||
"security-framework-sys 2.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.7.0",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -927,7 +1111,7 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"core-foundation-sys 0.8.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1019,6 +1203,12 @@ dependencies = [
|
||||
"parking_lot 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.58"
|
||||
@@ -1046,7 +1236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termscp"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytesize",
|
||||
@@ -1058,6 +1248,7 @@ dependencies = [
|
||||
"ftp4",
|
||||
"getopts",
|
||||
"hostname",
|
||||
"keyring",
|
||||
"lazy_static",
|
||||
"magic-crypt",
|
||||
"rand 0.8.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "termscp"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Christian Visintin"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0"
|
||||
@@ -11,6 +11,7 @@ homepage = "https://github.com/veeso/termscp"
|
||||
repository = "https://github.com/veeso/termscp"
|
||||
documentation = "https://docs.rs/termscp"
|
||||
readme = "README.md"
|
||||
include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -42,6 +43,9 @@ whoami = "1.0.1"
|
||||
[target.'cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))'.dependencies]
|
||||
users = "0.11.0"
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
|
||||
keyring = "0.10.1"
|
||||
|
||||
[[bin]]
|
||||
name = "termscp"
|
||||
path = "src/main.rs"
|
||||
|
||||
29
README.md
29
README.md
@@ -1,12 +1,12 @@
|
||||
# TermSCP
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0) [](https://github.com/veeso/termscp) [](https://crates.io/crates/termscp) [](https://crates.io/crates/termscp) [](https://docs.rs/termscp)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0) [](https://github.com/veeso/termscp) [](https://crates.io/crates/termscp) [](https://crates.io/crates/termscp) [](https://docs.rs/termscp)
|
||||
|
||||
[](https://github.com/veeso/termscp/actions) [](https://github.com/veeso/termscp/actions) [](https://github.com/veeso/termscp/actions) [](https://codecov.io/gh/veeso/termscp)
|
||||
|
||||
~ Basically, WinSCP on a terminal ~
|
||||
Developed by Christian Visintin
|
||||
Current version: 0.3.0 (10/01/2021)
|
||||
Current version: 0.3.1 (18/01/2021)
|
||||
|
||||
---
|
||||
|
||||
@@ -84,10 +84,17 @@ If you want to contribute to this project, don't forget to check out our contrib
|
||||
cargo install termscp
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Linux
|
||||
- pkg-config
|
||||
- libssh2
|
||||
- openssl
|
||||
|
||||
### Deb package 📦
|
||||
|
||||
Get `deb` package from [HERE](https://github.com/veeso/termscp/releases/latest/download/termscp_0.3.0_amd64.deb)
|
||||
or run `wget https://github.com/veeso/termscp/releases/latest/download/termscp_0.3.0_amd64.deb`
|
||||
Get `deb` package from [HERE](https://github.com/veeso/termscp/releases/latest/download/termscp_0.3.1_amd64.deb)
|
||||
or run `wget https://github.com/veeso/termscp/releases/latest/download/termscp_0.3.1_amd64.deb`
|
||||
|
||||
then install through dpkg:
|
||||
|
||||
@@ -99,8 +106,8 @@ gdebi termscp_*.deb
|
||||
|
||||
### RPM package 📦
|
||||
|
||||
Get `rpm` package from [HERE](https://github.com/veeso/termscp/releases/latest/download/termscp-0.3.0-1.x86_64.rpm)
|
||||
or run `wget https://github.com/veeso/termscp/releases/latest/download/termscp-0.3.0-1.x86_64.rpm`
|
||||
Get `rpm` package from [HERE](https://github.com/veeso/termscp/releases/latest/download/termscp-0.3.1-1.x86_64.rpm)
|
||||
or run `wget https://github.com/veeso/termscp/releases/latest/download/termscp-0.3.1-1.x86_64.rpm`
|
||||
|
||||
then install through rpm:
|
||||
|
||||
@@ -126,7 +133,7 @@ Start PowerShell as administrator and run
|
||||
choco install termscp
|
||||
```
|
||||
|
||||
Alternatively you can download the ZIP file from [HERE](https://github.com/veeso/termscp/releases/latest/download/termscp.0.3.0.nupkg)
|
||||
Alternatively you can download the ZIP file from [HERE](https://github.com/veeso/termscp/releases/latest/download/termscp.0.3.1.nupkg)
|
||||
|
||||
and then with PowerShell started with administrator previleges, run:
|
||||
|
||||
@@ -226,7 +233,12 @@ If you go to [gallery](#gallery-), there is a GIF showing how bookmarks work
|
||||
### Are my passwords Safe 😈
|
||||
|
||||
Well, kinda.
|
||||
As said before, bookmarks are saved in your configuration directory along with passwords. Passwords are obviously not plain text, they are encrypted with **AES-128**. Does this make them safe? Well, no, the key used to encrypt your passwords is generated at the first launch of termscp and stored on your drive. So it's still possible to retrieve the key to decrypt passwords. Luckily, the location of the key guarantees your key can't be read by users different from yours, but yeah, I still wouldn't save the password for a server exposed on the internet 😉.
|
||||
As said before, bookmarks are saved in your configuration directory along with passwords. Passwords are obviously not plain text, they are encrypted with **AES-128**. Does this make them safe? Well, depends on your operating system:
|
||||
|
||||
On Windows and MacOS the passwords are (if possible, but should be) in respectively the Windows Vault and the Keychain. This is actually super-safe and is directly managed by your operating system.
|
||||
|
||||
On Linux and BSD, on the other hand the key used to encrypt your passwords is stored on your drive. So it's still possible to retrieve the key to decrypt passwords. Luckily, the location of the key guarantees your key can't be read by users different from yours, but yeah, I still wouldn't save the password for a server exposed on the internet 😉.
|
||||
Actually [keyring-rs](https://github.com/hwchen/keyring-rs), supports Linux, but for different reasons I preferred not to make it available for this configuration. If you want to read more about my decision read [this issue](https://github.com/veeso/termscp/issues/2), while if you think this might have been implemented differently feel free to open an issue with your proposal.
|
||||
|
||||
---
|
||||
|
||||
@@ -350,6 +362,7 @@ TermSCP is powered by these aweseome projects:
|
||||
- [bytesize](https://github.com/hyunsik/bytesize)
|
||||
- [crossterm](https://github.com/crossterm-rs/crossterm)
|
||||
- [edit](https://github.com/milkey-mouse/edit)
|
||||
- [keyring-rs](https://github.com/hwchen/keyring-rs)
|
||||
- [rpassword](https://github.com/conradkleinespel/rpassword)
|
||||
- [rust-ftp](https://github.com/mattnenterprise/rust-ftp)
|
||||
- [ssh2-rs](https://github.com/alexcrichton/ssh2-rs)
|
||||
|
||||
6
dist/pkgs/arch/.SRCINFO
vendored
6
dist/pkgs/arch/.SRCINFO
vendored
@@ -1,14 +1,14 @@
|
||||
pkgbase = termscp-bin
|
||||
pkgdesc = TermSCP is a SCP/SFTP/FTPS client for command line with an integrated UI to explore the remote file system. Basically WinSCP on a terminal.
|
||||
pkgver = 0.3.0
|
||||
pkgver = 0.3.1
|
||||
pkgrel = 1
|
||||
url = https://github.com/veeso/termscp
|
||||
arch = x86_64
|
||||
license = GPL-3.0
|
||||
provides = termscp
|
||||
options = strip
|
||||
source = https://github.com/veeso/termscp/releases/download/v0.3.0/termscp-0.3.0-x86_64.tar.gz
|
||||
sha256sums = c9e777c48e30ff1ebf84dbe10f5471b2da753e324753bda2cb08109beab0637d
|
||||
source = https://github.com/veeso/termscp/releases/download/v0.3.1/termscp-0.3.1-x86_64.tar.gz
|
||||
sha256sums = dd056531554737595cbe5ac9ff741fdabf5e386299bbc0c81ea9e0f00fbbe2d0
|
||||
|
||||
pkgname = termscp-bin
|
||||
|
||||
|
||||
4
dist/pkgs/arch/PKGBUILD
vendored
4
dist/pkgs/arch/PKGBUILD
vendored
@@ -1,6 +1,6 @@
|
||||
# Maintainer: Christian Visintin
|
||||
pkgname=termscp
|
||||
pkgver=0.3.0
|
||||
pkgver=0.3.1
|
||||
pkgrel=1
|
||||
pkgdesc="TermSCP is a SCP/SFTP/FTPS client for command line with an integrated UI to explore the remote file system. Basically WinSCP on a terminal."
|
||||
url="https://github.com/veeso/termscp"
|
||||
@@ -9,7 +9,7 @@ arch=("x86_64")
|
||||
provides=("termscp")
|
||||
options=("strip")
|
||||
source=("https://github.com/veeso/termscp/releases/download/v$pkgver/termscp-$pkgver-x86_64.tar.gz")
|
||||
sha256sums=("c9e777c48e30ff1ebf84dbe10f5471b2da753e324753bda2cb08109beab0637d")
|
||||
sha256sums=("dd056531554737595cbe5ac9ff741fdabf5e386299bbc0c81ea9e0f00fbbe2d0")
|
||||
|
||||
package() {
|
||||
install -Dm755 termscp -t "$pkgdir/usr/bin/"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -27,7 +27,7 @@ use std::path::PathBuf;
|
||||
|
||||
// Deps
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
use crate::host::Localhost;
|
||||
use crate::host::{HostError, Localhost};
|
||||
use crate::ui::activities::{
|
||||
auth_activity::AuthActivity, filetransfer_activity::FileTransferActivity,
|
||||
filetransfer_activity::FileTransferParams, setup_activity::SetupActivity, Activity,
|
||||
@@ -60,11 +60,11 @@ impl ActivityManager {
|
||||
/// ### new
|
||||
///
|
||||
/// Initializes a new Activity Manager
|
||||
pub fn new(local_dir: &PathBuf, interval: Duration) -> Result<ActivityManager, ()> {
|
||||
pub fn new(local_dir: &PathBuf, interval: Duration) -> Result<ActivityManager, HostError> {
|
||||
// Prepare Context
|
||||
let host: Localhost = match Localhost::new(local_dir.clone()) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return Err(()),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let ctx: Context = Context::new(host);
|
||||
Ok(ActivityManager {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -36,9 +36,12 @@ use crate::utils::parser::{parse_datetime, parse_lstime};
|
||||
use ftp4::native_tls::TlsConnector;
|
||||
use ftp4::FtpStream;
|
||||
use regex::Regex;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
/// ## FtpFileTransfer
|
||||
///
|
||||
@@ -105,61 +108,28 @@ impl FtpFileTransfer {
|
||||
if metadata.get(2).unwrap().as_str().len() < 9 {
|
||||
return Err(());
|
||||
}
|
||||
// Get unix pex
|
||||
let unix_pex: (u8, u8, u8) = {
|
||||
let owner_pex: u8 = {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[0..3].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
|
||||
let pex = |range: Range<usize>| {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[range].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
let group_pex: u8 = {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[3..6].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
let others_pex: u8 = {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[6..9].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
(owner_pex, group_pex, others_pex)
|
||||
}
|
||||
count
|
||||
};
|
||||
|
||||
// Get unix pex
|
||||
let unix_pex = (pex(0..3), pex(3..6), pex(6..9));
|
||||
|
||||
// Parse mtime and convert to SystemTime
|
||||
let mtime: SystemTime = match parse_lstime(
|
||||
metadata.get(7).unwrap().as_str(),
|
||||
@@ -180,10 +150,12 @@ impl FtpFileTransfer {
|
||||
Err(_) => None,
|
||||
};
|
||||
// Get filesize
|
||||
let filesize: usize = match metadata.get(6).unwrap().as_str().parse::<usize>() {
|
||||
Ok(sz) => sz,
|
||||
Err(_) => 0,
|
||||
};
|
||||
let filesize: usize = metadata
|
||||
.get(6)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse::<usize>()
|
||||
.unwrap_or(0);
|
||||
let file_name: String = String::from(metadata.get(8).unwrap().as_str());
|
||||
// Check if file_name is '.' or '..'
|
||||
if file_name.as_str() == "." || file_name.as_str() == ".." {
|
||||
@@ -270,10 +242,7 @@ impl FtpFileTransfer {
|
||||
true => 0, // If is directory, filesize is 0
|
||||
false => match metadata.get(3) {
|
||||
// If is file, parse arg 3
|
||||
Some(val) => match val.as_str().parse::<usize>() {
|
||||
Ok(sz) => sz,
|
||||
Err(_) => 0,
|
||||
},
|
||||
Some(val) => val.as_str().parse::<usize>().unwrap_or(0),
|
||||
None => 0, // Should not happen
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -37,9 +37,10 @@ use crate::utils::parser::parse_lstime;
|
||||
use regex::Regex;
|
||||
use ssh2::{Channel, Session};
|
||||
use std::io::{BufReader, BufWriter, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
/// ## ScpFileTransfer
|
||||
///
|
||||
@@ -92,61 +93,28 @@ impl ScpFileTransfer {
|
||||
if metadata.get(2).unwrap().as_str().len() < 9 {
|
||||
return Err(());
|
||||
}
|
||||
// Get unix pex
|
||||
let unix_pex: (u8, u8, u8) = {
|
||||
let owner_pex: u8 = {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[0..3].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
|
||||
let pex = |range: Range<usize>| {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[range].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
let group_pex: u8 = {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[3..6].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
let others_pex: u8 = {
|
||||
let mut count: u8 = 0;
|
||||
for (i, c) in metadata.get(2).unwrap().as_str()[6..9].chars().enumerate() {
|
||||
match c {
|
||||
'-' => {}
|
||||
_ => {
|
||||
count += match i {
|
||||
0 => 4,
|
||||
1 => 2,
|
||||
2 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
(owner_pex, group_pex, others_pex)
|
||||
}
|
||||
count
|
||||
};
|
||||
|
||||
// Get unix pex
|
||||
let unix_pex = (pex(0..3), pex(3..6), pex(6..9));
|
||||
|
||||
// Parse mtime and convert to SystemTime
|
||||
let mtime: SystemTime = match parse_lstime(
|
||||
metadata.get(7).unwrap().as_str(),
|
||||
@@ -167,10 +135,7 @@ impl ScpFileTransfer {
|
||||
Err(_) => None,
|
||||
};
|
||||
// Get filesize
|
||||
let filesize: usize = match metadata.get(6).unwrap().as_str().parse::<usize>() {
|
||||
Ok(sz) => sz,
|
||||
Err(_) => 0,
|
||||
};
|
||||
let filesize: usize = metadata.get(6).unwrap().as_str().parse::<usize>().unwrap_or(0);
|
||||
// Get link and name
|
||||
let (file_name, symlink_path): (String, Option<PathBuf>) = match is_symlink {
|
||||
true => self.get_name_and_link(metadata.get(8).unwrap().as_str()),
|
||||
@@ -311,12 +276,34 @@ impl FileTransfer for ScpFileTransfer {
|
||||
password: Option<String>,
|
||||
) -> Result<Option<String>, FileTransferError> {
|
||||
// Setup tcp stream
|
||||
let tcp: TcpStream = match TcpStream::connect(format!("{}:{}", address, port)) {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => {
|
||||
let socket_addresses: Vec<SocketAddr> =
|
||||
match format!("{}:{}", address, port).to_socket_addrs() {
|
||||
Ok(s) => s.collect(),
|
||||
Err(err) => {
|
||||
return Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::BadAddress,
|
||||
format!("{}", err),
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut tcp: Option<TcpStream> = None;
|
||||
// Try addresses
|
||||
for socket_addr in socket_addresses.iter() {
|
||||
match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) {
|
||||
Ok(stream) => {
|
||||
tcp = Some(stream);
|
||||
break;
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
// If stream is None, return connection timeout
|
||||
let tcp: TcpStream = match tcp {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::BadAddress,
|
||||
format!("{}", err),
|
||||
FileTransferErrorType::ConnectionError,
|
||||
String::from("Connection timeout"),
|
||||
))
|
||||
}
|
||||
};
|
||||
@@ -771,7 +758,13 @@ impl FileTransfer for ScpFileTransfer {
|
||||
};
|
||||
(mtime, atime)
|
||||
};
|
||||
match session.scp_send(file_name, mode, local.size as u64, Some(times)) {
|
||||
// We need to get the size of local; NOTE: don't use the `size` attribute, since might be out of sync
|
||||
let file_size: u64 = match std::fs::metadata(local.abs_path.as_path()) {
|
||||
Ok(metadata) => metadata.len(),
|
||||
Err(_) => local.size as u64, // NOTE: fallback to fsentry size
|
||||
};
|
||||
// Send file
|
||||
match session.scp_send(file_name, mode, file_size, Some(times)) {
|
||||
Ok(channel) => Ok(Box::new(BufWriter::with_capacity(65536, channel))),
|
||||
Err(err) => Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::ProtocolError,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -34,7 +34,7 @@ use crate::system::sshkey_storage::SshKeyStorage;
|
||||
// Includes
|
||||
use ssh2::{FileStat, OpenFlags, OpenType, Session, Sftp};
|
||||
use std::io::{BufReader, BufWriter, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
@@ -203,12 +203,34 @@ impl FileTransfer for SftpFileTransfer {
|
||||
password: Option<String>,
|
||||
) -> Result<Option<String>, FileTransferError> {
|
||||
// Setup tcp stream
|
||||
let tcp: TcpStream = match TcpStream::connect(format!("{}:{}", address, port)) {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => {
|
||||
let socket_addresses: Vec<SocketAddr> =
|
||||
match format!("{}:{}", address, port).to_socket_addrs() {
|
||||
Ok(s) => s.collect(),
|
||||
Err(err) => {
|
||||
return Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::BadAddress,
|
||||
format!("{}", err),
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut tcp: Option<TcpStream> = None;
|
||||
// Try addresses
|
||||
for socket_addr in socket_addresses.iter() {
|
||||
match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) {
|
||||
Ok(stream) => {
|
||||
tcp = Some(stream);
|
||||
break;
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
// If stream is None, return connection timeout
|
||||
let tcp: TcpStream = match tcp {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return Err(FileTransferError::new_ex(
|
||||
FileTransferErrorType::BadAddress,
|
||||
format!("{}", err),
|
||||
FileTransferErrorType::ConnectionError,
|
||||
String::from("Connection timeout"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -30,6 +30,7 @@ extern crate bitflags;
|
||||
// Locals
|
||||
use super::FsEntry;
|
||||
// Ext
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::VecDeque;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
@@ -137,7 +138,7 @@ impl FileExplorer {
|
||||
///
|
||||
/// Iterate over files
|
||||
/// Filters are applied based on current options (e.g. hidden files not returned)
|
||||
pub fn iter_files(&self) -> Box<dyn Iterator<Item = &FsEntry> + '_> {
|
||||
pub fn iter_files(&self) -> impl Iterator<Item = &FsEntry> + '_ {
|
||||
// Filter
|
||||
let opts: ExplorerOpts = self.opts;
|
||||
Box::new(self.files.iter().filter(move |x| {
|
||||
@@ -154,7 +155,7 @@ impl FileExplorer {
|
||||
/// ### iter_files_all
|
||||
///
|
||||
/// Iterate all files; doesn't care about options
|
||||
pub fn iter_files_all(&self) -> Box<dyn Iterator<Item = &FsEntry> + '_> {
|
||||
pub fn iter_files_all(&self) -> impl Iterator<Item = &FsEntry> + '_ {
|
||||
Box::new(self.files.iter())
|
||||
}
|
||||
|
||||
@@ -238,16 +239,14 @@ impl FileExplorer {
|
||||
///
|
||||
/// Sort files by creation time; the newest comes first
|
||||
fn sort_files_by_creation_time(&mut self) {
|
||||
self.files
|
||||
.sort_by(|a: &FsEntry, b: &FsEntry| b.get_creation_time().cmp(&a.get_creation_time()));
|
||||
self.files.sort_by_key(|b: &FsEntry| Reverse(b.get_creation_time()));
|
||||
}
|
||||
|
||||
/// ### sort_files_by_size
|
||||
///
|
||||
/// Sort files by size
|
||||
fn sort_files_by_size(&mut self) {
|
||||
self.files
|
||||
.sort_by(|a: &FsEntry, b: &FsEntry| b.get_size().cmp(&a.get_size()));
|
||||
self.files.sort_by_key(|b: &FsEntry| Reverse(b.get_size()));
|
||||
}
|
||||
|
||||
/// ### sort_files_directories_first
|
||||
@@ -432,6 +431,19 @@ impl FileExplorer {
|
||||
}
|
||||
}
|
||||
|
||||
/// ### set_abs_index
|
||||
///
|
||||
/// Set absolute index
|
||||
pub fn set_abs_index(&mut self, idx: usize) {
|
||||
self.index = match idx >= self.files.len() {
|
||||
true => match self.files.len() {
|
||||
0 => 0,
|
||||
_ => self.files.len() - 1,
|
||||
},
|
||||
false => idx,
|
||||
};
|
||||
}
|
||||
|
||||
/// ### toggle_hidden_files
|
||||
///
|
||||
/// Enable/disable hidden files
|
||||
@@ -726,6 +738,33 @@ mod tests {
|
||||
assert_eq!(explorer.files.get(1).unwrap().get_name(), "README.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fs_explorer_set_abs_index() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
explorer.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES);
|
||||
// Create files (files are then sorted by name DEFAULT)
|
||||
explorer.set_files(vec![
|
||||
make_fs_entry("README.md", false),
|
||||
make_fs_entry("src/", true),
|
||||
make_fs_entry(".git/", true),
|
||||
make_fs_entry("CONTRIBUTING.md", false),
|
||||
make_fs_entry("CODE_OF_CONDUCT.md", false),
|
||||
make_fs_entry("CHANGELOG.md", false),
|
||||
make_fs_entry("LICENSE", false),
|
||||
make_fs_entry("Cargo.toml", false),
|
||||
make_fs_entry("Cargo.lock", false),
|
||||
make_fs_entry("codecov.yml", false),
|
||||
make_fs_entry(".gitignore", false),
|
||||
]);
|
||||
explorer.set_abs_index(3);
|
||||
assert_eq!(explorer.get_index(), 3);
|
||||
explorer.set_abs_index(12);
|
||||
assert_eq!(explorer.get_index(), 10);
|
||||
explorer.set_files(vec![]);
|
||||
explorer.set_abs_index(12);
|
||||
assert_eq!(explorer.get_index(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fs_explorer_sort_by_creation_time() {
|
||||
let mut explorer: FileExplorer = FileExplorer::default();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -167,8 +167,8 @@ fn main() {
|
||||
// Create activity manager (and context too)
|
||||
let mut manager: ActivityManager = match ActivityManager::new(&wrkdir, ticks) {
|
||||
Ok(m) => m,
|
||||
Err(_) => {
|
||||
eprintln!("Invalid directory '{}'", wrkdir.display());
|
||||
Err(err) => {
|
||||
eprintln!("Could not start activity manager: {}", err);
|
||||
std::process::exit(255);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -23,6 +23,12 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// Deps
|
||||
extern crate whoami;
|
||||
// Crate
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
use super::keys::keyringstorage::KeyringStorage;
|
||||
use super::keys::{filestorage::FileStorage, KeyStorage, KeyStorageError};
|
||||
// Local
|
||||
use crate::bookmarks::serializer::BookmarkSerializer;
|
||||
use crate::bookmarks::{Bookmark, SerializerError, SerializerErrorKind, UserHosts};
|
||||
@@ -31,8 +37,7 @@ use crate::utils::crypto;
|
||||
use crate::utils::fmt::fmt_time;
|
||||
use crate::utils::random::random_alphanumeric_with_len;
|
||||
// Ext
|
||||
use std::fs::{OpenOptions, Permissions};
|
||||
use std::io::{Read, Write};
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
@@ -53,23 +58,60 @@ impl BookmarksClient {
|
||||
///
|
||||
/// Instantiates a new BookmarksClient
|
||||
/// Bookmarks file path must be provided
|
||||
/// Key file must be provided
|
||||
/// Storage path for file provider must be provided
|
||||
pub fn new(
|
||||
bookmarks_file: &Path,
|
||||
key_file: &Path,
|
||||
storage_path: &Path,
|
||||
recents_size: usize,
|
||||
) -> Result<BookmarksClient, SerializerError> {
|
||||
// Create default hosts
|
||||
let default_hosts: UserHosts = Default::default();
|
||||
// If key file doesn't exist, create key, otherwise read it
|
||||
let key: String = match key_file.exists() {
|
||||
true => match BookmarksClient::load_key(key_file) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
false => match BookmarksClient::generate_key(key_file) {
|
||||
Ok(key) => key,
|
||||
Err(err) => return Err(err),
|
||||
// Make a key storage (windows / macos)
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
|
||||
let username: String = whoami::username();
|
||||
let storage: KeyringStorage = KeyringStorage::new(username.as_str());
|
||||
// Check if keyring storage is supported
|
||||
#[cfg(not(test))]
|
||||
let app_name: &str = "termscp";
|
||||
#[cfg(test)] // NOTE: when running test, add -test
|
||||
let app_name: &str = "termscp-test";
|
||||
match storage.is_supported() {
|
||||
true => (Box::new(storage), app_name),
|
||||
false => (Box::new(FileStorage::new(storage_path)), "bookmarks"),
|
||||
}
|
||||
};
|
||||
// Make a key storage (linux / unix)
|
||||
#[cfg(any(target_os = "linux", target_os = "unix"))]
|
||||
let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
|
||||
#[cfg(not(test))]
|
||||
let app_name: &str = "bookmarks";
|
||||
#[cfg(test)] // NOTE: when running test, add -test
|
||||
let app_name: &str = "bookmarks-test";
|
||||
(Box::new(FileStorage::new(storage_path)), app_name)
|
||||
};
|
||||
// Load key
|
||||
let key: String = match key_storage.get_key(service_id) {
|
||||
Ok(k) => k,
|
||||
Err(e) => match e {
|
||||
KeyStorageError::NoSuchKey => {
|
||||
// If no such key, generate key and set it into the storage
|
||||
let key: String = Self::generate_key();
|
||||
if let Err(e) = key_storage.set_key(service_id, key.as_str()) {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
format!("Could not write key to storage: {}", e),
|
||||
));
|
||||
}
|
||||
// Return key
|
||||
key
|
||||
}
|
||||
_ => {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
format!("Could not get key from storage: {}", e),
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
let mut client: BookmarksClient = BookmarksClient {
|
||||
@@ -96,7 +138,7 @@ impl BookmarksClient {
|
||||
/// ### iter_bookmarks
|
||||
///
|
||||
/// Iterate over bookmarks keys
|
||||
pub fn iter_bookmarks(&self) -> Box<dyn Iterator<Item = &String> + '_> {
|
||||
pub fn iter_bookmarks(&self) -> impl Iterator<Item = &String> + '_ {
|
||||
Box::new(self.hosts.bookmarks.keys())
|
||||
}
|
||||
|
||||
@@ -156,7 +198,7 @@ impl BookmarksClient {
|
||||
/// ### iter_recents
|
||||
///
|
||||
/// Iterate over recents keys
|
||||
pub fn iter_recents(&self) -> Box<dyn Iterator<Item = &String> + '_> {
|
||||
pub fn iter_recents(&self) -> impl Iterator<Item = &String> + '_ {
|
||||
Box::new(self.hosts.recents.keys())
|
||||
}
|
||||
|
||||
@@ -276,36 +318,10 @@ impl BookmarksClient {
|
||||
|
||||
/// ### generate_key
|
||||
///
|
||||
/// Generate a new AES key and write it to key file
|
||||
fn generate_key(key_file: &Path) -> Result<String, SerializerError> {
|
||||
/// Generate a new AES key
|
||||
fn generate_key() -> String {
|
||||
// Generate 256 bytes (2048 bits) key
|
||||
let key: String = random_alphanumeric_with_len(256);
|
||||
// Write file
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(key_file)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
// Write key to file
|
||||
if let Err(err) = file.write_all(key.as_bytes()) {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
));
|
||||
}
|
||||
// Set file to readonly
|
||||
let mut permissions: Permissions = file.metadata().unwrap().permissions();
|
||||
permissions.set_readonly(true);
|
||||
let _ = file.set_permissions(permissions);
|
||||
Ok(key)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
random_alphanumeric_with_len(256)
|
||||
}
|
||||
|
||||
/// ### make_bookmark
|
||||
@@ -331,28 +347,6 @@ impl BookmarksClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// ### load_key
|
||||
///
|
||||
/// Load key from key_file
|
||||
fn load_key(key_file: &Path) -> Result<String, SerializerError> {
|
||||
match OpenOptions::new().read(true).open(key_file) {
|
||||
Ok(mut file) => {
|
||||
let mut key: String = String::with_capacity(256);
|
||||
match file.read_to_string(&mut key) {
|
||||
Ok(_) => Ok(key),
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### encrypt_str
|
||||
///
|
||||
/// Encrypt provided string using AES-128. Encrypted buffer is then converted to BASE64
|
||||
@@ -375,6 +369,7 @@ impl BookmarksClient {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(target_os = "macos"))] // CI/CD blocks
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
@@ -382,6 +377,7 @@ mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_new() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -397,6 +393,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "unix", target_os = "linux"))]
|
||||
fn test_system_bookmarks_new_err() {
|
||||
assert!(BookmarksClient::new(
|
||||
Path::new("/tmp/oifoif/omar"),
|
||||
@@ -413,6 +410,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_new_from_existing() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -458,6 +456,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_manipulate_bookmarks() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -503,6 +502,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
|
||||
fn test_system_bookmarks_bad_bookmark_name() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -521,6 +521,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_manipulate_recents() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -555,6 +556,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_dup_recent() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -579,6 +581,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_system_bookmarks_recents_more_than_limit() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -626,6 +629,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
|
||||
fn test_system_bookmarks_add_bookmark_empty() {
|
||||
let tmp_dir: tempfile::TempDir = create_tmp_dir();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
@@ -646,10 +650,10 @@ mod tests {
|
||||
/// ### get_paths
|
||||
///
|
||||
/// Get paths for configuration and key for bookmarks
|
||||
|
||||
fn get_paths(dir: &Path) -> (PathBuf, PathBuf) {
|
||||
let mut k: PathBuf = PathBuf::from(dir);
|
||||
let k: PathBuf = PathBuf::from(dir);
|
||||
let mut c: PathBuf = k.clone();
|
||||
k.push("bookmarks.key");
|
||||
c.push("bookmarks.toml");
|
||||
(c, k)
|
||||
}
|
||||
@@ -657,6 +661,7 @@ mod tests {
|
||||
/// ### create_tmp_dir
|
||||
///
|
||||
/// Create temporary directory
|
||||
|
||||
fn create_tmp_dir() -> tempfile::TempDir {
|
||||
tempfile::TempDir::new().ok().unwrap()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -242,7 +242,7 @@ impl ConfigClient {
|
||||
/// ### iter_ssh_keys
|
||||
///
|
||||
/// Get an iterator through hosts in the ssh key storage
|
||||
pub fn iter_ssh_keys(&self) -> Box<dyn Iterator<Item = &String> + '_> {
|
||||
pub fn iter_ssh_keys(&self) -> impl Iterator<Item = &String> + '_ {
|
||||
Box::new(self.config.remote.ssh_keys.keys())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -59,14 +59,12 @@ pub fn init_config_dir() -> Result<Option<PathBuf>, String> {
|
||||
/// ### get_bookmarks_paths
|
||||
///
|
||||
/// Get paths for bookmarks client
|
||||
/// Returns: path of bookmarks.toml and path of key
|
||||
pub fn get_bookmarks_paths(config_dir: &Path) -> (PathBuf, PathBuf) {
|
||||
/// Returns: path of bookmarks.toml
|
||||
pub fn get_bookmarks_paths(config_dir: &Path) -> PathBuf {
|
||||
// Prepare paths
|
||||
let mut bookmarks_file: PathBuf = PathBuf::from(config_dir);
|
||||
bookmarks_file.push("bookmarks.toml");
|
||||
let mut key_file: PathBuf = PathBuf::from(config_dir);
|
||||
key_file.push(".bookmarks.key"); // key file is hidden
|
||||
(bookmarks_file, key_file)
|
||||
bookmarks_file
|
||||
}
|
||||
|
||||
/// ### get_config_paths
|
||||
@@ -123,10 +121,7 @@ mod tests {
|
||||
fn test_system_environment_get_bookmarks_paths() {
|
||||
assert_eq!(
|
||||
get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")),
|
||||
(
|
||||
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
|
||||
PathBuf::from("/home/omar/.config/termscp/.bookmarks.key")
|
||||
)
|
||||
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
163
src/system/keys/filestorage.rs
Normal file
163
src/system/keys/filestorage.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
//! ## FileStorage
|
||||
//!
|
||||
//! `filestorage` provides an implementation of the `KeyStorage` trait using a file
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Local
|
||||
use super::{KeyStorage, KeyStorageError};
|
||||
// Ext
|
||||
use std::fs::{OpenOptions, Permissions};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// ## FileStorage
|
||||
///
|
||||
/// File storage is an implementation o the `KeyStorage` which uses a file to store the key
|
||||
pub struct FileStorage {
|
||||
dir_path: PathBuf,
|
||||
}
|
||||
|
||||
impl FileStorage {
|
||||
/// ### new
|
||||
///
|
||||
/// Instantiates a new `FileStorage`
|
||||
pub fn new(dir_path: &Path) -> Self {
|
||||
FileStorage {
|
||||
dir_path: PathBuf::from(dir_path),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### make_file_path
|
||||
///
|
||||
/// Make file path for key file from `dir_path` and the application id
|
||||
fn make_file_path(&self, storage_id: &str) -> PathBuf {
|
||||
let mut p: PathBuf = self.dir_path.clone();
|
||||
let file_name = format!(".{}.key", storage_id);
|
||||
p.push(file_name);
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyStorage for FileStorage {
|
||||
/// ### get_key
|
||||
///
|
||||
/// Retrieve key from the key storage.
|
||||
/// The key might be acccess through an identifier, which identifies
|
||||
/// the key in the storage
|
||||
fn get_key(&self, storage_id: &str) -> Result<String, KeyStorageError> {
|
||||
let key_file: PathBuf = self.make_file_path(storage_id);
|
||||
// Check if file exists
|
||||
if !key_file.exists() {
|
||||
return Err(KeyStorageError::NoSuchKey);
|
||||
}
|
||||
// Read key from file
|
||||
match OpenOptions::new().read(true).open(key_file.as_path()) {
|
||||
Ok(mut file) => {
|
||||
let mut key: String = String::new();
|
||||
match file.read_to_string(&mut key) {
|
||||
Ok(_) => Ok(key),
|
||||
Err(_) => Err(KeyStorageError::ProviderError),
|
||||
}
|
||||
}
|
||||
Err(_) => Err(KeyStorageError::ProviderError),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### set_key
|
||||
///
|
||||
/// Set the key into the key storage
|
||||
fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError> {
|
||||
let key_file: PathBuf = self.make_file_path(storage_id);
|
||||
// Write key
|
||||
match OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(key_file.as_path())
|
||||
{
|
||||
Ok(mut file) => {
|
||||
// Write key to file
|
||||
if file.write_all(key.as_bytes()).is_err() {
|
||||
return Err(KeyStorageError::ProviderError);
|
||||
}
|
||||
// Set file to readonly
|
||||
let mut permissions: Permissions = file.metadata().unwrap().permissions();
|
||||
permissions.set_readonly(true);
|
||||
let _ = file.set_permissions(permissions);
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(KeyStorageError::ProviderError),
|
||||
}
|
||||
}
|
||||
|
||||
/// is_supported
|
||||
///
|
||||
/// Returns whether the key storage is supported on the host system
|
||||
fn is_supported(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_system_keys_filestorage_make_dir() {
|
||||
let storage: FileStorage = FileStorage::new(&Path::new("/tmp/"));
|
||||
assert_eq!(
|
||||
storage.make_file_path("bookmarks").as_path(),
|
||||
Path::new("/tmp/.bookmarks.key")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_keys_filestorage_ok() {
|
||||
let key_dir: tempfile::TempDir =
|
||||
tempfile::TempDir::new().expect("Could not create tempdir");
|
||||
let storage: FileStorage = FileStorage::new(key_dir.path());
|
||||
// Supported
|
||||
assert!(storage.is_supported());
|
||||
let app_name: &str = "termscp";
|
||||
let secret: &str = "Th15-15/My-Супер-Секрет";
|
||||
// Secret should not exist
|
||||
assert_eq!(
|
||||
storage.get_key(app_name).err().unwrap(),
|
||||
KeyStorageError::NoSuchKey
|
||||
);
|
||||
// Write secret
|
||||
assert!(storage.set_key(app_name, secret).is_ok());
|
||||
// Get secret
|
||||
assert_eq!(storage.get_key(app_name).ok().unwrap().as_str(), secret);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_keys_filestorage_err() {
|
||||
let bad_dir: &Path = Path::new("/piro/poro/pero/");
|
||||
let storage: FileStorage = FileStorage::new(bad_dir);
|
||||
let app_name: &str = "termscp";
|
||||
let secret: &str = "Th15-15/My-Супер-Секрет";
|
||||
assert!(storage.set_key(app_name, secret).is_err());
|
||||
}
|
||||
}
|
||||
129
src/system/keys/keyringstorage.rs
Normal file
129
src/system/keys/keyringstorage.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
//! ## KeyringStorage
|
||||
//!
|
||||
//! `keyringstorage` provides an implementation of the `KeyStorage` trait using the OS keyring
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Deps
|
||||
extern crate keyring;
|
||||
// Local
|
||||
use super::{KeyStorage, KeyStorageError};
|
||||
// Ext
|
||||
use keyring::{Keyring, KeyringError};
|
||||
|
||||
/// ## KeyringStorage
|
||||
///
|
||||
/// provides a `KeyStorage` implementation using the keyring crate
|
||||
pub struct KeyringStorage {
|
||||
username: String,
|
||||
}
|
||||
|
||||
impl KeyringStorage {
|
||||
/// ### new
|
||||
///
|
||||
/// Instantiates a new KeyringStorage
|
||||
pub fn new(username: &str) -> Self {
|
||||
KeyringStorage {
|
||||
username: username.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyStorage for KeyringStorage {
|
||||
/// ### get_key
|
||||
///
|
||||
/// Retrieve key from the key storage.
|
||||
/// The key might be acccess through an identifier, which identifies
|
||||
/// the key in the storage
|
||||
fn get_key(&self, storage_id: &str) -> Result<String, KeyStorageError> {
|
||||
let storage: Keyring = Keyring::new(storage_id, self.username.as_str());
|
||||
match storage.get_password() {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => match e {
|
||||
KeyringError::NoPasswordFound => Err(KeyStorageError::NoSuchKey),
|
||||
#[cfg(target_os = "windows")]
|
||||
KeyringError::WindowsVaultError => Err(KeyStorageError::NoSuchKey),
|
||||
#[cfg(target_os = "macos")]
|
||||
KeyringError::MacOsKeychainError(_) => Err(KeyStorageError::NoSuchKey),
|
||||
_ => panic!("{}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// ### set_key
|
||||
///
|
||||
/// Set the key into the key storage
|
||||
fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError> {
|
||||
let storage: Keyring = Keyring::new(storage_id, self.username.as_str());
|
||||
match storage.set_password(key) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(KeyStorageError::ProviderError),
|
||||
}
|
||||
}
|
||||
|
||||
/// is_supported
|
||||
///
|
||||
/// Returns whether the key storage is supported on the host system
|
||||
fn is_supported(&self) -> bool {
|
||||
let dummy: String = String::from("dummy-service");
|
||||
let storage: Keyring = Keyring::new(dummy.as_str(), self.username.as_str());
|
||||
// Check what kind of error is returned
|
||||
match storage.get_password() {
|
||||
Ok(_) => true,
|
||||
Err(err) => !matches!(err, KeyringError::NoBackendFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
extern crate whoami;
|
||||
use super::*;
|
||||
|
||||
use whoami::username;
|
||||
|
||||
#[test]
|
||||
fn test_system_keys_keyringstorage() {
|
||||
let username: String = username();
|
||||
let storage: KeyringStorage = KeyringStorage::new(username.as_str());
|
||||
assert!(storage.is_supported());
|
||||
let app_name: &str = "termscp-test2";
|
||||
let secret: &str = "Th15-15/My-Супер-Секрет";
|
||||
let kring: Keyring = Keyring::new(app_name, username.as_str());
|
||||
let _ = kring.delete_password();
|
||||
drop(kring);
|
||||
// Secret should not exist
|
||||
assert_eq!(
|
||||
storage.get_key(app_name).err().unwrap(),
|
||||
KeyStorageError::NoSuchKey
|
||||
);
|
||||
// Write secret
|
||||
assert!(storage.set_key(app_name, secret).is_ok());
|
||||
// Get secret
|
||||
assert_eq!(storage.get_key(app_name).ok().unwrap().as_str(), secret);
|
||||
|
||||
// Delete the key manually...
|
||||
let kring: Keyring = Keyring::new(app_name, username.as_str());
|
||||
assert!(kring.delete_password().is_ok());
|
||||
}
|
||||
}
|
||||
90
src/system/keys/mod.rs
Normal file
90
src/system/keys/mod.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! ## KeyStorage
|
||||
//!
|
||||
//! `keystorage` provides the trait to manipulate to a KeyStorage
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
* TermSCP is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* TermSCP is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Storages
|
||||
pub mod filestorage;
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
pub mod keyringstorage;
|
||||
|
||||
/// ## KeyStorageError
|
||||
///
|
||||
/// defines the error type for the `KeyStorage`
|
||||
#[derive(PartialEq, std::fmt::Debug)]
|
||||
pub enum KeyStorageError {
|
||||
//BadKey,
|
||||
ProviderError,
|
||||
NoSuchKey,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for KeyStorageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let err: String = String::from(match &self {
|
||||
//KeyStorageError::BadKey => "Bad key syntax",
|
||||
KeyStorageError::ProviderError => "Provider service error",
|
||||
KeyStorageError::NoSuchKey => "No such key",
|
||||
});
|
||||
write!(f, "{}", err)
|
||||
}
|
||||
}
|
||||
|
||||
/// ## KeyStorage
|
||||
///
|
||||
/// this traits provides the methods to communicate and interact with the key storage.
|
||||
pub trait KeyStorage {
|
||||
/// ### get_key
|
||||
///
|
||||
/// Retrieve key from the key storage.
|
||||
/// The key might be acccess through an identifier, which identifies
|
||||
/// the key in the storage
|
||||
fn get_key(&self, storage_id: &str) -> Result<String, KeyStorageError>;
|
||||
|
||||
/// ### set_key
|
||||
///
|
||||
/// Set the key into the key storage
|
||||
fn set_key(&self, storage_id: &str, key: &str) -> Result<(), KeyStorageError>;
|
||||
|
||||
/// is_supported
|
||||
///
|
||||
/// Returns whether the key storage is supported on the host system
|
||||
fn is_supported(&self) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_system_keys_mod_errors() {
|
||||
assert_eq!(
|
||||
format!("{}", KeyStorageError::ProviderError),
|
||||
String::from("Provider service error")
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", KeyStorageError::NoSuchKey),
|
||||
String::from("No such key")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -27,4 +27,5 @@
|
||||
pub mod bookmarks_client;
|
||||
pub mod config_client;
|
||||
pub mod environment;
|
||||
pub(crate) mod keys;
|
||||
pub mod sshkey_storage;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -224,11 +224,15 @@ impl AuthActivity {
|
||||
match environment::init_config_dir() {
|
||||
Ok(path) => {
|
||||
// If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system.
|
||||
if let Some(path) = path {
|
||||
let (bookmarks_file, key_file): (PathBuf, PathBuf) =
|
||||
environment::get_bookmarks_paths(path.as_path());
|
||||
if let Some(config_dir_path) = path {
|
||||
let bookmarks_file: PathBuf =
|
||||
environment::get_bookmarks_paths(config_dir_path.as_path());
|
||||
// Initialize client
|
||||
match BookmarksClient::new(bookmarks_file.as_path(), key_file.as_path(), 16) {
|
||||
match BookmarksClient::new(
|
||||
bookmarks_file.as_path(),
|
||||
config_dir_path.as_path(),
|
||||
16,
|
||||
) {
|
||||
Ok(cli) => self.bookmarks_client = Some(cli),
|
||||
Err(err) => {
|
||||
self.popup = Some(Popup::Alert(
|
||||
@@ -236,7 +240,7 @@ impl AuthActivity {
|
||||
format!(
|
||||
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
||||
bookmarks_file.display(),
|
||||
key_file.display(),
|
||||
config_dir_path.display(),
|
||||
err
|
||||
),
|
||||
))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
@@ -600,9 +600,12 @@ impl FileTransferActivity {
|
||||
match self.context.as_ref().unwrap().local.scan_dir(path) {
|
||||
Ok(files) => {
|
||||
// Set files and sort (sorting is implicit)
|
||||
let prev_index: usize = self.local.get_index();
|
||||
self.local.set_files(files);
|
||||
// Restore index
|
||||
self.local.set_abs_index(prev_index);
|
||||
// Set index; keep if possible, otherwise set to last item
|
||||
self.local.set_index(match self.local.get_current_file() {
|
||||
self.local.set_abs_index(match self.local.get_current_file() {
|
||||
Some(_) => self.local.get_index(),
|
||||
None => match self.local.count() {
|
||||
0 => 0,
|
||||
@@ -626,9 +629,12 @@ impl FileTransferActivity {
|
||||
match self.client.list_dir(path) {
|
||||
Ok(files) => {
|
||||
// Set files and sort (sorting is implicit)
|
||||
let prev_index: usize = self.remote.get_index();
|
||||
self.remote.set_files(files);
|
||||
// Restore index
|
||||
self.remote.set_abs_index(prev_index);
|
||||
// Set index; keep if possible, otherwise set to last item
|
||||
self.remote.set_index(match self.remote.get_current_file() {
|
||||
self.remote.set_abs_index(match self.remote.get_current_file() {
|
||||
Some(_) => self.remote.get_index(),
|
||||
None => match self.remote.count() {
|
||||
0 => 0,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2020-2021Christian Visintin - christian.visintin1997@gmail.com
|
||||
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
||||
*
|
||||
* This file is part of "TermSCP"
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user