Store credentials in OS keychain via keyring crate
Passwords are no longer stored in config.toml. Instead:
- New setup wizard (--configure) prompts for credentials on first run
and stores them in the OS keychain (macOS Keychain, GNOME Keyring /
KWallet on Linux, Windows Credential Manager)
- Env-var fallback: TUIMAIL_<KEY> for headless environments
- ProtonMail session token moves from session.json to the keychain
- Config file path moves to {config_dir}/tuimail/config.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a8ea1fb5bb
commit
facb44d561
13 changed files with 1219 additions and 65 deletions
673
Cargo.lock
generated
673
Cargo.lock
generated
|
|
@ -185,6 +185,123 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-broadcast"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-io"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"parking",
|
||||||
|
"polling",
|
||||||
|
"rustix",
|
||||||
|
"slab",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-lock"
|
||||||
|
version = "3.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-process"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"async-signal",
|
||||||
|
"async-task",
|
||||||
|
"blocking",
|
||||||
|
"cfg-if",
|
||||||
|
"event-listener",
|
||||||
|
"futures-lite",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-recursion"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-signal"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
|
||||||
|
dependencies = [
|
||||||
|
"async-io",
|
||||||
|
"async-lock",
|
||||||
|
"atomic-waker",
|
||||||
|
"cfg-if",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"slab",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-task"
|
||||||
|
version = "4.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic"
|
name = "atomic"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
@ -343,6 +460,19 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blocking"
|
||||||
|
version = "1.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-task",
|
||||||
|
"futures-io",
|
||||||
|
"futures-lite",
|
||||||
|
"piper",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blowfish"
|
name = "blowfish"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -441,6 +571,15 @@ dependencies = [
|
||||||
"rustversion",
|
"rustversion",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cbc"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.56"
|
version = "1.2.56"
|
||||||
|
|
@ -557,6 +696,15 @@ dependencies = [
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
|
@ -572,6 +720,16 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
|
@ -612,6 +770,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
|
@ -825,6 +989,35 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus"
|
||||||
|
version = "0.9.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21b3aa68d7e7abee336255bd7248ea965cc393f3e70411135a6f6a4b651345d4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libdbus-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dbus-secret-service"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"block-padding",
|
||||||
|
"cbc",
|
||||||
|
"dbus",
|
||||||
|
"fastrand",
|
||||||
|
"hkdf",
|
||||||
|
"num",
|
||||||
|
"once_cell",
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltae"
|
name = "deltae"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -961,6 +1154,27 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
|
@ -1126,6 +1340,33 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endi"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||||
|
dependencies = [
|
||||||
|
"enumflags2_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2_derive"
|
||||||
|
version = "0.7.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
@ -1174,6 +1415,27 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fancy-regex"
|
name = "fancy-regex"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -1323,6 +1585,25 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-lite"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
|
|
@ -1334,6 +1615,12 @@ dependencies = [
|
||||||
"syn 2.0.116",
|
"syn 2.0.116",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
|
|
@ -1354,6 +1641,7 @@ checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
|
|
@ -1489,6 +1777,12 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -1827,6 +2121,7 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"block-padding",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1954,6 +2249,23 @@ dependencies = [
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyring"
|
||||||
|
version = "3.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"dbus-secret-service",
|
||||||
|
"linux-keyutils",
|
||||||
|
"log",
|
||||||
|
"secret-service",
|
||||||
|
"security-framework 2.11.1",
|
||||||
|
"security-framework 3.6.0",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lab"
|
name = "lab"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -2017,12 +2329,31 @@ version = "0.2.182"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libdbus-sys"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "line-clipping"
|
name = "line-clipping"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
|
|
@ -2038,6 +2369,16 @@ version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-keyutils"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -2208,7 +2549,7 @@ dependencies = [
|
||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"openssl-sys",
|
"openssl-sys",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework",
|
"security-framework 3.6.0",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
@ -2262,6 +2603,20 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -2289,6 +2644,15 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -2326,6 +2690,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|
@ -2478,6 +2853,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
|
|
@ -2487,6 +2868,16 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-stream"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p256"
|
name = "p256"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
|
|
@ -2525,6 +2916,12 @@ dependencies = [
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -2801,6 +3198,17 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "piper"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"fastrand",
|
||||||
|
"futures-io",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkcs1"
|
name = "pkcs1"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
|
@ -2841,6 +3249,20 @@ dependencies = [
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polling"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"concurrent-queue",
|
||||||
|
"hermit-abi",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polyval"
|
name = "polyval"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
|
@ -2955,6 +3377,7 @@ dependencies = [
|
||||||
"cfb-mode",
|
"cfb-mode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"keyring",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"pgp",
|
"pgp",
|
||||||
"pwhash",
|
"pwhash",
|
||||||
|
|
@ -3208,6 +3631,17 @@ dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libredox",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.3"
|
||||||
|
|
@ -3434,6 +3868,38 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "secret-service"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"cbc",
|
||||||
|
"futures-util",
|
||||||
|
"generic-array",
|
||||||
|
"hkdf",
|
||||||
|
"num",
|
||||||
|
"once_cell",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.6.0"
|
version = "3.6.0"
|
||||||
|
|
@ -3441,7 +3907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38"
|
checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.1",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
|
|
@ -3525,6 +3991,17 @@ dependencies = [
|
||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -4208,13 +4685,16 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"dirs",
|
||||||
"fast_html2md",
|
"fast_html2md",
|
||||||
"imap",
|
"imap",
|
||||||
|
"keyring",
|
||||||
"lettre",
|
"lettre",
|
||||||
"mailparse",
|
"mailparse",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"proton-bridge",
|
"proton-bridge",
|
||||||
"quoted_printable",
|
"quoted_printable",
|
||||||
|
"rand 0.8.5",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -4243,6 +4723,17 @@ version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uds_windows"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
|
||||||
|
dependencies = [
|
||||||
|
"memoffset",
|
||||||
|
"tempfile",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
|
|
@ -4665,6 +5156,24 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
|
@ -4692,6 +5201,21 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4725,6 +5249,12 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.53.1",
|
"windows_x86_64_msvc 0.53.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4737,6 +5267,12 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4749,6 +5285,12 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4773,6 +5315,12 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4785,6 +5333,12 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4797,6 +5351,12 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4809,6 +5369,12 @@ version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -4947,6 +5513,16 @@ dependencies = [
|
||||||
"rand_core 0.5.1",
|
"rand_core 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg-home"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
|
@ -4985,6 +5561,62 @@ dependencies = [
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||||
|
dependencies = [
|
||||||
|
"async-broadcast",
|
||||||
|
"async-process",
|
||||||
|
"async-recursion",
|
||||||
|
"async-trait",
|
||||||
|
"enumflags2",
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"hex",
|
||||||
|
"nix",
|
||||||
|
"ordered-stream",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
|
"sha1",
|
||||||
|
"static_assertions",
|
||||||
|
"tracing",
|
||||||
|
"uds_windows",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
"xdg-home",
|
||||||
|
"zbus_macros",
|
||||||
|
"zbus_names",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_macros"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zbus_names"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
"zvariant",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.39"
|
version = "0.8.39"
|
||||||
|
|
@ -5084,3 +5716,40 @@ name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||||
|
dependencies = [
|
||||||
|
"endi",
|
||||||
|
"enumflags2",
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
"zvariant_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_derive"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
"zvariant_utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zvariant_utils"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.116",
|
||||||
|
]
|
||||||
|
|
|
||||||
13
Cargo.toml
13
Cargo.toml
|
|
@ -23,4 +23,15 @@ fast_html2md = "0.0"
|
||||||
tui-markdown = "0.3"
|
tui-markdown = "0.3"
|
||||||
quoted_printable = "0.5"
|
quoted_printable = "0.5"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "native-tls", "builder"] }
|
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "native-tls", "builder"] }
|
||||||
|
dirs = "5"
|
||||||
|
rand = { version = "0.8", features = ["getrandom"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["apple-native"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["linux-native-sync-persistent", "crypto-rust"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["windows-native"] }
|
||||||
79
USAGE.md
79
USAGE.md
|
|
@ -7,29 +7,48 @@ the email list on top, the message preview on the bottom.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
Copy the example config and fill in your credentials:
|
tuimail stores passwords securely in the **OS keychain** (macOS Keychain,
|
||||||
|
GNOME Keyring, KWallet, Windows Credential Manager). No passwords are ever
|
||||||
|
written to disk in plain text.
|
||||||
|
|
||||||
|
### First-time setup
|
||||||
|
|
||||||
|
Simply run tuimail — if no config file exists it launches an interactive
|
||||||
|
wizard automatically:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp config.toml.example config.toml
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `config.toml`:
|
The wizard prompts for your provider, server settings, and passwords, then
|
||||||
|
saves the config file and stores all passwords in the OS keychain.
|
||||||
|
|
||||||
```toml
|
### Re-configure / update credentials
|
||||||
[imap]
|
|
||||||
host = "imap.gmail.com" # your provider's IMAP server
|
|
||||||
port = 993
|
|
||||||
username = "you@example.com"
|
|
||||||
password = "your-password"
|
|
||||||
use_tls = true
|
|
||||||
|
|
||||||
[smtp]
|
```bash
|
||||||
host = "smtp.gmail.com" # your provider's SMTP server
|
cargo run -- --configure
|
||||||
port = 465
|
```
|
||||||
username = "you@example.com"
|
|
||||||
password = "your-password"
|
All prompts show current values in brackets. Press Enter to keep a value, or
|
||||||
tls_mode = "smtps" # none | starttls | smtps
|
type a new one. Password prompts show `[stored]` when a value already exists
|
||||||
from = "Your Name <you@example.com>"
|
in the keychain.
|
||||||
|
|
||||||
|
### Headless / CI environments (env-var fallback)
|
||||||
|
|
||||||
|
If the OS keychain is unavailable, export the passwords as environment
|
||||||
|
variables:
|
||||||
|
|
||||||
|
| Variable | Credential |
|
||||||
|
|----------|------------|
|
||||||
|
| `TUIMAIL_IMAP_PASSWORD` | IMAP password |
|
||||||
|
| `TUIMAIL_SMTP_PASSWORD` | SMTP password |
|
||||||
|
| `TUIMAIL_PROTON_PASSWORD` | ProtonMail login password |
|
||||||
|
| `TUIMAIL_PROTON_MAILBOX_PASSWORD` | ProtonMail mailbox password (two-password mode) |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TUIMAIL_IMAP_PASSWORD=hunter2 cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
**Common provider settings**
|
**Common provider settings**
|
||||||
|
|
@ -56,22 +75,16 @@ The bridge starts automatically in-process when `provider = "proton"` is set.
|
||||||
cargo build --features proton
|
cargo build --features proton
|
||||||
```
|
```
|
||||||
|
|
||||||
**2. Configure `config.toml`** (remove or comment out `[imap]` / `[smtp]`):
|
**2. Run the setup wizard:**
|
||||||
|
|
||||||
```toml
|
```bash
|
||||||
provider = "proton"
|
cargo run --features proton -- --configure
|
||||||
|
|
||||||
[proton]
|
|
||||||
username = "you@proton.me"
|
|
||||||
password = "your-proton-login-password"
|
|
||||||
# mailbox_password = "..." # only for two-password-mode accounts
|
|
||||||
|
|
||||||
[bridge]
|
|
||||||
imap_port = 1143
|
|
||||||
smtp_port = 1025
|
|
||||||
local_password = "changeme" # any string; used only locally
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The wizard prompts for your ProtonMail username and password (stored in
|
||||||
|
keychain), two-password mode, and bridge ports. The bridge local password is
|
||||||
|
auto-generated and stored in the keychain.
|
||||||
|
|
||||||
**3. Run:**
|
**3. Run:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -219,9 +232,10 @@ progress. Your current selection is preserved across refreshes.
|
||||||
| `host` | string | IMAP server hostname |
|
| `host` | string | IMAP server hostname |
|
||||||
| `port` | integer | IMAP port (usually 993 with TLS, 143 without) |
|
| `port` | integer | IMAP port (usually 993 with TLS, 143 without) |
|
||||||
| `username` | string | Login username (usually your full email address) |
|
| `username` | string | Login username (usually your full email address) |
|
||||||
| `password` | string | Password or app-specific password |
|
|
||||||
| `use_tls` | bool | `true` for IMAPS (port 993), `false` for plain/STARTTLS |
|
| `use_tls` | bool | `true` for IMAPS (port 993), `false` for plain/STARTTLS |
|
||||||
|
|
||||||
|
> Password is stored in the OS keychain. Use `--configure` to set or update it.
|
||||||
|
|
||||||
### `[smtp]`
|
### `[smtp]`
|
||||||
|
|
||||||
| Key | Type | Description |
|
| Key | Type | Description |
|
||||||
|
|
@ -229,6 +243,7 @@ progress. Your current selection is preserved across refreshes.
|
||||||
| `host` | string | SMTP server hostname |
|
| `host` | string | SMTP server hostname |
|
||||||
| `port` | integer | SMTP port |
|
| `port` | integer | SMTP port |
|
||||||
| `username` | string | Login username |
|
| `username` | string | Login username |
|
||||||
| `password` | string | Password or app-specific password |
|
|
||||||
| `tls_mode` | string | `none`, `starttls`, or `smtps` |
|
| `tls_mode` | string | `none`, `starttls`, or `smtps` |
|
||||||
| `from` | string | Sender address shown to recipients, e.g. `Name <addr>` |
|
| `from` | string | Sender address shown to recipients, e.g. `Name <addr>` |
|
||||||
|
|
||||||
|
> Password is stored in the OS keychain. Use `--configure` to set or update it.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
# Passwords are stored securely in the OS keychain.
|
||||||
|
# Run `tuimail --configure` to set up credentials.
|
||||||
|
#
|
||||||
|
# This file shows all non-sensitive fields you can set manually.
|
||||||
|
# Copy it to the tuimail config directory and edit as needed, then
|
||||||
|
# run `tuimail --configure` to store passwords in the keychain.
|
||||||
|
|
||||||
# ── Standard IMAP/SMTP provider (default) ─────────────────────────────────────
|
# ── Standard IMAP/SMTP provider (default) ─────────────────────────────────────
|
||||||
# provider = "imap" # optional — "imap" is the default
|
# provider = "imap" # optional — "imap" is the default
|
||||||
|
|
||||||
|
|
@ -5,14 +12,12 @@
|
||||||
host = "imap.gmail.com" # your provider's IMAP server
|
host = "imap.gmail.com" # your provider's IMAP server
|
||||||
port = 993
|
port = 993
|
||||||
username = "you@example.com"
|
username = "you@example.com"
|
||||||
password = "your-app-password"
|
|
||||||
use_tls = true
|
use_tls = true
|
||||||
|
|
||||||
[smtp]
|
[smtp]
|
||||||
host = "smtp.gmail.com" # your provider's SMTP server
|
host = "smtp.gmail.com" # your provider's SMTP server
|
||||||
port = 465
|
port = 465
|
||||||
username = "you@example.com"
|
username = "you@example.com"
|
||||||
password = "your-app-password"
|
|
||||||
# tls_mode options: none | starttls | smtps
|
# tls_mode options: none | starttls | smtps
|
||||||
tls_mode = "smtps"
|
tls_mode = "smtps"
|
||||||
from = "Your Name <you@example.com>"
|
from = "Your Name <you@example.com>"
|
||||||
|
|
@ -27,10 +32,7 @@ from = "Your Name <you@example.com>"
|
||||||
#
|
#
|
||||||
# [proton]
|
# [proton]
|
||||||
# username = "you@proton.me"
|
# username = "you@proton.me"
|
||||||
# password = "your-proton-login-password"
|
|
||||||
# # mailbox_password = "..." # only for two-password-mode accounts
|
|
||||||
#
|
#
|
||||||
# [bridge]
|
# [bridge]
|
||||||
# imap_port = 1143
|
# imap_port = 1143
|
||||||
# smtp_port = 1025
|
# smtp_port = 1025
|
||||||
# local_password = "changeme" # any string; used only locally between tuimail and the bridge
|
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,13 @@ env_logger = "0.11"
|
||||||
aes = "0.8"
|
aes = "0.8"
|
||||||
cfb-mode = "0.8"
|
cfb-mode = "0.8"
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["apple-native"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["linux-native-sync-persistent", "crypto-rust"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
keyring = { version = "3", features = ["windows-native"] }
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs;
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
@ -8,7 +7,8 @@ use crate::config::ProtonConfig;
|
||||||
use crate::srp;
|
use crate::srp;
|
||||||
|
|
||||||
const API_BASE: &str = "https://mail.proton.me/api";
|
const API_BASE: &str = "https://mail.proton.me/api";
|
||||||
const SESSION_FILE: &str = "session.json";
|
const SESSION_KEY: &str = "proton_session";
|
||||||
|
const SERVICE: &str = "tuimail";
|
||||||
/// Refresh 5 minutes before the token actually expires.
|
/// Refresh 5 minutes before the token actually expires.
|
||||||
const REFRESH_MARGIN_SECS: u64 = 300;
|
const REFRESH_MARGIN_SECS: u64 = 300;
|
||||||
|
|
||||||
|
|
@ -128,17 +128,23 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) -> Result<(), String> {
|
pub fn save(&self) -> Result<(), String> {
|
||||||
let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?;
|
let json = serde_json::to_string(self).map_err(|e| e.to_string())?;
|
||||||
fs::write(SESSION_FILE, json).map_err(|e| e.to_string())
|
keyring::Entry::new(SERVICE, SESSION_KEY)
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.set_password(&json)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load() -> Option<Self> {
|
pub fn load() -> Option<Self> {
|
||||||
let json = fs::read_to_string(SESSION_FILE).ok()?;
|
let entry = keyring::Entry::new(SERVICE, SESSION_KEY).ok()?;
|
||||||
|
let json = entry.get_password().ok()?;
|
||||||
serde_json::from_str(&json).ok()
|
serde_json::from_str(&json).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete() {
|
pub fn delete() {
|
||||||
let _ = fs::remove_file(SESSION_FILE);
|
if let Ok(entry) = keyring::Entry::new(SERVICE, SESSION_KEY) {
|
||||||
|
let _ = entry.delete_credential();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
|
#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
|
|
@ -41,7 +42,8 @@ pub struct SmtpConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
#[serde(default)]
|
||||||
|
pub password: Option<String>,
|
||||||
pub tls_mode: TlsMode,
|
pub tls_mode: TlsMode,
|
||||||
pub from: String,
|
pub from: String,
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +53,8 @@ pub struct ImapConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
#[serde(default)]
|
||||||
|
pub password: Option<String>,
|
||||||
pub use_tls: bool,
|
pub use_tls: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +63,8 @@ pub struct ImapConfig {
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct ProtonConfig {
|
pub struct ProtonConfig {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
#[serde(default)]
|
||||||
|
pub password: Option<String>,
|
||||||
pub mailbox_password: Option<String>,
|
pub mailbox_password: Option<String>,
|
||||||
pub user_key_passphrase: Option<String>,
|
pub user_key_passphrase: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
@ -70,16 +74,65 @@ pub struct ProtonConfig {
|
||||||
pub struct BridgeConfig {
|
pub struct BridgeConfig {
|
||||||
pub imap_port: u16,
|
pub imap_port: u16,
|
||||||
pub smtp_port: u16,
|
pub smtp_port: u16,
|
||||||
pub local_password: String,
|
#[serde(default)]
|
||||||
|
pub local_password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
/// Return the path to the config file: `{config_dir}/tuimail/config.toml`.
|
||||||
|
pub fn config_path() -> PathBuf {
|
||||||
|
dirs::config_dir()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("."))
|
||||||
|
.join("tuimail")
|
||||||
|
.join("config.toml")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let content = fs::read_to_string("config.toml")?;
|
let path = Self::config_path();
|
||||||
|
let content = fs::read_to_string(&path)
|
||||||
|
.map_err(|e| format!("Cannot read {}: {e}", path.display()))?;
|
||||||
let config: Config = toml::from_str(&content)?;
|
let config: Config = toml::from_str(&content)?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fill in password fields from the OS keychain (with env-var fallback).
|
||||||
|
/// Must be called after `load()` and before any provider operations.
|
||||||
|
pub fn inject_credentials(&mut self) -> Result<(), String> {
|
||||||
|
use crate::credentials;
|
||||||
|
match self.provider {
|
||||||
|
Provider::Imap => {
|
||||||
|
if let Some(ref mut imap) = self.imap {
|
||||||
|
if imap.password.is_none() {
|
||||||
|
imap.password = Some(credentials::get(credentials::IMAP_PASSWORD)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref mut smtp) = self.smtp {
|
||||||
|
if smtp.password.is_none() {
|
||||||
|
smtp.password = Some(credentials::get(credentials::SMTP_PASSWORD)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Provider::Proton => {
|
||||||
|
if let Some(ref mut proton) = self.proton {
|
||||||
|
if proton.password.is_none() {
|
||||||
|
proton.password = Some(credentials::get(credentials::PROTON_PASSWORD)?);
|
||||||
|
}
|
||||||
|
if proton.mailbox_password.is_none() {
|
||||||
|
proton.mailbox_password =
|
||||||
|
credentials::get(credentials::PROTON_MAILBOX_PASSWORD).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref mut bridge) = self.bridge {
|
||||||
|
if bridge.local_password.is_none() {
|
||||||
|
bridge.local_password =
|
||||||
|
Some(credentials::get(credentials::BRIDGE_LOCAL_PASSWORD)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the effective IMAP config regardless of provider.
|
/// Returns the effective IMAP config regardless of provider.
|
||||||
/// For `provider = "imap"` this is the `[imap]` section.
|
/// For `provider = "imap"` this is the `[imap]` section.
|
||||||
/// For `provider = "proton"` this is derived from `[bridge]`.
|
/// For `provider = "proton"` this is derived from `[bridge]`.
|
||||||
|
|
@ -88,12 +141,12 @@ impl Config {
|
||||||
Provider::Imap => self
|
Provider::Imap => self
|
||||||
.imap
|
.imap
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| "[imap] section missing from config.toml".to_string()),
|
.ok_or_else(|| "[imap] section missing from config".to_string()),
|
||||||
Provider::Proton => {
|
Provider::Proton => {
|
||||||
let b = self
|
let b = self
|
||||||
.bridge
|
.bridge
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| "[bridge] section missing from config.toml".to_string())?;
|
.ok_or_else(|| "[bridge] section missing from config".to_string())?;
|
||||||
Ok(ImapConfig {
|
Ok(ImapConfig {
|
||||||
host: "127.0.0.1".into(),
|
host: "127.0.0.1".into(),
|
||||||
port: b.imap_port,
|
port: b.imap_port,
|
||||||
|
|
@ -111,12 +164,12 @@ impl Config {
|
||||||
Provider::Imap => self
|
Provider::Imap => self
|
||||||
.smtp
|
.smtp
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or_else(|| "[smtp] section missing from config.toml".to_string()),
|
.ok_or_else(|| "[smtp] section missing from config".to_string()),
|
||||||
Provider::Proton => {
|
Provider::Proton => {
|
||||||
let b = self
|
let b = self
|
||||||
.bridge
|
.bridge
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| "[bridge] section missing from config.toml".to_string())?;
|
.ok_or_else(|| "[bridge] section missing from config".to_string())?;
|
||||||
let from = self
|
let from = self
|
||||||
.proton
|
.proton
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -143,14 +196,14 @@ impl Config {
|
||||||
Ok(proton_bridge::config::Config {
|
Ok(proton_bridge::config::Config {
|
||||||
proton: proton_bridge::config::ProtonConfig {
|
proton: proton_bridge::config::ProtonConfig {
|
||||||
username: p.username.clone(),
|
username: p.username.clone(),
|
||||||
password: p.password.clone(),
|
password: p.password.clone().unwrap_or_default(),
|
||||||
mailbox_password: p.mailbox_password.clone(),
|
mailbox_password: p.mailbox_password.clone(),
|
||||||
user_key_passphrase: p.user_key_passphrase.clone(),
|
user_key_passphrase: p.user_key_passphrase.clone(),
|
||||||
},
|
},
|
||||||
bridge: proton_bridge::config::BridgeConfig {
|
bridge: proton_bridge::config::BridgeConfig {
|
||||||
imap_port: b.imap_port,
|
imap_port: b.imap_port,
|
||||||
smtp_port: b.smtp_port,
|
smtp_port: b.smtp_port,
|
||||||
local_password: b.local_password.clone(),
|
local_password: b.local_password.clone().unwrap_or_default(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,15 @@ pub(crate) fn connect(config: &Config) -> Result<ImapSession, String> {
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let tls_stream = tls.connect(&imap_cfg.host, tcp)
|
let tls_stream = tls.connect(&imap_cfg.host, tcp)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
let password = imap_cfg.password.as_deref().unwrap_or("");
|
||||||
let session = imap::Client::new(tls_stream)
|
let session = imap::Client::new(tls_stream)
|
||||||
.login(&imap_cfg.username, &imap_cfg.password)
|
.login(&imap_cfg.username, password)
|
||||||
.map_err(|(e, _)| e.to_string())?;
|
.map_err(|(e, _)| e.to_string())?;
|
||||||
Ok(ImapSession::Tls(session))
|
Ok(ImapSession::Tls(session))
|
||||||
} else {
|
} else {
|
||||||
|
let password = imap_cfg.password.as_deref().unwrap_or("");
|
||||||
let session = imap::Client::new(tcp)
|
let session = imap::Client::new(tcp)
|
||||||
.login(&imap_cfg.username, &imap_cfg.password)
|
.login(&imap_cfg.username, password)
|
||||||
.map_err(|(e, _)| e.to_string())?;
|
.map_err(|(e, _)| e.to_string())?;
|
||||||
Ok(ImapSession::Plain(session))
|
Ok(ImapSession::Plain(session))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
src/credentials.rs
Normal file
39
src/credentials.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
pub const IMAP_PASSWORD: &str = "imap_password";
|
||||||
|
pub const SMTP_PASSWORD: &str = "smtp_password";
|
||||||
|
pub const PROTON_PASSWORD: &str = "proton_password";
|
||||||
|
pub const PROTON_MAILBOX_PASSWORD: &str = "proton_mailbox_password";
|
||||||
|
pub const BRIDGE_LOCAL_PASSWORD: &str = "bridge_local_password";
|
||||||
|
|
||||||
|
const SERVICE: &str = "tuimail";
|
||||||
|
|
||||||
|
pub fn get(key: &str) -> Result<String, String> {
|
||||||
|
// 1. OS keychain
|
||||||
|
let keychain_err = match keyring::Entry::new(SERVICE, key) {
|
||||||
|
Ok(entry) => match entry.get_password() {
|
||||||
|
Ok(val) => return Ok(val),
|
||||||
|
Err(e) => format!("{e}"),
|
||||||
|
},
|
||||||
|
Err(e) => format!("entry creation failed: {e}"),
|
||||||
|
};
|
||||||
|
// 2. env var: TUIMAIL_<KEY_UPPERCASE>
|
||||||
|
let env_key = format!("TUIMAIL_{}", key.to_uppercase());
|
||||||
|
std::env::var(&env_key).map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"Credential '{key}' not found (keychain: {keychain_err}). \
|
||||||
|
Run with --configure to set up credentials, or set {env_key}."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(key: &str, value: &str) -> Result<(), String> {
|
||||||
|
keyring::Entry::new(SERVICE, key)
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.set_password(value)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(key: &str) {
|
||||||
|
if let Ok(entry) = keyring::Entry::new(SERVICE, key) {
|
||||||
|
let _ = entry.delete_credential();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,8 @@ use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod credentials;
|
||||||
|
pub mod setup;
|
||||||
mod connect;
|
mod connect;
|
||||||
mod inbox;
|
mod inbox;
|
||||||
mod smtp;
|
mod smtp;
|
||||||
|
|
|
||||||
36
src/main.rs
36
src/main.rs
|
|
@ -12,8 +12,40 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let config = Config::load().unwrap_or_else(|e| {
|
// ── Parse --configure flag ──
|
||||||
eprintln!("Failed to load config.toml: {e}");
|
let reconfigure = std::env::args().any(|a| a == "--configure");
|
||||||
|
|
||||||
|
// ── First-time setup or reconfigure (before raw mode so stdin/stdout work) ──
|
||||||
|
let mut config = if !Config::config_path().exists() || reconfigure {
|
||||||
|
if reconfigure && Config::config_path().exists() {
|
||||||
|
let cfg = Config::load().unwrap_or_else(|e| {
|
||||||
|
eprintln!("Failed to load config: {e}");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
tuimail::setup::run_configure(&cfg).unwrap_or_else(|e| {
|
||||||
|
eprintln!("Configuration failed: {e}");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
Config::load().unwrap_or_else(|e| {
|
||||||
|
eprintln!("Failed to reload config: {e}");
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
tuimail::setup::run_first_time_setup().unwrap_or_else(|e| {
|
||||||
|
eprintln!("Setup failed: {e}");
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Config::load().unwrap_or_else(|e| {
|
||||||
|
eprintln!("Failed to load config: {e}");
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Inject credentials from keychain / env vars ──
|
||||||
|
config.inject_credentials().unwrap_or_else(|e| {
|
||||||
|
eprintln!("{e}");
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
314
src/setup.rs
Normal file
314
src/setup.rs
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
use std::io::{self, BufRead, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::config::{BridgeConfig, Config, ImapConfig, Provider, ProtonConfig, SmtpConfig, TlsMode};
|
||||||
|
use crate::credentials;
|
||||||
|
|
||||||
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn prompt(label: &str, default: Option<&str>) -> String {
|
||||||
|
let hint = default.map(|d| format!(" [{d}]")).unwrap_or_default();
|
||||||
|
print!("{label}{hint}: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut line = String::new();
|
||||||
|
io::stdin().lock().read_line(&mut line).unwrap();
|
||||||
|
let trimmed = line.trim().to_string();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
default.unwrap_or("").to_string()
|
||||||
|
} else {
|
||||||
|
trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optional password prompt — returns None (keep existing) when Enter is pressed with no input.
|
||||||
|
/// Use for --configure where a stored value may already exist.
|
||||||
|
fn prompt_password_optional(label: &str) -> Option<String> {
|
||||||
|
print!("{label} [stored, Enter to keep]: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut line = String::new();
|
||||||
|
io::stdin().lock().read_line(&mut line).unwrap();
|
||||||
|
let trimmed = line.trim().to_string();
|
||||||
|
if trimmed.is_empty() { None } else { Some(trimmed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Required password prompt — keeps asking until the user types something.
|
||||||
|
fn prompt_password_required(label: &str) -> String {
|
||||||
|
loop {
|
||||||
|
print!("{label}: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut line = String::new();
|
||||||
|
io::stdin().lock().read_line(&mut line).unwrap();
|
||||||
|
let trimmed = line.trim().to_string();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
println!("Password cannot be empty.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_bool(label: &str, default: bool) -> bool {
|
||||||
|
let hint = if default { "Y/n" } else { "y/N" };
|
||||||
|
print!("{label} [{hint}]: ");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
let mut line = String::new();
|
||||||
|
io::stdin().lock().read_line(&mut line).unwrap();
|
||||||
|
match line.trim().to_lowercase().as_str() {
|
||||||
|
"y" | "yes" => true,
|
||||||
|
"n" | "no" => false,
|
||||||
|
_ => default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_tls_mode(default: Option<&TlsMode>) -> TlsMode {
|
||||||
|
let default_str = default.map(|m| match m {
|
||||||
|
TlsMode::None => "none",
|
||||||
|
TlsMode::Starttls => "starttls",
|
||||||
|
TlsMode::Smtps => "smtps",
|
||||||
|
});
|
||||||
|
loop {
|
||||||
|
let val = prompt("SMTP TLS mode (none/starttls/smtps)", default_str);
|
||||||
|
match val.to_lowercase().as_str() {
|
||||||
|
"none" => return TlsMode::None,
|
||||||
|
"starttls" => return TlsMode::Starttls,
|
||||||
|
"smtps" => return TlsMode::Smtps,
|
||||||
|
_ => println!("Please enter: none, starttls, or smtps"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_hex(bytes: usize) -> String {
|
||||||
|
use rand::RngCore;
|
||||||
|
let mut buf = vec![0u8; bytes];
|
||||||
|
rand::thread_rng().fill_bytes(&mut buf);
|
||||||
|
buf.iter().map(|b| format!("{b:02x}")).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_config(config: &Config) -> Result<(), String> {
|
||||||
|
let path = Config::config_path();
|
||||||
|
let fallback = PathBuf::from(".");
|
||||||
|
let dir = path.parent().unwrap_or(&fallback);
|
||||||
|
std::fs::create_dir_all(dir).map_err(|e| e.to_string())?;
|
||||||
|
let content = build_toml(config);
|
||||||
|
std::fs::write(&path, content).map_err(|e| e.to_string())?;
|
||||||
|
println!("Config written to {}", path.display());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_toml(config: &Config) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
match config.provider {
|
||||||
|
Provider::Imap => {
|
||||||
|
// provider line only needed when non-default, but write it for clarity
|
||||||
|
out.push_str("provider = \"imap\"\n\n");
|
||||||
|
if let Some(imap) = &config.imap {
|
||||||
|
out.push_str("[imap]\n");
|
||||||
|
out.push_str(&format!("host = {:?}\n", imap.host));
|
||||||
|
out.push_str(&format!("port = {}\n", imap.port));
|
||||||
|
out.push_str(&format!("username = {:?}\n", imap.username));
|
||||||
|
out.push_str(&format!("use_tls = {}\n", imap.use_tls));
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
if let Some(smtp) = &config.smtp {
|
||||||
|
out.push_str("[smtp]\n");
|
||||||
|
out.push_str(&format!("host = {:?}\n", smtp.host));
|
||||||
|
out.push_str(&format!("port = {}\n", smtp.port));
|
||||||
|
out.push_str(&format!("username = {:?}\n", smtp.username));
|
||||||
|
let tls = match &smtp.tls_mode {
|
||||||
|
TlsMode::None => "none",
|
||||||
|
TlsMode::Starttls => "starttls",
|
||||||
|
TlsMode::Smtps => "smtps",
|
||||||
|
};
|
||||||
|
out.push_str(&format!("tls_mode = {:?}\n", tls));
|
||||||
|
out.push_str(&format!("from = {:?}\n", smtp.from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Provider::Proton => {
|
||||||
|
out.push_str("provider = \"proton\"\n\n");
|
||||||
|
if let Some(proton) = &config.proton {
|
||||||
|
out.push_str("[proton]\n");
|
||||||
|
out.push_str(&format!("username = {:?}\n", proton.username));
|
||||||
|
}
|
||||||
|
out.push('\n');
|
||||||
|
if let Some(bridge) = &config.bridge {
|
||||||
|
out.push_str("[bridge]\n");
|
||||||
|
out.push_str(&format!("imap_port = {}\n", bridge.imap_port));
|
||||||
|
out.push_str(&format!("smtp_port = {}\n", bridge.smtp_port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── public API ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// First-time setup wizard. Runs before raw mode on plain stdout.
|
||||||
|
pub fn run_first_time_setup() -> Result<Config, String> {
|
||||||
|
println!("=== tuimail first-time setup ===");
|
||||||
|
println!("Passwords will be stored in the OS keychain.\n");
|
||||||
|
|
||||||
|
let provider_str = loop {
|
||||||
|
let v = prompt("Provider (imap/proton)", Some("imap"));
|
||||||
|
match v.to_lowercase().as_str() {
|
||||||
|
"imap" | "proton" => break v.to_lowercase(),
|
||||||
|
_ => println!("Please enter 'imap' or 'proton'"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = if provider_str == "imap" {
|
||||||
|
setup_imap(None)
|
||||||
|
} else {
|
||||||
|
setup_proton(None)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
write_config(&config)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-run the wizard with existing values pre-filled (--configure).
|
||||||
|
pub fn run_configure(existing: &Config) -> Result<(), String> {
|
||||||
|
println!("=== tuimail configure ===");
|
||||||
|
println!("Press Enter to keep the current value. Passwords: Enter to keep stored credential.\n");
|
||||||
|
|
||||||
|
let config = match existing.provider {
|
||||||
|
Provider::Imap => setup_imap(Some(existing)),
|
||||||
|
Provider::Proton => setup_proton(Some(existing)),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
write_config(&config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── provider-specific flows ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn setup_imap(existing: Option<&Config>) -> Result<Config, String> {
|
||||||
|
let ex_imap = existing.and_then(|c| c.imap.as_ref());
|
||||||
|
let ex_smtp = existing.and_then(|c| c.smtp.as_ref());
|
||||||
|
|
||||||
|
println!("--- IMAP settings ---");
|
||||||
|
let imap_host = prompt("IMAP host", ex_imap.map(|i| i.host.as_str()));
|
||||||
|
let imap_port: u16 = prompt("IMAP port", ex_imap.map(|_| "").or(Some("993")))
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(993);
|
||||||
|
let imap_user = prompt("IMAP username (email)", ex_imap.map(|i| i.username.as_str()));
|
||||||
|
let imap_tls = prompt_bool("Use TLS for IMAP?", ex_imap.map(|i| i.use_tls).unwrap_or(true));
|
||||||
|
|
||||||
|
let imap_pass = if credentials::get(credentials::IMAP_PASSWORD).is_ok() {
|
||||||
|
prompt_password_optional("IMAP password")
|
||||||
|
} else {
|
||||||
|
Some(prompt_password_required("IMAP password"))
|
||||||
|
};
|
||||||
|
if let Some(ref pw) = imap_pass {
|
||||||
|
credentials::set(credentials::IMAP_PASSWORD, pw)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- SMTP settings ---");
|
||||||
|
let smtp_host = prompt("SMTP host", ex_smtp.map(|s| s.host.as_str()));
|
||||||
|
let smtp_port: u16 = prompt("SMTP port", ex_smtp.map(|_| "").or(Some("465")))
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(465);
|
||||||
|
let smtp_user = prompt("SMTP username (email)", ex_smtp.map(|s| s.username.as_str()));
|
||||||
|
let smtp_tls = prompt_tls_mode(ex_smtp.map(|s| &s.tls_mode));
|
||||||
|
let smtp_from = prompt("From address (e.g. Name <addr@example.com>)", ex_smtp.map(|s| s.from.as_str()));
|
||||||
|
|
||||||
|
let smtp_pass = if credentials::get(credentials::SMTP_PASSWORD).is_ok() {
|
||||||
|
prompt_password_optional("SMTP password")
|
||||||
|
} else {
|
||||||
|
Some(prompt_password_required("SMTP password"))
|
||||||
|
};
|
||||||
|
if let Some(ref pw) = smtp_pass {
|
||||||
|
credentials::set(credentials::SMTP_PASSWORD, pw)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
provider: Provider::Imap,
|
||||||
|
imap: Some(ImapConfig {
|
||||||
|
host: imap_host,
|
||||||
|
port: imap_port,
|
||||||
|
username: imap_user,
|
||||||
|
password: None, // stored in keychain
|
||||||
|
use_tls: imap_tls,
|
||||||
|
}),
|
||||||
|
smtp: Some(SmtpConfig {
|
||||||
|
host: smtp_host,
|
||||||
|
port: smtp_port,
|
||||||
|
username: smtp_user,
|
||||||
|
password: None, // stored in keychain
|
||||||
|
tls_mode: smtp_tls,
|
||||||
|
from: smtp_from,
|
||||||
|
}),
|
||||||
|
proton: None,
|
||||||
|
bridge: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_proton(existing: Option<&Config>) -> Result<Config, String> {
|
||||||
|
let ex_proton = existing.and_then(|c| c.proton.as_ref());
|
||||||
|
let ex_bridge = existing.and_then(|c| c.bridge.as_ref());
|
||||||
|
|
||||||
|
println!("--- ProtonMail settings ---");
|
||||||
|
let username = prompt("Proton account email", ex_proton.map(|p| p.username.as_str()));
|
||||||
|
|
||||||
|
let proton_pass = if credentials::get(credentials::PROTON_PASSWORD).is_ok() {
|
||||||
|
prompt_password_optional("Proton login password")
|
||||||
|
} else {
|
||||||
|
Some(prompt_password_required("Proton login password"))
|
||||||
|
};
|
||||||
|
if let Some(ref pw) = proton_pass {
|
||||||
|
credentials::set(credentials::PROTON_PASSWORD, pw)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let two_pw = prompt_bool(
|
||||||
|
"Use two-password mode?",
|
||||||
|
ex_proton.and_then(|p| p.mailbox_password.as_ref()).is_some(),
|
||||||
|
);
|
||||||
|
if two_pw {
|
||||||
|
let mbx_pass = if credentials::get(credentials::PROTON_MAILBOX_PASSWORD).is_ok() {
|
||||||
|
prompt_password_optional("Mailbox password")
|
||||||
|
} else {
|
||||||
|
Some(prompt_password_required("Mailbox password"))
|
||||||
|
};
|
||||||
|
if let Some(ref pw) = mbx_pass {
|
||||||
|
credentials::set(credentials::PROTON_MAILBOX_PASSWORD, pw)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
credentials::delete(credentials::PROTON_MAILBOX_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
let imap_port: u16 = prompt(
|
||||||
|
"Bridge IMAP port",
|
||||||
|
ex_bridge.map(|_| "").or(Some("1143")),
|
||||||
|
)
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(1143);
|
||||||
|
let smtp_port: u16 = prompt(
|
||||||
|
"Bridge SMTP port",
|
||||||
|
ex_bridge.map(|_| "").or(Some("1025")),
|
||||||
|
)
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(1025);
|
||||||
|
|
||||||
|
// Generate a new local bridge password only if one isn't already stored.
|
||||||
|
let local_pw_exists = credentials::get(credentials::BRIDGE_LOCAL_PASSWORD).is_ok();
|
||||||
|
if !local_pw_exists {
|
||||||
|
let local_pw = random_hex(16);
|
||||||
|
credentials::set(credentials::BRIDGE_LOCAL_PASSWORD, &local_pw)?;
|
||||||
|
println!("Generated bridge local password (stored in keychain).");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
provider: Provider::Proton,
|
||||||
|
imap: None,
|
||||||
|
smtp: None,
|
||||||
|
proton: Some(ProtonConfig {
|
||||||
|
username,
|
||||||
|
password: None,
|
||||||
|
mailbox_password: None,
|
||||||
|
user_key_passphrase: ex_proton.and_then(|p| p.user_key_passphrase.clone()),
|
||||||
|
}),
|
||||||
|
bridge: Some(BridgeConfig {
|
||||||
|
imap_port,
|
||||||
|
smtp_port,
|
||||||
|
local_password: None,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ pub(crate) fn send_email(
|
||||||
.body(body.to_string())
|
.body(body.to_string())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let creds = Credentials::new(cfg.username.clone(), cfg.password.clone());
|
let creds = Credentials::new(cfg.username.clone(), cfg.password.clone().unwrap_or_default());
|
||||||
|
|
||||||
let transport = match cfg.tls_mode {
|
let transport = match cfg.tls_mode {
|
||||||
TlsMode::Starttls => {
|
TlsMode::Starttls => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue