aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/tests.yaml67
-rw-r--r--.rustfmt.toml2
-rw-r--r--CHANGELOG.md114
-rw-r--r--Cargo.lock2016
-rw-r--r--Cargo.toml97
-rw-r--r--Makefile17
-rw-r--r--README.md21
-rwxr-xr-xbin/rbw-fzf7
-rwxr-xr-xbin/rbw-pinentry-keyring72
-rw-r--r--deny.toml42
-rw-r--r--src/actions.rs119
-rw-r--r--src/api.rs466
-rw-r--r--src/base64.rs15
-rw-r--r--src/bin/rbw-agent/actions.rs330
-rw-r--r--src/bin/rbw-agent/agent.rs198
-rw-r--r--src/bin/rbw-agent/daemon.rs31
-rw-r--r--src/bin/rbw-agent/main.rs29
-rw-r--r--src/bin/rbw-agent/notifications.rs174
-rw-r--r--src/bin/rbw-agent/sock.rs5
-rw-r--r--src/bin/rbw-agent/timeout.rs66
-rw-r--r--src/bin/rbw/actions.rs22
-rw-r--r--src/bin/rbw/commands.rs705
-rw-r--r--src/bin/rbw/main.rs205
-rw-r--r--src/cipherstring.rs118
-rw-r--r--src/config.rs119
-rw-r--r--src/db.rs6
-rw-r--r--src/dirs.rs56
-rw-r--r--src/edit.rs19
-rw-r--r--src/error.rs40
-rw-r--r--src/identity.rs51
-rw-r--r--src/lib.rs19
-rw-r--r--src/locked.rs14
-rw-r--r--src/pinentry.rs29
-rw-r--r--src/protocol.rs7
-rw-r--r--src/pwgen.rs3
35 files changed, 3848 insertions, 1453 deletions
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
new file mode 100644
index 0000000..c02a77b
--- /dev/null
+++ b/.github/workflows/tests.yaml
@@ -0,0 +1,67 @@
+name: tests
+on:
+ push:
+ branches: [main]
+ pull_request: {}
+env:
+ RUST_BACKTRACE: 1
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: cargo build --all-targets
+ build-musl:
+ runs-on: ubuntu-latest
+ steps:
+ - run: sudo apt-get install clang-11
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: x86_64-unknown-linux-musl
+ - run: TARGET_CC=clang-11 TARGET_AR=llvm-ar-11 cargo build --all-targets --target x86_64-unknown-linux-musl
+ build-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: cargo build --all-targets
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: cargo test
+ test-musl:
+ runs-on: ubuntu-latest
+ steps:
+ - run: sudo apt-get install clang-11
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: x86_64-unknown-linux-musl
+ - run: TARGET_CC=clang-11 TARGET_AR=llvm-ar-11 cargo test --target x86_64-unknown-linux-musl
+ test-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: cargo test
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ components: clippy, rustfmt
+ - run: cargo install --locked --debug cargo-deny
+ - run: cargo clippy --all-targets -- -Dwarnings
+ - run: cargo fmt --check
+ - run: cargo deny check
+ doc:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: cargo doc
diff --git a/.rustfmt.toml b/.rustfmt.toml
index 55b0b14..7b6182f 100644
--- a/.rustfmt.toml
+++ b/.rustfmt.toml
@@ -1,2 +1,2 @@
-edition = "2018"
+edition = "2021"
max_width = 78
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23143ec..4a3dfde 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,119 @@
# Changelog
+## [1.9.0] - 2024-01-01
+
+### Added
+
+* Secure notes can now be edited (Tin Lai, #137)
+* Piping passwords to `rbw edit` is now possible (Tin Lai, #138)
+
+### Fixed
+
+* More consistent behavior from `rbw get --field`, and fix some panics (Jörg Thalheim, #131)
+* Fix handling of pinentry EOF (Jörg Thalheim, #140)
+* Pass a user agent header to fix logging into the official bitwarden server (Maksim Karelov, #151)
+* Support the official bitwarden.eu server (Edvin Åkerfeldt, #152)
+
+## [1.8.3] - 2023-07-20
+
+### Fixed
+
+* Fixed running on linux without an X11 context available. (Benjamin Jacobs,
+ #126)
+
+## [1.8.2] - 2023-07-19
+
+### Fixed
+
+* Fixed several issues with notification-based background syncing, it should
+ be much more reliable now.
+
+## [1.8.1] - 2023-07-18
+
+### Fixed
+
+* `rbw config set notifications_url` now actually works
+
+## [1.8.0] - 2023-07-18
+
+### Added
+
+* `rbw get --clipboard` to copy the result to the clipboard instead of
+ displaying it on stdout. (eatradish, #120)
+* Background syncing now additionally happens when the server notifies the
+ agent of password updates, instead of needing to wait for the
+ `sync_interval` timer. (Bernd Schoolman, #115)
+* New helper script `rbw-pinentry-keyring` which can be used as an alternate
+ pinentry program (via `rbw config set pinentry rbw-pinentry-keyring`) to
+ automatically read the master password from the system keyring. Currently
+ only supports the Gnome keyring via `secret-tool`. (Kai Frische, #122)
+* Yubikeys in OTP mode are now supported for logging into a Bitwarden server.
+ (troyready, #123)
+
+### Fixed
+
+* Better error reporting when `rbw login` or `rbw register` fail.
+
+## [1.7.1] - 2023-03-27
+
+### Fixed
+
+* argon2 actually works now (#113, Bernd Schoolmann)
+
+## [1.7.0] - 2023-03-25
+
+### Added
+
+* `rbw` now automatically syncs the database from the server at a specified
+ interval while it is running. This defaults to once an hour, but is
+ configurable via the `sync_interval` option
+* Email 2FA is now supported (#111, René 'Necoro' Neumann)
+* argon2 KDF is now supported (#109, Bernd Schoolmann)
+
+### Fixed
+
+* `rbw --version` now works again
+
+## [1.6.0] - 2023-03-09
+
+### Added
+
+* `rbw get` now supports a `--raw` option to display the entire contents of
+ the entry in JSON format (#97, classabbyamp)
+
+## [1.5.0] - 2023-02-18
+
+### Added
+
+* Support for authenticating to self-hosted Bitwarden servers using client
+ certificates (#92, Filipe Pina)
+* Support multiple independent profiles via the `RBW_PROFILE` environment
+ variable (#93, Skia)
+* Add `rbw get --field` (#95, Jericho Keyne)
+
+### Fixed
+
+* Don't panic when not all stdout is read (#82, witcher)
+* Fixed duplicated alias names in help output (#46)
+
+## [1.4.3] - 2022-02-10
+
+### Fixed
+
+* Restored packaged scripts to the crate bundle, since they are used by some
+ downstream packages (no functional changes) (#81)
+
+## [1.4.2] - 2022-02-10
+
+### Changed
+
+* Device id is now stored in a separate file in the local data directory
+ instead of as part of the config (#74)
+
+### Fixed
+
+* Fix api renaming in official bitwarden server (#80)
+
## [1.4.1] - 2021-10-28
### Added
diff --git a/Cargo.lock b/Cargo.lock
index 1c11c3d..0078c68 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,52 +3,117 @@
version = 3
[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
name = "aes"
-version = "0.7.5"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
- "opaque-debug",
]
[[package]]
name = "aho-corasick"
-version = "0.7.18"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
-name = "ansi_term"
-version = "0.11.0"
+name = "anstream"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
dependencies = [
- "winapi",
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
-version = "1.0.44"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051"
+
+[[package]]
+name = "argon2"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
+checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
+dependencies = [
+ "base64ct",
+ "blake2",
+ "cpufeatures",
+ "password-hash",
+]
[[package]]
name = "arrayvec"
-version = "0.7.1"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-trait"
-version = "0.1.51"
+version = "0.1.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
+checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514"
dependencies = [
"proc-macro2",
"quote",
@@ -56,27 +121,25 @@ dependencies = [
]
[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
-]
-
-[[package]]
name = "autocfg"
-version = "0.1.7"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
-name = "autocfg"
-version = "1.0.1"
+name = "backtrace"
+version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
[[package]]
name = "base32"
@@ -86,15 +149,15 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]]
name = "base64"
-version = "0.13.0"
+version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
[[package]]
name = "base64ct"
-version = "1.0.1"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
@@ -103,59 +166,105 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
name = "block-buffer"
-version = "0.9.0"
+version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
-name = "block-modes"
-version = "0.8.1"
+name = "block-padding"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
- "block-padding",
- "cipher",
+ "generic-array",
]
[[package]]
-name = "block-padding"
-version = "0.2.1"
+name = "bumpalo"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
-name = "boxfnonce"
-version = "0.1.1"
+name = "byteorder"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
-name = "bumpalo"
-version = "3.8.0"
+name = "bytes"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
-name = "byteorder"
-version = "1.4.3"
+name = "calloop"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf"
+dependencies = [
+ "bitflags 2.4.1",
+ "log",
+ "polling",
+ "rustix",
+ "slab",
+ "thiserror",
+]
[[package]]
-name = "bytes"
-version = "1.1.0"
+name = "calloop-wayland-source"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
+dependencies = [
+ "calloop",
+ "rustix",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
[[package]]
name = "cc"
-version = "1.0.71"
+version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
[[package]]
name = "cfg-if"
@@ -165,40 +274,114 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
-version = "0.3.0"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
- "generic-array",
+ "crypto-common",
+ "inout",
]
[[package]]
name = "clap"
-version = "2.33.3"
+version = "4.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
dependencies = [
- "ansi_term",
- "atty",
- "bitflags",
+ "anstream",
+ "anstyle",
+ "clap_lex",
"strsim",
- "term_size",
- "textwrap",
- "unicode-width",
- "vec_map",
+ "terminal_size",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a51919c5608a32e34ea1d6be321ad070065e17613e168c5b6977024290f2630b"
+dependencies = [
+ "clap",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
+
+[[package]]
+name = "clipboard-win"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342"
+dependencies = [
+ "lazy-bytes-cast",
+ "winapi",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
]
[[package]]
name = "const-oid"
-version = "0.6.2"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "copypasta"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d35364349bf9e9e1c3a035ddcb00d188d23a3c40c50244c03c27a99fc6a65ae"
+dependencies = [
+ "clipboard-win",
+ "objc",
+ "objc-foundation",
+ "objc_id",
+ "smithay-clipboard",
+ "x11-clipboard",
+]
[[package]]
name = "core-foundation"
-version = "0.9.2"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
@@ -206,112 +389,162 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.3"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
-version = "0.2.1"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
dependencies = [
"libc",
]
[[package]]
-name = "crypto-bigint"
-version = "0.2.11"
+name = "crossbeam-utils"
+version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
+checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
dependencies = [
- "generic-array",
- "rand_core",
- "subtle",
+ "cfg-if",
]
[[package]]
-name = "crypto-mac"
-version = "0.11.1"
+name = "crypto-common"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
- "subtle",
+ "typenum",
]
[[package]]
+name = "cursor-icon"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
+
+[[package]]
name = "daemonize"
-version = "0.4.1"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815"
+checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e"
dependencies = [
- "boxfnonce",
"libc",
]
[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
+[[package]]
name = "der"
-version = "0.4.4"
+version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
dependencies = [
"const-oid",
- "crypto-bigint",
+ "pem-rfc7468",
+ "zeroize",
]
[[package]]
name = "digest"
-version = "0.9.0"
+version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "generic-array",
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
]
[[package]]
name = "directories"
-version = "4.0.1"
+version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+checksum = "74be3be809c18e089de43bdc504652bb2bc473fca8756131f8689db8cf079ba9"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
-version = "0.3.6"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
+checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
dependencies = [
"libc",
"redox_users",
- "winapi",
+ "windows-sys 0.45.0",
]
[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
name = "encoding_rs"
-version = "0.8.29"
+version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [
"cfg-if",
]
[[package]]
name = "env_logger"
-version = "0.9.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
dependencies = [
- "atty",
"humantime",
+ "is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -319,43 +552,67 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
-version = "1.0.1"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
- "matches",
"percent-encoding",
]
[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-channel"
-version = "0.3.17"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
+ "futures-sink",
]
[[package]]
name = "futures-core"
-version = "0.3.17"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
[[package]]
name = "futures-io"
-version = "0.3.17"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
-version = "0.3.17"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
- "autocfg 1.0.1",
- "proc-macro-hack",
"proc-macro2",
"quote",
"syn",
@@ -363,50 +620,59 @@ dependencies = [
[[package]]
name = "futures-sink"
-version = "0.3.17"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
-version = "0.3.17"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
-version = "0.3.17"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
- "autocfg 1.0.1",
+ "futures-channel",
"futures-core",
"futures-io",
"futures-macro",
+ "futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
- "proc-macro-hack",
- "proc-macro-nested",
"slab",
]
[[package]]
name = "generic-array"
-version = "0.14.4"
+version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
+name = "gethostname"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "getrandom"
-version = "0.2.3"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [
"cfg-if",
"libc",
@@ -414,10 +680,16 @@ dependencies = [
]
[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
name = "h2"
-version = "0.3.7"
+version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
+checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
dependencies = [
"bytes",
"fnv",
@@ -434,53 +706,45 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.11.2"
+version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
-version = "0.3.3"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
-dependencies = [
- "unicode-segmentation",
-]
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
-version = "0.1.19"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hkdf"
-version = "0.11.0"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
- "digest",
"hmac",
]
[[package]]
name = "hmac"
-version = "0.11.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
- "crypto-mac",
"digest",
]
[[package]]
name = "http"
-version = "0.2.5"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
+checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
@@ -489,9 +753,9 @@ dependencies = [
[[package]]
name = "http-body"
-version = "0.4.4"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
@@ -500,15 +764,15 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.5.1"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
-version = "1.0.1"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
@@ -518,9 +782,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
-version = "0.14.14"
+version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
@@ -542,108 +806,149 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.22.1"
+version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
+checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
+ "http",
"hyper",
- "log",
"rustls",
"tokio",
"tokio-rustls",
- "webpki",
]
[[package]]
name = "idna"
-version = "0.2.3"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
- "matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
-version = "1.7.0"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
- "autocfg 1.0.1",
+ "equivalent",
"hashbrown",
]
[[package]]
-name = "instant"
-version = "0.1.12"
+name = "inout"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
- "cfg-if",
+ "block-padding",
+ "generic-array",
]
[[package]]
name = "ipnet"
-version = "2.3.1"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys 0.52.0",
+]
[[package]]
name = "itoa"
-version = "0.4.8"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "js-sys"
-version = "0.3.55"
+version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
dependencies = [
"wasm-bindgen",
]
[[package]]
+name = "lazy-bytes-cast"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b"
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
- "spin",
+ "spin 0.5.2",
]
[[package]]
name = "libc"
-version = "0.2.105"
+version = "0.2.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+
+[[package]]
+name = "libloading"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
[[package]]
name = "libm"
-version = "0.2.1"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "libredox"
+version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "lock_api"
-version = "0.4.5"
+version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
+ "autocfg",
"scopeguard",
]
[[package]]
name = "log"
-version = "0.4.14"
+version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "mach"
@@ -655,83 +960,83 @@ dependencies = [
]
[[package]]
-name = "matches"
-version = "0.1.9"
+name = "malloc_buf"
+version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
[[package]]
name = "memchr"
-version = "2.4.1"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "memmap2"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92"
+dependencies = [
+ "libc",
+]
[[package]]
name = "memoffset"
-version = "0.6.4"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
]
[[package]]
name = "mime"
-version = "0.3.16"
+version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
-name = "mio"
-version = "0.7.14"
+name = "miniz_oxide"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
- "libc",
- "log",
- "miow",
- "ntapi",
- "winapi",
+ "adler",
]
[[package]]
-name = "miow"
-version = "0.3.7"
+name = "mio"
+version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
- "winapi",
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
]
[[package]]
name = "nix"
-version = "0.23.0"
+version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
- "bitflags",
- "cc",
+ "bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset",
-]
-
-[[package]]
-name = "ntapi"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
-dependencies = [
- "winapi",
+ "pin-utils",
]
[[package]]
name = "num-bigint-dig"
-version = "0.7.0"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
- "autocfg 0.1.7",
"byteorder",
"lazy_static",
"libm",
@@ -745,93 +1050,123 @@ dependencies = [
[[package]]
name = "num-integer"
-version = "0.1.44"
+version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
-version = "0.1.42"
+version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
-version = "0.2.14"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
"libm",
]
[[package]]
name = "num_cpus"
-version = "1.13.0"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
-name = "once_cell"
-version = "1.8.0"
+name = "objc"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
[[package]]
-name = "opaque-debug"
-version = "0.3.0"
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl-probe"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "parking_lot"
-version = "0.11.2"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
- "instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
-version = "0.8.5"
+version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
- "instant",
"libc",
"redox_syscall",
"smallvec",
- "winapi",
+ "windows-targets 0.48.5",
]
[[package]]
name = "password-hash"
-version = "0.3.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core",
@@ -839,64 +1174,41 @@ dependencies = [
]
[[package]]
-name = "paw"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09c0fc9b564dbc3dc2ed7c92c0c144f4de340aa94514ce2b446065417c4084e9"
-dependencies = [
- "paw-attributes",
- "paw-raw",
-]
-
-[[package]]
-name = "paw-attributes"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f35583365be5d148e959284f42526841917b7bfa09e2d1a7ad5dde2cf0eaa39"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "paw-raw"
-version = "1.0.0"
+name = "paste"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f0b59668fe80c5afe998f0c0bf93322bf2cd66cafeeb80581f291716f3467f2"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pbkdf2"
-version = "0.9.0"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
- "crypto-mac",
+ "digest",
"hmac",
- "password-hash",
- "sha2",
]
[[package]]
name = "pem-rfc7468"
-version = "0.2.3"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f22eb0e3c593294a99e9ff4b24cf6b752d43f193aa4415fe5077c159996d497"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
-version = "2.1.0"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
-version = "0.2.7"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
@@ -906,98 +1218,87 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs1"
-version = "0.2.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "116bee8279d783c0cf370efa1a94632f2108e5ef0bb32df31f051647810a4e2c"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der",
- "pem-rfc7468",
- "zeroize",
+ "pkcs8",
+ "spki",
]
[[package]]
name = "pkcs8"
-version = "0.7.6"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
- "pem-rfc7468",
- "pkcs1",
"spki",
- "zeroize",
]
[[package]]
-name = "ppv-lite86"
-version = "0.2.15"
+name = "pkg-config"
+version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
+checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
+name = "polling"
+version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
+ "cfg-if",
+ "concurrent-queue",
+ "pin-project-lite",
+ "rustix",
+ "tracing",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "proc-macro-hack"
-version = "0.5.19"
+name = "ppv-lite86"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
-name = "proc-macro-nested"
-version = "0.1.7"
+name = "proc-macro2"
+version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
+checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
-name = "proc-macro2"
-version = "1.0.32"
+name = "quick-xml"
+version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
- "unicode-xid",
+ "memchr",
]
[[package]]
name = "quote"
-version = "1.0.10"
+version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
-version = "0.8.4"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
- "rand_hc",
]
[[package]]
@@ -1012,62 +1313,63 @@ dependencies = [
[[package]]
name = "rand_core"
-version = "0.6.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
-name = "rand_hc"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
-dependencies = [
- "rand_core",
-]
-
-[[package]]
name = "rbw"
-version = "1.4.1"
+version = "1.9.0"
dependencies = [
"aes",
"anyhow",
+ "argon2",
"arrayvec",
"async-trait",
"base32",
"base64",
- "block-modes",
"block-padding",
+ "cbc",
+ "clap",
+ "clap_complete",
+ "copypasta",
"daemonize",
"directories",
"env_logger",
+ "futures",
+ "futures-channel",
+ "futures-util",
"hkdf",
"hmac",
"humantime",
+ "is-terminal",
"libc",
"log",
"nix",
- "paw",
"pbkdf2",
"percent-encoding",
+ "pkcs8",
"rand",
"region",
"reqwest",
+ "rmpv",
"rsa",
"serde",
"serde_json",
"serde_path_to_error",
"serde_repr",
- "sha-1",
+ "sha1",
"sha2",
- "structopt",
"tempfile",
- "term_size",
+ "terminal_size",
"textwrap",
"thiserror",
"tokio",
+ "tokio-stream",
+ "tokio-tungstenite",
"totp-lite",
"url",
"uuid",
@@ -1076,28 +1378,41 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.2.10"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
]
[[package]]
name = "redox_users"
-version = "0.4.0"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom",
- "redox_syscall",
+ "libredox",
+ "thiserror",
]
[[package]]
name = "regex"
-version = "1.5.4"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
@@ -1106,9 +1421,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.25"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "region"
@@ -1116,50 +1431,45 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"libc",
"mach",
"winapi",
]
[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi",
-]
-
-[[package]]
name = "reqwest"
-version = "0.11.6"
+version = "0.11.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
+checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
+ "h2",
"http",
"http-body",
"hyper",
"hyper-rustls",
"ipnet",
"js-sys",
- "lazy_static",
"log",
"mime",
+ "once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-native-certs",
+ "rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
+ "system-configuration",
"tokio",
"tokio-rustls",
+ "tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -1169,91 +1479,153 @@ dependencies = [
[[package]]
name = "ring"
-version = "0.16.20"
+version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
dependencies = [
"cc",
+ "getrandom",
"libc",
- "once_cell",
- "spin",
+ "spin 0.9.8",
"untrusted",
- "web-sys",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
-name = "rsa"
-version = "0.5.0"
+name = "rmp"
+version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d"
+checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
dependencies = [
"byteorder",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "rmpv"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e0e0214a4a2b444ecce41a4025792fc31f77c7bb89c46d253953ea8c65701ec"
+dependencies = [
+ "num-traits",
+ "rmp",
+]
+
+[[package]]
+name = "rsa"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
+dependencies = [
+ "const-oid",
"digest",
- "lazy_static",
"num-bigint-dig",
"num-integer",
- "num-iter",
"num-traits",
"pkcs1",
"pkcs8",
- "rand",
+ "rand_core",
+ "signature",
+ "spki",
"subtle",
"zeroize",
]
[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustix"
+version = "0.38.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
+dependencies = [
+ "bitflags 2.4.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "rustls"
-version = "0.19.1"
+version = "0.21.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
+checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
dependencies = [
- "base64",
"log",
"ring",
+ "rustls-webpki",
"sct",
- "webpki",
]
[[package]]
name = "rustls-native-certs"
-version = "0.5.0"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092"
+checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
- "rustls",
+ "rustls-pemfile",
"schannel",
"security-framework",
]
[[package]]
+name = "rustls-pemfile"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
+dependencies = [
+ "base64",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.101.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
name = "ryu"
-version = "1.0.5"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "schannel"
-version = "0.1.19"
+version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
- "lazy_static",
- "winapi",
+ "windows-sys 0.52.0",
]
[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
name = "scopeguard"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
-version = "0.6.1"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
+checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
@@ -1261,11 +1633,11 @@ dependencies = [
[[package]]
name = "security-framework"
-version = "2.4.2"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
+checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -1274,9 +1646,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.4.2"
+version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
+checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1284,18 +1656,18 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.130"
+version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.130"
+version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
@@ -1304,9 +1676,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.68"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
+checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9"
dependencies = [
"itoa",
"ryu",
@@ -1315,18 +1687,19 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
-version = "0.1.5"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0421d4f173fab82d72d6babf36d57fae38b994ca5c2d78e704260ba6d12118b"
+checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [
+ "itoa",
"serde",
]
[[package]]
name = "serde_repr"
-version = "0.1.7"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
+checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
dependencies = [
"proc-macro2",
"quote",
@@ -1335,9 +1708,9 @@ dependencies = [
[[package]]
name = "serde_urlencoded"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
@@ -1346,60 +1719,111 @@ dependencies = [
]
[[package]]
-name = "sha-1"
-version = "0.9.8"
+name = "sha1"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
- "block-buffer",
"cfg-if",
"cpufeatures",
"digest",
- "opaque-debug",
]
[[package]]
name = "sha2"
-version = "0.9.8"
+version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
- "block-buffer",
"cfg-if",
"cpufeatures",
"digest",
- "opaque-debug",
]
[[package]]
name = "signal-hook-registry"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core",
+]
+
+[[package]]
name = "slab"
-version = "0.4.5"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
[[package]]
name = "smallvec"
-version = "1.7.0"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
+
+[[package]]
+name = "smawk"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f"
+dependencies = [
+ "bitflags 2.4.1",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix",
+ "thiserror",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smithay-clipboard"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+checksum = "0bb62b280ce5a5cba847669933a0948d00904cf83845c944eae96a4738cea1a6"
+dependencies = [
+ "libc",
+ "smithay-client-toolkit",
+ "wayland-backend",
+]
[[package]]
name = "socket2"
-version = "0.4.2"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -1409,131 +1833,122 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
name = "spki"
-version = "0.4.1"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
+ "base64ct",
"der",
]
[[package]]
name = "strsim"
-version = "0.8.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
-name = "structopt"
-version = "0.3.25"
+name = "subtle"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
-dependencies = [
- "clap",
- "lazy_static",
- "paw",
- "structopt-derive",
-]
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
-name = "structopt-derive"
-version = "0.4.18"
+name = "syn"
+version = "2.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+checksum = "0eae3c679c56dc214320b67a1bc04ef3dfbd6411f6443974b5e4893231298e66"
dependencies = [
- "heck",
- "proc-macro-error",
"proc-macro2",
"quote",
- "syn",
+ "unicode-ident",
]
[[package]]
-name = "subtle"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
-
-[[package]]
-name = "syn"
-version = "1.0.81"
+name = "system-configuration"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
]
[[package]]
-name = "synstructure"
-version = "0.12.6"
+name = "system-configuration-sys"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "unicode-xid",
+ "core-foundation-sys",
+ "libc",
]
[[package]]
name = "tempfile"
-version = "3.2.0"
+version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
dependencies = [
"cfg-if",
- "libc",
- "rand",
+ "fastrand",
"redox_syscall",
- "remove_dir_all",
- "winapi",
+ "rustix",
+ "windows-sys 0.52.0",
]
[[package]]
-name = "term_size"
-version = "0.3.2"
+name = "termcolor"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
dependencies = [
- "libc",
- "winapi",
+ "winapi-util",
]
[[package]]
-name = "termcolor"
-version = "1.1.2"
+name = "terminal_size"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
- "winapi-util",
+ "rustix",
+ "windows-sys 0.48.0",
]
[[package]]
name = "textwrap"
-version = "0.11.0"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
dependencies = [
- "term_size",
+ "smawk",
+ "unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
-version = "1.0.30"
+version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.30"
+version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19"
dependencies = [
"proc-macro2",
"quote",
@@ -1542,44 +1957,43 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.5.0"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.12.0"
+version = "1.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
dependencies = [
- "autocfg 1.0.1",
+ "backtrace",
"bytes",
"libc",
- "memchr",
"mio",
"num_cpus",
- "once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
+ "socket2",
"tokio-macros",
- "winapi",
+ "windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
-version = "1.5.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
@@ -1588,172 +2002,220 @@ dependencies = [
[[package]]
name = "tokio-rustls"
-version = "0.22.0"
+version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
+checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls",
"tokio",
- "webpki",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls",
+ "rustls-native-certs",
+ "tokio",
+ "tokio-rustls",
+ "tungstenite",
]
[[package]]
name = "tokio-util"
-version = "0.6.8"
+version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
- "log",
"pin-project-lite",
"tokio",
+ "tracing",
]
[[package]]
name = "totp-lite"
-version = "1.0.3"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b18009e8be74bfb2e2cc59a63d078d95c042858a1ca1128a294e1f9ce225148b"
+checksum = "f8e43134db17199f7f721803383ac5854edd0d3d523cc34dba321d6acfbe76c3"
dependencies = [
"digest",
"hmac",
- "sha-1",
+ "sha1",
"sha2",
]
[[package]]
name = "tower-service"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
-version = "0.1.29"
+version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
- "cfg-if",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
-version = "0.1.21"
+version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
- "lazy_static",
+ "once_cell",
]
[[package]]
name = "try-lock"
-version = "0.2.3"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tungstenite"
+version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand",
+ "rustls",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
[[package]]
name = "typenum"
-version = "1.14.0"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-bidi"
-version = "0.3.7"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
+checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416"
[[package]]
-name = "unicode-normalization"
-version = "0.1.19"
+name = "unicode-ident"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
-dependencies = [
- "tinyvec",
-]
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
-name = "unicode-segmentation"
-version = "1.8.0"
+name = "unicode-linebreak"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]]
-name = "unicode-width"
-version = "0.1.9"
+name = "unicode-normalization"
+version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
[[package]]
-name = "unicode-xid"
-version = "0.2.2"
+name = "unicode-width"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "untrusted"
-version = "0.7.1"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.2.2"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
- "matches",
"percent-encoding",
]
[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
name = "uuid"
-version = "0.8.2"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
dependencies = [
"getrandom",
]
[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
-
-[[package]]
name = "version_check"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
- "log",
"try-lock",
]
[[package]]
name = "wasi"
-version = "0.10.2+wasi-snapshot-preview1"
+version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.78"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -1761,13 +2223,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.78"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
- "lazy_static",
"log",
+ "once_cell",
"proc-macro2",
"quote",
"syn",
@@ -1776,9 +2238,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.28"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
+checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
dependencies = [
"cfg-if",
"js-sys",
@@ -1788,9 +2250,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.78"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1798,9 +2260,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.78"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
@@ -1811,28 +2273,114 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.78"
+version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
[[package]]
-name = "web-sys"
-version = "0.3.55"
+name = "wayland-backend"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4"
dependencies = [
- "js-sys",
- "wasm-bindgen",
+ "cc",
+ "downcast-rs",
+ "nix",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
]
[[package]]
-name = "webpki"
-version = "0.21.4"
+name = "wayland-client"
+version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
+checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3"
dependencies = [
- "ring",
- "untrusted",
+ "bitflags 2.4.1",
+ "nix",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-csd-frame"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
+dependencies = [
+ "bitflags 2.4.1",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b"
+dependencies = [
+ "nix",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c"
+dependencies = [
+ "bitflags 2.4.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6"
+dependencies = [
+ "bitflags 2.4.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
]
[[package]]
@@ -1853,9 +2401,18 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
dependencies = [
"winapi",
]
@@ -1867,31 +2424,258 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
name = "winreg"
-version = "0.7.0"
+version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
+checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
- "winapi",
+ "cfg-if",
+ "windows-sys 0.48.0",
]
[[package]]
-name = "zeroize"
-version = "1.4.2"
+name = "x11-clipboard"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
+checksum = "b41aca1115b1f195f21c541c5efb423470848d48143127d0f07f8b90c27440df"
dependencies = [
- "zeroize_derive",
+ "x11rb",
]
[[package]]
-name = "zeroize_derive"
-version = "1.2.0"
+name = "x11rb"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdff2024a851a322b08f179173ae2ba620445aef1e838f0c196820eade4ae0c7"
+checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
+ "gethostname",
+ "nix",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol",
]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
+dependencies = [
+ "nix",
+]
+
+[[package]]
+name = "xcursor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911"
+
+[[package]]
+name = "xkeysym"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621"
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
diff --git a/Cargo.toml b/Cargo.toml
index 44575fc..0e389a3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,8 +1,8 @@
[package]
name = "rbw"
-version = "1.4.1"
+version = "1.9.0"
authors = ["Jesse Luehrs <doy@tozt.net>"]
-edition = "2018"
+edition = "2021"
description = "Unofficial Bitwarden CLI"
repository = "https://git.tozt.net/rbw"
@@ -10,48 +10,61 @@ readme = "README.md"
keywords = ["bitwarden"]
categories = ["command-line-utilities", "cryptography"]
license = "MIT"
+include = ["src/**/*", "bin/**/*", "LICENSE", "README.md", "CHANGELOG.md"]
[dependencies]
-aes = "0.7"
-anyhow = "1.0"
-arrayvec = "0.7"
-async-trait = "0.1"
-base32 = "0.4"
-base64 = "0.13"
-block-modes = "0.8"
-block-padding = "0.2"
-daemonize = "0.4"
-directories = "4.0"
-env_logger = "0.9"
-hkdf = "0.11"
-hmac = { version = "0.11", features = ["std"] }
-humantime = "2.1"
-libc = "0.2"
-log = "0.4"
-nix = "0.23"
-paw = "1.0"
-pbkdf2 = "0.9"
-percent-encoding = "2.1"
-rand = "0.8"
-region = "3.0"
-reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls-native-roots"] }
-rsa = "0.5"
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-serde_path_to_error = "0.1"
-serde_repr = "0.1"
-sha-1 = "0.9"
-sha2 = "0.9"
-structopt = { version = "0.3", features = ["paw", "wrap_help"] }
-tempfile = "3.2"
-term_size = "0.3"
-textwrap = "0.11"
-thiserror = "1.0"
-tokio = { version = "1.12", features = ["full"] }
-totp-lite = "1.0"
-url = "2.2"
-uuid = { version = "0.8", features = ["v4"] }
-zeroize = "1.4"
+aes = "0.8.3"
+anyhow = "1.0.78"
+argon2 = "0.5.2"
+arrayvec = "0.7.4"
+async-trait = "0.1.76"
+base32 = "0.4.0"
+base64 = "0.21.5"
+block-padding = "0.3.3"
+cbc = { version = "0.1.2", features = ["alloc", "std"] }
+clap = { version = "4.4.12", features = ["wrap_help", "derive"] }
+clap_complete = "4.4.5"
+daemonize = "0.5.0"
+# TODO: directories 5.0.1 uses MPL code, which isn't license-compatible
+# we should switch to something else at some point
+directories = "=5.0.0"
+env_logger = "0.10.1"
+futures = "0.3.30"
+futures-channel = "0.3.30"
+futures-util = "0.3.30"
+hkdf = "0.12.4"
+hmac = { version = "0.12.1", features = ["std"] }
+humantime = "2.1.0"
+libc = "0.2.151"
+log = "0.4.20"
+nix = "0.26"
+pbkdf2 = "0.12.2"
+percent-encoding = "2.3.1"
+pkcs8 = "0.10.2"
+rand = "0.8.5"
+region = "3.0.0"
+reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "json", "rustls-tls-native-roots"] }
+rsa = "0.9.6"
+serde = { version = "1.0.193", features = ["derive"] }
+serde_json = "1.0.109"
+serde_path_to_error = "0.1.14"
+serde_repr = "0.1.17"
+sha1 = "0.10.6"
+sha2 = "0.10.8"
+tempfile = "3.9.0"
+terminal_size = "0.3.0"
+textwrap = "0.16.0"
+thiserror = "1.0.53"
+tokio = { version = "1.35.1", features = ["full"] }
+tokio-stream = { version = "0.1.14", features = ["net"] }
+totp-lite = "2.0.1"
+url = "2.5.0"
+uuid = { version = "1.6.1", features = ["v4"] }
+zeroize = "1.7.0"
+copypasta = "0.10.0"
+rmpv = "1.0.1"
+tokio-tungstenite = { version = "0.20", features = ["rustls-tls-native-roots"] }
+is-terminal = "0.4.10"
[package.metadata.deb]
depends = "pinentry"
diff --git a/Makefile b/Makefile
index cedf8b5..509c2ee 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
-NAME = $(shell cargo metadata --no-deps --format-version 1 | jq '.packages[0].name')
-VERSION = $(shell cargo metadata --no-deps --format-version 1 | jq '.packages[0].version')
+NAME = $(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].name')
+VERSION = $(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
DEB_PACKAGE = $(NAME)_$(VERSION)_amd64.deb
+TGZ_PACKAGE = $(NAME)_$(VERSION)_linux_amd64.tar.gz
all: build
.PHONY: all
@@ -41,7 +42,7 @@ completion: release
@./target/x86_64-unknown-linux-musl/release/rbw gen-completions fish > target/x86_64-unknown-linux-musl/release/completion/fish
.PHONY: completion
-package: pkg/$(DEB_PACKAGE)
+package: pkg/$(DEB_PACKAGE) pkg/$(TGZ_PACKAGE)
.PHONY: package
pkg:
@@ -51,13 +52,16 @@ pkg/$(DEB_PACKAGE): release completion | pkg
@cargo deb --no-build --target x86_64-unknown-linux-musl && mv target/x86_64-unknown-linux-musl/debian/$(DEB_PACKAGE) pkg
pkg/$(DEB_PACKAGE).minisig: pkg/$(DEB_PACKAGE)
- @minisign -Sm pkg/$(DEB_PACKAGE)
+ @minisign -Sm $<
+
+pkg/$(TGZ_PACKAGE): release completion | pkg
+ @tar czf $@ -C target/x86_64-unknown-linux-musl/release rbw rbw-agent completion
release-dir-deb:
@ssh tozt.net mkdir -p releases/rbw/deb
.PHONY: release-dir-deb
-publish: publish-crates-io publish-git-tags publish-deb
+publish: publish-crates-io publish-git-tags publish-deb publish-github
.PHONY: publish
publish-crates-io: test
@@ -74,3 +78,6 @@ publish-git-tags: test
publish-deb: test pkg/$(DEB_PACKAGE) pkg/$(DEB_PACKAGE).minisig release-dir-deb
@scp pkg/$(DEB_PACKAGE) pkg/$(DEB_PACKAGE).minisig tozt.net:releases/rbw/deb
.PHONY: publish-deb
+
+publish-github: test pkg/$(TGZ_PACKAGE)
+ @perl -nle'print if /^## \Q[$(VERSION)]/../^## (?!\Q[$(VERSION)]\E)/' CHANGELOG.md | head -n-2 | gh release create $(VERSION) --verify-tag --notes-file - pkg/$(TGZ_PACKAGE)
diff --git a/README.md b/README.md
index 7a7a794..84c36f1 100644
--- a/README.md
+++ b/README.md
@@ -23,8 +23,8 @@ and merge pull requests implementing those features.
### Arch Linux
-`rbw` is available in the [community
-repository](https://archlinux.org/packages/community/x86_64/rbw/).
+`rbw` is available in the [extra
+repository](https://archlinux.org/packages/extra/x86_64/rbw/).
Alternatively, you can install
[`rbw-git`](https://aur.archlinux.org/packages/rbw-git/) from the AUR, which
will always build from the latest master commit.
@@ -37,6 +37,11 @@ You can download a Debian package from
[`minisign`](https://github.com/jedisct1/minisign), and can be verified using
the public key `RWTM0AZ5RpROOfAIWx1HvYQ6pw1+FKwN6526UFTKNImP/Hz3ynCFst3r`.
+### Alpine
+
+`rbw` is available in the [testing repository](https://pkgs.alpinelinux.org/packages?name=rbw).
+If you are not using the `edge` version of alpine you have to [enable the repository manually](https://wiki.alpinelinux.org/wiki/Repositories#Testing).
+
### Other
With a working Rust installation, `rbw` can be installed via `cargo install
@@ -56,9 +61,15 @@ configuration options:
* `identity_url`: The URL of the Bitwarden identity server to use. If unset,
will use the `/identity` path on the configured `base_url`, or
`https://identity.bitwarden.com/` if no `base_url` is set.
+* `notifications_url`: The URL of the Bitwarden notifications server to use.
+ If unset, will use the `/notifications` path on the configured `base_url`,
+ or `https://notifications.bitwarden.com/` if no `base_url` is set.
* `lock_timeout`: The number of seconds to keep the master keys in memory for
before requiring the password to be entered again. Defaults to `3600` (one
hour).
+* `sync_interval`: `rbw` will automatically sync the database from the server
+ at an interval of this many seconds, while the agent is running. Setting
+ this value to `0` disables this behavior. Defaults to `3600` (one hour).
* `pinentry`: The
[pinentry](https://www.gnupg.org/related_software/pinentry/index.html)
executable to use. Defaults to `pinentry`.
@@ -82,7 +93,9 @@ out by running `rbw purge`, and you can explicitly lock the database by running
functionality.
Run `rbw get <name>` to get your passwords. If you also want to get the username
-or the note associated, you can use the flag `--full`.
+or the note associated, you can use the flag `--full`. You can also use the flag
+`--field={field}` to get whatever default or custom field you want. The `--raw`
+flag will show the output as JSON.
*Note to users of the official Bitwarden server (at bitwarden.com)*: The
official server has a tendency to detect command line traffic as bot traffic
@@ -95,3 +108,5 @@ the instructions [here](https://bitwarden.com/help/article/personal-api-key/).
## Related projects
* [rofi-rbw](https://github.com/fdw/rofi-rbw): A rofi frontend for Bitwarden
+* [bw-ssh](https://framagit.org/Glandos/bw-ssh/): Manage SSH key passphrases in Bitwarden
+* [rbw-menu](https://github.com/rbuchberger/rbw-menu): Tiny menu picker for rbw
diff --git a/bin/rbw-fzf b/bin/rbw-fzf
index cbf15c4..308dd89 100755
--- a/bin/rbw-fzf
+++ b/bin/rbw-fzf
@@ -2,4 +2,9 @@
set -eu
set -o pipefail
-rbw ls --fields name,user,folder | perl -plE'/^([^\t]*)\t([^\t]*)\t([^\t]*)$/; $_ = join("/", grep { length } ($3, $1, $2)) . "\0$_"' | sort | fzf --with-nth=1 -d '\x00' | perl -ple'/^([^\0]*)\0([^\t]*)\t([^\t]*)\t([^\t]*)$/; $_ = "$2 $3"; $_ .= " --folder=\"$4\"" if length $4' | xargs -r rbw get
+rbw ls --fields name,user,folder | \
+ perl -plE'/^([^\t]*)\t([^\t]*)\t([^\t]*)$/; $_ = join("/", grep { length } ($3, $1, $2)) . "\0$_"' | \
+ sort | \
+ fzf --with-nth=1 -d '\x00' | \
+ perl -ple'/^([^\0]*)\0([^\t]*)\t([^\t]*)\t([^\t]*)$/; $_ = "$2 $3"; $_ .= " --folder=\"$4\"" if length $4' | \
+ xargs -r rbw get
diff --git a/bin/rbw-pinentry-keyring b/bin/rbw-pinentry-keyring
new file mode 100755
index 0000000..1626853
--- /dev/null
+++ b/bin/rbw-pinentry-keyring
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+[[ -z "${RBW_PROFILE}" ]] && rbw_profile='rbw' || rbw_profile="rbw-${RBW_PROFILE}"
+
+set -eEuo pipefail
+
+function help() {
+ cat <<EOHELP
+Use this script as pinentry to store master password for rbw into your keyring
+
+Usage
+- run "rbw-pinentry-keyring setup" once to save master password to keyring
+- add "rbw-pinentry-keyring" as "pinentry" in rbw config (${XDG_CONFIG_HOME}/rbw/config.json)
+- use rbw as normal
+Notes
+- needs "secret-tool" to access keyring
+- setup tested with pinentry-gnome3, but you can run the "secret-tool store"-command manually as well
+- master passwords are stored into the keyring as plaintext, so secure your keyring appropriately
+- supports multiple profiles, simply set RBW_PROFILE during setup
+- can easily be rewritten to use other backends than keyring by setting the "secret_value"-variable
+EOHELP
+}
+
+function setup() {
+ cmd="SETTITLE rbw\n"
+ cmd+="SETPROMPT Master Password\n"
+ cmd+="SETDESC Please enter the master password for '$rbw_profile'\n"
+ cmd+="GETPIN\n"
+ password="$(printf "$cmd" | pinentry | grep -E "^D " | cut -d' ' -f2)"
+ if [ -n "$password" ]; then
+ echo -n "$password" | secret-tool store --label="$rbw_profile master password" application rbw profile "$rbw_profile" type master_password
+ fi
+}
+
+function getpin() {
+ echo 'OK'
+
+ while IFS=' ' read -r command args ; do
+ case "$command" in
+ SETPROMPT|SETTITLE| SETDESC)
+ echo 'OK'
+ ;;
+ GETPIN)
+ secret_value="$(secret-tool lookup application rbw profile "$rbw_profile" type master_password)"
+ if [ -z "$secret_value" ]; then
+ exit 1
+ fi
+ printf 'D %s\n' "$secret_value"
+ echo 'OK'
+ ;;
+ BYE)
+ exit
+ ;;
+ *)
+ echo 'ERR Unknown command'
+ ;;
+ esac
+ done
+}
+
+command="$1"
+case "$command" in
+ -h|--help|help)
+ help
+ ;;
+ -s|--setup|setup)
+ setup
+ ;;
+ *)
+ getpin
+ ;;
+esac
diff --git a/deny.toml b/deny.toml
index 994369f..0d8fbfe 100644
--- a/deny.toml
+++ b/deny.toml
@@ -1,24 +1,50 @@
targets = [
{ triple = "x86_64-unknown-linux-musl" },
{ triple = "x86_64-unknown-linux-gnu" },
+ { triple = "x86_64-apple-darwin" },
+ { triple = "aarch64-apple-darwin" },
]
[advisories]
yanked = "deny"
unsound = "deny"
+ignore = [
+ # this is only an unmaintained warning, and will hopefully be addressed
+ # by https://github.com/darfink/region-rs/pull/27
+ "RUSTSEC-2020-0168",
+ # this is a timing attack against using the rsa crate for encryption, but
+ # we only use rsa decryption here
+ "RUSTSEC-2023-0071",
+]
[bans]
+multiple-versions = "deny"
+wildcards = "deny"
deny = [
{ name = "openssl-sys" },
]
skip = [
- # this is pulled in by rsa -> num-bigint-dig, but it's just a build dep so
- # i don't care much
- { name = "autocfg", version = "0.1.7" }
+ # the ecosystem is pretty split on these at the moment, should keep an
+ # eye on this to remove once more things have standardized on version 2
+ { name = "bitflags", version = "1.3.2" },
+ { name = "bitflags", version = "2.4.1" },
+
+ # see https://github.com/dignifiedquire/num-bigint/pull/58 and
+ # https://github.com/RustCrypto/RSA/issues/390 which should hopefully
+ # resolve this soon
+ { name = "spin", version = "0.5.2" },
+ { name = "spin", version = "0.9.8" },
]
[licenses]
-allow = ["MIT", "BSD-3-Clause", "Apache-2.0", "ISC"]
+allow = [
+ "MIT",
+ "BSD-2-Clause",
+ "BSD-3-Clause",
+ "Apache-2.0",
+ "ISC",
+ "Unicode-DFS-2016",
+]
copyleft = "deny"
exceptions = [
{ name = "ring", allow = ["OpenSSL", "MIT", "ISC"] }
@@ -31,3 +57,11 @@ expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 }
]
+
+[[licenses.clarify]]
+name = "encoding_rs"
+version = "*"
+expression = "(Apache-2.0 OR MIT) AND BSD-3-Clause"
+license-files = [
+ { path = "COPYRIGHT", hash = 0x39f8ad31 }
+]
diff --git a/src/actions.rs b/src/actions.rs
index 02ec854..b07cf44 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -4,11 +4,11 @@ pub async fn register(
email: &str,
apikey: crate::locked::ApiKey,
) -> Result<()> {
- let config = crate::config::Config::load_async().await?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, config) = api_client_async().await?;
- client.register(email, &config.device_id, &apikey).await?;
+ client
+ .register(email, &crate::config::device_id(&config).await?, &apikey)
+ .await?;
Ok(())
}
@@ -18,40 +18,70 @@ pub async fn login(
password: crate::locked::Password,
two_factor_token: Option<&str>,
two_factor_provider: Option<crate::api::TwoFactorProviderType>,
-) -> Result<(String, String, u32, String)> {
- let config = crate::config::Config::load_async().await?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+) -> Result<(
+ String,
+ String,
+ crate::api::KdfType,
+ u32,
+ Option<u32>,
+ Option<u32>,
+ String,
+)> {
+ let (client, config) = api_client_async().await?;
+ let (kdf, iterations, memory, parallelism) =
+ client.prelogin(email).await?;
- let iterations = client.prelogin(email).await?;
- let identity =
- crate::identity::Identity::new(email, &password, iterations)?;
+ let identity = crate::identity::Identity::new(
+ email,
+ &password,
+ kdf,
+ iterations,
+ memory,
+ parallelism,
+ )?;
let (access_token, refresh_token, protected_key) = client
.login(
email,
- &config.device_id,
+ &crate::config::device_id(&config).await?,
&identity.master_password_hash,
two_factor_token,
two_factor_provider,
)
.await?;
- Ok((access_token, refresh_token, iterations, protected_key))
+ Ok((
+ access_token,
+ refresh_token,
+ kdf,
+ iterations,
+ memory,
+ parallelism,
+ protected_key,
+ ))
}
-pub async fn unlock(
+pub fn unlock<S: std::hash::BuildHasher>(
email: &str,
password: &crate::locked::Password,
+ kdf: crate::api::KdfType,
iterations: u32,
+ memory: Option<u32>,
+ parallelism: Option<u32>,
protected_key: &str,
protected_private_key: &str,
- protected_org_keys: &std::collections::HashMap<String, String>,
+ protected_org_keys: &std::collections::HashMap<String, String, S>,
) -> Result<(
crate::locked::Keys,
std::collections::HashMap<String, crate::locked::Keys>,
)> {
- let identity =
- crate::identity::Identity::new(email, password, iterations)?;
+ let identity = crate::identity::Identity::new(
+ email,
+ password,
+ kdf,
+ iterations,
+ memory,
+ parallelism,
+ )?;
let protected_key =
crate::cipherstring::CipherString::new(protected_key)?;
@@ -119,9 +149,7 @@ async fn sync_once(
std::collections::HashMap<String, String>,
Vec<crate::db::Entry>,
)> {
- let config = crate::config::Config::load_async().await?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client_async().await?;
client.sync(access_token).await
}
@@ -145,10 +173,8 @@ fn add_once(
notes: Option<&str>,
folder_id: Option<&str>,
) -> Result<()> {
- let config = crate::config::Config::load()?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
- client.add(access_token, name, data, notes, folder_id.as_deref())?;
+ let (client, _) = api_client()?;
+ client.add(access_token, name, data, notes, folder_id)?;
Ok(())
}
@@ -187,9 +213,7 @@ fn edit_once(
folder_uuid: Option<&str>,
history: &[crate::db::HistoryEntry],
) -> Result<()> {
- let config = crate::config::Config::load()?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client()?;
client.edit(
access_token,
id,
@@ -214,9 +238,7 @@ pub fn remove(
}
fn remove_once(access_token: &str, id: &str) -> Result<()> {
- let config = crate::config::Config::load()?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client()?;
client.remove(access_token, id)?;
Ok(())
}
@@ -231,9 +253,7 @@ pub fn list_folders(
}
fn list_folders_once(access_token: &str) -> Result<Vec<(String, String)>> {
- let config = crate::config::Config::load()?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client()?;
client.folders(access_token)
}
@@ -248,9 +268,7 @@ pub fn create_folder(
}
fn create_folder_once(access_token: &str, name: &str) -> Result<String> {
- let config = crate::config::Config::load()?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client()?;
client.create_folder(access_token, name)
}
@@ -300,15 +318,32 @@ where
}
fn exchange_refresh_token(refresh_token: &str) -> Result<String> {
- let config = crate::config::Config::load()?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client()?;
client.exchange_refresh_token(refresh_token)
}
async fn exchange_refresh_token_async(refresh_token: &str) -> Result<String> {
- let config = crate::config::Config::load_async().await?;
- let client =
- crate::api::Client::new(&config.base_url(), &config.identity_url());
+ let (client, _) = api_client()?;
client.exchange_refresh_token_async(refresh_token).await
}
+
+fn api_client() -> Result<(crate::api::Client, crate::config::Config)> {
+ let config = crate::config::Config::load()?;
+ let client = crate::api::Client::new(
+ &config.base_url(),
+ &config.identity_url(),
+ config.client_cert_path(),
+ );
+ Ok((client, config))
+}
+
+async fn api_client_async(
+) -> Result<(crate::api::Client, crate::config::Config)> {
+ let config = crate::config::Config::load_async().await?;
+ let client = crate::api::Client::new(
+ &config.base_url(),
+ &config.identity_url(),
+ config.client_cert_path(),
+ );
+ Ok((client, config))
+}
diff --git a/src/api.rs b/src/api.rs
index 14c11fd..ca3c43b 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -1,9 +1,15 @@
+// serde_repr generates some as conversions that we can't seem to silence from
+// here, unfortunately
+#![allow(clippy::as_conversions)]
+
use crate::prelude::*;
use crate::json::{
DeserializeJsonWithPath as _, DeserializeJsonWithPathAsync as _,
};
+use tokio::io::AsyncReadExt as _;
+
#[derive(
serde_repr::Serialize_repr,
serde_repr::Deserialize_repr,
@@ -35,7 +41,7 @@ impl std::fmt::Display for UriMatchType {
RegularExpression => "regular_expression",
Never => "never",
};
- write!(f, "{}", s)
+ write!(f, "{s}")
}
}
@@ -51,6 +57,33 @@ pub enum TwoFactorProviderType {
WebAuthn = 7,
}
+impl TwoFactorProviderType {
+ #[must_use]
+ pub fn message(&self) -> &str {
+ match *self {
+ Self::Authenticator => "Enter the 6 digit verification code from your authenticator app.",
+ Self::Yubikey => "Insert your Yubikey and push the button.",
+ Self::Email => "Enter the PIN you received via email.",
+ _ => "Enter the code."
+ }
+ }
+
+ #[must_use]
+ pub fn header(&self) -> &str {
+ match *self {
+ Self::Authenticator => "Authenticator App",
+ Self::Yubikey => "Yubikey",
+ Self::Email => "Email Code",
+ _ => "Two Factor Authentication",
+ }
+ }
+
+ #[must_use]
+ pub fn grab(&self) -> bool {
+ !matches!(self, Self::Email)
+ }
+}
+
impl<'de> serde::Deserialize<'de> for TwoFactorProviderType {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
@@ -107,7 +140,7 @@ impl std::convert::TryFrom<u64> for TwoFactorProviderType {
6 => Ok(Self::OrganizationDuo),
7 => Ok(Self::WebAuthn),
_ => Err(Error::InvalidTwoFactorProvider {
- ty: format!("{}", ty),
+ ty: format!("{ty}"),
}),
}
}
@@ -131,6 +164,96 @@ impl std::str::FromStr for TwoFactorProviderType {
}
}
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum KdfType {
+ Pbkdf2 = 0,
+ Argon2id = 1,
+}
+
+impl<'de> serde::Deserialize<'de> for KdfType {
+ fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ struct KdfTypeVisitor;
+ impl<'de> serde::de::Visitor<'de> for KdfTypeVisitor {
+ type Value = KdfType;
+
+ fn expecting(
+ &self,
+ formatter: &mut std::fmt::Formatter,
+ ) -> std::fmt::Result {
+ formatter.write_str("kdf id")
+ }
+
+ fn visit_str<E>(
+ self,
+ value: &str,
+ ) -> std::result::Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ value.parse().map_err(serde::de::Error::custom)
+ }
+
+ fn visit_u64<E>(
+ self,
+ value: u64,
+ ) -> std::result::Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ std::convert::TryFrom::try_from(value)
+ .map_err(serde::de::Error::custom)
+ }
+ }
+
+ deserializer.deserialize_any(KdfTypeVisitor)
+ }
+}
+
+impl std::convert::TryFrom<u64> for KdfType {
+ type Error = Error;
+
+ fn try_from(ty: u64) -> Result<Self> {
+ match ty {
+ 0 => Ok(Self::Pbkdf2),
+ 1 => Ok(Self::Argon2id),
+ _ => Err(Error::InvalidKdfType {
+ ty: format!("{ty}"),
+ }),
+ }
+ }
+}
+
+impl std::str::FromStr for KdfType {
+ type Err = Error;
+
+ fn from_str(ty: &str) -> Result<Self> {
+ match ty {
+ "0" => Ok(Self::Pbkdf2),
+ "1" => Ok(Self::Argon2id),
+ _ => Err(Error::InvalidKdfType { ty: ty.to_string() }),
+ }
+ }
+}
+
+impl serde::Serialize for KdfType {
+ fn serialize<S>(
+ &self,
+ serializer: S,
+ ) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let s = match self {
+ Self::Pbkdf2 => "0",
+ Self::Argon2id => "1",
+ };
+ serializer.serialize_str(s)
+ }
+}
+
#[derive(serde::Serialize, Debug)]
struct PreloginReq {
email: String,
@@ -138,10 +261,14 @@ struct PreloginReq {
#[derive(serde::Deserialize, Debug)]
struct PreloginRes {
- #[serde(rename = "Kdf")]
- kdf: u32,
- #[serde(rename = "KdfIterations")]
+ #[serde(rename = "Kdf", alias = "kdf")]
+ kdf: KdfType,
+ #[serde(rename = "KdfIterations", alias = "kdfIterations")]
kdf_iterations: u32,
+ #[serde(rename = "KdfMemory", alias = "kdfMemory")]
+ kdf_memory: Option<u32>,
+ #[serde(rename = "KdfParallelism", alias = "kdfParallelism")]
+ kdf_parallelism: Option<u32>,
}
#[derive(serde::Serialize, Debug)]
@@ -169,10 +296,8 @@ struct ConnectPasswordReq {
#[derive(serde::Deserialize, Debug)]
struct ConnectPasswordRes {
access_token: String,
- expires_in: u32,
- token_type: String,
refresh_token: String,
- #[serde(rename = "Key")]
+ #[serde(rename = "Key", alias = "key")]
key: String,
}
@@ -180,15 +305,15 @@ struct ConnectPasswordRes {
struct ConnectErrorRes {
error: String,
error_description: Option<String>,
- #[serde(rename = "ErrorModel")]
+ #[serde(rename = "ErrorModel", alias = "errorModel")]
error_model: Option<ConnectErrorResErrorModel>,
- #[serde(rename = "TwoFactorProviders")]
+ #[serde(rename = "TwoFactorProviders", alias = "twoFactorProviders")]
two_factor_providers: Option<Vec<TwoFactorProviderType>>,
}
#[derive(serde::Deserialize, Debug)]
struct ConnectErrorResErrorModel {
- #[serde(rename = "Message")]
+ #[serde(rename = "Message", alias = "message")]
message: String,
}
@@ -202,46 +327,43 @@ struct ConnectRefreshTokenReq {
#[derive(serde::Deserialize, Debug)]
struct ConnectRefreshTokenRes {
access_token: String,
- expires_in: u32,
- token_type: String,
- refresh_token: String,
}
#[derive(serde::Deserialize, Debug)]
struct SyncRes {
- #[serde(rename = "Ciphers")]
+ #[serde(rename = "Ciphers", alias = "ciphers")]
ciphers: Vec<SyncResCipher>,
- #[serde(rename = "Profile")]
+ #[serde(rename = "Profile", alias = "profile")]
profile: SyncResProfile,
- #[serde(rename = "Folders")]
+ #[serde(rename = "Folders", alias = "folders")]
folders: Vec<SyncResFolder>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct SyncResCipher {
- #[serde(rename = "Id")]
+ #[serde(rename = "Id", alias = "id")]
id: String,
- #[serde(rename = "FolderId")]
+ #[serde(rename = "FolderId", alias = "folderId")]
folder_id: Option<String>,
- #[serde(rename = "OrganizationId")]
+ #[serde(rename = "OrganizationId", alias = "organizationId")]
organization_id: Option<String>,
- #[serde(rename = "Name")]
+ #[serde(rename = "Name", alias = "name")]
name: String,
- #[serde(rename = "Login")]
+ #[serde(rename = "Login", alias = "login")]
login: Option<CipherLogin>,
- #[serde(rename = "Card")]
+ #[serde(rename = "Card", alias = "card")]
card: Option<CipherCard>,
- #[serde(rename = "Identity")]
+ #[serde(rename = "Identity", alias = "identity")]
identity: Option<CipherIdentity>,
- #[serde(rename = "SecureNote")]
+ #[serde(rename = "SecureNote", alias = "secureNote")]
secure_note: Option<CipherSecureNote>,
- #[serde(rename = "Notes")]
+ #[serde(rename = "Notes", alias = "notes")]
notes: Option<String>,
- #[serde(rename = "PasswordHistory")]
+ #[serde(rename = "PasswordHistory", alias = "passwordHistory")]
password_history: Option<Vec<SyncResPasswordHistory>>,
- #[serde(rename = "Fields")]
+ #[serde(rename = "Fields", alias = "fields")]
fields: Option<Vec<SyncResField>>,
- #[serde(rename = "DeletedDate")]
+ #[serde(rename = "DeletedDate", alias = "deletedDate")]
deleted_date: Option<String>,
}
@@ -253,32 +375,37 @@ impl SyncResCipher {
if self.deleted_date.is_some() {
return None;
}
- let history = if let Some(history) = &self.password_history {
- history
- .iter()
- .filter_map(|entry| {
- // Gets rid of entries with a non-existent password
- entry.password.clone().map(|p| crate::db::HistoryEntry {
- last_used_date: entry.last_used_date.clone(),
- password: p,
- })
- })
- .collect()
- } else {
- vec![]
- };
+ let history =
+ self.password_history
+ .as_ref()
+ .map_or_else(Vec::new, |history| {
+ history
+ .iter()
+ .filter_map(|entry| {
+ // Gets rid of entries with a non-existent
+ // password
+ entry.password.clone().map(|p| {
+ crate::db::HistoryEntry {
+ last_used_date: entry
+ .last_used_date
+ .clone(),
+ password: p,
+ }
+ })
+ })
+ .collect()
+ });
- let (folder, folder_id) = if let Some(folder_id) = &self.folder_id {
- let mut folder_name = None;
- for folder in folders {
- if &folder.id == folder_id {
- folder_name = Some(folder.name.clone());
+ let (folder, folder_id) =
+ self.folder_id.as_ref().map_or((None, None), |folder_id| {
+ let mut folder_name = None;
+ for folder in folders {
+ if &folder.id == folder_id {
+ folder_name = Some(folder.name.clone());
+ }
}
- }
- (folder_name, Some(folder_id))
- } else {
- (None, None)
- };
+ (folder_name, Some(folder_id))
+ });
let data = if let Some(login) = &self.login {
crate::db::EntryData::Login {
username: login.username.clone(),
@@ -332,7 +459,7 @@ impl SyncResCipher {
} else {
return None;
};
- let fields = if let Some(fields) = &self.fields {
+ let fields = self.fields.as_ref().map_or_else(Vec::new, |fields| {
fields
.iter()
.map(|field| crate::db::Field {
@@ -340,9 +467,7 @@ impl SyncResCipher {
value: field.value.clone(),
})
.collect()
- } else {
- vec![]
- };
+ });
Some(crate::db::Entry {
id: self.id.clone(),
org_id: self.organization_id.clone(),
@@ -359,101 +484,101 @@ impl SyncResCipher {
#[derive(serde::Deserialize, Debug)]
struct SyncResProfile {
- #[serde(rename = "Key")]
+ #[serde(rename = "Key", alias = "key")]
key: String,
- #[serde(rename = "PrivateKey")]
+ #[serde(rename = "PrivateKey", alias = "privateKey")]
private_key: String,
- #[serde(rename = "Organizations")]
+ #[serde(rename = "Organizations", alias = "organizations")]
organizations: Vec<SyncResProfileOrganization>,
}
#[derive(serde::Deserialize, Debug)]
struct SyncResProfileOrganization {
- #[serde(rename = "Id")]
+ #[serde(rename = "Id", alias = "id")]
id: String,
- #[serde(rename = "Key")]
+ #[serde(rename = "Key", alias = "key")]
key: String,
}
#[derive(serde::Deserialize, Debug, Clone)]
struct SyncResFolder {
- #[serde(rename = "Id")]
+ #[serde(rename = "Id", alias = "id")]
id: String,
- #[serde(rename = "Name")]
+ #[serde(rename = "Name", alias = "name")]
name: String,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct CipherLogin {
- #[serde(rename = "Username")]
+ #[serde(rename = "Username", alias = "username")]
username: Option<String>,
- #[serde(rename = "Password")]
+ #[serde(rename = "Password", alias = "password")]
password: Option<String>,
- #[serde(rename = "Totp")]
+ #[serde(rename = "Totp", alias = "totp")]
totp: Option<String>,
- #[serde(rename = "Uris")]
+ #[serde(rename = "Uris", alias = "uris")]
uris: Option<Vec<CipherLoginUri>>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct CipherLoginUri {
- #[serde(rename = "Uri")]
+ #[serde(rename = "Uri", alias = "uri")]
uri: Option<String>,
- #[serde(rename = "Match")]
+ #[serde(rename = "Match", alias = "match")]
match_type: Option<UriMatchType>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct CipherCard {
- #[serde(rename = "CardholderName")]
+ #[serde(rename = "CardholderName", alias = "cardHolderName")]
cardholder_name: Option<String>,
- #[serde(rename = "Number")]
+ #[serde(rename = "Number", alias = "number")]
number: Option<String>,
- #[serde(rename = "Brand")]
+ #[serde(rename = "Brand", alias = "brand")]
brand: Option<String>,
- #[serde(rename = "ExpMonth")]
+ #[serde(rename = "ExpMonth", alias = "expMonth")]
exp_month: Option<String>,
- #[serde(rename = "ExpYear")]
+ #[serde(rename = "ExpYear", alias = "expYear")]
exp_year: Option<String>,
- #[serde(rename = "Code")]
+ #[serde(rename = "Code", alias = "code")]
code: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct CipherIdentity {
- #[serde(rename = "Title")]
+ #[serde(rename = "Title", alias = "title")]
title: Option<String>,
- #[serde(rename = "FirstName")]
+ #[serde(rename = "FirstName", alias = "firstName")]
first_name: Option<String>,
- #[serde(rename = "MiddleName")]
+ #[serde(rename = "MiddleName", alias = "middleName")]
middle_name: Option<String>,
- #[serde(rename = "LastName")]
+ #[serde(rename = "LastName", alias = "lastName")]
last_name: Option<String>,
- #[serde(rename = "Address1")]
+ #[serde(rename = "Address1", alias = "address1")]
address1: Option<String>,
- #[serde(rename = "Address2")]
+ #[serde(rename = "Address2", alias = "address2")]
address2: Option<String>,
- #[serde(rename = "Address3")]
+ #[serde(rename = "Address3", alias = "address3")]
address3: Option<String>,
- #[serde(rename = "City")]
+ #[serde(rename = "City", alias = "city")]
city: Option<String>,
- #[serde(rename = "State")]
+ #[serde(rename = "State", alias = "state")]
state: Option<String>,
- #[serde(rename = "PostalCode")]
+ #[serde(rename = "PostalCode", alias = "postalCode")]
postal_code: Option<String>,
- #[serde(rename = "Country")]
+ #[serde(rename = "Country", alias = "country")]
country: Option<String>,
- #[serde(rename = "Phone")]
+ #[serde(rename = "Phone", alias = "phone")]
phone: Option<String>,
- #[serde(rename = "Email")]
+ #[serde(rename = "Email", alias = "email")]
email: Option<String>,
- #[serde(rename = "SSN")]
+ #[serde(rename = "SSN", alias = "ssn")]
ssn: Option<String>,
- #[serde(rename = "LicenseNumber")]
+ #[serde(rename = "LicenseNumber", alias = "licenseNumber")]
license_number: Option<String>,
- #[serde(rename = "PassportNumber")]
+ #[serde(rename = "PassportNumber", alias = "passportNumber")]
passport_number: Option<String>,
- #[serde(rename = "Username")]
+ #[serde(rename = "Username", alias = "username")]
username: Option<String>,
}
@@ -464,19 +589,19 @@ struct CipherSecureNote {}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct SyncResPasswordHistory {
- #[serde(rename = "LastUsedDate")]
+ #[serde(rename = "LastUsedDate", alias = "lastUsedDate")]
last_used_date: String,
- #[serde(rename = "Password")]
+ #[serde(rename = "Password", alias = "password")]
password: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
struct SyncResField {
- #[serde(rename = "Type")]
+ #[serde(rename = "Type", alias = "type")]
ty: u32,
- #[serde(rename = "Name")]
+ #[serde(rename = "Name", alias = "name")]
name: Option<String>,
- #[serde(rename = "Value")]
+ #[serde(rename = "Value", alias = "value")]
value: Option<String>,
}
@@ -530,15 +655,15 @@ struct CiphersPutReqHistory {
#[derive(serde::Deserialize, Debug)]
struct FoldersRes {
- #[serde(rename = "Data")]
+ #[serde(rename = "Data", alias = "data")]
data: Vec<FoldersResData>,
}
#[derive(serde::Deserialize, Debug)]
struct FoldersResData {
- #[serde(rename = "Id")]
+ #[serde(rename = "Id", alias = "id")]
id: String,
- #[serde(rename = "Name")]
+ #[serde(rename = "Name", alias = "name")]
name: String,
}
@@ -551,21 +676,64 @@ struct FoldersPostReq {
pub struct Client {
base_url: String,
identity_url: String,
+ client_cert_path: Option<std::path::PathBuf>,
}
impl Client {
- pub fn new(base_url: &str, identity_url: &str) -> Self {
+ #[must_use]
+ pub fn new(
+ base_url: &str,
+ identity_url: &str,
+ client_cert_path: Option<&std::path::Path>,
+ ) -> Self {
Self {
base_url: base_url.to_string(),
identity_url: identity_url.to_string(),
+ client_cert_path: client_cert_path
+ .map(std::path::Path::to_path_buf),
}
}
- pub async fn prelogin(&self, email: &str) -> Result<u32> {
+ async fn reqwest_client(&self) -> Result<reqwest::Client> {
+ if let Some(client_cert_path) = self.client_cert_path.as_ref() {
+ let mut buf = Vec::new();
+ let mut f = tokio::fs::File::open(client_cert_path)
+ .await
+ .map_err(|e| Error::LoadClientCert {
+ source: e,
+ file: client_cert_path.clone(),
+ })?;
+ f.read_to_end(&mut buf).await.map_err(|e| {
+ Error::LoadClientCert {
+ source: e,
+ file: client_cert_path.clone(),
+ }
+ })?;
+ let pem = reqwest::Identity::from_pem(&buf).map_err(|e| {
+ Error::LoadClientCertReqwest {
+ source: e,
+ file: client_cert_path.clone(),
+ }
+ })?;
+ Ok(reqwest::Client::builder().identity(pem).build().map_err(
+ |e| Error::LoadClientCertReqwest {
+ source: e,
+ file: client_cert_path.clone(),
+ },
+ )?)
+ } else {
+ Ok(reqwest::Client::new())
+ }
+ }
+
+ pub async fn prelogin(
+ &self,
+ email: &str,
+ ) -> Result<(KdfType, u32, Option<u32>, Option<u32>)> {
let prelogin = PreloginReq {
email: email.to_string(),
};
- let client = reqwest::Client::new();
+ let client = self.reqwest_client().await?;
let res = client
.post(&self.api_url("/accounts/prelogin"))
.json(&prelogin)
@@ -573,7 +741,12 @@ impl Client {
.await
.map_err(|source| Error::Reqwest { source })?;
let prelogin_res: PreloginRes = res.json_with_path().await?;
- Ok(prelogin_res.kdf_iterations)
+ Ok((
+ prelogin_res.kdf,
+ prelogin_res.kdf_iterations,
+ prelogin_res.kdf_memory,
+ prelogin_res.kdf_parallelism,
+ ))
}
pub async fn register(
@@ -596,22 +769,34 @@ impl Client {
device_type: 8,
device_identifier: device_id.to_string(),
device_name: "rbw".to_string(),
- device_push_token: "".to_string(),
+ device_push_token: String::new(),
two_factor_token: None,
two_factor_provider: None,
};
- let client = reqwest::Client::new();
+ let client = self.reqwest_client().await?;
let res = client
.post(&self.identity_url("/connect/token"))
.form(&connect_req)
.send()
.await
.map_err(|source| Error::Reqwest { source })?;
- if let reqwest::StatusCode::OK = res.status() {
+ if res.status() == reqwest::StatusCode::OK {
Ok(())
} else {
let code = res.status().as_u16();
- Err(classify_login_error(&res.json_with_path().await?, code))
+ match res.text().await {
+ Ok(body) => match body.clone().json_with_path() {
+ Ok(json) => Err(classify_login_error(&json, code)),
+ Err(e) => {
+ log::warn!("{e}: {body}");
+ Err(Error::RequestFailed { status: code })
+ }
+ },
+ Err(e) => {
+ log::warn!("failed to read response body: {e}");
+ Err(Error::RequestFailed { status: code })
+ }
+ }
}
}
@@ -626,30 +811,34 @@ impl Client {
let connect_req = ConnectPasswordReq {
grant_type: "password".to_string(),
username: email.to_string(),
- password: Some(base64::encode(password_hash.hash())),
+ password: Some(crate::base64::encode(password_hash.hash())),
scope: "api offline_access".to_string(),
client_id: "desktop".to_string(),
client_secret: None,
device_type: 8,
device_identifier: device_id.to_string(),
device_name: "rbw".to_string(),
- device_push_token: "".to_string(),
+ device_push_token: String::new(),
two_factor_token: two_factor_token
.map(std::string::ToString::to_string),
two_factor_provider: two_factor_provider.map(|ty| ty as u32),
};
- let client = reqwest::Client::new();
+ let client = self.reqwest_client().await?;
let res = client
.post(&self.identity_url("/connect/token"))
.form(&connect_req)
.header(
"auth-email",
- base64::encode_config(email, base64::URL_SAFE_NO_PAD),
+ crate::base64::encode_url_safe_no_pad(email),
+ )
+ .header(
+ "user-agent",
+ format!("rbw/{}", env!("CARGO_PKG_VERSION")),
)
.send()
.await
.map_err(|source| Error::Reqwest { source })?;
- if let reqwest::StatusCode::OK = res.status() {
+ if res.status() == reqwest::StatusCode::OK {
let connect_res: ConnectPasswordRes =
res.json_with_path().await?;
Ok((
@@ -659,7 +848,19 @@ impl Client {
))
} else {
let code = res.status().as_u16();
- Err(classify_login_error(&res.json_with_path().await?, code))
+ match res.text().await {
+ Ok(body) => match body.clone().json_with_path() {
+ Ok(json) => Err(classify_login_error(&json, code)),
+ Err(e) => {
+ log::warn!("{e}: {body}");
+ Err(Error::RequestFailed { status: code })
+ }
+ },
+ Err(e) => {
+ log::warn!("failed to read response body: {e}");
+ Err(Error::RequestFailed { status: code })
+ }
+ }
}
}
@@ -672,10 +873,10 @@ impl Client {
std::collections::HashMap<String, String>,
Vec<crate::db::Entry>,
)> {
- let client = reqwest::Client::new();
+ let client = self.reqwest_client().await?;
let res = client
.get(&self.api_url("/sync"))
- .header("Authorization", format!("Bearer {}", access_token))
+ .header("Authorization", format!("Bearer {access_token}"))
.send()
.await
.map_err(|source| Error::Reqwest { source })?;
@@ -816,8 +1017,8 @@ impl Client {
}
let client = reqwest::blocking::Client::new();
let res = client
- .post(&self.api_url("/ciphers"))
- .header("Authorization", format!("Bearer {}", access_token))
+ .post(self.api_url("/ciphers"))
+ .header("Authorization", format!("Bearer {access_token}"))
.json(&req)
.send()
.map_err(|source| Error::Reqwest { source })?;
@@ -844,7 +1045,12 @@ impl Client {
history: &[crate::db::HistoryEntry],
) -> Result<()> {
let mut req = CiphersPutReq {
- ty: 1,
+ ty: match data {
+ crate::db::EntryData::Login { .. } => 1,
+ crate::db::EntryData::SecureNote { .. } => 2,
+ crate::db::EntryData::Card { .. } => 3,
+ crate::db::EntryData::Identity { .. } => 4,
+ },
folder_id: folder_uuid.map(std::string::ToString::to_string),
organization_id: org_id.map(std::string::ToString::to_string),
name: name.to_string(),
@@ -949,8 +1155,8 @@ impl Client {
}
let client = reqwest::blocking::Client::new();
let res = client
- .put(&self.api_url(&format!("/ciphers/{}", id)))
- .header("Authorization", format!("Bearer {}", access_token))
+ .put(self.api_url(&format!("/ciphers/{id}")))
+ .header("Authorization", format!("Bearer {access_token}"))
.json(&req)
.send()
.map_err(|source| Error::Reqwest { source })?;
@@ -968,8 +1174,8 @@ impl Client {
pub fn remove(&self, access_token: &str, id: &str) -> Result<()> {
let client = reqwest::blocking::Client::new();
let res = client
- .delete(&self.api_url(&format!("/ciphers/{}", id)))
- .header("Authorization", format!("Bearer {}", access_token))
+ .delete(self.api_url(&format!("/ciphers/{id}")))
+ .header("Authorization", format!("Bearer {access_token}"))
.send()
.map_err(|source| Error::Reqwest { source })?;
match res.status() {
@@ -989,8 +1195,8 @@ impl Client {
) -> Result<Vec<(String, String)>> {
let client = reqwest::blocking::Client::new();
let res = client
- .get(&self.api_url("/folders"))
- .header("Authorization", format!("Bearer {}", access_token))
+ .get(self.api_url("/folders"))
+ .header("Authorization", format!("Bearer {access_token}"))
.send()
.map_err(|source| Error::Reqwest { source })?;
match res.status() {
@@ -1021,8 +1227,8 @@ impl Client {
};
let client = reqwest::blocking::Client::new();
let res = client
- .post(&self.api_url("/folders"))
- .header("Authorization", format!("Bearer {}", access_token))
+ .post(self.api_url("/folders"))
+ .header("Authorization", format!("Bearer {access_token}"))
.json(&req)
.send()
.map_err(|source| Error::Reqwest { source })?;
@@ -1051,7 +1257,7 @@ impl Client {
};
let client = reqwest::blocking::Client::new();
let res = client
- .post(&self.identity_url("/connect/token"))
+ .post(self.identity_url("/connect/token"))
.form(&connect_req)
.send()
.map_err(|source| Error::Reqwest { source })?;
@@ -1068,7 +1274,7 @@ impl Client {
client_id: "desktop".to_string(),
refresh_token: refresh_token.to_string(),
};
- let client = reqwest::Client::new();
+ let client = self.reqwest_client().await?;
let res = client
.post(&self.identity_url("/connect/token"))
.form(&connect_req)
diff --git a/src/base64.rs b/src/base64.rs
new file mode 100644
index 0000000..86971bc
--- /dev/null
+++ b/src/base64.rs
@@ -0,0 +1,15 @@
+use base64::Engine as _;
+
+pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
+ base64::engine::general_purpose::STANDARD.encode(input)
+}
+
+pub fn encode_url_safe_no_pad<T: AsRef<[u8]>>(input: T) -> String {
+ base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(input)
+}
+
+pub fn decode<T: AsRef<[u8]>>(
+ input: T,
+) -> Result<Vec<u8>, base64::DecodeError> {
+ base64::engine::general_purpose::STANDARD.decode(input)
+}
diff --git a/src/bin/rbw-agent/actions.rs b/src/bin/rbw-agent/actions.rs
index 1cc71c3..ff39510 100644
--- a/src/bin/rbw-agent/actions.rs
+++ b/src/bin/rbw-agent/actions.rs
@@ -10,9 +10,7 @@ pub async fn register(
let url_str = config_base_url().await?;
let url = reqwest::Url::parse(&url_str)
.context("failed to parse base url")?;
- let host = if let Some(host) = url.host_str() {
- host
- } else {
+ let Some(host) = url.host_str() else {
return Err(anyhow::anyhow!(
"couldn't find host in rbw base url {}",
url_str
@@ -33,7 +31,7 @@ pub async fn register(
let client_id = rbw::pinentry::getpin(
&config_pinentry().await?,
"API key client__id",
- &format!("Log in to {}", host),
+ &format!("Log in to {host}"),
err.as_deref(),
tty,
false,
@@ -43,7 +41,7 @@ pub async fn register(
let client_secret = rbw::pinentry::getpin(
&config_pinentry().await?,
"API key client__secret",
- &format!("Log in to {}", host),
+ &format!("Log in to {host}"),
err.as_deref(),
tty,
false,
@@ -61,10 +59,9 @@ pub async fn register(
message,
})
.context("failed to log in to bitwarden instance");
- } else {
- err_msg = Some(message);
- continue;
}
+ err_msg = Some(message);
+ continue;
}
Err(e) => {
return Err(e)
@@ -81,7 +78,7 @@ pub async fn register(
pub async fn login(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
tty: Option<&str>,
) -> anyhow::Result<()> {
let db = load_db().await.unwrap_or_else(|_| rbw::db::Db::new());
@@ -90,9 +87,7 @@ pub async fn login(
let url_str = config_base_url().await?;
let url = reqwest::Url::parse(&url_str)
.context("failed to parse base url")?;
- let host = if let Some(host) = url.host_str() {
- host
- } else {
+ let Some(host) = url.host_str() else {
return Err(anyhow::anyhow!(
"couldn't find host in rbw base url {}",
url_str
@@ -102,7 +97,7 @@ pub async fn login(
let email = config_email().await?;
let mut err_msg = None;
- for i in 1_u8..=3 {
+ 'attempts: for i in 1_u8..=3 {
let err = if i > 1 {
// this unwrap is safe because we only ever continue the loop
// if we have set err_msg
@@ -113,7 +108,7 @@ pub async fn login(
let password = rbw::pinentry::getpin(
&config_pinentry().await?,
"Master Password",
- &format!("Log in to {}", host),
+ &format!("Log in to {host}"),
err.as_deref(),
tty,
true,
@@ -126,55 +121,70 @@ pub async fn login(
Ok((
access_token,
refresh_token,
+ kdf,
iterations,
+ memory,
+ parallelism,
protected_key,
)) => {
login_success(
- sock,
- state,
+ state.clone(),
access_token,
refresh_token,
+ kdf,
iterations,
+ memory,
+ parallelism,
protected_key,
password,
db,
email,
)
.await?;
- break;
+ break 'attempts;
}
Err(rbw::error::Error::TwoFactorRequired { providers }) => {
- if providers.contains(
- &rbw::api::TwoFactorProviderType::Authenticator,
- ) {
- let (
- access_token,
- refresh_token,
- iterations,
- protected_key,
- ) = two_factor(
- tty,
- &email,
- password.clone(),
- rbw::api::TwoFactorProviderType::Authenticator,
- )
- .await?;
- login_success(
- sock,
- state,
- access_token,
- refresh_token,
- iterations,
- protected_key,
- password,
- db,
- email,
- )
- .await?;
- break;
- } else {
- return Err(anyhow::anyhow!("TODO"));
+ let supported_types = vec![
+ rbw::api::TwoFactorProviderType::Authenticator,
+ rbw::api::TwoFactorProviderType::Yubikey,
+ rbw::api::TwoFactorProviderType::Email,
+ ];
+
+ for provider in supported_types {
+ if providers.contains(&provider) {
+ let (
+ access_token,
+ refresh_token,
+ kdf,
+ iterations,
+ memory,
+ parallelism,
+ protected_key,
+ ) = two_factor(
+ tty,
+ &email,
+ password.clone(),
+ provider,
+ )
+ .await?;
+ login_success(
+ state.clone(),
+ access_token,
+ refresh_token,
+ kdf,
+ iterations,
+ memory,
+ parallelism,
+ protected_key,
+ password,
+ db,
+ email,
+ )
+ .await?;
+ break 'attempts;
+ }
}
+ return Err(anyhow::anyhow!("TODO"));
}
Err(rbw::error::Error::IncorrectPassword { message }) => {
if i == 3 {
@@ -182,10 +192,9 @@ pub async fn login(
message,
})
.context("failed to log in to bitwarden instance");
- } else {
- err_msg = Some(message);
- continue;
}
+ err_msg = Some(message);
+ continue;
}
Err(e) => {
return Err(e)
@@ -205,7 +214,15 @@ async fn two_factor(
email: &str,
password: rbw::locked::Password,
provider: rbw::api::TwoFactorProviderType,
-) -> anyhow::Result<(String, String, u32, String)> {
+) -> anyhow::Result<(
+ String,
+ String,
+ rbw::api::KdfType,
+ u32,
+ Option<u32>,
+ Option<u32>,
+ String,
+)> {
let mut err_msg = None;
for i in 1_u8..=3 {
let err = if i > 1 {
@@ -217,11 +234,11 @@ async fn two_factor(
};
let code = rbw::pinentry::getpin(
&config_pinentry().await?,
- "Authenticator App",
- "Enter the 6 digit verification code from your authenticator app.",
+ provider.header(),
+ provider.message(),
err.as_deref(),
tty,
- true,
+ provider.grab(),
)
.await
.context("failed to read code from pinentry")?;
@@ -235,11 +252,22 @@ async fn two_factor(
)
.await
{
- Ok((access_token, refresh_token, iterations, protected_key)) => {
+ Ok((
+ access_token,
+ refresh_token,
+ kdf,
+ iterations,
+ memory,
+ parallelism,
+ protected_key,
+ )) => {
return Ok((
access_token,
refresh_token,
+ kdf,
iterations,
+ memory,
+ parallelism,
protected_key,
))
}
@@ -249,10 +277,9 @@ async fn two_factor(
message,
})
.context("failed to log in to bitwarden instance");
- } else {
- err_msg = Some(message);
- continue;
}
+ err_msg = Some(message);
+ continue;
}
// can get this if the user passes an empty string
Err(rbw::error::Error::TwoFactorRequired { .. }) => {
@@ -262,10 +289,9 @@ async fn two_factor(
message,
})
.context("failed to log in to bitwarden instance");
- } else {
- err_msg = Some(message);
- continue;
}
+ err_msg = Some(message);
+ continue;
}
Err(e) => {
return Err(e)
@@ -278,11 +304,13 @@ async fn two_factor(
}
async fn login_success(
- sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
access_token: String,
refresh_token: String,
+ kdf: rbw::api::KdfType,
iterations: u32,
+ memory: Option<u32>,
+ parallelism: Option<u32>,
protected_key: String,
password: rbw::locked::Password,
mut db: rbw::db::Db,
@@ -290,35 +318,37 @@ async fn login_success(
) -> anyhow::Result<()> {
db.access_token = Some(access_token.to_string());
db.refresh_token = Some(refresh_token.to_string());
+ db.kdf = Some(kdf);
db.iterations = Some(iterations);
+ db.memory = memory;
+ db.parallelism = parallelism;
db.protected_key = Some(protected_key.to_string());
save_db(&db).await?;
- sync(sock, false).await?;
+ sync(None, state.clone()).await?;
let db = load_db().await?;
- let protected_private_key =
- if let Some(protected_private_key) = db.protected_private_key {
- protected_private_key
- } else {
- return Err(anyhow::anyhow!(
- "failed to find protected private key in db"
- ));
- };
+ let Some(protected_private_key) = db.protected_private_key else {
+ return Err(anyhow::anyhow!(
+ "failed to find protected private key in db"
+ ));
+ };
let res = rbw::actions::unlock(
&email,
&password,
+ kdf,
iterations,
+ memory,
+ parallelism,
&protected_key,
&protected_private_key,
&db.protected_org_keys,
- )
- .await;
+ );
match res {
Ok((keys, org_keys)) => {
- let mut state = state.write().await;
+ let mut state = state.lock().await;
state.priv_key = Some(keys);
state.org_keys = Some(org_keys);
}
@@ -330,39 +360,40 @@ async fn login_success(
pub async fn unlock(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
tty: Option<&str>,
) -> anyhow::Result<()> {
- if state.read().await.needs_unlock() {
+ if state.lock().await.needs_unlock() {
let db = load_db().await?;
- let iterations = if let Some(iterations) = db.iterations {
- iterations
- } else {
+ let Some(kdf) = db.kdf else {
+ return Err(anyhow::anyhow!("failed to find kdf type in db"));
+ };
+
+ let Some(iterations) = db.iterations else {
return Err(anyhow::anyhow!(
"failed to find number of iterations in db"
));
};
- let protected_key = if let Some(protected_key) = db.protected_key {
- protected_key
- } else {
+
+ let memory = db.memory;
+ let parallelism = db.parallelism;
+
+ let Some(protected_key) = db.protected_key else {
return Err(anyhow::anyhow!(
"failed to find protected key in db"
));
};
- let protected_private_key =
- if let Some(protected_private_key) = db.protected_private_key {
- protected_private_key
- } else {
- return Err(anyhow::anyhow!(
- "failed to find protected private key in db"
- ));
- };
+ let Some(protected_private_key) = db.protected_private_key else {
+ return Err(anyhow::anyhow!(
+ "failed to find protected private key in db"
+ ));
+ };
let email = config_email().await?;
let mut err_msg = None;
- for i in 1u8..=3 {
+ for i in 1_u8..=3 {
let err = if i > 1 {
// this unwrap is safe because we only ever continue the loop
// if we have set err_msg
@@ -373,7 +404,10 @@ pub async fn unlock(
let password = rbw::pinentry::getpin(
&config_pinentry().await?,
"Master Password",
- "Unlock the local database",
+ &format!(
+ "Unlock the local database for '{}'",
+ rbw::dirs::profile()
+ ),
err.as_deref(),
tty,
true,
@@ -383,13 +417,14 @@ pub async fn unlock(
match rbw::actions::unlock(
&email,
&password,
+ kdf,
iterations,
+ memory,
+ parallelism,
&protected_key,
&protected_private_key,
&db.protected_org_keys,
- )
- .await
- {
+ ) {
Ok((keys, org_keys)) => {
unlock_success(state, keys, org_keys).await?;
break;
@@ -400,10 +435,9 @@ pub async fn unlock(
message,
})
.context("failed to unlock database");
- } else {
- err_msg = Some(message);
- continue;
}
+ err_msg = Some(message);
+ continue;
}
Err(e) => return Err(e).context("failed to unlock database"),
}
@@ -416,11 +450,11 @@ pub async fn unlock(
}
async fn unlock_success(
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
keys: rbw::locked::Keys,
org_keys: std::collections::HashMap<String, rbw::locked::Keys>,
) -> anyhow::Result<()> {
- let mut state = state.write().await;
+ let mut state = state.lock().await;
state.priv_key = Some(keys);
state.org_keys = Some(org_keys);
Ok(())
@@ -428,9 +462,9 @@ async fn unlock_success(
pub async fn lock(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
) -> anyhow::Result<()> {
- state.write().await.clear();
+ state.lock().await.clear();
respond_ack(sock).await?;
@@ -439,10 +473,10 @@ pub async fn lock(
pub async fn check_lock(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
_tty: Option<&str>,
) -> anyhow::Result<()> {
- if state.read().await.needs_unlock() {
+ if state.lock().await.needs_unlock() {
return Err(anyhow::anyhow!("agent is locked"));
}
@@ -452,8 +486,8 @@ pub async fn check_lock(
}
pub async fn sync(
- sock: &mut crate::sock::Sock,
- ack: bool,
+ sock: Option<&mut crate::sock::Sock>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
) -> anyhow::Result<()> {
let mut db = load_db().await?;
@@ -482,7 +516,11 @@ pub async fn sync(
db.entries = entries;
save_db(&db).await?;
- if ack {
+ if let Err(e) = subscribe_to_notifications(state.clone()).await {
+ eprintln!("failed to subscribe to notifications: {e}");
+ }
+
+ if let Some(sock) = sock {
respond_ack(sock).await?;
}
@@ -491,14 +529,12 @@ pub async fn sync(
pub async fn decrypt(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
cipherstring: &str,
org_id: Option<&str>,
) -> anyhow::Result<()> {
- let state = state.read().await;
- let keys = if let Some(keys) = state.key(org_id) {
- keys
- } else {
+ let state = state.lock().await;
+ let Some(keys) = state.key(org_id) else {
return Err(anyhow::anyhow!(
"failed to find decryption keys in in-memory state"
));
@@ -519,14 +555,12 @@ pub async fn decrypt(
pub async fn encrypt(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<crate::agent::State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
plaintext: &str,
org_id: Option<&str>,
) -> anyhow::Result<()> {
- let state = state.read().await;
- let keys = if let Some(keys) = state.key(org_id) {
- keys
- } else {
+ let state = state.lock().await;
+ let Some(keys) = state.key(org_id) else {
return Err(anyhow::anyhow!(
"failed to find encryption keys in in-memory state"
));
@@ -542,6 +576,25 @@ pub async fn encrypt(
Ok(())
}
+pub async fn clipboard_store(
+ sock: &mut crate::sock::Sock,
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
+ text: &str,
+) -> anyhow::Result<()> {
+ state
+ .lock()
+ .await
+ .clipboard
+ .set_contents(text.to_owned())
+ .map_err(|e| {
+ anyhow::anyhow!("couldn't store value to clipboard: {e}")
+ })?;
+
+ respond_ack(sock).await?;
+
+ Ok(())
+}
+
pub async fn version(sock: &mut crate::sock::Sock) -> anyhow::Result<()> {
sock.send(&rbw::protocol::Response::Version {
version: rbw::protocol::version(),
@@ -579,11 +632,10 @@ async fn respond_encrypt(
async fn config_email() -> anyhow::Result<String> {
let config = rbw::config::Config::load_async().await?;
- if let Some(email) = config.email {
- Ok(email)
- } else {
- Err(anyhow::anyhow!("failed to find email address in config"))
- }
+ config.email.map_or_else(
+ || Err(anyhow::anyhow!("failed to find email address in config")),
+ Ok,
+ )
}
async fn load_db() -> anyhow::Result<rbw::db::Db> {
@@ -617,3 +669,35 @@ async fn config_pinentry() -> anyhow::Result<String> {
let config = rbw::config::Config::load_async().await?;
Ok(config.pinentry)
}
+
+pub async fn subscribe_to_notifications(
+ state: std::sync::Arc<tokio::sync::Mutex<crate::agent::State>>,
+) -> anyhow::Result<()> {
+ if state.lock().await.notifications_handler.is_connected() {
+ return Ok(());
+ }
+
+ let config = rbw::config::Config::load_async()
+ .await
+ .context("Config is missing")?;
+ let email = config.email.clone().context("Config is missing email")?;
+ let db = rbw::db::Db::load_async(config.server_name().as_str(), &email)
+ .await?;
+ let access_token =
+ db.access_token.context("Error getting access token")?;
+
+ let websocket_url = format!(
+ "{}/hub?access_token={}",
+ config.notifications_url(),
+ access_token
+ )
+ .replace("https://", "wss://");
+
+ let mut state = state.lock().await;
+ state
+ .notifications_handler
+ .connect(websocket_url)
+ .await
+ .err()
+ .map_or_else(|| Ok(()), |err| Err(anyhow::anyhow!(err.to_string())))
+}
diff --git a/src/bin/rbw-agent/agent.rs b/src/bin/rbw-agent/agent.rs
index fae8c7b..1345967 100644
--- a/src/bin/rbw-agent/agent.rs
+++ b/src/bin/rbw-agent/agent.rs
@@ -1,24 +1,25 @@
use anyhow::Context as _;
+use futures_util::StreamExt as _;
-#[derive(Debug)]
-pub enum TimeoutEvent {
- Set,
- Clear,
-}
+use crate::notifications;
pub struct State {
pub priv_key: Option<rbw::locked::Keys>,
pub org_keys:
Option<std::collections::HashMap<String, rbw::locked::Keys>>,
- pub timeout_chan: tokio::sync::mpsc::UnboundedSender<TimeoutEvent>,
+ pub timeout: crate::timeout::Timeout,
+ pub timeout_duration: std::time::Duration,
+ pub sync_timeout: crate::timeout::Timeout,
+ pub sync_timeout_duration: std::time::Duration,
+ pub notifications_handler: crate::notifications::Handler,
+ pub clipboard: Box<dyn copypasta::ClipboardProvider>,
}
impl State {
pub fn key(&self, org_id: Option<&str>) -> Option<&rbw::locked::Keys> {
- match org_id {
- Some(id) => self.org_keys.as_ref().and_then(|h| h.get(id)),
- None => self.priv_key.as_ref(),
- }
+ org_id.map_or(self.priv_key.as_ref(), |id| {
+ self.org_keys.as_ref().and_then(|h| h.get(id))
+ })
}
pub fn needs_unlock(&self) -> bool {
@@ -26,103 +27,163 @@ impl State {
}
pub fn set_timeout(&mut self) {
- // no real better option to unwrap here
- self.timeout_chan.send(TimeoutEvent::Set).unwrap();
+ self.timeout.set(self.timeout_duration);
}
pub fn clear(&mut self) {
self.priv_key = None;
- self.org_keys = Default::default();
- // no real better option to unwrap here
- self.timeout_chan.send(TimeoutEvent::Clear).unwrap();
+ self.org_keys = None;
+ self.timeout.clear();
+ }
+
+ pub fn set_sync_timeout(&mut self) {
+ self.sync_timeout.set(self.sync_timeout_duration);
}
}
pub struct Agent {
- timeout_duration: tokio::time::Duration,
- timeout: Option<std::pin::Pin<Box<tokio::time::Sleep>>>,
- timeout_chan: tokio::sync::mpsc::UnboundedReceiver<TimeoutEvent>,
- state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ timer_r: tokio::sync::mpsc::UnboundedReceiver<()>,
+ sync_timer_r: tokio::sync::mpsc::UnboundedReceiver<()>,
+ state: std::sync::Arc<tokio::sync::Mutex<State>>,
}
impl Agent {
pub fn new() -> anyhow::Result<Self> {
let config = rbw::config::Config::load()?;
let timeout_duration =
- tokio::time::Duration::from_secs(config.lock_timeout);
- let (w, r) = tokio::sync::mpsc::unbounded_channel();
+ std::time::Duration::from_secs(config.lock_timeout);
+ let sync_timeout_duration =
+ std::time::Duration::from_secs(config.sync_interval);
+ let (timeout, timer_r) = crate::timeout::Timeout::new();
+ let (sync_timeout, sync_timer_r) = crate::timeout::Timeout::new();
+ if sync_timeout_duration > std::time::Duration::ZERO {
+ sync_timeout.set(sync_timeout_duration);
+ }
+ let notifications_handler = crate::notifications::Handler::new();
+ let clipboard: Box<dyn copypasta::ClipboardProvider> =
+ copypasta::ClipboardContext::new().map_or_else(
+ |e| {
+ log::warn!("couldn't create clipboard context: {e}");
+ let clipboard = Box::new(
+ // infallible
+ copypasta::nop_clipboard::NopClipboardContext::new()
+ .unwrap(),
+ );
+ let clipboard: Box<dyn copypasta::ClipboardProvider> =
+ clipboard;
+ clipboard
+ },
+ |clipboard| {
+ let clipboard = Box::new(clipboard);
+ let clipboard: Box<dyn copypasta::ClipboardProvider> =
+ clipboard;
+ clipboard
+ },
+ );
Ok(Self {
- timeout_duration,
- timeout: None,
- timeout_chan: r,
- state: std::sync::Arc::new(tokio::sync::RwLock::new(State {
+ timer_r,
+ sync_timer_r,
+ state: std::sync::Arc::new(tokio::sync::Mutex::new(State {
priv_key: None,
- org_keys: Default::default(),
- timeout_chan: w,
+ org_keys: None,
+ timeout,
+ timeout_duration,
+ sync_timeout,
+ sync_timeout_duration,
+ notifications_handler,
+ clipboard,
})),
})
}
- fn set_timeout(&mut self) {
- self.timeout =
- Some(Box::pin(tokio::time::sleep(self.timeout_duration)));
- }
-
- fn clear_timeout(&mut self) {
- self.timeout = None;
- }
-
pub async fn run(
- &mut self,
+ self,
listener: tokio::net::UnixListener,
) -> anyhow::Result<()> {
- // tokio only supports timeouts up to 2^36 milliseconds
- let mut forever = Box::pin(tokio::time::sleep(
- tokio::time::Duration::from_secs(60 * 60 * 24 * 365 * 2),
- ));
- loop {
- let timeout = if let Some(timeout) = &mut self.timeout {
- timeout
- } else {
- &mut forever
- };
- tokio::select! {
- sock = listener.accept() => {
+ pub enum Event {
+ Request(std::io::Result<tokio::net::UnixStream>),
+ Timeout(()),
+ Sync(()),
+ }
+
+ let notifications = self
+ .state
+ .lock()
+ .await
+ .notifications_handler
+ .get_channel()
+ .await;
+ let notifications =
+ tokio_stream::wrappers::UnboundedReceiverStream::new(
+ notifications,
+ )
+ .map(|message| match message {
+ notifications::Message::Logout => Event::Timeout(()),
+ notifications::Message::Sync => Event::Sync(()),
+ })
+ .boxed();
+
+ let mut stream = futures_util::stream::select_all([
+ tokio_stream::wrappers::UnixListenerStream::new(listener)
+ .map(Event::Request)
+ .boxed(),
+ tokio_stream::wrappers::UnboundedReceiverStream::new(
+ self.timer_r,
+ )
+ .map(Event::Timeout)
+ .boxed(),
+ tokio_stream::wrappers::UnboundedReceiverStream::new(
+ self.sync_timer_r,
+ )
+ .map(Event::Sync)
+ .boxed(),
+ notifications,
+ ]);
+ while let Some(event) = stream.next().await {
+ match event {
+ Event::Request(res) => {
let mut sock = crate::sock::Sock::new(
- sock.context("failed to accept incoming connection")?.0
+ res.context("failed to accept incoming connection")?,
);
let state = self.state.clone();
tokio::spawn(async move {
- let res
- = handle_request(&mut sock, state.clone()).await;
+ let res =
+ handle_request(&mut sock, state.clone()).await;
if let Err(e) = res {
// unwrap is the only option here
sock.send(&rbw::protocol::Response::Error {
- error: format!("{:#}", e),
- }).await.unwrap();
+ error: format!("{e:#}"),
+ })
+ .await
+ .unwrap();
}
});
}
- _ = timeout => {
+ Event::Timeout(()) => {
+ self.state.lock().await.clear();
+ }
+ Event::Sync(()) => {
let state = self.state.clone();
- tokio::spawn(async move{
- state.write().await.clear();
+ tokio::spawn(async move {
+ // this could fail if we aren't logged in, but we
+ // don't care about that
+ if let Err(e) =
+ crate::actions::sync(None, state.clone()).await
+ {
+ eprintln!("failed to sync: {e:#}");
+ }
});
- }
- Some(ev) = self.timeout_chan.recv() => {
- match ev {
- TimeoutEvent::Set => self.set_timeout(),
- TimeoutEvent::Clear => self.clear_timeout(),
- }
+ self.state.lock().await.set_sync_timeout();
}
}
}
+ Ok(())
}
}
async fn handle_request(
sock: &mut crate::sock::Sock,
- state: std::sync::Arc<tokio::sync::RwLock<State>>,
+ state: std::sync::Arc<tokio::sync::Mutex<State>>,
) -> anyhow::Result<()> {
let req = sock.recv().await?;
let req = match req {
@@ -161,7 +222,7 @@ async fn handle_request(
false
}
rbw::protocol::Action::Sync => {
- crate::actions::sync(sock, true).await?;
+ crate::actions::sync(Some(sock), state.clone()).await?;
false
}
rbw::protocol::Action::Decrypt {
@@ -187,6 +248,11 @@ async fn handle_request(
.await?;
true
}
+ rbw::protocol::Action::ClipboardStore { text } => {
+ crate::actions::clipboard_store(sock, state.clone(), text)
+ .await?;
+ true
+ }
rbw::protocol::Action::Quit => std::process::exit(0),
rbw::protocol::Action::Version => {
crate::actions::version(sock).await?;
@@ -195,7 +261,7 @@ async fn handle_request(
};
if set_timeout {
- state.write().await.set_timeout();
+ state.lock().await.set_timeout();
}
Ok(())
diff --git a/src/bin/rbw-agent/daemon.rs b/src/bin/rbw-agent/daemon.rs
index 923a217..8cb9998 100644
--- a/src/bin/rbw-agent/daemon.rs
+++ b/src/bin/rbw-agent/daemon.rs
@@ -30,32 +30,37 @@ pub fn daemonize() -> anyhow::Result<StartupAck> {
.open(rbw::dirs::agent_stderr_file())?;
let (r, w) = nix::unistd::pipe()?;
- let res = daemonize::Daemonize::new()
+ let daemonize = daemonize::Daemonize::new()
.pid_file(rbw::dirs::pid_file())
.stdout(stdout)
- .stderr(stderr)
- .exit_action(move || {
+ .stderr(stderr);
+ let res = match daemonize.execute() {
+ daemonize::Outcome::Parent(_) => {
// unwraps are necessary because not really a good way to handle
// errors here otherwise
let _ = nix::unistd::close(w);
let mut buf = [0; 1];
nix::unistd::read(r, &mut buf).unwrap();
nix::unistd::close(r).unwrap();
- })
- .start();
+ std::process::exit(0);
+ }
+ daemonize::Outcome::Child(res) => res,
+ };
+
let _ = nix::unistd::close(r);
match res {
Ok(_) => (),
Err(e) => {
- match e {
- daemonize::DaemonizeError::LockPidfile(_) => {
- // this means that there is already an agent running, so
- // return a special exit code to allow the cli to detect
- // this case and not error out
- std::process::exit(23);
- }
- _ => panic!("failed to daemonize: {}", e),
+ // XXX super gross, but daemonize removed the ability to match
+ // on specific error types for some reason?
+ if e.to_string().contains("unable to lock pid file") {
+ // this means that there is already an agent running, so
+ // return a special exit code to allow the cli to detect
+ // this case and not error out
+ std::process::exit(23);
+ } else {
+ panic!("failed to daemonize: {e}");
}
}
}
diff --git a/src/bin/rbw-agent/main.rs b/src/bin/rbw-agent/main.rs
index 69411ae..8434c72 100644
--- a/src/bin/rbw-agent/main.rs
+++ b/src/bin/rbw-agent/main.rs
@@ -1,4 +1,19 @@
+#![warn(clippy::cargo)]
+#![warn(clippy::pedantic)]
+#![warn(clippy::nursery)]
+#![warn(clippy::as_conversions)]
+#![warn(clippy::get_unwrap)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::missing_const_for_fn)]
+#![allow(clippy::similar_names)]
+#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+#![allow(clippy::type_complexity)]
+#![allow(clippy::multiple_crate_versions)]
+#![allow(clippy::large_enum_variant)]
+// this one looks plausibly useful, but currently has too many bugs
+#![allow(clippy::significant_drop_tightening)]
use anyhow::Context as _;
@@ -6,7 +21,9 @@ mod actions;
mod agent;
mod daemon;
mod debugger;
+mod notifications;
mod sock;
+mod timeout;
async fn tokio_main(
startup_ack: Option<crate::daemon::StartupAck>,
@@ -17,7 +34,7 @@ async fn tokio_main(
startup_ack.ack()?;
}
- let mut agent = crate::agent::Agent::new()?;
+ let agent = crate::agent::Agent::new()?;
agent.run(listener).await?;
Ok(())
@@ -29,11 +46,9 @@ fn real_main() -> anyhow::Result<()> {
)
.init();
- let no_daemonize = if let Some(arg) = std::env::args().nth(1) {
- arg == "--no-daemonize"
- } else {
- false
- };
+ let no_daemonize = std::env::args()
+ .nth(1)
+ .map_or(false, |arg| arg == "--no-daemonize");
let startup_ack = if no_daemonize {
None
@@ -69,7 +84,7 @@ fn main() {
if let Err(e) = res {
// XXX log file?
- eprintln!("{:#}", e);
+ eprintln!("{e:#}");
std::process::exit(1);
}
}
diff --git a/src/bin/rbw-agent/notifications.rs b/src/bin/rbw-agent/notifications.rs
new file mode 100644
index 0000000..8176603
--- /dev/null
+++ b/src/bin/rbw-agent/notifications.rs
@@ -0,0 +1,174 @@
+use futures_util::{SinkExt as _, StreamExt as _};
+
+#[derive(Clone, Copy, Debug)]
+pub enum Message {
+ Sync,
+ Logout,
+}
+
+pub struct Handler {
+ write: Option<
+ futures::stream::SplitSink<
+ tokio_tungstenite::WebSocketStream<
+ tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
+ >,
+ tokio_tungstenite::tungstenite::Message,
+ >,
+ >,
+ read_handle: Option<tokio::task::JoinHandle<()>>,
+ sending_channels: std::sync::Arc<
+ tokio::sync::RwLock<Vec<tokio::sync::mpsc::UnboundedSender<Message>>>,
+ >,
+}
+
+impl Handler {
+ pub fn new() -> Self {
+ Self {
+ write: None,
+ read_handle: None,
+ sending_channels: std::sync::Arc::new(tokio::sync::RwLock::new(
+ Vec::new(),
+ )),
+ }
+ }
+
+ pub async fn connect(
+ &mut self,
+ url: String,
+ ) -> Result<(), Box<dyn std::error::Error>> {
+ if self.is_connected() {
+ self.disconnect().await?;
+ }
+
+ let (write, read_handle) =
+ subscribe_to_notifications(url, self.sending_channels.clone())
+ .await?;
+
+ self.write = Some(write);
+ self.read_handle = Some(read_handle);
+ Ok(())
+ }
+
+ pub fn is_connected(&self) -> bool {
+ self.write.is_some()
+ && self.read_handle.is_some()
+ && !self.read_handle.as_ref().unwrap().is_finished()
+ }
+
+ pub async fn disconnect(
+ &mut self,
+ ) -> Result<(), Box<dyn std::error::Error>> {
+ self.sending_channels.write().await.clear();
+ if let Some(mut write) = self.write.take() {
+ write
+ .send(tokio_tungstenite::tungstenite::Message::Close(None))
+ .await?;
+ write.close().await?;
+ self.read_handle.take().unwrap().await?;
+ }
+ self.write = None;
+ self.read_handle = None;
+ Ok(())
+ }
+
+ pub async fn get_channel(
+ &mut self,
+ ) -> tokio::sync::mpsc::UnboundedReceiver<Message> {
+ let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
+ self.sending_channels.write().await.push(tx);
+ rx
+ }
+}
+
+async fn subscribe_to_notifications(
+ url: String,
+ sending_channels: std::sync::Arc<
+ tokio::sync::RwLock<Vec<tokio::sync::mpsc::UnboundedSender<Message>>>,
+ >,
+) -> Result<
+ (
+ futures_util::stream::SplitSink<
+ tokio_tungstenite::WebSocketStream<
+ tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
+ >,
+ tokio_tungstenite::tungstenite::Message,
+ >,
+ tokio::task::JoinHandle<()>,
+ ),
+ Box<dyn std::error::Error>,
+> {
+ let url = url::Url::parse(url.as_str())?;
+ let (ws_stream, _response) =
+ tokio_tungstenite::connect_async(url).await?;
+ let (mut write, read) = ws_stream.split();
+
+ write
+ .send(tokio_tungstenite::tungstenite::Message::Text(
+ "{\"protocol\":\"messagepack\",\"version\":1}\x1e".to_string(),
+ ))
+ .await
+ .unwrap();
+
+ let read_future = async move {
+ let sending_channels = &sending_channels;
+ read.for_each(|message| async move {
+ match message {
+ Ok(message) => {
+ if let Some(message) = parse_message(message) {
+ let sending_channels = sending_channels.read().await;
+ let sending_channels = sending_channels.as_slice();
+ for channel in sending_channels {
+ channel.send(message).unwrap();
+ }
+ }
+ }
+ Err(e) => {
+ eprintln!("websocket error: {e:?}");
+ }
+ }
+ })
+ .await;
+ };
+
+ Ok((write, tokio::spawn(read_future)))
+}
+
+fn parse_message(
+ message: tokio_tungstenite::tungstenite::Message,
+) -> Option<Message> {
+ let tokio_tungstenite::tungstenite::Message::Binary(data) = message
+ else {
+ return None;
+ };
+
+ // the first few bytes with the 0x80 bit set, plus one byte terminating the length contain the length of the message
+ let len_buffer_length = data.iter().position(|&x| (x & 0x80) == 0)? + 1;
+
+ let unpacked_messagepack =
+ rmpv::decode::read_value(&mut &data[len_buffer_length..]).ok()?;
+
+ let unpacked_message = unpacked_messagepack.as_array()?;
+ let message_type = unpacked_message.first()?.as_u64()?;
+ // invocation
+ if message_type != 1 {
+ return None;
+ }
+ let target = unpacked_message.get(3)?.as_str()?;
+ if target != "ReceiveMessage" {
+ return None;
+ }
+
+ let args = unpacked_message.get(4)?.as_array()?;
+ let map = args.first()?.as_map()?;
+ for (k, v) in map {
+ if k.as_str()? == "Type" {
+ let ty = v.as_i64()?;
+ return match ty {
+ 11 => Some(Message::Logout),
+ _ => Some(Message::Sync),
+ };
+ }
+ }
+
+ None
+}
diff --git a/src/bin/rbw-agent/sock.rs b/src/bin/rbw-agent/sock.rs
index 311176c..280b8cc 100644
--- a/src/bin/rbw-agent/sock.rs
+++ b/src/bin/rbw-agent/sock.rs
@@ -36,9 +36,8 @@ impl Sock {
buf.read_line(&mut line)
.await
.context("failed to read message from socket")?;
- Ok(serde_json::from_str(&line).map_err(|e| {
- format!("failed to parse message '{}': {}", line, e)
- }))
+ Ok(serde_json::from_str(&line)
+ .map_err(|e| format!("failed to parse message '{line}': {e}")))
}
}
diff --git a/src/bin/rbw-agent/timeout.rs b/src/bin/rbw-agent/timeout.rs
new file mode 100644
index 0000000..e2aba06
--- /dev/null
+++ b/src/bin/rbw-agent/timeout.rs
@@ -0,0 +1,66 @@
+use futures_util::StreamExt as _;
+
+#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
+enum Streams {
+ Requests,
+ Timer,
+}
+
+#[derive(Debug)]
+enum Action {
+ Set(std::time::Duration),
+ Clear,
+}
+
+pub struct Timeout {
+ req_w: tokio::sync::mpsc::UnboundedSender<Action>,
+}
+
+impl Timeout {
+ pub fn new() -> (Self, tokio::sync::mpsc::UnboundedReceiver<()>) {
+ let (req_w, req_r) = tokio::sync::mpsc::unbounded_channel();
+ let (timer_w, timer_r) = tokio::sync::mpsc::unbounded_channel();
+ tokio::spawn(async move {
+ enum Event {
+ Request(Action),
+ Timer,
+ }
+ let mut stream = tokio_stream::StreamMap::new();
+ stream.insert(
+ Streams::Requests,
+ tokio_stream::wrappers::UnboundedReceiverStream::new(req_r)
+ .map(Event::Request)
+ .boxed(),
+ );
+ while let Some(event) = stream.next().await {
+ match event {
+ (_, Event::Request(Action::Set(dur))) => {
+ stream.insert(
+ Streams::Timer,
+ futures_util::stream::once(tokio::time::sleep(
+ dur,
+ ))
+ .map(|()| Event::Timer)
+ .boxed(),
+ );
+ }
+ (_, Event::Request(Action::Clear)) => {
+ stream.remove(&Streams::Timer);
+ }
+ (_, Event::Timer) => {
+ timer_w.send(()).unwrap();
+ }
+ }
+ }
+ });
+ (Self { req_w }, timer_r)
+ }
+
+ pub fn set(&self, dur: std::time::Duration) {
+ self.req_w.send(Action::Set(dur)).unwrap();
+ }
+
+ pub fn clear(&self) {
+ self.req_w.send(Action::Clear).unwrap();
+ }
+}
diff --git a/src/bin/rbw/actions.rs b/src/bin/rbw/actions.rs
index 39fde15..e0e7a2e 100644
--- a/src/bin/rbw/actions.rs
+++ b/src/bin/rbw/actions.rs
@@ -31,11 +31,11 @@ pub fn quit() -> anyhow::Result<()> {
let pidfile = rbw::dirs::pid_file();
let mut pid = String::new();
std::fs::File::open(pidfile)?.read_to_string(&mut pid)?;
- let pid = nix::unistd::Pid::from_raw(pid.parse()?);
+ let pid = nix::unistd::Pid::from_raw(pid.trim_end().parse()?);
sock.send(&rbw::protocol::Request {
- tty: nix::unistd::ttyname(0)
- .ok()
- .and_then(|p| p.to_str().map(|s| s.to_string())),
+ tty: nix::unistd::ttyname(0).ok().and_then(|p| {
+ p.to_str().map(std::string::ToString::to_string)
+ }),
action: rbw::protocol::Action::Quit,
})?;
wait_for_exit(pid);
@@ -59,7 +59,7 @@ pub fn decrypt(
sock.send(&rbw::protocol::Request {
tty: nix::unistd::ttyname(0)
.ok()
- .and_then(|p| p.to_str().map(|s| s.to_string())),
+ .and_then(|p| p.to_str().map(std::string::ToString::to_string)),
action: rbw::protocol::Action::Decrypt {
cipherstring: cipherstring.to_string(),
org_id: org_id.map(std::string::ToString::to_string),
@@ -84,7 +84,7 @@ pub fn encrypt(
sock.send(&rbw::protocol::Request {
tty: nix::unistd::ttyname(0)
.ok()
- .and_then(|p| p.to_str().map(|s| s.to_string())),
+ .and_then(|p| p.to_str().map(std::string::ToString::to_string)),
action: rbw::protocol::Action::Encrypt {
plaintext: plaintext.to_string(),
org_id: org_id.map(std::string::ToString::to_string),
@@ -101,12 +101,18 @@ pub fn encrypt(
}
}
+pub fn clipboard_store(text: &str) -> anyhow::Result<()> {
+ simple_action(rbw::protocol::Action::ClipboardStore {
+ text: text.to_string(),
+ })
+}
+
pub fn version() -> anyhow::Result<u32> {
let mut sock = connect()?;
sock.send(&rbw::protocol::Request {
tty: nix::unistd::ttyname(0)
.ok()
- .and_then(|p| p.to_str().map(|s| s.to_string())),
+ .and_then(|p| p.to_str().map(std::string::ToString::to_string)),
action: rbw::protocol::Action::Version,
})?;
@@ -126,7 +132,7 @@ fn simple_action(action: rbw::protocol::Action) -> anyhow::Result<()> {
sock.send(&rbw::protocol::Request {
tty: nix::unistd::ttyname(0)
.ok()
- .and_then(|p| p.to_str().map(|s| s.to_string())),
+ .and_then(|p| p.to_str().map(std::string::ToString::to_string)),
action,
})?;
diff --git a/src/bin/rbw/commands.rs b/src/bin/rbw/commands.rs
index 9efd966..a4c0a26 100644
--- a/src/bin/rbw/commands.rs
+++ b/src/bin/rbw/commands.rs
@@ -1,4 +1,7 @@
use anyhow::Context as _;
+use serde::Serialize;
+use std::io;
+use std::io::prelude::Write;
const MISSING_CONFIG_HELP: &str =
"Before using rbw, you must configure the email address you would like to \
@@ -10,7 +13,7 @@ const MISSING_CONFIG_HELP: &str =
and, if your server has a non-default identity url:\n\n \
rbw config set identity_url <url>\n";
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize)]
#[cfg_attr(test, derive(Eq, PartialEq))]
struct DecryptedCipher {
id: String,
@@ -23,25 +26,25 @@ struct DecryptedCipher {
}
impl DecryptedCipher {
- fn display_short(&self, desc: &str) -> bool {
+ fn display_short(&self, desc: &str, clipboard: bool) -> bool {
match &self.data {
DecryptedData::Login { password, .. } => {
- if let Some(password) = password {
- println!("{}", password);
- true
- } else {
- eprintln!("entry for '{}' had no password", desc);
- false
- }
+ password.as_ref().map_or_else(
+ || {
+ eprintln!("entry for '{desc}' had no password");
+ false
+ },
+ |password| val_display_or_store(clipboard, password),
+ )
}
DecryptedData::Card { number, .. } => {
- if let Some(number) = number {
- println!("{}", number);
- true
- } else {
- eprintln!("entry for '{}' had no card number", desc);
- false
- }
+ number.as_ref().map_or_else(
+ || {
+ eprintln!("entry for '{desc}' had no card number");
+ false
+ },
+ |number| val_display_or_store(clipboard, number),
+ )
}
DecryptedData::Identity {
title,
@@ -54,30 +57,272 @@ impl DecryptedCipher {
[title, first_name, middle_name, last_name]
.iter()
.copied()
- .cloned()
.flatten()
+ .cloned()
.collect();
if names.is_empty() {
- eprintln!("entry for '{}' had no name", desc);
+ eprintln!("entry for '{desc}' had no name");
false
} else {
- println!("{}", names.join(" "));
- true
+ val_display_or_store(clipboard, &names.join(" "))
}
}
- DecryptedData::SecureNote {} => {
- if let Some(notes) = &self.notes {
- println!("{}", notes);
- true
- } else {
- eprintln!("entry for '{}' had no notes", desc);
+ DecryptedData::SecureNote {} => self.notes.as_ref().map_or_else(
+ || {
+ eprintln!("entry for '{desc}' had no notes");
false
+ },
+ |notes| val_display_or_store(clipboard, notes),
+ ),
+ }
+ }
+
+ fn display_field(&self, desc: &str, field: &str, clipboard: bool) {
+ let field = field.to_lowercase();
+ let field = field.as_str();
+ match &self.data {
+ DecryptedData::Login {
+ username,
+ totp,
+ uris,
+ ..
+ } => match field {
+ "notes" => {
+ if let Some(notes) = &self.notes {
+ val_display_or_store(clipboard, notes);
+ }
}
- }
+ "username" | "user" => {
+ if let Some(username) = &username {
+ val_display_or_store(clipboard, username);
+ }
+ }
+ "totp" | "code" => {
+ if let Some(totp) = totp {
+ match generate_totp(totp) {
+ Ok(code) => {
+ val_display_or_store(clipboard, &code);
+ }
+ Err(e) => {
+ eprintln!("{e}");
+ }
+ }
+ }
+ }
+ "uris" | "urls" | "sites" => {
+ if let Some(uris) = uris {
+ let uri_strs: Vec<_> = uris
+ .iter()
+ .map(|uri| uri.uri.to_string())
+ .collect();
+ val_display_or_store(clipboard, &uri_strs.join("\n"));
+ }
+ }
+ "password" => {
+ self.display_short(desc, clipboard);
+ }
+ _ => {
+ for f in &self.fields {
+ if let Some(name) = &f.name {
+ if name.to_lowercase().as_str().contains(field) {
+ val_display_or_store(
+ clipboard,
+ f.value.as_deref().unwrap_or(""),
+ );
+ break;
+ }
+ }
+ }
+ }
+ },
+ DecryptedData::Card {
+ cardholder_name,
+ brand,
+ exp_month,
+ exp_year,
+ code,
+ ..
+ } => match field {
+ "number" | "card" => {
+ self.display_short(desc, clipboard);
+ }
+ "exp" => {
+ if let (Some(month), Some(year)) = (exp_month, exp_year) {
+ val_display_or_store(
+ clipboard,
+ &format!("{month}/{year}"),
+ );
+ }
+ }
+ "exp_month" | "month" => {
+ if let Some(exp_month) = exp_month {
+ val_display_or_store(clipboard, exp_month);
+ }
+ }
+ "exp_year" | "year" => {
+ if let Some(exp_year) = exp_year {
+ val_display_or_store(clipboard, exp_year);
+ }
+ }
+ "cvv" => {
+ if let Some(code) = code {
+ val_display_or_store(clipboard, code);
+ }
+ }
+ "name" | "cardholder" => {
+ if let Some(cardholder_name) = cardholder_name {
+ val_display_or_store(clipboard, cardholder_name);
+ }
+ }
+ "brand" | "type" => {
+ if let Some(brand) = brand {
+ val_display_or_store(clipboard, brand);
+ }
+ }
+ "notes" => {
+ if let Some(notes) = &self.notes {
+ val_display_or_store(clipboard, notes);
+ }
+ }
+ _ => {
+ for f in &self.fields {
+ if let Some(name) = &f.name {
+ if name.to_lowercase().as_str().contains(field) {
+ val_display_or_store(
+ clipboard,
+ f.value.as_deref().unwrap_or(""),
+ );
+ break;
+ }
+ }
+ }
+ }
+ },
+ DecryptedData::Identity {
+ address1,
+ address2,
+ address3,
+ city,
+ state,
+ postal_code,
+ country,
+ phone,
+ email,
+ ssn,
+ license_number,
+ passport_number,
+ username,
+ ..
+ } => match field {
+ "name" => {
+ self.display_short(desc, clipboard);
+ }
+ "email" => {
+ if let Some(email) = email {
+ val_display_or_store(clipboard, email);
+ }
+ }
+ "address" => {
+ let mut strs = vec![];
+ if let Some(address1) = address1 {
+ strs.push(address1.clone());
+ }
+ if let Some(address2) = address2 {
+ strs.push(address2.clone());
+ }
+ if let Some(address3) = address3 {
+ strs.push(address3.clone());
+ }
+ if !strs.is_empty() {
+ val_display_or_store(clipboard, &strs.join("\n"));
+ }
+ }
+ "city" => {
+ if let Some(city) = city {
+ val_display_or_store(clipboard, city);
+ }
+ }
+ "state" => {
+ if let Some(state) = state {
+ val_display_or_store(clipboard, state);
+ }
+ }
+ "postcode" | "zipcode" | "zip" => {
+ if let Some(postal_code) = postal_code {
+ val_display_or_store(clipboard, postal_code);
+ }
+ }
+ "country" => {
+ if let Some(country) = country {
+ val_display_or_store(clipboard, country);
+ }
+ }
+ "phone" => {
+ if let Some(phone) = phone {
+ val_display_or_store(clipboard, phone);
+ }
+ }
+ "ssn" => {
+ if let Some(ssn) = ssn {
+ val_display_or_store(clipboard, ssn);
+ }
+ }
+ "license" => {
+ if let Some(license_number) = license_number {
+ val_display_or_store(clipboard, license_number);
+ }
+ }
+ "passport" => {
+ if let Some(passport_number) = passport_number {
+ val_display_or_store(clipboard, passport_number);
+ }
+ }
+ "username" => {
+ if let Some(username) = username {
+ val_display_or_store(clipboard, username);
+ }
+ }
+ "notes" => {
+ if let Some(notes) = &self.notes {
+ val_display_or_store(clipboard, notes);
+ }
+ }
+ _ => {
+ for f in &self.fields {
+ if let Some(name) = &f.name {
+ if name.to_lowercase().as_str().contains(field) {
+ val_display_or_store(
+ clipboard,
+ f.value.as_deref().unwrap_or(""),
+ );
+ break;
+ }
+ }
+ }
+ }
+ },
+ DecryptedData::SecureNote {} => match field {
+ "note" | "notes" => {
+ self.display_short(desc, clipboard);
+ }
+ _ => {
+ for f in &self.fields {
+ if let Some(name) = &f.name {
+ if name.to_lowercase().as_str().contains(field) {
+ val_display_or_store(
+ clipboard,
+ f.value.as_deref().unwrap_or(""),
+ );
+ break;
+ }
+ }
+ }
+ }
+ },
}
}
- fn display_long(&self, desc: &str) {
+ fn display_long(&self, desc: &str, clipboard: bool) {
match &self.data {
DecryptedData::Login {
username,
@@ -85,29 +330,31 @@ impl DecryptedCipher {
uris,
..
} => {
- let mut displayed = self.display_short(desc);
+ let mut displayed = self.display_short(desc, clipboard);
displayed |=
- self.display_field("Username", username.as_deref());
+ display_field("Username", username.as_deref(), clipboard);
displayed |=
- self.display_field("TOTP Secret", totp.as_deref());
+ display_field("TOTP Secret", totp.as_deref(), clipboard);
if let Some(uris) = uris {
for uri in uris {
displayed |=
- self.display_field("URI", Some(&uri.uri));
+ display_field("URI", Some(&uri.uri), clipboard);
let match_type =
- uri.match_type.map(|ty| format!("{}", ty));
- displayed |= self.display_field(
+ uri.match_type.map(|ty| format!("{ty}"));
+ displayed |= display_field(
"Match type",
match_type.as_deref(),
+ clipboard,
);
}
}
for field in &self.fields {
- displayed |= self.display_field(
+ displayed |= display_field(
field.name.as_deref().unwrap_or("(null)"),
Some(field.value.as_deref().unwrap_or("")),
+ clipboard,
);
}
@@ -115,7 +362,7 @@ impl DecryptedCipher {
if displayed {
println!();
}
- println!("{}", notes);
+ println!("{notes}");
}
}
DecryptedData::Card {
@@ -126,24 +373,28 @@ impl DecryptedCipher {
code,
..
} => {
- let mut displayed = self.display_short(desc);
+ let mut displayed = self.display_short(desc, clipboard);
if let (Some(exp_month), Some(exp_year)) =
(exp_month, exp_year)
{
- println!("Expiration: {}/{}", exp_month, exp_year);
+ println!("Expiration: {exp_month}/{exp_year}");
displayed = true;
}
- displayed |= self.display_field("CVV", code.as_deref());
+ displayed |= display_field("CVV", code.as_deref(), clipboard);
+ displayed |= display_field(
+ "Name",
+ cardholder_name.as_deref(),
+ clipboard,
+ );
displayed |=
- self.display_field("Name", cardholder_name.as_deref());
- displayed |= self.display_field("Brand", brand.as_deref());
+ display_field("Brand", brand.as_deref(), clipboard);
if let Some(notes) = &self.notes {
if displayed {
println!();
}
- println!("{}", notes);
+ println!("{notes}");
}
}
DecryptedData::Identity {
@@ -162,65 +413,76 @@ impl DecryptedCipher {
username,
..
} => {
- let mut displayed = self.display_short(desc);
+ let mut displayed = self.display_short(desc, clipboard);
displayed |=
- self.display_field("Address", address1.as_deref());
+ display_field("Address", address1.as_deref(), clipboard);
+ displayed |=
+ display_field("Address", address2.as_deref(), clipboard);
displayed |=
- self.display_field("Address", address2.as_deref());
+ display_field("Address", address3.as_deref(), clipboard);
displayed |=
- self.display_field("Address", address3.as_deref());
- displayed |= self.display_field("City", city.as_deref());
- displayed |= self.display_field("State", state.as_deref());
+ display_field("City", city.as_deref(), clipboard);
displayed |=
- self.display_field("Postcode", postal_code.as_deref());
+ display_field("State", state.as_deref(), clipboard);
+ displayed |= display_field(
+ "Postcode",
+ postal_code.as_deref(),
+ clipboard,
+ );
displayed |=
- self.display_field("Country", country.as_deref());
- displayed |= self.display_field("Phone", phone.as_deref());
- displayed |= self.display_field("Email", email.as_deref());
- displayed |= self.display_field("SSN", ssn.as_deref());
+ display_field("Country", country.as_deref(), clipboard);
displayed |=
- self.display_field("License", license_number.as_deref());
- displayed |= self
- .display_field("Passport", passport_number.as_deref());
+ display_field("Phone", phone.as_deref(), clipboard);
displayed |=
- self.display_field("Username", username.as_deref());
+ display_field("Email", email.as_deref(), clipboard);
+ displayed |= display_field("SSN", ssn.as_deref(), clipboard);
+ displayed |= display_field(
+ "License",
+ license_number.as_deref(),
+ clipboard,
+ );
+ displayed |= display_field(
+ "Passport",
+ passport_number.as_deref(),
+ clipboard,
+ );
+ displayed |=
+ display_field("Username", username.as_deref(), clipboard);
if let Some(notes) = &self.notes {
if displayed {
println!();
}
- println!("{}", notes);
+ println!("{notes}");
}
}
DecryptedData::SecureNote {} => {
- self.display_short(desc);
+ self.display_short(desc, clipboard);
}
}
}
- fn display_field(&self, name: &str, field: Option<&str>) -> bool {
- if let Some(field) = field {
- println!("{}: {}", name, field);
- true
- } else {
- false
- }
- }
-
fn display_name(&self) -> String {
match &self.data {
DecryptedData::Login { username, .. } => {
- if let Some(username) = username {
- format!("{}@{}", username, self.name)
- } else {
- self.name.clone()
- }
+ username.as_ref().map_or_else(
+ || self.name.clone(),
+ |username| format!("{}@{}", username, self.name),
+ )
}
_ => self.name.clone(),
}
}
+ fn display_json(&self, desc: &str) -> anyhow::Result<()> {
+ serde_json::to_writer_pretty(std::io::stdout(), &self)
+ .context(format!("failed to write entry '{desc}' to stdout"))?;
+ println!();
+
+ Ok(())
+ }
+
fn exact_match(
&self,
name: &str,
@@ -312,7 +574,23 @@ impl DecryptedCipher {
}
}
-#[derive(Debug, Clone)]
+fn val_display_or_store(clipboard: bool, password: &str) -> bool {
+ if clipboard {
+ match clipboard_store(password) {
+ Ok(()) => true,
+ Err(e) => {
+ eprintln!("{e}");
+ false
+ }
+ }
+ } else {
+ println!("{password}");
+ true
+ }
+}
+
+#[derive(Debug, Clone, Serialize)]
+#[serde(untagged)]
#[cfg_attr(test, derive(Eq, PartialEq))]
enum DecryptedData {
Login {
@@ -351,21 +629,21 @@ enum DecryptedData {
SecureNote,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize)]
#[cfg_attr(test, derive(Eq, PartialEq))]
struct DecryptedField {
name: Option<String>,
value: Option<String>,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize)]
#[cfg_attr(test, derive(Eq, PartialEq))]
struct DecryptedHistoryEntry {
last_used_date: String,
password: String,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize)]
#[cfg_attr(test, derive(Eq, PartialEq))]
struct DecryptedUri {
uri: String,
@@ -393,11 +671,16 @@ impl std::convert::TryFrom<&String> for ListField {
}
}
-const HELP: &str = r#"
+const HELP_PW: &str = r"
# The first line of this file will be the password, and the remainder of the
# file (after any blank lines after the password) will be stored as a note.
# Lines with leading # will be ignored.
-"#;
+";
+
+const HELP_NOTES: &str = r"
+# The content of this file will be stored as a note.
+# Lines with leading # will be ignored.
+";
pub fn config_show() -> anyhow::Result<()> {
let config = rbw::config::Config::load()?;
@@ -415,6 +698,13 @@ pub fn config_set(key: &str, value: &str) -> anyhow::Result<()> {
"email" => config.email = Some(value.to_string()),
"base_url" => config.base_url = Some(value.to_string()),
"identity_url" => config.identity_url = Some(value.to_string()),
+ "notifications_url" => {
+ config.notifications_url = Some(value.to_string());
+ }
+ "client_cert_path" => {
+ config.client_cert_path =
+ Some(std::path::PathBuf::from(value.to_string()));
+ }
"lock_timeout" => {
let timeout = value
.parse()
@@ -425,6 +715,12 @@ pub fn config_set(key: &str, value: &str) -> anyhow::Result<()> {
config.lock_timeout = timeout;
}
}
+ "sync_interval" => {
+ let interval = value
+ .parse()
+ .context("failed to parse value for sync_interval")?;
+ config.sync_interval = interval;
+ }
"pinentry" => config.pinentry = value.to_string(),
_ => return Err(anyhow::anyhow!("invalid config key: {}", key)),
}
@@ -447,8 +743,10 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> {
"email" => config.email = None,
"base_url" => config.base_url = None,
"identity_url" => config.identity_url = None,
+ "notifications_url" => config.notifications_url = None,
+ "client_cert_path" => config.client_cert_path = None,
"lock_timeout" => {
- config.lock_timeout = rbw::config::default_lock_timeout()
+ config.lock_timeout = rbw::config::default_lock_timeout();
}
"pinentry" => config.pinentry = rbw::config::default_pinentry(),
_ => return Err(anyhow::anyhow!("invalid config key: {}", key)),
@@ -465,6 +763,13 @@ pub fn config_unset(key: &str) -> anyhow::Result<()> {
Ok(())
}
+fn clipboard_store(val: &str) -> anyhow::Result<()> {
+ ensure_agent()?;
+ crate::actions::clipboard_store(val)?;
+
+ Ok(())
+}
+
pub fn register() -> anyhow::Result<()> {
ensure_agent()?;
crate::actions::register()?;
@@ -514,8 +819,7 @@ pub fn list(fields: &[String]) -> anyhow::Result<()> {
let mut ciphers: Vec<DecryptedCipher> = db
.entries
.iter()
- .cloned()
- .map(|entry| decrypt_cipher(&entry))
+ .map(decrypt_cipher)
.collect::<anyhow::Result<_>>()?;
ciphers.sort_unstable_by(|a, b| a.name.cmp(&b.name));
@@ -526,20 +830,27 @@ pub fn list(fields: &[String]) -> anyhow::Result<()> {
ListField::Name => cipher.name.clone(),
ListField::Id => cipher.id.clone(),
ListField::User => match &cipher.data {
- DecryptedData::Login { username, .. } => username
- .as_ref()
- .map(std::string::ToString::to_string)
- .unwrap_or_else(|| "".to_string()),
- _ => "".to_string(),
+ DecryptedData::Login { username, .. } => {
+ username.as_ref().map_or_else(
+ String::new,
+ std::string::ToString::to_string,
+ )
+ }
+ _ => String::new(),
},
- ListField::Folder => cipher
- .folder
- .as_ref()
- .map(std::string::ToString::to_string)
- .unwrap_or_else(|| "".to_string()),
+ ListField::Folder => cipher.folder.as_ref().map_or_else(
+ String::new,
+ std::string::ToString::to_string,
+ ),
})
.collect();
- println!("{}", values.join("\t"));
+
+ // write to stdout but don't panic when pipe get's closed
+ // this happens when piping stdout in a shell
+ match writeln!(&mut io::stdout(), "{}", values.join("\t")) {
+ Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => Ok(()),
+ res => res,
+ }?;
}
Ok(())
@@ -549,7 +860,10 @@ pub fn get(
name: &str,
user: Option<&str>,
folder: Option<&str>,
+ field: Option<&str>,
full: bool,
+ raw: bool,
+ clipboard: bool,
) -> anyhow::Result<()> {
unlock()?;
@@ -557,17 +871,20 @@ pub fn get(
let desc = format!(
"{}{}",
- user.map(|s| format!("{}@", s))
- .unwrap_or_else(|| "".to_string()),
+ user.map_or_else(String::new, |s| format!("{s}@")),
name
);
let (_, decrypted) = find_entry(&db, name, user, folder)
- .with_context(|| format!("couldn't find entry for '{}'", desc))?;
- if full {
- decrypted.display_long(&desc);
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
+ if raw {
+ decrypted.display_json(&desc)?;
+ } else if full {
+ decrypted.display_long(&desc, clipboard);
+ } else if let Some(field) = field {
+ decrypted.display_field(&desc, field, clipboard);
} else {
- decrypted.display_short(&desc);
+ decrypted.display_short(&desc, clipboard);
}
Ok(())
@@ -584,17 +901,16 @@ pub fn code(
let desc = format!(
"{}{}",
- user.map(|s| format!("{}@", s))
- .unwrap_or_else(|| "".to_string()),
+ user.map_or_else(String::new, |s| format!("{s}@")),
name
);
let (_, decrypted) = find_entry(&db, name, user, folder)
- .with_context(|| format!("couldn't find entry for '{}'", desc))?;
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
if let DecryptedData::Login { totp, .. } = decrypted.data {
if let Some(totp) = totp {
- println!("{}", generate_totp(&totp)?)
+ println!("{}", generate_totp(&totp)?);
} else {
return Err(anyhow::anyhow!(
"entry does not contain a totp secret"
@@ -610,7 +926,7 @@ pub fn code(
pub fn add(
name: &str,
username: Option<&str>,
- uris: Vec<(String, Option<rbw::api::UriMatchType>)>,
+ uris: &[(String, Option<rbw::api::UriMatchType>)],
folder: Option<&str>,
) -> anyhow::Result<()> {
unlock()?;
@@ -627,7 +943,7 @@ pub fn add(
.map(|username| crate::actions::encrypt(username, None))
.transpose()?;
- let contents = rbw::edit::edit("", HELP)?;
+ let contents = rbw::edit::edit("", HELP_PW)?;
let (password, notes) = parse_editor(&contents);
let password = password
@@ -707,13 +1023,13 @@ pub fn add(
pub fn generate(
name: Option<&str>,
username: Option<&str>,
- uris: Vec<(String, Option<rbw::api::UriMatchType>)>,
+ uris: &[(String, Option<rbw::api::UriMatchType>)],
folder: Option<&str>,
len: usize,
ty: rbw::pwgen::Type,
) -> anyhow::Result<()> {
let password = rbw::pwgen::pwgen(ty, len);
- println!("{}", password);
+ println!("{password}");
if let Some(name) = name {
unlock()?;
@@ -813,24 +1129,22 @@ pub fn edit(
let desc = format!(
"{}{}",
- username
- .map(|s| format!("{}@", s))
- .unwrap_or_else(|| "".to_string()),
+ username.map_or_else(String::new, |s| format!("{s}@")),
name
);
let (entry, decrypted) = find_entry(&db, name, username, folder)
- .with_context(|| format!("couldn't find entry for '{}'", desc))?;
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
let (data, notes, history) = match &decrypted.data {
DecryptedData::Login { password, .. } => {
let mut contents =
format!("{}\n", password.as_deref().unwrap_or(""));
if let Some(notes) = decrypted.notes {
- contents.push_str(&format!("\n{}\n", notes));
+ contents.push_str(&format!("\n{notes}\n"));
}
- let contents = rbw::edit::edit(&contents, HELP)?;
+ let contents = rbw::edit::edit(&contents, HELP_NOTES)?;
let (password, notes) = parse_editor(&contents);
let password = password
@@ -847,16 +1161,15 @@ pub fn edit(
})
.transpose()?;
let mut history = entry.history.clone();
- let (entry_username, entry_password, entry_uris, entry_totp) =
- match &entry.data {
- rbw::db::EntryData::Login {
- username,
- password,
- uris,
- totp,
- } => (username, password, uris, totp),
- _ => unreachable!(),
- };
+ let rbw::db::EntryData::Login {
+ username: entry_username,
+ password: entry_password,
+ uris: entry_uris,
+ totp: entry_totp,
+ } = &entry.data
+ else {
+ unreachable!();
+ };
if let Some(prev_password) = entry_password.clone() {
let new_history_entry = rbw::db::HistoryEntry {
@@ -874,14 +1187,34 @@ pub fn edit(
let data = rbw::db::EntryData::Login {
username: entry_username.clone(),
password,
- uris: entry_uris.to_vec(),
+ uris: entry_uris.clone(),
totp: entry_totp.clone(),
};
(data, notes, history)
}
+ DecryptedData::SecureNote {} => {
+ let data = rbw::db::EntryData::SecureNote {};
+
+ let editor_content = decrypted.notes.map_or_else(
+ || "\n".to_string(),
+ |notes| format!("{notes}\n"),
+ );
+ let contents = rbw::edit::edit(&editor_content, HELP_NOTES)?;
+
+ // prepend blank line to be parsed as pw by `parse_editor`
+ let (_, notes) = parse_editor(&format!("\n{contents}\n"));
+
+ let notes = notes
+ .map(|notes| {
+ crate::actions::encrypt(&notes, entry.org_id.as_deref())
+ })
+ .transpose()?;
+
+ (data, notes, entry.history)
+ }
_ => {
return Err(anyhow::anyhow!(
- "modifications are only supported for login entries"
+ "modifications are only supported for login and note entries"
));
}
};
@@ -918,14 +1251,12 @@ pub fn remove(
let desc = format!(
"{}{}",
- username
- .map(|s| format!("{}@", s))
- .unwrap_or_else(|| "".to_string()),
+ username.map_or_else(String::new, |s| format!("{s}@")),
name
);
let (entry, _) = find_entry(&db, name, username, folder)
- .with_context(|| format!("couldn't find entry for '{}'", desc))?;
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
if let (Some(access_token), ()) =
rbw::actions::remove(access_token, refresh_token, &entry.id)?
@@ -950,14 +1281,12 @@ pub fn history(
let desc = format!(
"{}{}",
- username
- .map(|s| format!("{}@", s))
- .unwrap_or_else(|| "".to_string()),
+ username.map_or_else(String::new, |s| format!("{s}@")),
name
);
let (_, decrypted) = find_entry(&db, name, username, folder)
- .with_context(|| format!("couldn't find entry for '{}'", desc))?;
+ .with_context(|| format!("couldn't find entry for '{desc}'"))?;
for history in decrypted.history {
println!("{}: {}", history.last_used_date, history.password);
}
@@ -1017,7 +1346,7 @@ fn ensure_agent_once() -> anyhow::Result<()> {
let agent_path = std::env::var("RBW_AGENT");
let agent_path = agent_path
.as_ref()
- .map(|s| s.as_str())
+ .map(std::string::String::as_str)
.unwrap_or("rbw-agent");
let status = std::process::Command::new(agent_path)
.status()
@@ -1056,26 +1385,23 @@ fn find_entry(
username: Option<&str>,
folder: Option<&str>,
) -> anyhow::Result<(rbw::db::Entry, DecryptedCipher)> {
- match uuid::Uuid::parse_str(name) {
- Ok(_) => {
- for cipher in &db.entries {
- if name == cipher.id {
- return Ok((cipher.clone(), decrypt_cipher(cipher)?));
- }
+ if uuid::Uuid::parse_str(name).is_ok() {
+ for cipher in &db.entries {
+ if name == cipher.id {
+ return Ok((cipher.clone(), decrypt_cipher(cipher)?));
}
- Err(anyhow::anyhow!("no entry found"))
- }
- Err(_) => {
- let ciphers: Vec<(rbw::db::Entry, DecryptedCipher)> = db
- .entries
- .iter()
- .cloned()
- .map(|entry| {
- decrypt_cipher(&entry).map(|decrypted| (entry, decrypted))
- })
- .collect::<anyhow::Result<_>>()?;
- find_entry_raw(&ciphers, name, username, folder)
}
+ Err(anyhow::anyhow!("no entry found"))
+ } else {
+ let ciphers: Vec<(rbw::db::Entry, DecryptedCipher)> = db
+ .entries
+ .iter()
+ .cloned()
+ .map(|entry| {
+ decrypt_cipher(&entry).map(|decrypted| (entry, decrypted))
+ })
+ .collect::<anyhow::Result<_>>()?;
+ find_entry_raw(&ciphers, name, username, folder)
}
}
@@ -1087,10 +1413,10 @@ fn find_entry_raw(
) -> anyhow::Result<(rbw::db::Entry, DecryptedCipher)> {
let mut matches: Vec<(rbw::db::Entry, DecryptedCipher)> = entries
.iter()
- .cloned()
- .filter(|(_, decrypted_cipher)| {
+ .filter(|&(_, decrypted_cipher)| {
decrypted_cipher.exact_match(name, username, folder, true)
})
+ .cloned()
.collect();
if matches.len() == 1 {
@@ -1100,10 +1426,10 @@ fn find_entry_raw(
if folder.is_none() {
matches = entries
.iter()
- .cloned()
- .filter(|(_, decrypted_cipher)| {
+ .filter(|&(_, decrypted_cipher)| {
decrypted_cipher.exact_match(name, username, folder, false)
})
+ .cloned()
.collect();
if matches.len() == 1 {
@@ -1113,10 +1439,10 @@ fn find_entry_raw(
matches = entries
.iter()
- .cloned()
- .filter(|(_, decrypted_cipher)| {
+ .filter(|&(_, decrypted_cipher)| {
decrypted_cipher.partial_match(name, username, folder, true)
})
+ .cloned()
.collect();
if matches.len() == 1 {
@@ -1126,10 +1452,10 @@ fn find_entry_raw(
if folder.is_none() {
matches = entries
.iter()
- .cloned()
- .filter(|(_, decrypted_cipher)| {
+ .filter(|&(_, decrypted_cipher)| {
decrypted_cipher.partial_match(name, username, folder, false)
})
+ .cloned()
.collect();
if matches.len() == 1 {
return Ok(matches[0].clone());
@@ -1435,8 +1761,11 @@ fn parse_editor(contents: &str) -> (Option<String>, Option<String>) {
let mut notes: String = lines
.skip_while(|line| line.is_empty())
.filter(|line| !line.starts_with('#'))
- .map(|line| format!("{}\n", line))
- .collect();
+ .fold(String::new(), |mut notes, line| {
+ notes.push_str(line);
+ notes.push('\n');
+ notes
+ });
while notes.ends_with('\n') {
notes.pop();
}
@@ -1447,32 +1776,35 @@ fn parse_editor(contents: &str) -> (Option<String>, Option<String>) {
fn load_db() -> anyhow::Result<rbw::db::Db> {
let config = rbw::config::Config::load()?;
- if let Some(email) = &config.email {
- rbw::db::Db::load(&config.server_name(), email)
- .map_err(anyhow::Error::new)
- } else {
- Err(anyhow::anyhow!("failed to find email address in config"))
- }
+ config.email.as_ref().map_or_else(
+ || Err(anyhow::anyhow!("failed to find email address in config")),
+ |email| {
+ rbw::db::Db::load(&config.server_name(), email)
+ .map_err(anyhow::Error::new)
+ },
+ )
}
fn save_db(db: &rbw::db::Db) -> anyhow::Result<()> {
let config = rbw::config::Config::load()?;
- if let Some(email) = &config.email {
- db.save(&config.server_name(), email)
- .map_err(anyhow::Error::new)
- } else {
- Err(anyhow::anyhow!("failed to find email address in config"))
- }
+ config.email.as_ref().map_or_else(
+ || Err(anyhow::anyhow!("failed to find email address in config")),
+ |email| {
+ db.save(&config.server_name(), email)
+ .map_err(anyhow::Error::new)
+ },
+ )
}
fn remove_db() -> anyhow::Result<()> {
let config = rbw::config::Config::load()?;
- if let Some(email) = &config.email {
- rbw::db::Db::remove(&config.server_name(), email)
- .map_err(anyhow::Error::new)
- } else {
- Err(anyhow::anyhow!("failed to find email address in config"))
- }
+ config.email.as_ref().map_or_else(
+ || Err(anyhow::anyhow!("failed to find email address in config")),
+ |email| {
+ rbw::db::Db::remove(&config.server_name(), email)
+ .map_err(anyhow::Error::new)
+ },
+ )
}
fn parse_totp_secret(secret: &str) -> anyhow::Result<Vec<u8>> {
@@ -1500,7 +1832,7 @@ fn parse_totp_secret(secret: &str) -> anyhow::Result<Vec<u8>> {
};
base32::decode(
base32::Alphabet::RFC4648 { padding: false },
- &secret_str.replace(" ", ""),
+ &secret_str.replace(' ', ""),
)
.ok_or_else(|| anyhow::anyhow!("totp secret was not valid base32"))
}
@@ -1517,6 +1849,13 @@ fn generate_totp(secret: &str) -> anyhow::Result<String> {
))
}
+fn display_field(name: &str, field: Option<&str>, clipboard: bool) -> bool {
+ field.map_or_else(
+ || false,
+ |field| val_display_or_store(clipboard, &format!("{name}: {field}")),
+ )
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -1612,7 +1951,7 @@ mod test {
) -> bool {
let res = find_entry_raw(entries, name, username, folder);
if let Err(e) = res {
- format!("{}", e).contains("no entry found")
+ format!("{e}").contains("no entry found")
} else {
false
}
@@ -1626,7 +1965,7 @@ mod test {
) -> bool {
let res = find_entry_raw(entries, name, username, folder);
if let Err(e) = res {
- format!("{}", e).contains("multiple entries found")
+ format!("{e}").contains("multiple entries found")
} else {
false
}
diff --git a/src/bin/rbw/main.rs b/src/bin/rbw/main.rs
index 85631c5..72e4220 100644
--- a/src/bin/rbw/main.rs
+++ b/src/bin/rbw/main.rs
@@ -1,23 +1,36 @@
+#![warn(clippy::cargo)]
+#![warn(clippy::pedantic)]
+#![warn(clippy::nursery)]
+#![warn(clippy::as_conversions)]
+#![warn(clippy::get_unwrap)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::missing_const_for_fn)]
+#![allow(clippy::similar_names)]
+#![allow(clippy::struct_excessive_bools)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+#![allow(clippy::type_complexity)]
+#![allow(clippy::multiple_crate_versions)]
#![allow(clippy::large_enum_variant)]
use anyhow::Context as _;
+use clap::{CommandFactory as _, Parser as _};
use std::io::Write as _;
-use structopt::StructOpt as _;
mod actions;
mod commands;
mod sock;
-#[derive(Debug, structopt::StructOpt)]
-#[structopt(about = "Unofficial Bitwarden CLI")]
+#[derive(Debug, clap::Parser)]
+#[command(version, about = "Unofficial Bitwarden CLI")]
enum Opt {
- #[structopt(about = "Get or set configuration options")]
+ #[command(about = "Get or set configuration options")]
Config {
- #[structopt(subcommand)]
+ #[command(subcommand)]
config: Config,
},
- #[structopt(
+ #[command(
about = "Register this device with the Bitwarden server",
long_about = "Register this device with the Bitwarden server\n\n\
The official Bitwarden server includes bot detection to prevent \
@@ -28,60 +41,63 @@ enum Opt {
)]
Register,
- #[structopt(about = "Log in to the Bitwarden server")]
+ #[command(about = "Log in to the Bitwarden server")]
Login,
- #[structopt(about = "Unlock the local Bitwarden database")]
+ #[command(about = "Unlock the local Bitwarden database")]
Unlock,
- #[structopt(about = "Check if the local Bitwarden database is unlocked")]
+ #[command(about = "Check if the local Bitwarden database is unlocked")]
Unlocked,
- #[structopt(about = "Update the local copy of the Bitwarden database")]
+ #[command(about = "Update the local copy of the Bitwarden database")]
Sync,
- #[structopt(
+ #[command(
about = "List all entries in the local Bitwarden database",
visible_alias = "ls"
)]
List {
- #[structopt(
+ #[arg(
long,
help = "Fields to display. \
Available options are id, name, user, folder. \
Multiple fields will be separated by tabs.",
default_value = "name",
- use_delimiter = true
+ use_value_delimiter = true
)]
fields: Vec<String>,
},
- #[structopt(about = "Display the password for a given entry")]
+ #[command(about = "Display the password for a given entry")]
Get {
- #[structopt(help = "Name or UUID of the entry to display")]
+ #[arg(help = "Name or UUID of the entry to display")]
name: String,
- #[structopt(help = "Username of the entry to display")]
+ #[arg(help = "Username of the entry to display")]
user: Option<String>,
- #[structopt(long, help = "Folder name to search in")]
+ #[arg(long, help = "Folder name to search in")]
folder: Option<String>,
- #[structopt(
- long,
- help = "Display the notes in addition to the password"
- )]
+ #[arg(short, long, help = "Field to get")]
+ field: Option<String>,
+ #[arg(long, help = "Display the notes in addition to the password")]
full: bool,
+ #[structopt(long, help = "Display output as JSON")]
+ raw: bool,
+ #[structopt(long, help = "Copy result to clipboard")]
+ clipboard: bool,
},
- #[structopt(about = "Display the authenticator code for a given entry")]
+ #[command(about = "Display the authenticator code for a given entry")]
Code {
- #[structopt(help = "Name or UUID of the entry to display")]
+ #[arg(help = "Name or UUID of the entry to display")]
name: String,
- #[structopt(help = "Username of the entry to display")]
+ #[arg(help = "Username of the entry to display")]
user: Option<String>,
- #[structopt(long, help = "Folder name to search in")]
+ #[arg(long, help = "Folder name to search in")]
folder: Option<String>,
},
- #[structopt(
+ #[command(
about = "Add a new password to the database",
long_about = "Add a new password to the database\n\n\
This command will open a text editor to enter \
@@ -91,28 +107,27 @@ enum Opt {
remainder will be saved as a note."
)]
Add {
- #[structopt(help = "Name of the password entry")]
+ #[arg(help = "Name of the password entry")]
name: String,
- #[structopt(help = "Username for the password entry")]
+ #[arg(help = "Username for the password entry")]
user: Option<String>,
- #[structopt(
+ #[arg(
long,
help = "URI for the password entry",
- multiple = true,
number_of_values = 1
)]
uri: Vec<String>,
- #[structopt(long, help = "Folder for the password entry")]
+ #[arg(long, help = "Folder for the password entry")]
folder: Option<String>,
},
- #[structopt(
+ #[command(
about = "Generate a new password",
long_about = "Generate a new password\n\n\
If given a password entry name, also save the generated \
password to the database.",
visible_alias = "gen",
- group = structopt::clap::ArgGroup::with_name("password-type").args(&[
+ group = clap::ArgGroup::new("password-type").args(&[
"no-symbols",
"only-numbers",
"nonconfusables",
@@ -120,39 +135,38 @@ enum Opt {
])
)]
Generate {
- #[structopt(help = "Length of the password to generate")]
+ #[arg(help = "Length of the password to generate")]
len: usize,
- #[structopt(help = "Name of the password entry")]
+ #[arg(help = "Name of the password entry")]
name: Option<String>,
- #[structopt(help = "Username for the password entry")]
+ #[arg(help = "Username for the password entry")]
user: Option<String>,
- #[structopt(
+ #[arg(
long,
help = "URI for the password entry",
- multiple = true,
number_of_values = 1
)]
uri: Vec<String>,
- #[structopt(long, help = "Folder for the password entry")]
+ #[arg(long, help = "Folder for the password entry")]
folder: Option<String>,
- #[structopt(
+ #[arg(
long = "no-symbols",
help = "Generate a password with no special characters"
)]
no_symbols: bool,
- #[structopt(
+ #[arg(
long = "only-numbers",
help = "Generate a password consisting of only numbers"
)]
only_numbers: bool,
- #[structopt(
+ #[arg(
long,
help = "Generate a password without visually similar \
characters (useful for passwords intended to be \
written down)"
)]
nonconfusables: bool,
- #[structopt(
+ #[arg(
long,
help = "Generate a password of multiple dictionary \
words chosen from the EFF word list. The len \
@@ -162,7 +176,7 @@ enum Opt {
diceware: bool,
},
- #[structopt(
+ #[command(
about = "Modify an existing password",
long_about = "Modify an existing password\n\n\
This command will open a text editor with the existing \
@@ -173,50 +187,48 @@ enum Opt {
as a note."
)]
Edit {
- #[structopt(help = "Name or UUID of the password entry")]
+ #[arg(help = "Name or UUID of the password entry")]
name: String,
- #[structopt(help = "Username for the password entry")]
+ #[arg(help = "Username for the password entry")]
user: Option<String>,
- #[structopt(long, help = "Folder name to search in")]
+ #[arg(long, help = "Folder name to search in")]
folder: Option<String>,
},
- #[structopt(about = "Remove a given entry", visible_alias = "rm")]
+ #[command(about = "Remove a given entry", visible_alias = "rm")]
Remove {
- #[structopt(help = "Name or UUID of the password entry")]
+ #[arg(help = "Name or UUID of the password entry")]
name: String,
- #[structopt(help = "Username for the password entry")]
+ #[arg(help = "Username for the password entry")]
user: Option<String>,
- #[structopt(long, help = "Folder name to search in")]
+ #[arg(long, help = "Folder name to search in")]
folder: Option<String>,
},
- #[structopt(about = "View the password history for a given entry")]
+ #[command(about = "View the password history for a given entry")]
History {
- #[structopt(help = "Name or UUID of the password entry")]
+ #[arg(help = "Name or UUID of the password entry")]
name: String,
- #[structopt(help = "Username for the password entry")]
+ #[arg(help = "Username for the password entry")]
user: Option<String>,
- #[structopt(long, help = "Folder name to search in")]
+ #[arg(long, help = "Folder name to search in")]
folder: Option<String>,
},
- #[structopt(about = "Lock the password database")]
+ #[command(about = "Lock the password database")]
Lock,
- #[structopt(about = "Remove the local copy of the password database")]
+ #[command(about = "Remove the local copy of the password database")]
Purge,
- #[structopt(
- name = "stop-agent",
- about = "Terminate the background agent"
- )]
+ #[command(name = "stop-agent", about = "Terminate the background agent")]
StopAgent,
- #[structopt(
+
+ #[command(
name = "gen-completions",
about = "Generate completion script for the given shell"
)]
- GenCompletions { shell: String },
+ GenCompletions { shell: clap_complete::Shell },
}
impl Opt {
@@ -246,20 +258,20 @@ impl Opt {
}
}
-#[derive(Debug, structopt::StructOpt)]
+#[derive(Debug, clap::Parser)]
enum Config {
- #[structopt(about = "Show the values of all configuration settings")]
+ #[command(about = "Show the values of all configuration settings")]
Show,
- #[structopt(about = "Set a configuration option")]
+ #[command(about = "Set a configuration option")]
Set {
- #[structopt(help = "Configuration key to set")]
+ #[arg(help = "Configuration key to set")]
key: String,
- #[structopt(help = "Value to set the configuration option to")]
+ #[arg(help = "Value to set the configuration option to")]
value: String,
},
- #[structopt(about = "Reset a configuration option to its default")]
+ #[command(about = "Reset a configuration option to its default")]
Unset {
- #[structopt(help = "Configuration key to unset")]
+ #[arg(help = "Configuration key to unset")]
key: String,
},
}
@@ -275,15 +287,18 @@ impl Config {
}
}
-#[paw::main]
-fn main(opt: Opt) {
+fn main() {
+ let opt = Opt::parse();
+
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or("info"),
)
.format(|buf, record| {
- if let Some((w, _)) = term_size::dimensions() {
+ if let Some((terminal_size::Width(w), _)) =
+ terminal_size::terminal_size()
+ {
let out = format!("{}: {}", record.level(), record.args());
- writeln!(buf, "{}", textwrap::fill(&out, w - 1))
+ writeln!(buf, "{}", textwrap::fill(&out, usize::from(w) - 1))
} else {
writeln!(buf, "{}: {}", record.level(), record.args())
}
@@ -306,8 +321,19 @@ fn main(opt: Opt) {
name,
user,
folder,
+ field,
full,
- } => commands::get(name, user.as_deref(), folder.as_deref(), *full),
+ raw,
+ clipboard,
+ } => commands::get(
+ name,
+ user.as_deref(),
+ folder.as_deref(),
+ field.as_deref(),
+ *full,
+ *raw,
+ *clipboard,
+ ),
Opt::Code { name, user, folder } => {
commands::code(name, user.as_deref(), folder.as_deref())
}
@@ -319,7 +345,7 @@ fn main(opt: Opt) {
} => commands::add(
name,
user.as_deref(),
- uri.iter()
+ &uri.iter()
// XXX not sure what the ui for specifying the match type
// should be
.map(|uri| (uri.clone(), None))
@@ -351,7 +377,7 @@ fn main(opt: Opt) {
commands::generate(
name.as_deref(),
user.as_deref(),
- uri.iter()
+ &uri.iter()
// XXX not sure what the ui for specifying the match type
// should be
.map(|uri| (uri.clone(), None))
@@ -373,25 +399,20 @@ fn main(opt: Opt) {
Opt::Lock => commands::lock(),
Opt::Purge => commands::purge(),
Opt::StopAgent => commands::stop_agent(),
- Opt::GenCompletions { shell } => gen_completions(shell),
+ Opt::GenCompletions { shell } => {
+ clap_complete::generate(
+ *shell,
+ &mut Opt::command(),
+ "rbw",
+ &mut std::io::stdout(),
+ );
+ Ok(())
+ }
}
.context(format!("rbw {}", opt.subcommand_name()));
if let Err(e) = res {
- eprintln!("{:#}", e);
+ eprintln!("{e:#}");
std::process::exit(1);
}
}
-
-fn gen_completions(shell: &str) -> anyhow::Result<()> {
- let shell = match shell {
- "bash" => structopt::clap::Shell::Bash,
- "zsh" => structopt::clap::Shell::Zsh,
- "fish" => structopt::clap::Shell::Fish,
- "powershell" => structopt::clap::Shell::PowerShell,
- "elvish" => structopt::clap::Shell::Elvish,
- _ => return Err(anyhow::anyhow!("unknown shell {}", shell)),
- };
- Opt::clap().gen_completions_to("rbw", shell, &mut std::io::stdout());
- Ok(())
-}
diff --git a/src/cipherstring.rs b/src/cipherstring.rs
index 39254c7..883cb34 100644
--- a/src/cipherstring.rs
+++ b/src/cipherstring.rs
@@ -1,10 +1,11 @@
use crate::prelude::*;
-use block_modes::BlockMode as _;
-use block_padding::Padding as _;
-use hmac::{Mac as _, NewMac as _};
+use aes::cipher::{
+ BlockDecryptMut as _, BlockEncryptMut as _, KeyIvInit as _,
+};
+use hmac::Mac as _;
+use pkcs8::DecodePrivateKey as _;
use rand::RngCore as _;
-use rsa::pkcs8::FromPrivateKey as _;
use zeroize::Zeroize as _;
pub enum CipherString {
@@ -51,15 +52,15 @@ impl CipherString {
});
}
- let iv = base64::decode(parts[0])
+ let iv = crate::base64::decode(parts[0])
.map_err(|source| Error::InvalidBase64 { source })?;
- let ciphertext = base64::decode(parts[1])
+ let ciphertext = crate::base64::decode(parts[1])
.map_err(|source| Error::InvalidBase64 { source })?;
let mac =
if parts.len() > 2 {
- Some(base64::decode(parts[2]).map_err(|source| {
- Error::InvalidBase64 { source }
- })?)
+ Some(crate::base64::decode(parts[2]).map_err(
+ |source| Error::InvalidBase64 { source },
+ )?)
} else {
None
};
@@ -76,7 +77,7 @@ impl CipherString {
// https://github.com/bitwarden/jslib/blob/785b681f61f81690de6df55159ab07ae710bcfad/src/enums/encryptionType.ts#L8
// format is: <cipher_text_b64>|<hmac_sig>
let contents = contents.split('|').next().unwrap();
- let ciphertext = base64::decode(contents)
+ let ciphertext = crate::base64::decode(contents)
.map_err(|source| Error::InvalidBase64 { source })?;
Ok(Self::Asymmetric { ciphertext })
}
@@ -98,12 +99,12 @@ impl CipherString {
) -> Result<Self> {
let iv = random_iv();
- let cipher = block_modes::Cbc::<
- aes::Aes256,
- block_modes::block_padding::Pkcs7,
- >::new_from_slices(keys.enc_key(), &iv)
- .map_err(|source| Error::CreateBlockMode { source })?;
- let ciphertext = cipher.encrypt_vec(plaintext);
+ let cipher = cbc::Encryptor::<aes::Aes256>::new(
+ keys.enc_key().into(),
+ iv.as_slice().into(),
+ );
+ let ciphertext =
+ cipher.encrypt_padded_vec_mut::<block_padding::Pkcs7>(plaintext);
let mut digest =
hmac::Hmac::<sha2::Sha256>::new_from_slice(keys.mac_key())
@@ -136,7 +137,7 @@ impl CipherString {
mac.as_deref(),
)?;
cipher
- .decrypt_vec(ciphertext)
+ .decrypt_padded_vec_mut::<block_padding::Pkcs7>(ciphertext)
.map_err(|source| Error::Decrypt { source })
} else {
Err(Error::InvalidCipherString {
@@ -166,7 +167,7 @@ impl CipherString {
mac.as_deref(),
)?;
cipher
- .decrypt(res.data_mut())
+ .decrypt_padded_mut::<block_padding::Pkcs7>(res.data_mut())
.map_err(|source| Error::Decrypt { source })?;
Ok(res)
} else {
@@ -184,15 +185,12 @@ impl CipherString {
) -> Result<crate::locked::Vec> {
if let Self::Asymmetric { ciphertext } = self {
let privkey_data = private_key.private_key();
- let privkey_data = block_padding::Pkcs7::unpad(privkey_data)
- .map_err(|_| Error::Padding)?;
+ let privkey_data =
+ pkcs7_unpad(privkey_data).ok_or(Error::Padding)?;
let pkey = rsa::RsaPrivateKey::from_pkcs8_der(privkey_data)
.map_err(|source| Error::RsaPkcs8 { source })?;
let mut bytes = pkey
- .decrypt(
- rsa::padding::PaddingScheme::new_oaep::<sha1::Sha1>(),
- ciphertext,
- )
+ .decrypt(rsa::Oaep::new::<sha1::Sha1>(), ciphertext)
.map_err(|source| Error::Rsa { source })?;
// XXX it'd be great if the rsa crate would let us decrypt
@@ -218,8 +216,7 @@ fn decrypt_common_symmetric(
iv: &[u8],
ciphertext: &[u8],
mac: Option<&[u8]>,
-) -> Result<block_modes::Cbc<aes::Aes256, block_modes::block_padding::Pkcs7>>
-{
+) -> Result<cbc::Decryptor<aes::Aes256>> {
if let Some(mac) = mac {
let mut key =
hmac::Hmac::<sha2::Sha256>::new_from_slice(keys.mac_key())
@@ -227,15 +224,12 @@ fn decrypt_common_symmetric(
key.update(iv);
key.update(ciphertext);
- if key.verify(mac).is_err() {
+ if key.verify(mac.into()).is_err() {
return Err(Error::InvalidMac);
}
}
- block_modes::Cbc::<
- aes::Aes256,
- block_modes::block_padding::Pkcs7,
- >::new_from_slices(keys.enc_key(), iv)
+ cbc::Decryptor::<aes::Aes256>::new_from_slices(keys.enc_key(), iv)
.map_err(|source| Error::CreateBlockMode { source })
}
@@ -247,18 +241,18 @@ impl std::fmt::Display for CipherString {
ciphertext,
mac,
} => {
- let iv = base64::encode(&iv);
- let ciphertext = base64::encode(&ciphertext);
+ let iv = crate::base64::encode(iv);
+ let ciphertext = crate::base64::encode(ciphertext);
if let Some(mac) = &mac {
- let mac = base64::encode(&mac);
- write!(f, "2.{}|{}|{}", iv, ciphertext, mac)
+ let mac = crate::base64::encode(mac);
+ write!(f, "2.{iv}|{ciphertext}|{mac}")
} else {
- write!(f, "2.{}|{}", iv, ciphertext)
+ write!(f, "2.{iv}|{ciphertext}")
}
}
Self::Asymmetric { ciphertext } => {
- let ciphertext = base64::encode(&ciphertext);
- write!(f, "4.{}", ciphertext)
+ let ciphertext = crate::base64::encode(ciphertext);
+ write!(f, "4.{ciphertext}")
}
}
}
@@ -270,3 +264,51 @@ fn random_iv() -> Vec<u8> {
rng.fill_bytes(&mut iv);
iv
}
+
+// XXX this should ideally just be block_padding::Pkcs7::unpad, but i can't
+// figure out how to get the generic types to work out
+fn pkcs7_unpad(b: &[u8]) -> Option<&[u8]> {
+ if b.is_empty() {
+ return None;
+ }
+
+ let padding_val = b[b.len() - 1];
+ if padding_val == 0 {
+ return None;
+ }
+
+ let padding_len = usize::from(padding_val);
+ if padding_len > b.len() {
+ return None;
+ }
+
+ for c in b.iter().copied().skip(b.len() - padding_len) {
+ if c != padding_val {
+ return None;
+ }
+ }
+
+ Some(&b[..b.len() - padding_len])
+}
+
+#[test]
+fn test_pkcs7_unpad() {
+ let tests = [
+ (&[][..], None),
+ (&[0x01][..], Some(&[][..])),
+ (&[0x02, 0x02][..], Some(&[][..])),
+ (&[0x03, 0x03, 0x03][..], Some(&[][..])),
+ (&[0x69, 0x01][..], Some(&[0x69][..])),
+ (&[0x69, 0x02, 0x02][..], Some(&[0x69][..])),
+ (&[0x69, 0x03, 0x03, 0x03][..], Some(&[0x69][..])),
+ (&[0x02][..], None),
+ (&[0x03][..], None),
+ (&[0x69, 0x69, 0x03, 0x03][..], None),
+ (&[0x00][..], None),
+ (&[0x02, 0x00][..], None),
+ ];
+ for (input, expected) in tests {
+ let got = pkcs7_unpad(input);
+ assert_eq!(got, expected);
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index bbc39f7..efb1b5f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,51 +1,59 @@
use crate::prelude::*;
use std::io::{Read as _, Write as _};
-use tokio::io::AsyncReadExt as _;
+use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Config {
pub email: Option<String>,
pub base_url: Option<String>,
pub identity_url: Option<String>,
+ pub notifications_url: Option<String>,
#[serde(default = "default_lock_timeout")]
pub lock_timeout: u64,
+ #[serde(default = "default_sync_interval")]
+ pub sync_interval: u64,
#[serde(default = "default_pinentry")]
pub pinentry: String,
- #[serde(default = "stub_device_id")]
- pub device_id: String,
+ pub client_cert_path: Option<std::path::PathBuf>,
+ // backcompat, no longer generated in new configs
+ #[serde(skip_serializing)]
+ pub device_id: Option<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
- email: Default::default(),
- base_url: Default::default(),
- identity_url: Default::default(),
+ email: None,
+ base_url: None,
+ identity_url: None,
+ notifications_url: None,
lock_timeout: default_lock_timeout(),
+ sync_interval: default_sync_interval(),
pinentry: default_pinentry(),
- device_id: default_device_id(),
+ client_cert_path: None,
+ device_id: None,
}
}
}
+#[must_use]
pub fn default_lock_timeout() -> u64 {
3600
}
-pub fn default_pinentry() -> String {
- "pinentry".to_string()
-}
-
-fn default_device_id() -> String {
- uuid::Uuid::new_v4().to_hyphenated().to_string()
+#[must_use]
+pub fn default_sync_interval() -> u64 {
+ 3600
}
-fn stub_device_id() -> String {
- String::from("fix")
+#[must_use]
+pub fn default_pinentry() -> String {
+ "pinentry".to_string()
}
impl Config {
+ #[must_use]
pub fn new() -> Self {
Self::default()
}
@@ -127,36 +135,103 @@ impl Config {
}
pub fn validate() -> Result<()> {
- let mut config = Self::load()?;
+ let config = Self::load()?;
if config.email.is_none() {
return Err(Error::ConfigMissingEmail);
}
- if config.device_id == stub_device_id() {
- config.device_id = default_device_id();
- config.save()?;
- }
Ok(())
}
+ #[must_use]
pub fn base_url(&self) -> String {
self.base_url.clone().map_or_else(
|| "https://api.bitwarden.com".to_string(),
- |url| format!("{}/api", url.trim_end_matches('/')),
+ |url| {
+ let clean_url = url.trim_end_matches('/').to_string();
+ if clean_url == "https://api.bitwarden.eu" {
+ clean_url
+ } else {
+ format!("{clean_url}/api")
+ }
+ },
)
}
+ #[must_use]
pub fn identity_url(&self) -> String {
self.identity_url.clone().unwrap_or_else(|| {
self.base_url.clone().map_or_else(
|| "https://identity.bitwarden.com".to_string(),
- |url| format!("{}/identity", url.trim_end_matches('/')),
+ |url| {
+ let clean_url = url.trim_end_matches('/').to_string();
+ if clean_url == "https://identity.bitwarden.eu" {
+ clean_url
+ } else {
+ format!("{clean_url}/identity")
+ }
+ },
+ )
+ })
+ }
+
+ #[must_use]
+ pub fn notifications_url(&self) -> String {
+ self.notifications_url.clone().unwrap_or_else(|| {
+ self.base_url.clone().map_or_else(
+ || "https://notifications.bitwarden.com".to_string(),
+ |url| {
+ let clean_url = url.trim_end_matches('/').to_string();
+ if clean_url == "https://notifications.bitwarden.eu" {
+ clean_url
+ } else {
+ format!("{clean_url}/notifications")
+ }
+ },
)
})
}
+ #[must_use]
+ pub fn client_cert_path(&self) -> Option<&std::path::Path> {
+ self.client_cert_path.as_deref()
+ }
+
+ #[must_use]
pub fn server_name(&self) -> String {
self.base_url
.clone()
.unwrap_or_else(|| "default".to_string())
}
}
+
+pub async fn device_id(config: &Config) -> Result<String> {
+ let file = crate::dirs::device_id_file();
+ if let Ok(mut fh) = tokio::fs::File::open(&file).await {
+ let mut s = String::new();
+ fh.read_to_string(&mut s)
+ .await
+ .map_err(|e| Error::LoadDeviceId {
+ source: e,
+ file: file.clone(),
+ })?;
+ Ok(s.trim().to_string())
+ } else {
+ let id = config.device_id.as_ref().map_or_else(
+ || uuid::Uuid::new_v4().hyphenated().to_string(),
+ String::to_string,
+ );
+ let mut fh = tokio::fs::File::create(&file).await.map_err(|e| {
+ Error::LoadDeviceId {
+ source: e,
+ file: file.clone(),
+ }
+ })?;
+ fh.write_all(id.as_bytes()).await.map_err(|e| {
+ Error::LoadDeviceId {
+ source: e,
+ file: file.clone(),
+ }
+ })?;
+ Ok(id)
+ }
+}
diff --git a/src/db.rs b/src/db.rs
index 5359321..73a69d1 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -164,7 +164,10 @@ pub struct Db {
pub access_token: Option<String>,
pub refresh_token: Option<String>,
+ pub kdf: Option<crate::api::KdfType>,
pub iterations: Option<u32>,
+ pub memory: Option<u32>,
+ pub parallelism: Option<u32>,
pub protected_key: Option<String>,
pub protected_private_key: Option<String>,
pub protected_org_keys: std::collections::HashMap<String, String>,
@@ -173,6 +176,7 @@ pub struct Db {
}
impl Db {
+ #[must_use]
pub fn new() -> Self {
Self::default()
}
@@ -287,10 +291,12 @@ impl Db {
Ok(())
}
+ #[must_use]
pub fn needs_login(&self) -> bool {
self.access_token.is_none()
|| self.refresh_token.is_none()
|| self.iterations.is_none()
+ || self.kdf.is_none()
|| self.protected_key.is_none()
}
}
diff --git a/src/dirs.rs b/src/dirs.rs
index 285a0d5..e838e12 100644
--- a/src/dirs.rs
+++ b/src/dirs.rs
@@ -37,59 +37,89 @@ pub fn make_all() -> Result<()> {
Ok(())
}
+#[must_use]
pub fn config_file() -> std::path::PathBuf {
config_dir().join("config.json")
}
const INVALID_PATH: &percent_encoding::AsciiSet =
&percent_encoding::CONTROLS.add(b'/').add(b'%').add(b':');
+#[must_use]
pub fn db_file(server: &str, email: &str) -> std::path::PathBuf {
let server =
percent_encoding::percent_encode(server.as_bytes(), INVALID_PATH)
.to_string();
- cache_dir().join(format!("{}:{}.json", server, email))
+ cache_dir().join(format!("{server}:{email}.json"))
}
+#[must_use]
pub fn pid_file() -> std::path::PathBuf {
runtime_dir().join("pidfile")
}
+#[must_use]
pub fn agent_stdout_file() -> std::path::PathBuf {
data_dir().join("agent.out")
}
+#[must_use]
pub fn agent_stderr_file() -> std::path::PathBuf {
data_dir().join("agent.err")
}
+#[must_use]
+pub fn device_id_file() -> std::path::PathBuf {
+ data_dir().join("device_id")
+}
+
+#[must_use]
pub fn socket_file() -> std::path::PathBuf {
runtime_dir().join("socket")
}
+#[must_use]
fn config_dir() -> std::path::PathBuf {
- let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap();
+ let project_dirs =
+ directories::ProjectDirs::from("", "", &profile()).unwrap();
project_dirs.config_dir().to_path_buf()
}
+#[must_use]
fn cache_dir() -> std::path::PathBuf {
- let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap();
+ let project_dirs =
+ directories::ProjectDirs::from("", "", &profile()).unwrap();
project_dirs.cache_dir().to_path_buf()
}
+#[must_use]
fn data_dir() -> std::path::PathBuf {
- let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap();
+ let project_dirs =
+ directories::ProjectDirs::from("", "", &profile()).unwrap();
project_dirs.data_dir().to_path_buf()
}
+#[must_use]
fn runtime_dir() -> std::path::PathBuf {
- let project_dirs = directories::ProjectDirs::from("", "", "rbw").unwrap();
- match project_dirs.runtime_dir() {
- Some(dir) => dir.to_path_buf(),
- None => format!(
- "{}/rbw-{}",
- std::env::temp_dir().to_string_lossy(),
- nix::unistd::getuid().as_raw()
- )
- .into(),
+ let project_dirs =
+ directories::ProjectDirs::from("", "", &profile()).unwrap();
+ project_dirs.runtime_dir().map_or_else(
+ || {
+ format!(
+ "{}/{}-{}",
+ std::env::temp_dir().to_string_lossy(),
+ &profile(),
+ nix::unistd::getuid().as_raw()
+ )
+ .into()
+ },
+ std::path::Path::to_path_buf,
+ )
+}
+
+#[must_use]
+pub fn profile() -> String {
+ match std::env::var("RBW_PROFILE") {
+ Ok(profile) if !profile.is_empty() => format!("rbw-{profile}"),
+ _ => "rbw".to_string(),
}
}
diff --git a/src/edit.rs b/src/edit.rs
index 1a831a7..360f31f 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -2,7 +2,17 @@ use crate::prelude::*;
use std::io::{Read as _, Write as _};
+use is_terminal::IsTerminal as _;
+
pub fn edit(contents: &str, help: &str) -> Result<String> {
+ if !std::io::stdin().is_terminal() {
+ // directly read from piped content
+ return match std::io::read_to_string(std::io::stdin()) {
+ Err(e) => Err(Error::FailedToReadFromStdin { err: e }),
+ Ok(res) => Ok(res),
+ };
+ }
+
let mut var = "VISUAL";
let editor = std::env::var_os(var).unwrap_or_else(|| {
var = "EDITOR";
@@ -30,6 +40,7 @@ pub fn edit(contents: &str, help: &str) -> Result<String> {
let editor = std::path::Path::new(&editor);
let mut editor_args = vec![];
+ #[allow(clippy::single_match_else)] // more to come
match editor.file_name() {
Some(editor) => match editor.to_str() {
Some("vim" | "nvim") => {
@@ -52,7 +63,7 @@ pub fn edit(contents: &str, help: &str) -> Result<String> {
(editor, editor_args)
};
- let res = std::process::Command::new(&cmd).args(&args).status();
+ let res = std::process::Command::new(cmd).args(&args).status();
match res {
Ok(res) => {
if !res.success() {
@@ -80,8 +91,6 @@ pub fn edit(contents: &str, help: &str) -> Result<String> {
}
fn contains_shell_metacharacters(cmd: &std::ffi::OsStr) -> bool {
- match cmd.to_str() {
- Some(s) => s.contains(&[' ', '$', '\'', '"'][..]),
- None => false,
- }
+ cmd.to_str()
+ .map_or(false, |s| s.contains(&[' ', '$', '\'', '"'][..]))
}
diff --git a/src/error.rs b/src/error.rs
index 8116de2..8a3b5e2 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -4,14 +4,10 @@ pub enum Error {
ConfigMissingEmail,
#[error("failed to create block mode decryptor")]
- CreateBlockMode {
- source: block_modes::InvalidKeyIvLength,
- },
+ CreateBlockMode { source: aes::cipher::InvalidLength },
#[error("failed to create block mode decryptor")]
- CreateHmac {
- source: hmac::crypto_mac::InvalidKeyLength,
- },
+ CreateHmac { source: aes::cipher::InvalidLength },
#[error("failed to create directory at {}", .file.display())]
CreateDirectory {
@@ -20,11 +16,14 @@ pub enum Error {
},
#[error("failed to decrypt")]
- Decrypt { source: block_modes::BlockModeError },
+ Decrypt { source: block_padding::UnpadError },
#[error("failed to parse pinentry output ({out:?})")]
FailedToParsePinentry { out: String },
+ #[error("failed to read from stdin: {err}")]
+ FailedToReadFromStdin { err: std::io::Error },
+
#[error(
"failed to run editor {}: {err}",
.editor.to_string_lossy(),
@@ -116,6 +115,24 @@ pub enum Error {
file: std::path::PathBuf,
},
+ #[error("failed to load device id from {}", .file.display())]
+ LoadDeviceId {
+ source: tokio::io::Error,
+ file: std::path::PathBuf,
+ },
+
+ #[error("failed to load client cert from {}", .file.display())]
+ LoadClientCert {
+ source: tokio::io::Error,
+ file: std::path::PathBuf,
+ },
+
+ #[error("failed to load client cert from {}", .file.display())]
+ LoadClientCertReqwest {
+ source: reqwest::Error,
+ file: std::path::PathBuf,
+ },
+
#[error("invalid padding")]
Padding,
@@ -125,6 +142,12 @@ pub enum Error {
#[error("pbkdf2 requires at least 1 iteration (got 0)")]
Pbkdf2ZeroIterations,
+ #[error("failed to run pbkdf2")]
+ Pbkdf2,
+
+ #[error("failed to run argon2")]
+ Argon2,
+
#[error("pinentry cancelled")]
PinentryCancelled,
@@ -207,6 +230,9 @@ pub enum Error {
#[error("error writing to pinentry stdin")]
WriteStdin { source: tokio::io::Error },
+
+ #[error("invalid kdf type: {ty}")]
+ InvalidKdfType { ty: String },
}
pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/identity.rs b/src/identity.rs
index 90d4fad..7836a11 100644
--- a/src/identity.rs
+++ b/src/identity.rs
@@ -1,5 +1,7 @@
use crate::prelude::*;
+use sha1::Digest as _;
+
pub struct Identity {
pub email: String,
pub keys: crate::locked::Keys,
@@ -10,7 +12,10 @@ impl Identity {
pub fn new(
email: &str,
password: &crate::locked::Password,
+ kdf: crate::api::KdfType,
iterations: u32,
+ memory: Option<u32>,
+ parallelism: Option<u32>,
) -> Result<Self> {
let iterations = std::num::NonZeroU32::new(iterations)
.ok_or(Error::Pbkdf2ZeroIterations)?;
@@ -19,12 +24,43 @@ impl Identity {
keys.extend(std::iter::repeat(0).take(64));
let enc_key = &mut keys.data_mut()[0..32];
- pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(
- password.password(),
- email.as_bytes(),
- iterations.get(),
- enc_key,
- );
+
+ match kdf {
+ crate::api::KdfType::Pbkdf2 => {
+ pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(
+ password.password(),
+ email.as_bytes(),
+ iterations.get(),
+ enc_key,
+ )
+ .map_err(|_| Error::Pbkdf2)?;
+ }
+
+ crate::api::KdfType::Argon2id => {
+ let mut hasher = sha2::Sha256::new();
+ hasher.update(email.as_bytes());
+ let salt = hasher.finalize();
+
+ let argon2_config = argon2::Argon2::new(
+ argon2::Algorithm::Argon2id,
+ argon2::Version::V0x13,
+ argon2::Params::new(
+ memory.unwrap() * 1024,
+ iterations.get(),
+ parallelism.unwrap(),
+ Some(32),
+ )
+ .unwrap(),
+ );
+ argon2::Argon2::hash_password_into(
+ &argon2_config,
+ password.password(),
+ &salt,
+ enc_key,
+ )
+ .map_err(|_| Error::Argon2)?;
+ }
+ };
let mut hash = crate::locked::Vec::new();
hash.extend(std::iter::repeat(0).take(32));
@@ -33,7 +69,8 @@ impl Identity {
password.password(),
1,
hash.data_mut(),
- );
+ )
+ .map_err(|_| Error::Pbkdf2)?;
let hkdf = hkdf::Hkdf::<sha2::Sha256>::from_prk(enc_key)
.map_err(|_| Error::HkdfExpand)?;
diff --git a/src/lib.rs b/src/lib.rs
index 4a13e25..fd14fcf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,21 +1,24 @@
+#![warn(clippy::cargo)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
-#![allow(clippy::default_trait_access)]
-#![allow(clippy::implicit_hasher)]
-#![allow(clippy::large_enum_variant)]
+#![warn(clippy::as_conversions)]
+#![warn(clippy::get_unwrap)]
+#![allow(clippy::cognitive_complexity)]
#![allow(clippy::missing_const_for_fn)]
-#![allow(clippy::missing_errors_doc)]
-#![allow(clippy::missing_panics_doc)]
-#![allow(clippy::must_use_candidate)]
#![allow(clippy::similar_names)]
-#![allow(clippy::single_match)]
+#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::type_complexity)]
-#![allow(clippy::unused_async)]
+#![allow(clippy::multiple_crate_versions)]
+#![allow(clippy::large_enum_variant)]
+// we aren't really documenting apis anyway
+#![allow(clippy::missing_errors_doc)]
+#![allow(clippy::missing_panics_doc)]
pub mod actions;
pub mod api;
+pub mod base64;
pub mod cipherstring;
pub mod config;
pub mod db;
diff --git a/src/locked.rs b/src/locked.rs
index 4ddf021..bfa642d 100644
--- a/src/locked.rs
+++ b/src/locked.rs
@@ -18,10 +18,12 @@ impl Default for Vec {
}
impl Vec {
+ #[must_use]
pub fn new() -> Self {
Self::default()
}
+ #[must_use]
pub fn data(&self) -> &[u8] {
self.data.as_slice()
}
@@ -65,10 +67,12 @@ pub struct Password {
}
impl Password {
+ #[must_use]
pub fn new(password: Vec) -> Self {
Self { password }
}
+ #[must_use]
pub fn password(&self) -> &[u8] {
self.password.data()
}
@@ -80,14 +84,17 @@ pub struct Keys {
}
impl Keys {
+ #[must_use]
pub fn new(keys: Vec) -> Self {
Self { keys }
}
+ #[must_use]
pub fn enc_key(&self) -> &[u8] {
&self.keys.data()[0..32]
}
+ #[must_use]
pub fn mac_key(&self) -> &[u8] {
&self.keys.data()[32..64]
}
@@ -99,10 +106,12 @@ pub struct PasswordHash {
}
impl PasswordHash {
+ #[must_use]
pub fn new(hash: Vec) -> Self {
Self { hash }
}
+ #[must_use]
pub fn hash(&self) -> &[u8] {
self.hash.data()
}
@@ -114,10 +123,12 @@ pub struct PrivateKey {
}
impl PrivateKey {
+ #[must_use]
pub fn new(private_key: Vec) -> Self {
Self { private_key }
}
+ #[must_use]
pub fn private_key(&self) -> &[u8] {
self.private_key.data()
}
@@ -130,6 +141,7 @@ pub struct ApiKey {
}
impl ApiKey {
+ #[must_use]
pub fn new(client_id: Password, client_secret: Password) -> Self {
Self {
client_id,
@@ -137,10 +149,12 @@ impl ApiKey {
}
}
+ #[must_use]
pub fn client_id(&self) -> &[u8] {
self.client_id.password()
}
+ #[must_use]
pub fn client_secret(&self) -> &[u8] {
self.client_secret.password()
}
diff --git a/src/pinentry.rs b/src/pinentry.rs
index b4d2bb0..e2a83ed 100644
--- a/src/pinentry.rs
+++ b/src/pinentry.rs
@@ -1,5 +1,6 @@
use crate::prelude::*;
+use std::convert::TryFrom as _;
use tokio::io::AsyncWriteExt as _;
pub async fn getpin(
@@ -33,18 +34,18 @@ pub async fn getpin(
.map_err(|source| Error::WriteStdin { source })?;
ncommands += 1;
stdin
- .write_all(format!("SETPROMPT {}\n", prompt).as_bytes())
+ .write_all(format!("SETPROMPT {prompt}\n").as_bytes())
.await
.map_err(|source| Error::WriteStdin { source })?;
ncommands += 1;
stdin
- .write_all(format!("SETDESC {}\n", desc).as_bytes())
+ .write_all(format!("SETDESC {desc}\n").as_bytes())
.await
.map_err(|source| Error::WriteStdin { source })?;
ncommands += 1;
if let Some(err) = err {
stdin
- .write_all(format!("SETERROR {}\n", err).as_bytes())
+ .write_all(format!("SETERROR {err}\n").as_bytes())
.await
.map_err(|source| Error::WriteStdin { source })?;
ncommands += 1;
@@ -76,15 +77,13 @@ pub async fn getpin(
Ok(crate::locked::Password::new(buf))
}
-async fn read_password<
- R: tokio::io::AsyncRead + tokio::io::AsyncReadExt + Unpin,
->(
+async fn read_password<R>(
mut ncommands: u8,
data: &mut [u8],
mut r: R,
) -> Result<usize>
where
- R: Send,
+ R: tokio::io::AsyncRead + tokio::io::AsyncReadExt + Unpin + Send,
{
let mut len = 0;
loop {
@@ -119,7 +118,7 @@ where
});
}
return Err(Error::PinentryErrorMessage {
- error: format!("unknown error ({})", code),
+ error: format!("unknown error ({code})"),
});
}
None => {
@@ -138,6 +137,14 @@ where
.read(&mut data[len..])
.await
.map_err(|source| Error::PinentryReadOutput { source })?;
+ if bytes == 0 {
+ return Err(Error::PinentryReadOutput {
+ source: std::io::Error::new(
+ std::io::ErrorKind::UnexpectedEof,
+ "unexpected EOF",
+ ),
+ });
+ }
len += bytes;
}
}
@@ -161,9 +168,11 @@ fn percent_decode(buf: &mut [u8]) -> usize {
if c == b'%' && read_idx + 2 < len {
if let Some(h) = char::from(buf[read_idx + 1]).to_digit(16) {
- #[allow(clippy::cast_possible_truncation)]
if let Some(l) = char::from(buf[read_idx + 2]).to_digit(16) {
- c = h as u8 * 0x10 + l as u8;
+ // h and l were parsed from a single hex digit, so they
+ // must be in the range 0-15, so these unwraps are safe
+ c = u8::try_from(h).unwrap() * 0x10
+ + u8::try_from(l).unwrap();
read_idx += 2;
}
}
diff --git a/src/protocol.rs b/src/protocol.rs
index 14fa7f9..e883441 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -1,8 +1,6 @@
-// https://github.com/rust-lang/rust-clippy/issues/6902
-#![allow(clippy::use_self)]
-
// eventually it would be nice to make this a const function so that we could
// just get the version from a variable directly, but this is fine for now
+#[must_use]
pub fn version() -> u32 {
let major = env!("CARGO_PKG_VERSION_MAJOR");
let minor = env!("CARGO_PKG_VERSION_MINOR");
@@ -36,6 +34,9 @@ pub enum Action {
plaintext: String,
org_id: Option<String>,
},
+ ClipboardStore {
+ text: String,
+ },
Quit,
Version,
}
diff --git a/src/pwgen.rs b/src/pwgen.rs
index 55151e6..a112d73 100644
--- a/src/pwgen.rs
+++ b/src/pwgen.rs
@@ -15,6 +15,7 @@ pub enum Type {
Diceware,
}
+#[must_use]
pub fn pwgen(ty: Type, len: usize) -> String {
let mut rng = rand::thread_rng();
@@ -101,6 +102,6 @@ mod test {
for c in s.chars() {
set.insert(c);
}
- assert!(set.len() < s.len())
+ assert!(set.len() < s.len());
}
}