mirror of
https://github.com/chatmail/core.git
synced 2026-04-13 11:40:41 +03:00
Compare commits
45 Commits
try_stress
...
fix-repl-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e579afc3bd | ||
|
|
7b3a962498 | ||
|
|
41bba7e780 | ||
|
|
419b7d1d5c | ||
|
|
6d8b4a7ec0 | ||
|
|
84963e198e | ||
|
|
408e9946af | ||
|
|
43f49f8917 | ||
|
|
b6161c431b | ||
|
|
a236a619ad | ||
|
|
cdbd3d7d84 | ||
|
|
8efc880b77 | ||
|
|
4bade7e13a | ||
|
|
53099bbfd1 | ||
|
|
7d1d02bf3b | ||
|
|
4f477ec6d2 | ||
|
|
0a4d6fe09b | ||
|
|
8640bd5ee6 | ||
|
|
19a6a30fe2 | ||
|
|
23b6974386 | ||
|
|
103ee966f4 | ||
|
|
6100a23e80 | ||
|
|
9f7f387540 | ||
|
|
307357df70 | ||
|
|
bd903d8e8f | ||
|
|
3db6d5a458 | ||
|
|
4330da232c | ||
|
|
157dd44df0 | ||
|
|
460e60063c | ||
|
|
ec601a3381 | ||
|
|
2156c6cd7a | ||
|
|
13811c06ee | ||
|
|
230d40daa0 | ||
|
|
45aba61ac8 | ||
|
|
2adeadfd73 | ||
|
|
477e689c74 | ||
|
|
00e8f2271a | ||
|
|
9442df0cf8 | ||
|
|
d9de33820f | ||
|
|
f13fbe4398 | ||
|
|
811655bc98 | ||
|
|
0760bfaf7b | ||
|
|
177cd52039 | ||
|
|
1ab6186eaa | ||
|
|
26b0c43cc4 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 1.34.0
|
||||
|
||||
- new api for io, thread and event handling #1356,
|
||||
see the example atop of `deltachat.h` to get an overview
|
||||
|
||||
- LOTS of speed improvements due to async processing #1356
|
||||
|
||||
- enable WAL mode for sqlite #1492
|
||||
|
||||
- process incoming messages in bulk #1527
|
||||
|
||||
- improve finding out the sent-folder #1488
|
||||
|
||||
- several bug fixes
|
||||
|
||||
|
||||
## 1.33.0
|
||||
|
||||
- let `dc_set_muted()` also mute one-to-one chats #1470
|
||||
|
||||
181
Cargo.lock
generated
181
Cargo.lock
generated
@@ -114,7 +114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
|
||||
dependencies = [
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -136,9 +136,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-imap"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dacff85e0a4ef94b196344a715bfb655d1ec806693d7a566569154b5ea74284"
|
||||
checksum = "8113a1ae7ed80ce764f7474f4323b66f945367c2195d046cfa5efdde30fca04c"
|
||||
dependencies = [
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
@@ -230,7 +230,7 @@ checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -582,30 +582,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.7.3"
|
||||
@@ -660,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d"
|
||||
dependencies = [
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -697,7 +673,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"strsim",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -708,7 +684,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -733,7 +709,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.33.0"
|
||||
version = "1.34.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"anyhow",
|
||||
@@ -784,6 +760,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"smol",
|
||||
"stop-token",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
@@ -798,12 +775,12 @@ name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.33.0"
|
||||
version = "1.34.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
@@ -825,7 +802,7 @@ dependencies = [
|
||||
"derive_builder_core",
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -837,7 +814,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -913,7 +890,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"rand 0.7.3",
|
||||
"time",
|
||||
"version_check 0.9.1",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1029,7 +1006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"version_check 0.9.1",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1059,7 +1036,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -1192,7 +1169,7 @@ dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1522,9 +1499,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.39"
|
||||
version = "0.3.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7"
|
||||
checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1537,9 +1514,9 @@ checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
|
||||
|
||||
[[package]]
|
||||
name = "kv-log-macro"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2d3beed37e5483887d81eb39de6de03a8346531410e1306ca48a9a89bd3a51"
|
||||
checksum = "4ff57d6d215f7ca7eb35a9a64d656ba4d9d2bef114d741dc08048e75e2f5d418"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
@@ -1592,9 +1569,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.70"
|
||||
version = "0.2.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
|
||||
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -1757,19 +1734,6 @@ dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
@@ -1794,7 +1758,7 @@ checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
|
||||
dependencies = [
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check 0.9.1",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1824,7 +1788,7 @@ checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1936,9 +1900,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.56"
|
||||
version = "0.9.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e"
|
||||
checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"cc",
|
||||
@@ -2070,7 +2034,7 @@ checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2146,9 +2110,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.15"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
|
||||
checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
@@ -2158,9 +2122,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.15"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70a50b9351bfa8d65a7d93ce712dc63d2fd15ddbf2c36990fc7cac344859c04f"
|
||||
checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0",
|
||||
]
|
||||
@@ -2440,9 +2404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.7"
|
||||
version = "1.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||
checksum = "226ddd1197737bcb937489322ec1b9edaac1709d46792886e70f2113923585a6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2452,9 +2416,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.17"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
@@ -2483,7 +2447,7 @@ checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2579,7 +2543,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix 0.13.1",
|
||||
"nix",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
@@ -2710,7 +2674,7 @@ checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2750,9 +2714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0"
|
||||
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"digest",
|
||||
@@ -2804,14 +2768,15 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
|
||||
[[package]]
|
||||
name = "smol"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7"
|
||||
source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#1ba84935e4e48be927c7f8f4a9cb5c05614135c3"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"crossbeam",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"nix 0.17.0",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"piper",
|
||||
"scoped-tls-hkt",
|
||||
@@ -2890,7 +2855,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2932,9 +2897,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.23"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269"
|
||||
checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
@@ -2958,7 +2923,7 @@ checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
"unicode-xid 0.2.0",
|
||||
]
|
||||
|
||||
@@ -3012,7 +2977,7 @@ checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3086,7 +3051,7 @@ version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check 0.9.1",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3171,9 +3136,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
@@ -3209,9 +3174,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551"
|
||||
checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -3219,24 +3184,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94"
|
||||
checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.12"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04"
|
||||
checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -3246,9 +3211,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776"
|
||||
checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3"
|
||||
dependencies = [
|
||||
"quote 1.0.6",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3256,28 +3221,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a"
|
||||
checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad"
|
||||
checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.39"
|
||||
version = "0.3.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642"
|
||||
checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -3285,9 +3250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wepoll-binding"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7511014d92edaa2cba607fd6dc62974915bebc3e16f91f911a4bb240b4a3a68c"
|
||||
checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"wepoll-sys",
|
||||
@@ -3370,6 +3335,6 @@ checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.6",
|
||||
"syn 1.0.23",
|
||||
"syn 1.0.27",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.33.0"
|
||||
version = "1.34.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
@@ -23,7 +23,7 @@ num-traits = "0.2.6"
|
||||
async-smtp = { version = "0.3" }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
async-imap = "0.3.0"
|
||||
async-imap = "0.3.1"
|
||||
async-native-tls = { version = "0.3.3" }
|
||||
async-std = { version = "1.6.0", features = ["unstable"] }
|
||||
base64 = "0.11"
|
||||
@@ -50,7 +50,7 @@ bitflags = "1.1.0"
|
||||
debug_stub_derive = "0.3.0"
|
||||
sanitize-filename = "0.2.1"
|
||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||
mailparse = "0.12.0"
|
||||
mailparse = "0.12.1"
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
native-tls = "0.2.3"
|
||||
image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] }
|
||||
@@ -72,6 +72,7 @@ pretty_assertions = "0.6.1"
|
||||
pretty_env_logger = "0.3.0"
|
||||
proptest = "0.9.4"
|
||||
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
|
||||
smol = "0.1.10"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
@@ -82,6 +83,7 @@ members = [
|
||||
[[example]]
|
||||
name = "simple"
|
||||
path = "examples/simple.rs"
|
||||
required-features = ["repl"]
|
||||
|
||||
[[example]]
|
||||
name = "repl"
|
||||
@@ -96,3 +98,5 @@ repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"]
|
||||
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"]
|
||||
nightly = ["pgp/nightly"]
|
||||
|
||||
[patch.crates-io]
|
||||
smol = { git = "https://github.com/dignifiedquire/smol-1", branch = "isolate-nix" }
|
||||
|
||||
@@ -132,4 +132,5 @@ or its language bindings:
|
||||
- [iOS](https://github.com/deltachat/deltachat-ios)
|
||||
- [Desktop](https://github.com/deltachat/deltachat-desktop)
|
||||
- [Pidgin](https://code.ur.gs/lupine/purple-plugin-delta/)
|
||||
- [Telepathy](https://code.ur.gs/lupine/telepathy-padfoot/)
|
||||
- several **Bots**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.33.0"
|
||||
version = "1.34.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -32,63 +32,40 @@ typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
*
|
||||
* Let's start.
|
||||
*
|
||||
* First of all, you have to **define an event-handler-function**
|
||||
* that is called by the library on specific events
|
||||
* (eg. when the configuration is done or when fresh messages arrive).
|
||||
* With this function you can create a Delta Chat context then:
|
||||
* First of all, you have to **create a context object**
|
||||
* bound to a database.
|
||||
* The database is a normal sqlite-file and is created as needed:
|
||||
*
|
||||
* ~~~
|
||||
* #include <deltachat.h>
|
||||
* dc_context_t* context = dc_context_new(NULL, "example.db", NULL);
|
||||
* ~~~
|
||||
*
|
||||
* uintptr_t event_handler_func(dc_context_t* context, int event,
|
||||
* uintptr_t data1, uintptr_t data2)
|
||||
* After that, make sure, you can **receive events from the context**.
|
||||
* For that purpose, create an event emitter you can ask for events.
|
||||
* If there are no event, the emitter will wait until there is one,
|
||||
* so, in many situations you will do this in a thread:
|
||||
*
|
||||
* ~~~
|
||||
* void* event_handler(void* context)
|
||||
* {
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* dc_context_t* context = dc_context_new(event_handler_func, NULL, NULL);
|
||||
* ~~~
|
||||
*
|
||||
* After that, you should make sure,
|
||||
* sending and receiving jobs are processed as needed.
|
||||
* For this purpose, you have to **create two threads:**
|
||||
*
|
||||
* ~~~
|
||||
* #include <pthread.h>
|
||||
*
|
||||
* void* imap_thread_func(void* context)
|
||||
* {
|
||||
* while (true) {
|
||||
* dc_perform_imap_jobs(context);
|
||||
* dc_perform_imap_fetch(context);
|
||||
* dc_perform_imap_idle(context);
|
||||
* dc_event_emitter_t* emitter = dc_get_event_emitter(context);
|
||||
* dc_event_t* event;
|
||||
* while ((event = dc_get_next_event(emitter)) != NULL) {
|
||||
* // use the event as needed, eg. dc_event_get_id() returns the type.
|
||||
* // once you're done, unref the event to avoid memory leakage:
|
||||
* dc_event_unref(event);
|
||||
* }
|
||||
* dc_event_emitter_unref(emitter);
|
||||
* }
|
||||
*
|
||||
* void* smtp_thread_func(void* context)
|
||||
* {
|
||||
* while (true) {
|
||||
* dc_perform_smtp_jobs(context);
|
||||
* dc_perform_smtp_idle(context);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* static pthread_t imap_thread, smtp_thread;
|
||||
* pthread_create(&imap_thread, NULL, imap_thread_func, context);
|
||||
* pthread_create(&smtp_thread, NULL, smtp_thread_func, context);
|
||||
* static pthread_t event_thread;
|
||||
* pthread_create(&event_thread, NULL, event_handler, context);
|
||||
* ~~~
|
||||
*
|
||||
* The example above uses "pthreads",
|
||||
* however, you can also use anything else for thread handling.
|
||||
* All deltachat-core-functions, unless stated otherwise, are thread-safe.
|
||||
*
|
||||
* After that you can **define and open a database.**
|
||||
* The database is a normal sqlite-file and is created as needed:
|
||||
*
|
||||
* ~~~
|
||||
* dc_open(context, "example.db", NULL);
|
||||
* ~~~
|
||||
*
|
||||
* Now you can **configure the context:**
|
||||
*
|
||||
* ~~~
|
||||
@@ -98,15 +75,22 @@ typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
* dc_configure(context);
|
||||
* ~~~
|
||||
*
|
||||
* dc_configure() returns immediately, the configuration itself may take a while
|
||||
* and is done by a job in the imap-thread you've defined above.
|
||||
* dc_configure() returns immediately,
|
||||
* the configuration itself runs in background and may take a while.
|
||||
* Once done, the #DC_EVENT_CONFIGURE_PROGRESS reports success
|
||||
* to the event_handler_func() that is also defined above.
|
||||
* to the event_handler() you've defined above.
|
||||
*
|
||||
* The configuration result is saved in the database,
|
||||
* on subsequent starts it is not needed to call dc_configure()
|
||||
* (you can check this using dc_is_configured()).
|
||||
*
|
||||
* On a successfully configured context,
|
||||
* you can finally **connect to the servers:**
|
||||
*
|
||||
* ~~~
|
||||
* dc_start_io(context);
|
||||
* ~~~
|
||||
*
|
||||
* Now you can **send the first message:**
|
||||
*
|
||||
* ~~~
|
||||
@@ -118,11 +102,11 @@ typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
* ~~~
|
||||
*
|
||||
* dc_send_text_msg() returns immediately;
|
||||
* the sending itself is done by a job in the smtp-thread you've defined above.
|
||||
* the sending itself is done in the background.
|
||||
* If you check the testing address (bob)
|
||||
* and you should have received a normal email.
|
||||
* Answer this email in any email program with "Got it!"
|
||||
* and the imap-thread you've create above will **receive the message**.
|
||||
* and the IO you started above will **receive the message**.
|
||||
*
|
||||
* You can then **list all messages** of a chat as follow:
|
||||
*
|
||||
@@ -166,19 +150,6 @@ typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
* - The issue-tracker for the core library is here:
|
||||
* <https://github.com/deltachat/deltachat-core-rust/issues>
|
||||
*
|
||||
* The following points are important mainly
|
||||
* for the authors of the library itself:
|
||||
*
|
||||
* - For indentation, use tabs.
|
||||
* Alignments that are not placed at the beginning of a line
|
||||
* should be done with spaces.
|
||||
*
|
||||
* - For padding between functions,
|
||||
* classes etc. use 2 empty lines
|
||||
*
|
||||
* - Source files are encoded as UTF-8 with Unix line endings
|
||||
* (a simple `LF`, `0x0A` or `\n`)
|
||||
*
|
||||
* If you need further assistance,
|
||||
* please do not hesitate to contact us
|
||||
* through the channels shown at https://delta.chat/en/contribute
|
||||
@@ -191,24 +162,6 @@ typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* TODO: document
|
||||
*/
|
||||
dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter);
|
||||
|
||||
dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context);
|
||||
void dc_event_emitter_unref(dc_event_emitter_t* emitter);
|
||||
|
||||
int dc_event_get_id (dc_event_t* event);
|
||||
int dc_event_get_data1_int(dc_event_t* event);
|
||||
int dc_event_get_data2_int(dc_event_t* event);
|
||||
char* dc_event_get_data3_str(dc_event_t* event);
|
||||
|
||||
/**
|
||||
* TODO: document
|
||||
*/
|
||||
void dc_event_unref (dc_event_t* event);
|
||||
|
||||
/**
|
||||
* @class dc_context_t
|
||||
*
|
||||
@@ -226,21 +179,6 @@ void dc_event_unref (dc_event_t* event);
|
||||
* opened, connected and mails are fetched.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param cb a callback function that is called for events (update,
|
||||
* state changes etc.) and to get some information from the client (eg. translation
|
||||
* for a given string).
|
||||
* See @ref DC_EVENT for a list of possible events that may be passed to the callback.
|
||||
* - The callback MAY be called from _any_ thread, not only the main/GUI thread!
|
||||
* - The callback MUST NOT call any dc_* and related functions unless stated
|
||||
* otherwise!
|
||||
* - The callback SHOULD return _fast_, for GUI updates etc. you should
|
||||
* post yourself an asynchronous message to your GUI thread, if needed.
|
||||
* - events do not expect a return value, just always return 0.
|
||||
* @param dbfile The file to use to store the database, something like `~/file` won't
|
||||
* work on all systems, if in doubt, use absolute paths.
|
||||
* @param blobdir A directory to store the blobs in; a trailing slash is not needed.
|
||||
* If you pass NULL or the empty string, deltachat-core creates a directory
|
||||
* beside _dbfile_ with the same name and the suffix `-blobs`.
|
||||
* @param os_name is only for decorative use
|
||||
* and is shown eg. in the `X-Mailer:` header
|
||||
* in the form "Delta Chat Core <version>/<os_name>".
|
||||
@@ -248,6 +186,11 @@ void dc_event_unref (dc_event_t* event);
|
||||
* the used environment and/or the version here.
|
||||
* It is okay to give NULL, in this case `X-Mailer:` header
|
||||
* is set to "Delta Chat Core <version>".
|
||||
* @param dbfile The file to use to store the database,
|
||||
* something like `~/file` won't work, use absolute paths.
|
||||
* @param blobdir A directory to store the blobs in; a trailing slash is not needed.
|
||||
* If you pass NULL or the empty string, deltachat-core creates a directory
|
||||
* beside _dbfile_ with the same name and the suffix `-blobs`.
|
||||
* @return A context object with some public members.
|
||||
* The object must be passed to the other context functions
|
||||
* and must be freed using dc_context_unref() after usage.
|
||||
@@ -269,6 +212,25 @@ dc_context_t* dc_context_new (const char* os_name, const char* d
|
||||
*/
|
||||
void dc_context_unref (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Create the event emitter that is used to receive events.
|
||||
* The library will emit various @ref DC_EVENT events as "new message", "message read" etc.
|
||||
* To get these events, you have to create an event emitter using this function
|
||||
* and call dc_get_next_event() on the emitter.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new().
|
||||
* @return Returns the event emitter, NULL on errors.
|
||||
* Must be freed using dc_event_emitter_unref() after usage.
|
||||
*
|
||||
* Note: Use only one event emitter per context.
|
||||
* Having more than one event emitter running at the same time on the same context
|
||||
* will result in events randomly delivered to the one or to the other.
|
||||
*/
|
||||
dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Get the blob directory.
|
||||
*
|
||||
@@ -468,9 +430,7 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
|
||||
|
||||
/**
|
||||
* Configure a context.
|
||||
* For this purpose, the function creates a job
|
||||
* that is executed in the IMAP-thread then;
|
||||
* this requires to call dc_perform_imap_jobs() regularly.
|
||||
* While configuration IO must not be started, if needed stop IO using dc_stop_io() first.
|
||||
* If the context is already configured,
|
||||
* this function will try to change the configuration.
|
||||
*
|
||||
@@ -537,6 +497,8 @@ int dc_is_configured (const dc_context_t* context);
|
||||
|
||||
/**
|
||||
* Start job and IMAP/SMTP tasks.
|
||||
* If IO is already running, nothing happens.
|
||||
* To check the current IO state, use dc_is_io_running().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new().
|
||||
@@ -556,6 +518,8 @@ int dc_is_io_running(const dc_context_t* context);
|
||||
|
||||
/**
|
||||
* Stop job and IMAP/SMTP tasks and return when they are finished.
|
||||
* If IO is not running, nothing happens.
|
||||
* To check the current IO state, use dc_is_io_running().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new().
|
||||
@@ -1674,9 +1638,6 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co
|
||||
|
||||
/**
|
||||
* Import/export things.
|
||||
* For this purpose, the function creates a job that is executed in the IMAP-thread then;
|
||||
* this requires to call dc_perform_imap_jobs() regularly.
|
||||
*
|
||||
* What to do is defined by the _what_ parameter which may be one of the following:
|
||||
*
|
||||
* - **DC_IMEX_EXPORT_BACKUP** (11) - Export a backup to the directory given as `param1`.
|
||||
@@ -3857,11 +3818,122 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_EMPTY_INBOX 0x02 // Deprecated, flag for dc_empty_server(): Clear all INBOX messages
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_event_emitter_t
|
||||
*
|
||||
* Opaque object that is used to get events.
|
||||
* You can get an event emitter from a context using dc_get_event_emitter().
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the next event from an event emitter object.
|
||||
*
|
||||
* @memberof dc_event_emitter_t
|
||||
* @param emitter Event emitter object as returned from dc_get_event_emitter().
|
||||
* @return An event as an dc_event_t object.
|
||||
* You can query the event for information using dc_event_get_id(), dc_event_get_data1_int() and so on;
|
||||
* if you are done with the event, you have to free the event using dc_event_unref().
|
||||
* If NULL is returned, the context belonging to the event emitter is unref'd and the no more events will come;
|
||||
* in this case, free the event emitter using dc_event_emitter_unref().
|
||||
*/
|
||||
dc_event_t* dc_get_next_event(dc_event_emitter_t* emitter);
|
||||
|
||||
|
||||
/**
|
||||
* Free an event emitter object.
|
||||
*
|
||||
* @memberof dc_event_emitter_t
|
||||
* @param emitter Event emitter object as returned from dc_get_event_emitter().
|
||||
* If NULL is given, nothing is done and an error is logged.
|
||||
* @return None.
|
||||
*/
|
||||
void dc_event_emitter_unref(dc_event_emitter_t* emitter);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_event_t
|
||||
*
|
||||
* Opaque object describing a single event.
|
||||
* To get events, call dc_get_next_event() on an event emitter created by dc_get_event_emitter().
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the event-id from an event object.
|
||||
* The event-id is one of the @ref DC_EVENT constants.
|
||||
* There may be additional data belonging to an event,
|
||||
* to get them, use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data2_str().
|
||||
*
|
||||
* @memberof dc_event_t
|
||||
* @param event Event object as returned from dc_get_next_event().
|
||||
* @return once of the @ref DC_EVENT constants.
|
||||
* 0 on errors.
|
||||
*/
|
||||
int dc_event_get_id(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* Get a data associated with an event object.
|
||||
* The meaning of the data depends on the event-id
|
||||
* returned as @ref DC_EVENT constants by dc_event_get_id().
|
||||
* See also dc_event_get_data2_int() and dc_event_get_data2_str().
|
||||
*
|
||||
* @memberof dc_event_t
|
||||
* @param event Event object as returned from dc_get_next_event().
|
||||
* @return "data1" as a signed integer, at least 32bit,
|
||||
* the meaning depends on the event type associated with this event.
|
||||
*/
|
||||
int dc_event_get_data1_int(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* Get a data associated with an event object.
|
||||
* The meaning of the data depends on the event-id
|
||||
* returned as @ref DC_EVENT constants by dc_event_get_id().
|
||||
* See also dc_event_get_data2_int() and dc_event_get_data2_str().
|
||||
*
|
||||
* @memberof dc_event_t
|
||||
* @param event Event object as returned from dc_get_next_event().
|
||||
* @return "data2" as a signed integer, at least 32bit,
|
||||
* the meaning depends on the event type associated with this event.
|
||||
*/
|
||||
int dc_event_get_data2_int(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* Get a data associated with an event object.
|
||||
* The meaning of the data depends on the event-id
|
||||
* returned as @ref DC_EVENT constants by dc_event_get_id().
|
||||
* See also dc_event_get_data1_int() and dc_event_get_data2_int().
|
||||
*
|
||||
* @memberof dc_event_t
|
||||
* @param event Event object as returned from dc_get_next_event().
|
||||
* @return "data2" as a string,
|
||||
* the meaning depends on the event type associated with this event.
|
||||
* Once you're done with the string, you have to unref it using dc_unref_str().
|
||||
*/
|
||||
char* dc_event_get_data2_str(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* Free memory used by an event object.
|
||||
* If you forget to do this for an event, this will result in memory leakage.
|
||||
*
|
||||
* @memberof dc_event_t
|
||||
* @param event Event object as returned from dc_get_next_event().
|
||||
* @return None.
|
||||
*/
|
||||
void dc_event_unref(dc_event_t* event);
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_EVENT DC_EVENT
|
||||
*
|
||||
* These constants are used as events
|
||||
* reported to the callback given to dc_context_new().
|
||||
* These constants are used as event-id
|
||||
* in events returned by dc_get_next_event().
|
||||
*
|
||||
* Events typically come with some additional data,
|
||||
* use dc_event_get_data1_int(), dc_event_get_data2_int() and dc_event_get_data2_str() to read this data.
|
||||
* The meaning of the data depends on the event.
|
||||
*
|
||||
* @addtogroup DC_EVENT
|
||||
* @{
|
||||
@@ -3869,13 +3941,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
|
||||
/**
|
||||
* The library-user may write an informational string to the log.
|
||||
* Passed to the callback given to dc_context_new().
|
||||
*
|
||||
* This event should not be reported to the end-user using a popup or something like that.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_INFO 100
|
||||
|
||||
@@ -3884,8 +3954,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when SMTP connection is established and login was successful.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_SMTP_CONNECTED 101
|
||||
|
||||
@@ -3894,8 +3963,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when IMAP connection is established and login was successful.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_IMAP_CONNECTED 102
|
||||
|
||||
@@ -3903,8 +3971,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when a message was successfully sent to the SMTP server.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_SMTP_MESSAGE_SENT 103
|
||||
|
||||
@@ -3912,8 +3979,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when a message was successfully marked as deleted on the IMAP server.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_DELETED 104
|
||||
|
||||
@@ -3921,8 +3987,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when a message was successfully moved on IMAP.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||
|
||||
@@ -3930,8 +3995,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when an IMAP folder was emptied.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) folder name.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Folder name.
|
||||
*/
|
||||
#define DC_EVENT_IMAP_FOLDER_EMPTIED 106
|
||||
|
||||
@@ -3939,8 +4003,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when a new blob file was successfully written
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Path name
|
||||
*/
|
||||
#define DC_EVENT_NEW_BLOB_FILE 150
|
||||
|
||||
@@ -3948,27 +4011,23 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* Emitted when a blob file was successfully deleted
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) path name
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Path name
|
||||
*/
|
||||
#define DC_EVENT_DELETED_BLOB_FILE 151
|
||||
|
||||
/**
|
||||
* The library-user should write a warning string to the log.
|
||||
* Passed to the callback given to dc_context_new().
|
||||
*
|
||||
* This event should not be reported to the end-user using a popup or something like that.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Warning string in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Warning string in english language.
|
||||
*/
|
||||
#define DC_EVENT_WARNING 300
|
||||
|
||||
|
||||
/**
|
||||
* The library-user should report an error to the end-user.
|
||||
* Passed to the callback given to dc_context_new().
|
||||
*
|
||||
* As most things are asynchronous, things may go wrong at any time and the user
|
||||
* should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
@@ -3980,10 +4039,9 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* in a messasge box then.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Error string, always set, never NULL.
|
||||
* @param data2 (char*) Error string, always set, never NULL.
|
||||
* Some error strings are taken from dc_set_stock_translation(),
|
||||
* however, most error strings will be in english language.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
*/
|
||||
#define DC_EVENT_ERROR 400
|
||||
|
||||
@@ -4005,8 +4063,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*
|
||||
* @param data1 (int) 1=first/new network error, should be reported the user;
|
||||
* 0=subsequent network error, should be logged only
|
||||
* @param data2 (const char*) Error string, always set, never NULL.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 (char*) Error string, always set, never NULL.
|
||||
*/
|
||||
#define DC_EVENT_ERROR_NETWORK 401
|
||||
|
||||
@@ -4019,9 +4076,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* dc_send_text_msg() or another sending function.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (const char*) Info string in english language.
|
||||
* Must not be unref'd or modified
|
||||
* and is valid only until the callback returns.
|
||||
* @param data2 (char*) Info string in english language.
|
||||
*/
|
||||
#define DC_EVENT_ERROR_SELF_NOT_IN_GROUP 410
|
||||
|
||||
@@ -4139,9 +4194,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* A typical purpose for a handler of this event may be to make the file public to some system
|
||||
* services.
|
||||
*
|
||||
* @param data1 (const char*) Path and file name.
|
||||
* Must not be unref'd or modified and is valid only until the callback returns.
|
||||
* @param data2 0
|
||||
* @param data1 0
|
||||
* @param data2 (char*) Path and file name.
|
||||
*/
|
||||
#define DC_EVENT_IMEX_FILE_WRITTEN 2052
|
||||
|
||||
@@ -4369,7 +4423,11 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_STR_LOCATION 66
|
||||
#define DC_STR_STICKER 67
|
||||
#define DC_STR_DEVICE_MESSAGES 68
|
||||
#define DC_STR_COUNT 68
|
||||
#define DC_STR_SAVED_MESSAGES 69
|
||||
#define DC_STR_DEVICE_MESSAGES_HINT 70
|
||||
#define DC_STR_WELCOME_MESSAGE 71
|
||||
#define DC_STR_UNKNOWN_SENDER_FOR_CHAT 72
|
||||
#define DC_STR_COUNT 72
|
||||
|
||||
/*
|
||||
* @}
|
||||
|
||||
@@ -403,9 +403,9 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn dc_event_get_data3_str(event: *mut dc_event_t) -> *mut libc::c_char {
|
||||
pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut libc::c_char {
|
||||
if event.is_null() {
|
||||
eprintln!("ignoring careless call to dc_event_get_data3_str()");
|
||||
eprintln!("ignoring careless call to dc_event_get_data2_str()");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ use deltachat::context::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::Event;
|
||||
use log::{error, info, warn};
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
use rustyline::config::OutputStreamType;
|
||||
use rustyline::error::ReadlineError;
|
||||
@@ -36,34 +35,34 @@ use self::cmdline::*;
|
||||
/// Event Handler
|
||||
fn receive_event(event: Event) {
|
||||
let yellow = Color::Yellow.normal();
|
||||
let red = Color::Red.normal();
|
||||
match event {
|
||||
Event::Info(msg) => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
info!("{}", msg);
|
||||
println!("[INFO] {}", msg);
|
||||
}
|
||||
Event::SmtpConnected(msg) => {
|
||||
info!("[SMTP_CONNECTED] {}", msg);
|
||||
println!("[INFO SMTP_CONNECTED] {}", msg);
|
||||
}
|
||||
Event::ImapConnected(msg) => {
|
||||
info!("[IMAP_CONNECTED] {}", msg);
|
||||
println!("[INFO IMAP_CONNECTED] {}", msg);
|
||||
}
|
||||
Event::SmtpMessageSent(msg) => {
|
||||
info!("[SMTP_MESSAGE_SENT] {}", msg);
|
||||
println!("[INFO SMTP_MESSAGE_SENT] {}", msg);
|
||||
}
|
||||
Event::Warning(msg) => {
|
||||
warn!("{}", msg);
|
||||
println!("[WARNING] {}", msg);
|
||||
}
|
||||
Event::Error(msg) => {
|
||||
error!("{}", msg);
|
||||
println!("[ERROR] {}", red.paint(msg));
|
||||
}
|
||||
Event::ErrorNetwork(msg) => {
|
||||
error!("[NETWORK] msg={}", msg);
|
||||
println!("[ERROR NETWORK] msg={}", red.paint(msg));
|
||||
}
|
||||
Event::ErrorSelfNotInGroup(msg) => {
|
||||
error!("[SELF_NOT_IN_GROUP] {}", msg);
|
||||
println!("[ERROR SELF_NOT_IN_GROUP] {}", red.paint(msg));
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id } => {
|
||||
info!(
|
||||
println!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received MSGS_CHANGED(chat_id={}, msg_id={})",
|
||||
@@ -72,40 +71,40 @@ fn receive_event(event: Event) {
|
||||
);
|
||||
}
|
||||
Event::ContactsChanged(_) => {
|
||||
info!("{}", yellow.paint("Received CONTACTS_CHANGED()"));
|
||||
println!("{}", yellow.paint("Received CONTACTS_CHANGED()"));
|
||||
}
|
||||
Event::LocationChanged(contact) => {
|
||||
info!(
|
||||
println!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact))
|
||||
);
|
||||
}
|
||||
Event::ConfigureProgress(progress) => {
|
||||
info!(
|
||||
println!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress))
|
||||
);
|
||||
}
|
||||
Event::ImexProgress(progress) => {
|
||||
info!(
|
||||
println!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress))
|
||||
);
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
info!(
|
||||
println!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received IMEX_FILE_WRITTEN({})", file.display()))
|
||||
);
|
||||
}
|
||||
Event::ChatModified(chat) => {
|
||||
info!(
|
||||
println!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received CHAT_MODIFIED({})", chat))
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
info!("Received {:?}", event);
|
||||
println!("Received {}", yellow.paint(format!("{:?}", event)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,10 +145,8 @@ const IMEX_COMMANDS: [&str; 12] = [
|
||||
"stop",
|
||||
];
|
||||
|
||||
const DB_COMMANDS: [&str; 11] = [
|
||||
const DB_COMMANDS: [&str; 9] = [
|
||||
"info",
|
||||
"open",
|
||||
"close",
|
||||
"set",
|
||||
"get",
|
||||
"oauth2",
|
||||
@@ -290,48 +287,59 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
.edit_mode(EditMode::Emacs)
|
||||
.output_stream(OutputStreamType::Stdout)
|
||||
.build();
|
||||
let h = DcHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
highlighter: MatchingBracketHighlighter::new(),
|
||||
hinter: HistoryHinter {},
|
||||
};
|
||||
let mut rl = Editor::with_config(config);
|
||||
rl.set_helper(Some(h));
|
||||
rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward);
|
||||
rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward);
|
||||
if rl.load_history(".dc-history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
|
||||
let mut selected_chat = ChatId::default();
|
||||
let (reader_s, reader_r) = async_std::sync::channel(100);
|
||||
let input_loop = async_std::task::spawn_blocking(move || {
|
||||
let h = DcHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
highlighter: MatchingBracketHighlighter::new(),
|
||||
hinter: HistoryHinter {},
|
||||
};
|
||||
let mut rl = Editor::with_config(config);
|
||||
rl.set_helper(Some(h));
|
||||
rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward);
|
||||
rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward);
|
||||
if rl.load_history(".dc-history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
|
||||
loop {
|
||||
let p = "> ";
|
||||
let readline = rl.readline(&p);
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str());
|
||||
match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => {}
|
||||
Ok(ExitResult::Exit) => break,
|
||||
Err(err) => println!("Error: {}", err),
|
||||
loop {
|
||||
let p = "> ";
|
||||
let readline = rl.readline(&p);
|
||||
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str());
|
||||
async_std::task::block_on(reader_s.send(line));
|
||||
}
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
println!("Exiting...");
|
||||
drop(reader_s);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
drop(reader_s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
println!("Exiting...");
|
||||
context.stop_io().await;
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rl.save_history(".dc-history.txt")?;
|
||||
println!("history saved");
|
||||
Ok::<_, Error>(())
|
||||
});
|
||||
|
||||
while let Ok(line) = reader_r.recv().await {
|
||||
match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => {}
|
||||
Ok(ExitResult::Exit) => break,
|
||||
Err(err) => println!("Error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
rl.save_history(".dc-history.txt")?;
|
||||
println!("history saved");
|
||||
context.stop_io().await;
|
||||
input_loop.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
extern crate deltachat;
|
||||
|
||||
use std::time;
|
||||
use tempfile::tempdir;
|
||||
|
||||
use deltachat::chat;
|
||||
@@ -8,35 +5,42 @@ use deltachat::chatlist::*;
|
||||
use deltachat::config;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::message::Message;
|
||||
use deltachat::Event;
|
||||
|
||||
fn cb(event: Event) {
|
||||
print!("[{:?}]", event);
|
||||
|
||||
match event {
|
||||
Event::ConfigureProgress(progress) => {
|
||||
println!(" progress: {}", progress);
|
||||
log::info!("progress: {}", progress);
|
||||
}
|
||||
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
||||
println!(" {}", msg);
|
||||
Event::Info(msg) => {
|
||||
log::info!("{}", msg);
|
||||
}
|
||||
_ => {
|
||||
println!();
|
||||
Event::Warning(msg) => {
|
||||
log::warn!("{}", msg);
|
||||
}
|
||||
Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
||||
log::error!("{}", msg);
|
||||
}
|
||||
event => {
|
||||
log::info!("{:?}", event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run with `RUST_LOG=simple=info cargo run --release --example simple --features repl -- email pw`.
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::try_init_timed().ok();
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
log::info!("creating database {:?}", dbfile);
|
||||
let ctx = Context::new("FakeOs".into(), dbfile.into())
|
||||
.await
|
||||
.expect("Failed to create context");
|
||||
let info = ctx.get_info().await;
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {:#?}", info);
|
||||
log::info!("info: {:#?}", info);
|
||||
|
||||
let events = ctx.get_event_emitter();
|
||||
let events_spawn = async_std::task::spawn(async move {
|
||||
@@ -45,7 +49,7 @@ async fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
println!("configuring");
|
||||
log::info!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 3, "requires email password");
|
||||
let email = args[1].clone();
|
||||
@@ -59,35 +63,38 @@ async fn main() {
|
||||
|
||||
ctx.configure().await.unwrap();
|
||||
|
||||
println!("------ RUN ------");
|
||||
ctx.clone().start_io().await;
|
||||
println!("--- SENDING A MESSAGE ---");
|
||||
log::info!("------ RUN ------");
|
||||
ctx.start_io().await;
|
||||
log::info!("--- SENDING A MESSAGE ---");
|
||||
|
||||
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com")
|
||||
.await
|
||||
.unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&ctx, contact_id).await.unwrap();
|
||||
|
||||
for i in 0..2 {
|
||||
for i in 0..1 {
|
||||
log::info!("sending message {}", i);
|
||||
chat::send_text_msg(&ctx, chat_id, format!("Hi, here is my {}nth message!", i))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
println!("fetching chats..");
|
||||
// wait for the message to be sent out
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
|
||||
log::info!("fetching chats..");
|
||||
let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap();
|
||||
|
||||
for i in 0..chats.len() {
|
||||
let summary = chats.get_summary(&ctx, 0, None).await;
|
||||
let text1 = summary.get_text1();
|
||||
let text2 = summary.get_text2();
|
||||
println!("chat: {} - {:?} - {:?}", i, text1, text2,);
|
||||
let msg = Message::load_from_db(&ctx, chats.get_msg_id(i).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
log::info!("[{}] msg: {:?}", i, msg);
|
||||
}
|
||||
|
||||
async_std::task::sleep(duration).await;
|
||||
|
||||
println!("stopping");
|
||||
log::info!("stopping");
|
||||
ctx.stop_io().await;
|
||||
println!("closing");
|
||||
log::info!("closing");
|
||||
drop(ctx);
|
||||
events_spawn.await;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class Account(object):
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
if self._dc_context == ffi.NULL:
|
||||
raise ValueError("FAILED dc_context_new: {} {}".format(os_name, db_path))
|
||||
raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path))
|
||||
|
||||
self._shutdown_event = Event()
|
||||
self._event_thread = EventThread(self)
|
||||
|
||||
@@ -172,7 +172,7 @@ class EventThread(threading.Thread):
|
||||
# function which provides us signature info of an event call
|
||||
evt_name = deltachat.get_dc_event_name(evt)
|
||||
if lib.dc_event_has_string_data(evt):
|
||||
data2 = from_dc_charpointer(lib.dc_event_get_data3_str(event))
|
||||
data2 = from_dc_charpointer(lib.dc_event_get_data2_str(event))
|
||||
else:
|
||||
data2 = lib.dc_event_get_data2_int(event)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_db_busy_error(acfactory, tmpdir):
|
||||
print("%3.2f %s" % (time.time() - starttime, string))
|
||||
|
||||
# make a number of accounts
|
||||
accounts = acfactory.get_many_online_accounts(5, quiet=True)
|
||||
accounts = acfactory.get_many_online_accounts(3, quiet=True)
|
||||
log("created %s accounts" % len(accounts))
|
||||
|
||||
# put a bigfile into each account
|
||||
@@ -41,7 +41,7 @@ def test_db_busy_error(acfactory, tmpdir):
|
||||
# each replier receives all events and sends report events to receive_queue
|
||||
repliers = []
|
||||
for acc in accounts:
|
||||
replier = AutoReplier(acc, log=log, num_send=1000, num_bigfiles=0, report_func=report_func)
|
||||
replier = AutoReplier(acc, log=log, num_send=500, num_bigfiles=5, report_func=report_func)
|
||||
acc.add_account_plugin(replier)
|
||||
repliers.append(replier)
|
||||
|
||||
@@ -57,9 +57,9 @@ def test_db_busy_error(acfactory, tmpdir):
|
||||
log("timeout waiting for next event")
|
||||
pytest.fail("timeout exceeded")
|
||||
if report_type == ReportType.exit:
|
||||
replier.log("EXIT".format(alive_count))
|
||||
replier.log("EXIT")
|
||||
elif report_type == ReportType.ffi_error:
|
||||
replier.log("ERROR: {}".format(addr, report_args[0]))
|
||||
replier.log("ERROR: {}".format(report_args[0]))
|
||||
elif report_type == ReportType.message_echo:
|
||||
continue
|
||||
else:
|
||||
@@ -108,6 +108,7 @@ class AutoReplier:
|
||||
@deltachat.account_hookimpl
|
||||
def ac_incoming_message(self, message):
|
||||
if self.current_sent >= self.num_send:
|
||||
self.report_func(self, ReportType.exit)
|
||||
return
|
||||
message.accept_sender_contact()
|
||||
message.mark_seen()
|
||||
@@ -115,7 +116,7 @@ class AutoReplier:
|
||||
|
||||
self.current_sent += 1
|
||||
# we are still alive, let's send a reply
|
||||
if self.num_bigfiles and self.current_sent % self.num_bigfiles == 0:
|
||||
if self.num_bigfiles and self.current_sent % (self.num_send / self.num_bigfiles) == 0:
|
||||
message.chat.send_text("send big file as reply to: {}".format(message.text))
|
||||
msg = message.chat.send_file(self.account.bigfile)
|
||||
else:
|
||||
|
||||
@@ -199,17 +199,17 @@ impl Context {
|
||||
}
|
||||
Config::InboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||
self.interrupt_inbox().await;
|
||||
self.interrupt_inbox(false).await;
|
||||
ret
|
||||
}
|
||||
Config::SentboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||
self.interrupt_sentbox().await;
|
||||
self.interrupt_sentbox(false).await;
|
||||
ret
|
||||
}
|
||||
Config::MvboxWatch => {
|
||||
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||
self.interrupt_mvbox().await;
|
||||
self.interrupt_mvbox(false).await;
|
||||
ret
|
||||
}
|
||||
Config::Selfstatus => {
|
||||
|
||||
@@ -4,7 +4,7 @@ mod auto_mozilla;
|
||||
mod auto_outlook;
|
||||
mod read_url;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use anyhow::{bail, ensure, format_err, Context as _, Result};
|
||||
use async_std::prelude::*;
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
@@ -66,58 +66,11 @@ impl Context {
|
||||
}
|
||||
|
||||
async fn inner_configure(&self) -> Result<()> {
|
||||
let mut success = false;
|
||||
let mut param_autoconfig: Option<LoginParam> = None;
|
||||
|
||||
info!(self, "Configure ...");
|
||||
|
||||
// Variables that are shared between steps:
|
||||
let mut param = LoginParam::from_database(self, "").await;
|
||||
// need all vars here to be mutable because rust thinks the same step could be called multiple times
|
||||
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
|
||||
let mut param_domain = "undefined.undefined".to_owned();
|
||||
let mut param_addr_urlencoded: String =
|
||||
"Internal Error: this value should never be used".to_owned();
|
||||
let mut keep_flags = 0;
|
||||
|
||||
let mut step_counter: u8 = 0;
|
||||
let (_s, r) = async_std::sync::channel(1);
|
||||
let mut imap = Imap::new(r);
|
||||
let was_configured_before = self.is_configured().await;
|
||||
|
||||
while !self.shall_stop_ongoing().await {
|
||||
step_counter += 1;
|
||||
|
||||
match exec_step(
|
||||
self,
|
||||
&mut imap,
|
||||
&mut param,
|
||||
&mut param_domain,
|
||||
&mut param_autoconfig,
|
||||
&mut param_addr_urlencoded,
|
||||
&mut keep_flags,
|
||||
&mut step_counter,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(step) => {
|
||||
success = true;
|
||||
match step {
|
||||
Step::Continue => {}
|
||||
Step::Done => break,
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(self, "{}", err);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if imap.is_connected() {
|
||||
imap.disconnect(self).await;
|
||||
}
|
||||
let mut param = LoginParam::from_database(self, "").await;
|
||||
let success = configure(self, &mut param).await;
|
||||
|
||||
if let Some(provider) = provider::get_provider_info(¶m.addr) {
|
||||
if !was_configured_before {
|
||||
@@ -141,329 +94,296 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
// remember the entered parameters on success
|
||||
// and restore to last-entered on failure.
|
||||
// this way, the parameters visible to the ui are always in-sync with the current configuration.
|
||||
if success {
|
||||
LoginParam::from_database(self, "")
|
||||
.await
|
||||
.save_to_database(self, "configured_raw_")
|
||||
.await
|
||||
.ok();
|
||||
match success {
|
||||
Ok(_) => {
|
||||
progress!(self, 1000);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
error!(self, "Configure Failed: {}", err);
|
||||
progress!(self, 0);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress!(self, 1000);
|
||||
Ok(())
|
||||
async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
|
||||
let mut param_autoconfig: Option<LoginParam> = None;
|
||||
let mut keep_flags = 0;
|
||||
|
||||
// Read login parameters from the database
|
||||
progress!(ctx, 1);
|
||||
ensure!(!param.addr.is_empty(), "Please enter an email address.");
|
||||
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
|
||||
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
// if dc_get_oauth2_addr() is not available in the oauth2 implementation, just use the given one.
|
||||
progress!(ctx, 10);
|
||||
if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.mail_pw)
|
||||
.await
|
||||
.and_then(|e| e.parse().ok())
|
||||
{
|
||||
info!(ctx, "Authorized address is {}", oauth2_addr);
|
||||
param.addr = oauth2_addr;
|
||||
ctx.sql
|
||||
.set_raw_config(ctx, "addr", Some(param.addr.as_str()))
|
||||
.await?;
|
||||
}
|
||||
progress!(ctx, 20);
|
||||
}
|
||||
// no oauth? - just continue it's no error
|
||||
|
||||
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
|
||||
let param_domain = parsed.domain;
|
||||
let param_addr_urlencoded = utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string();
|
||||
|
||||
// Step 2: Autoconfig
|
||||
progress!(ctx, 200);
|
||||
|
||||
// param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then
|
||||
// param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for
|
||||
// autoconfig or not
|
||||
if param.mail_server.is_empty()
|
||||
&& param.mail_port == 0
|
||||
&& param.send_server.is_empty()
|
||||
&& param.send_port == 0
|
||||
&& param.send_user.is_empty()
|
||||
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
|
||||
{
|
||||
// no advanced parameters entered by the user: query provider-database or do Autoconfig
|
||||
keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
|
||||
if let Some(new_param) = get_offline_autoconfig(ctx, ¶m) {
|
||||
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
|
||||
param_autoconfig = Some(new_param);
|
||||
}
|
||||
|
||||
if param_autoconfig.is_none() {
|
||||
param_autoconfig =
|
||||
get_autoconfig(ctx, param, ¶m_domain, ¶m_addr_urlencoded).await;
|
||||
}
|
||||
}
|
||||
|
||||
// C. Do we have any autoconfig result?
|
||||
progress!(ctx, 500);
|
||||
if let Some(ref cfg) = param_autoconfig {
|
||||
info!(ctx, "Got autoconfig: {}", &cfg);
|
||||
if !cfg.mail_user.is_empty() {
|
||||
param.mail_user = cfg.mail_user.clone();
|
||||
}
|
||||
// all other values are always NULL when entering autoconfig
|
||||
param.mail_server = cfg.mail_server.clone();
|
||||
param.mail_port = cfg.mail_port;
|
||||
param.send_server = cfg.send_server.clone();
|
||||
param.send_port = cfg.send_port;
|
||||
param.send_user = cfg.send_user.clone();
|
||||
param.server_flags = cfg.server_flags;
|
||||
// although param_autoconfig's data are no longer needed from,
|
||||
// it is used to later to prevent trying variations of port/server/logins
|
||||
}
|
||||
param.server_flags |= keep_flags;
|
||||
|
||||
// Step 3: Fill missing fields with defaults
|
||||
if param.mail_server.is_empty() {
|
||||
param.mail_server = format!("imap.{}", param_domain,)
|
||||
}
|
||||
if param.mail_port == 0 {
|
||||
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
|
||||
143
|
||||
} else {
|
||||
LoginParam::from_database(self, "configured_raw_")
|
||||
.await
|
||||
.save_to_database(self, "")
|
||||
.await
|
||||
.ok();
|
||||
|
||||
progress!(self, 0);
|
||||
bail!("Configure failed")
|
||||
993
|
||||
}
|
||||
}
|
||||
if param.mail_user.is_empty() {
|
||||
param.mail_user = param.addr.clone();
|
||||
}
|
||||
if param.send_server.is_empty() && !param.mail_server.is_empty() {
|
||||
param.send_server = param.mail_server.clone();
|
||||
if param.send_server.starts_with("imap.") {
|
||||
param.send_server = param.send_server.replacen("imap", "smtp", 1);
|
||||
}
|
||||
}
|
||||
if param.send_port == 0 {
|
||||
param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
|
||||
587
|
||||
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
|
||||
25
|
||||
} else {
|
||||
465
|
||||
}
|
||||
}
|
||||
if param.send_user.is_empty() && !param.mail_user.is_empty() {
|
||||
param.send_user = param.mail_user.clone();
|
||||
}
|
||||
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
|
||||
param.send_pw = param.mail_pw.clone()
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_AUTH_NORMAL as i32
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 143 {
|
||||
DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
} else {
|
||||
DC_LP_IMAP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) {
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 587 {
|
||||
DC_LP_SMTP_SOCKET_STARTTLS as i32
|
||||
} else if param.send_port == 25 {
|
||||
DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
} else {
|
||||
DC_LP_SMTP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
|
||||
// do we have a complete configuration?
|
||||
ensure!(
|
||||
!param.mail_server.is_empty()
|
||||
&& param.mail_port != 0
|
||||
&& !param.mail_user.is_empty()
|
||||
&& !param.mail_pw.is_empty()
|
||||
&& !param.send_server.is_empty()
|
||||
&& param.send_port != 0
|
||||
&& !param.send_user.is_empty()
|
||||
&& !param.send_pw.is_empty()
|
||||
&& param.server_flags != 0,
|
||||
"Account settings incomplete."
|
||||
);
|
||||
|
||||
progress!(ctx, 600);
|
||||
// try to connect to IMAP - if we did not got an autoconfig,
|
||||
// do some further tries with different settings and username variations
|
||||
let (_s, r) = async_std::sync::channel(1);
|
||||
let mut imap = Imap::new(r);
|
||||
|
||||
try_imap_connections(ctx, param, param_autoconfig.is_some(), &mut imap).await?;
|
||||
progress!(ctx, 800);
|
||||
|
||||
try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?;
|
||||
progress!(ctx, 900);
|
||||
|
||||
let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await
|
||||
|| ctx.get_config_bool(Config::MvboxMove).await;
|
||||
|
||||
imap.configure_folders(ctx, create_mvbox)
|
||||
.await
|
||||
.context("configuring folders failed")?;
|
||||
|
||||
imap.select_with_uidvalidity(ctx, "INBOX")
|
||||
.await
|
||||
.context("could not read INBOX status")?;
|
||||
|
||||
drop(imap);
|
||||
|
||||
progress!(ctx, 910);
|
||||
// configuration success - write back the configured parameters with the
|
||||
// "configured_" prefix; also write the "configured"-flag */
|
||||
// the trailing underscore is correct
|
||||
param.save_to_database(ctx, "configured_").await?;
|
||||
ctx.sql.set_raw_config_bool(ctx, "configured", true).await?;
|
||||
|
||||
progress!(ctx, 920);
|
||||
|
||||
e2ee::ensure_secret_key_exists(ctx).await?;
|
||||
info!(ctx, "key generation completed");
|
||||
|
||||
progress!(ctx, 940);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn exec_step(
|
||||
ctx: &Context,
|
||||
imap: &mut Imap,
|
||||
param: &mut LoginParam,
|
||||
param_domain: &mut String,
|
||||
param_autoconfig: &mut Option<LoginParam>,
|
||||
param_addr_urlencoded: &mut String,
|
||||
keep_flags: &mut i32,
|
||||
step_counter: &mut u8,
|
||||
) -> Result<Step> {
|
||||
const STEP_12_USE_AUTOCONFIG: u8 = 12;
|
||||
const STEP_13_AFTER_AUTOCONFIG: u8 = 13;
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum AutoconfigProvider {
|
||||
Mozilla,
|
||||
Outlook,
|
||||
}
|
||||
|
||||
match *step_counter {
|
||||
// Read login parameters from the database
|
||||
1 => {
|
||||
progress!(ctx, 1);
|
||||
ensure!(!param.addr.is_empty(), "Please enter an email address.");
|
||||
}
|
||||
// Step 1: Load the parameters and check email-address and password
|
||||
2 => {
|
||||
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
|
||||
// just use the given one.
|
||||
progress!(ctx, 10);
|
||||
if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, ¶m.addr, ¶m.mail_pw)
|
||||
.await
|
||||
.and_then(|e| e.parse().ok())
|
||||
{
|
||||
info!(ctx, "Authorized address is {}", oauth2_addr);
|
||||
param.addr = oauth2_addr;
|
||||
ctx.sql
|
||||
.set_raw_config(ctx, "addr", Some(param.addr.as_str()))
|
||||
.await?;
|
||||
}
|
||||
progress!(ctx, 20);
|
||||
}
|
||||
// no oauth? - just continue it's no error
|
||||
}
|
||||
3 => {
|
||||
if let Ok(parsed) = param.addr.parse() {
|
||||
let parsed: EmailAddress = parsed;
|
||||
*param_domain = parsed.domain;
|
||||
*param_addr_urlencoded =
|
||||
utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC).to_string();
|
||||
} else {
|
||||
bail!("Bad email-address.");
|
||||
}
|
||||
}
|
||||
// Step 2: Autoconfig
|
||||
4 => {
|
||||
progress!(ctx, 200);
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct AutoconfigSource {
|
||||
provider: AutoconfigProvider,
|
||||
url: String,
|
||||
}
|
||||
|
||||
if param.mail_server.is_empty()
|
||||
&& param.mail_port == 0
|
||||
/* && param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
|
||||
&& param.send_server.is_empty()
|
||||
&& param.send_port == 0
|
||||
&& param.send_user.is_empty()
|
||||
/* && param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
|
||||
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
|
||||
{
|
||||
// no advanced parameters entered by the user: query provider-database or do Autoconfig
|
||||
*keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
|
||||
if let Some(new_param) = get_offline_autoconfig(ctx, ¶m) {
|
||||
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
|
||||
*param_autoconfig = Some(new_param);
|
||||
*step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
|
||||
}
|
||||
} else {
|
||||
// advanced parameters entered by the user: skip Autoconfig
|
||||
*step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
|
||||
}
|
||||
}
|
||||
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
|
||||
5 => {
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
impl AutoconfigSource {
|
||||
fn all(domain: &str, addr: &str) -> [Self; 5] {
|
||||
[
|
||||
AutoconfigSource {
|
||||
provider: AutoconfigProvider::Mozilla,
|
||||
url: format!(
|
||||
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
*param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
progress!(ctx, 300);
|
||||
if param_autoconfig.is_none() {
|
||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
|
||||
let url = format!(
|
||||
domain, addr,
|
||||
),
|
||||
},
|
||||
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
|
||||
AutoconfigSource {
|
||||
provider: AutoconfigProvider::Mozilla,
|
||||
url: format!(
|
||||
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
*param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
/* Outlook section start ------------- */
|
||||
/* Outlook uses always SSL but different domains (this comment describes the next two steps) */
|
||||
7 => {
|
||||
progress!(ctx, 310);
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain);
|
||||
*param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
8 => {
|
||||
progress!(ctx, 320);
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
domain, addr
|
||||
),
|
||||
},
|
||||
AutoconfigSource {
|
||||
provider: AutoconfigProvider::Outlook,
|
||||
url: format!("https://{}/autodiscover/autodiscover.xml", domain),
|
||||
},
|
||||
// Outlook uses always SSL but different domains (this comment describes the next two steps)
|
||||
AutoconfigSource {
|
||||
provider: AutoconfigProvider::Outlook,
|
||||
url: format!(
|
||||
"https://{}{}/autodiscover/autodiscover.xml",
|
||||
"autodiscover.", param_domain
|
||||
);
|
||||
*param_autoconfig = outlk_autodiscover(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
/* ----------- Outlook section end */
|
||||
9 => {
|
||||
progress!(ctx, 330);
|
||||
if param_autoconfig.is_none() {
|
||||
let url = format!(
|
||||
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
|
||||
param_domain, param_addr_urlencoded
|
||||
);
|
||||
*param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
10 => {
|
||||
progress!(ctx, 340);
|
||||
if param_autoconfig.is_none() {
|
||||
// do not transfer the email-address unencrypted
|
||||
let url = format!(
|
||||
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
|
||||
param_domain
|
||||
);
|
||||
*param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
|
||||
11 => {
|
||||
progress!(ctx, 350);
|
||||
if param_autoconfig.is_none() {
|
||||
/* always SSL for Thunderbird's database */
|
||||
let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
|
||||
*param_autoconfig = moz_autoconfigure(ctx, &url, ¶m).await.ok();
|
||||
}
|
||||
}
|
||||
/* C. Do we have any autoconfig result?
|
||||
If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above
|
||||
*/
|
||||
STEP_12_USE_AUTOCONFIG => {
|
||||
progress!(ctx, 500);
|
||||
if let Some(ref cfg) = param_autoconfig {
|
||||
info!(ctx, "Got autoconfig: {}", &cfg);
|
||||
if !cfg.mail_user.is_empty() {
|
||||
param.mail_user = cfg.mail_user.clone();
|
||||
}
|
||||
param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */
|
||||
param.mail_port = cfg.mail_port;
|
||||
param.send_server = cfg.send_server.clone();
|
||||
param.send_port = cfg.send_port;
|
||||
param.send_user = cfg.send_user.clone();
|
||||
param.server_flags = cfg.server_flags;
|
||||
/* although param_autoconfig's data are no longer needed from,
|
||||
it is used to later to prevent trying variations of port/server/logins */
|
||||
}
|
||||
param.server_flags |= *keep_flags;
|
||||
}
|
||||
// Step 3: Fill missing fields with defaults
|
||||
// If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above
|
||||
STEP_13_AFTER_AUTOCONFIG => {
|
||||
if param.mail_server.is_empty() {
|
||||
param.mail_server = format!("imap.{}", param_domain,)
|
||||
}
|
||||
if param.mail_port == 0 {
|
||||
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
|
||||
143
|
||||
} else {
|
||||
993
|
||||
}
|
||||
}
|
||||
if param.mail_user.is_empty() {
|
||||
param.mail_user = param.addr.clone();
|
||||
}
|
||||
if param.send_server.is_empty() && !param.mail_server.is_empty() {
|
||||
param.send_server = param.mail_server.clone();
|
||||
if param.send_server.starts_with("imap.") {
|
||||
param.send_server = param.send_server.replacen("imap", "smtp", 1);
|
||||
}
|
||||
}
|
||||
if param.send_port == 0 {
|
||||
param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
|
||||
587
|
||||
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
|
||||
25
|
||||
} else {
|
||||
465
|
||||
}
|
||||
}
|
||||
if param.send_user.is_empty() && !param.mail_user.is_empty() {
|
||||
param.send_user = param.mail_user.clone();
|
||||
}
|
||||
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
|
||||
param.send_pw = param.mail_pw.clone()
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_AUTH_NORMAL as i32
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 143 {
|
||||
DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
} else {
|
||||
DC_LP_IMAP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) {
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 587 {
|
||||
DC_LP_SMTP_SOCKET_STARTTLS as i32
|
||||
} else if param.send_port == 25 {
|
||||
DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
} else {
|
||||
DC_LP_SMTP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
/* do we have a complete configuration? */
|
||||
if param.mail_server.is_empty()
|
||||
|| param.mail_port == 0
|
||||
|| param.mail_user.is_empty()
|
||||
|| param.mail_pw.is_empty()
|
||||
|| param.send_server.is_empty()
|
||||
|| param.send_port == 0
|
||||
|| param.send_user.is_empty()
|
||||
|| param.send_pw.is_empty()
|
||||
|| param.server_flags == 0
|
||||
{
|
||||
bail!("Account settings incomplete.");
|
||||
}
|
||||
}
|
||||
14 => {
|
||||
progress!(ctx, 600);
|
||||
/* try to connect to IMAP - if we did not got an autoconfig,
|
||||
do some further tries with different settings and username variations */
|
||||
try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?;
|
||||
}
|
||||
15 => {
|
||||
progress!(ctx, 800);
|
||||
try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?;
|
||||
}
|
||||
16 => {
|
||||
progress!(ctx, 900);
|
||||
"autodiscover.", domain
|
||||
),
|
||||
},
|
||||
// always SSL for Thunderbird's database
|
||||
AutoconfigSource {
|
||||
provider: AutoconfigProvider::Mozilla,
|
||||
url: format!("https://autoconfig.thunderbird.net/v1.1/{}", domain),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await
|
||||
|| ctx.get_config_bool(Config::MvboxMove).await;
|
||||
async fn fetch(&self, ctx: &Context, param: &LoginParam) -> Result<LoginParam> {
|
||||
let params = match self.provider {
|
||||
AutoconfigProvider::Mozilla => moz_autoconfigure(ctx, &self.url, ¶m).await?,
|
||||
AutoconfigProvider::Outlook => outlk_autodiscover(ctx, &self.url, ¶m).await?,
|
||||
};
|
||||
|
||||
if let Err(err) = imap.configure_folders(ctx, create_mvbox).await {
|
||||
bail!("configuring folders failed: {:?}", err);
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = imap.select_with_uidvalidity(ctx, "INBOX").await {
|
||||
bail!("could not read INBOX status: {:?}", err);
|
||||
}
|
||||
}
|
||||
17 => {
|
||||
progress!(ctx, 910);
|
||||
// configuration success - write back the configured parameters with the
|
||||
// "configured_" prefix; also write the "configured"-flag */
|
||||
// the trailing underscore is correct
|
||||
param.save_to_database(ctx, "configured_").await?;
|
||||
ctx.sql.set_raw_config_bool(ctx, "configured", true).await?;
|
||||
}
|
||||
18 => {
|
||||
progress!(ctx, 920);
|
||||
// we generate the keypair just now - we could also postpone this until the first message is sent, however,
|
||||
// this may result in a unexpected and annoying delay when the user sends his very first message
|
||||
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
|
||||
e2ee::ensure_secret_key_exists(ctx).await?;
|
||||
info!(ctx, "key generation completed");
|
||||
progress!(ctx, 940);
|
||||
return Ok(Step::Done);
|
||||
}
|
||||
_ => {
|
||||
bail!("Internal error: step counter out of bound");
|
||||
/// Retrieve available autoconfigurations.
|
||||
///
|
||||
/// A Search configurations from the domain used in the email-address, prefer encrypted
|
||||
/// B. If we have no configuration yet, search configuration in Thunderbird's centeral database
|
||||
async fn get_autoconfig(
|
||||
ctx: &Context,
|
||||
param: &LoginParam,
|
||||
param_domain: &str,
|
||||
param_addr_urlencoded: &str,
|
||||
) -> Option<LoginParam> {
|
||||
let sources = AutoconfigSource::all(param_domain, param_addr_urlencoded);
|
||||
|
||||
let mut progress = 300;
|
||||
for source in &sources {
|
||||
let res = source.fetch(ctx, param).await;
|
||||
progress!(ctx, progress);
|
||||
progress += 10;
|
||||
if let Ok(res) = res {
|
||||
return Some(res);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Step::Continue)
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Step {
|
||||
Done,
|
||||
Continue,
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
|
||||
info!(
|
||||
context,
|
||||
@@ -479,37 +399,35 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<Login
|
||||
// however, rewriting the code to "if let" would make things less obvious,
|
||||
// esp. if we allow more combinations of servers (pop, jmap).
|
||||
// therefore, #[allow(clippy::unnecessary_unwrap)] is added above.
|
||||
if imap.is_some() && smtp.is_some() {
|
||||
let imap = imap.unwrap();
|
||||
let smtp = smtp.unwrap();
|
||||
if let Some(imap) = imap {
|
||||
if let Some(smtp) = smtp {
|
||||
let mut p = LoginParam::new();
|
||||
p.addr = param.addr.clone();
|
||||
|
||||
let mut p = LoginParam::new();
|
||||
p.addr = param.addr.clone();
|
||||
p.mail_server = imap.hostname.to_string();
|
||||
p.mail_user = imap.apply_username_pattern(param.addr.clone());
|
||||
p.mail_port = imap.port as i32;
|
||||
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
||||
p.server_flags |= match imap.socket {
|
||||
provider::Socket::STARTTLS => DC_LP_IMAP_SOCKET_STARTTLS,
|
||||
provider::Socket::SSL => DC_LP_IMAP_SOCKET_SSL,
|
||||
};
|
||||
|
||||
p.mail_server = imap.hostname.to_string();
|
||||
p.mail_user = imap.apply_username_pattern(param.addr.clone());
|
||||
p.mail_port = imap.port as i32;
|
||||
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
||||
p.server_flags |= match imap.socket {
|
||||
provider::Socket::STARTTLS => DC_LP_IMAP_SOCKET_STARTTLS,
|
||||
provider::Socket::SSL => DC_LP_IMAP_SOCKET_SSL,
|
||||
};
|
||||
p.send_server = smtp.hostname.to_string();
|
||||
p.send_user = smtp.apply_username_pattern(param.addr.clone());
|
||||
p.send_port = smtp.port as i32;
|
||||
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
||||
p.server_flags |= match smtp.socket {
|
||||
provider::Socket::STARTTLS => DC_LP_SMTP_SOCKET_STARTTLS as i32,
|
||||
provider::Socket::SSL => DC_LP_SMTP_SOCKET_SSL as i32,
|
||||
};
|
||||
|
||||
p.send_server = smtp.hostname.to_string();
|
||||
p.send_user = smtp.apply_username_pattern(param.addr.clone());
|
||||
p.send_port = smtp.port as i32;
|
||||
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
|
||||
p.server_flags |= match smtp.socket {
|
||||
provider::Socket::STARTTLS => DC_LP_SMTP_SOCKET_STARTTLS as i32,
|
||||
provider::Socket::SSL => DC_LP_SMTP_SOCKET_SSL as i32,
|
||||
};
|
||||
|
||||
info!(context, "offline autoconfig found: {}", p);
|
||||
return Some(p);
|
||||
} else {
|
||||
info!(context, "offline autoconfig found, but no servers defined");
|
||||
return None;
|
||||
info!(context, "offline autoconfig found: {}", p);
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
info!(context, "offline autoconfig found, but no servers defined");
|
||||
return None;
|
||||
}
|
||||
provider::Status::BROKEN => {
|
||||
info!(context, "offline autoconfig found, provider is broken");
|
||||
@@ -523,19 +441,32 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<Login
|
||||
|
||||
async fn try_imap_connections(
|
||||
context: &Context,
|
||||
mut param: &mut LoginParam,
|
||||
param: &mut LoginParam,
|
||||
was_autoconfig: bool,
|
||||
imap: &mut Imap,
|
||||
) -> Result<bool> {
|
||||
// progress 650 and 660
|
||||
if let Ok(val) = try_imap_connection(context, &mut param, was_autoconfig, 0, imap).await {
|
||||
return Ok(val);
|
||||
}
|
||||
progress!(context, 670);
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
|
||||
param.server_flags |= DC_LP_IMAP_SOCKET_SSL;
|
||||
param.mail_port = 993;
|
||||
) -> Result<()> {
|
||||
// manually_set_param is used to check whether a particular setting was set manually by the user.
|
||||
// If yes, we do not want to change it to avoid confusing error messages
|
||||
// (you set port 443, but the app tells you it couldn't connect on port 993).
|
||||
let manually_set_param = LoginParam::from_database(context, "").await;
|
||||
|
||||
// progress 650 and 660
|
||||
if try_imap_connection(context, param, &manually_set_param, was_autoconfig, 0, imap)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Ok(()); // we directly return here if it was autoconfig or the connection succeeded
|
||||
}
|
||||
|
||||
progress!(context, 670);
|
||||
// try_imap_connection() changed the flags and port. Change them back:
|
||||
if manually_set_param.server_flags & DC_LP_IMAP_SOCKET_FLAGS == 0 {
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
|
||||
param.server_flags |= DC_LP_IMAP_SOCKET_SSL;
|
||||
}
|
||||
if manually_set_param.mail_port == 0 {
|
||||
param.mail_port = 993;
|
||||
}
|
||||
if let Some(at) = param.mail_user.find('@') {
|
||||
param.mail_user = param.mail_user.split_at(at).0.to_string();
|
||||
}
|
||||
@@ -543,35 +474,43 @@ async fn try_imap_connections(
|
||||
param.send_user = param.send_user.split_at(at).0.to_string();
|
||||
}
|
||||
// progress 680 and 690
|
||||
try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await
|
||||
try_imap_connection(context, param, &manually_set_param, was_autoconfig, 1, imap).await
|
||||
}
|
||||
|
||||
async fn try_imap_connection(
|
||||
context: &Context,
|
||||
param: &mut LoginParam,
|
||||
manually_set_param: &LoginParam,
|
||||
was_autoconfig: bool,
|
||||
variation: usize,
|
||||
imap: &mut Imap,
|
||||
) -> Result<bool> {
|
||||
if try_imap_one_param(context, ¶m, imap).await.is_ok() {
|
||||
return Ok(true);
|
||||
) -> Result<()> {
|
||||
if try_imap_one_param(context, param, imap).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if was_autoconfig {
|
||||
return Ok(false);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
progress!(context, 650 + variation * 30);
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
|
||||
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS;
|
||||
if try_imap_one_param(context, ¶m, imap).await.is_ok() {
|
||||
return Ok(true);
|
||||
|
||||
if manually_set_param.server_flags & DC_LP_IMAP_SOCKET_FLAGS == 0 {
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
|
||||
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS;
|
||||
|
||||
if try_imap_one_param(context, ¶m, imap).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
progress!(context, 660 + variation * 30);
|
||||
param.mail_port = 143;
|
||||
|
||||
try_imap_one_param(context, ¶m, imap).await?;
|
||||
|
||||
Ok(true)
|
||||
if manually_set_param.mail_port == 0 {
|
||||
param.mail_port = 143;
|
||||
try_imap_one_param(context, param, imap).await
|
||||
} else {
|
||||
Err(format_err!("no more possible configs"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Imap) -> Result<()> {
|
||||
@@ -590,41 +529,51 @@ async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Im
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if context.shall_stop_ongoing().await {
|
||||
bail!("Interrupted");
|
||||
}
|
||||
|
||||
bail!("Could not connect: {}", inf);
|
||||
}
|
||||
|
||||
async fn try_smtp_connections(
|
||||
context: &Context,
|
||||
mut param: &mut LoginParam,
|
||||
param: &mut LoginParam,
|
||||
was_autoconfig: bool,
|
||||
) -> Result<()> {
|
||||
// manually_set_param is used to check whether a particular setting was set manually by the user.
|
||||
// If yes, we do not want to change it to avoid confusing error messages
|
||||
// (you set port 443, but the app tells you it couldn't connect on port 993).
|
||||
let manually_set_param = LoginParam::from_database(context, "").await;
|
||||
|
||||
let mut smtp = Smtp::new();
|
||||
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
|
||||
if try_smtp_one_param(context, ¶m, &mut smtp).await.is_ok() {
|
||||
// try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do
|
||||
// a second try with STARTTLS-587
|
||||
if try_smtp_one_param(context, param, &mut smtp).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if was_autoconfig {
|
||||
return Ok(());
|
||||
}
|
||||
progress!(context, 850);
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
param.send_port = 587;
|
||||
|
||||
if try_smtp_one_param(context, ¶m, &mut smtp).await.is_ok() {
|
||||
if manually_set_param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32) == 0 {
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
}
|
||||
if manually_set_param.send_port == 0 {
|
||||
param.send_port = 587;
|
||||
}
|
||||
|
||||
if try_smtp_one_param(context, param, &mut smtp).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
progress!(context, 860);
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
param.send_port = 25;
|
||||
try_smtp_one_param(context, ¶m, &mut smtp).await?;
|
||||
|
||||
Ok(())
|
||||
if manually_set_param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32) == 0 {
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
}
|
||||
if manually_set_param.send_port == 0 {
|
||||
param.send_port = 25;
|
||||
}
|
||||
try_smtp_one_param(context, param, &mut smtp).await
|
||||
}
|
||||
|
||||
async fn try_smtp_one_param(context: &Context, param: &LoginParam, smtp: &mut Smtp) -> Result<()> {
|
||||
|
||||
105
src/contact.rs
105
src/contact.rs
@@ -3,6 +3,8 @@
|
||||
use async_std::path::PathBuf;
|
||||
use deltachat_derive::*;
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::ChatId;
|
||||
@@ -12,7 +14,7 @@ use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::{bail, ensure, format_err, Result};
|
||||
use crate::events::Event;
|
||||
use crate::key::{DcKey, Key, SignedPublicKey};
|
||||
use crate::key::{DcKey, SignedPublicKey};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{MessageState, MsgId};
|
||||
use crate::mimeparser::AvatarAction;
|
||||
@@ -238,6 +240,8 @@ impl Contact {
|
||||
"Cannot create contact with empty address"
|
||||
);
|
||||
|
||||
let (name, addr) = sanitize_name_and_addr(name, addr);
|
||||
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?;
|
||||
let blocked = Contact::is_blocked_load(context, contact_id).await;
|
||||
@@ -512,8 +516,9 @@ impl Contact {
|
||||
let mut modify_cnt = 0;
|
||||
|
||||
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
|
||||
let (name, addr) = sanitize_name_and_addr(name, addr);
|
||||
let name = normalize_name(name);
|
||||
match Contact::add_or_lookup(context, name, addr, Origin::AddressBook).await {
|
||||
match Contact::add_or_lookup(context, name, &addr, Origin::AddressBook).await {
|
||||
Err(err) => {
|
||||
warn!(
|
||||
context,
|
||||
@@ -691,18 +696,20 @@ impl Contact {
|
||||
})
|
||||
.await;
|
||||
ret += &p;
|
||||
let self_key = Key::from(SignedPublicKey::load_self(context).await?);
|
||||
let p = context.stock_str(StockMessage::FingerPrints).await;
|
||||
ret += &format!(" {}:", p);
|
||||
|
||||
let fingerprint_self = self_key.formatted_fingerprint();
|
||||
let fingerprint_self = SignedPublicKey::load_self(context)
|
||||
.await?
|
||||
.fingerprint()
|
||||
.to_string();
|
||||
let fingerprint_other_verified = peerstate
|
||||
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.map(|k| k.fingerprint().to_string())
|
||||
.unwrap_or_default();
|
||||
let fingerprint_other_unverified = peerstate
|
||||
.peek_key(PeerstateVerifiedStatus::Unverified)
|
||||
.map(|k| k.formatted_fingerprint())
|
||||
.map(|k| k.fingerprint().to_string())
|
||||
.unwrap_or_default();
|
||||
if loginparam.addr < peerstate.addr {
|
||||
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
|
||||
@@ -1028,6 +1035,24 @@ pub fn addr_normalize(addr: &str) -> &str {
|
||||
norm
|
||||
}
|
||||
|
||||
fn sanitize_name_and_addr(name: impl AsRef<str>, addr: impl AsRef<str>) -> (String, String) {
|
||||
lazy_static! {
|
||||
static ref ADDR_WITH_NAME_REGEX: Regex = Regex::new("(.*)<(.*)>").unwrap();
|
||||
}
|
||||
if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
|
||||
(
|
||||
if name.as_ref().is_empty() {
|
||||
normalize_name(&captures[1])
|
||||
} else {
|
||||
name.as_ref().to_string()
|
||||
},
|
||||
captures[2].to_string(),
|
||||
)
|
||||
} else {
|
||||
(name.as_ref().to_string(), addr.as_ref().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
return;
|
||||
@@ -1202,6 +1227,10 @@ mod tests {
|
||||
assert_eq!(may_be_valid_addr("u@d.tt"), true);
|
||||
assert_eq!(may_be_valid_addr("u@.tt"), false);
|
||||
assert_eq!(may_be_valid_addr("@d.tt"), false);
|
||||
assert_eq!(may_be_valid_addr("<da@d.tt"), false);
|
||||
assert_eq!(may_be_valid_addr("sk <@d.tt>"), false);
|
||||
assert_eq!(may_be_valid_addr("as@sd.de>"), false);
|
||||
assert_eq!(may_be_valid_addr("ask dkl@dd.tt"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1284,9 +1313,10 @@ mod tests {
|
||||
"Name two\ntwo@deux.net\n",
|
||||
"Invalid\n+1234567890\n", // invalid, should be ignored
|
||||
"\nthree@drei.sam\n",
|
||||
"Name two\ntwo@deux.net\n" // should not be added again
|
||||
"Name two\ntwo@deux.net\n", // should not be added again
|
||||
"\nWonderland, Alice <alice@w.de>\n",
|
||||
);
|
||||
assert_eq!(Contact::add_address_book(&t.ctx, book).await.unwrap(), 3);
|
||||
assert_eq!(Contact::add_address_book(&t.ctx, book).await.unwrap(), 4);
|
||||
|
||||
// check first added contact, this does not modify because of lower origin
|
||||
let (contact_id, sth_modified) =
|
||||
@@ -1362,6 +1392,19 @@ mod tests {
|
||||
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
|
||||
assert!(!contact.is_blocked());
|
||||
|
||||
// Fourth contact:
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(&t.ctx, "", "alice@w.de", Origin::IncomingUnknownTo)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::None);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name(), "Alice Wonderland");
|
||||
assert_eq!(contact.get_display_name(), "Alice Wonderland");
|
||||
assert_eq!(contact.get_addr(), "alice@w.de");
|
||||
assert_eq!(contact.get_name_n_addr(), "Alice Wonderland (alice@w.de)");
|
||||
|
||||
// check SELF
|
||||
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
@@ -1528,4 +1571,50 @@ mod tests {
|
||||
assert!(addr_cmp(" aa@aa.ORG ", "AA@AA.ORG"));
|
||||
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_name_in_address() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
let contact_id = Contact::create(&t.ctx, "", "<dave@example.org>")
|
||||
.await
|
||||
.unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name(), "");
|
||||
assert_eq!(contact.get_addr(), "dave@example.org");
|
||||
|
||||
let contact_id = Contact::create(&t.ctx, "", "Mueller, Dave <dave@example.org>")
|
||||
.await
|
||||
.unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name(), "Dave Mueller");
|
||||
assert_eq!(contact.get_addr(), "dave@example.org");
|
||||
|
||||
let contact_id = Contact::create(&t.ctx, "name1", "name2 <dave@example.org>")
|
||||
.await
|
||||
.unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name(), "name1");
|
||||
assert_eq!(contact.get_addr(), "dave@example.org");
|
||||
|
||||
assert!(Contact::create(&t.ctx, "", "<dskjfdslk@sadklj.dk")
|
||||
.await
|
||||
.is_err());
|
||||
assert!(Contact::create(&t.ctx, "", "<dskjf>dslk@sadklj.dk>")
|
||||
.await
|
||||
.is_err());
|
||||
assert!(Contact::create(&t.ctx, "", "dskjfdslksadklj.dk")
|
||||
.await
|
||||
.is_err());
|
||||
assert!(Contact::create(&t.ctx, "", "dskjfdslk@sadklj.dk>")
|
||||
.await
|
||||
.is_err());
|
||||
assert!(Contact::create(&t.ctx, "", "dskjf@dslk@sadkljdk")
|
||||
.await
|
||||
.is_err());
|
||||
assert!(Contact::create(&t.ctx, "", "dskjf dslk@d.e").await.is_err());
|
||||
assert!(Contact::create(&t.ctx, "", "<dskjf dslk@sadklj.dk")
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::dc_tools::duration_to_str;
|
||||
use crate::error::*;
|
||||
use crate::events::{Event, EventEmitter, Events};
|
||||
use crate::job::{self, Action};
|
||||
use crate::key::{DcKey, Key, SignedPublicKey};
|
||||
use crate::key::{DcKey, SignedPublicKey};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::lot::Lot;
|
||||
use crate::message::{self, Message, MessengerMessage, MsgId};
|
||||
@@ -138,10 +138,15 @@ impl Context {
|
||||
/// Starts the IO scheduler.
|
||||
pub async fn start_io(&self) {
|
||||
info!(self, "starting IO");
|
||||
assert!(!self.is_io_running().await, "context is already running");
|
||||
if self.is_io_running().await {
|
||||
info!(self, "IO is already running");
|
||||
return;
|
||||
}
|
||||
|
||||
let l = &mut *self.inner.scheduler.write().await;
|
||||
l.start(self.clone()).await;
|
||||
{
|
||||
let l = &mut *self.inner.scheduler.write().await;
|
||||
l.start(self.clone()).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the IO scheduler is running.
|
||||
@@ -152,6 +157,11 @@ impl Context {
|
||||
/// Stops the IO scheduler.
|
||||
pub async fn stop_io(&self) {
|
||||
info!(self, "stopping IO");
|
||||
if !self.is_io_running().await {
|
||||
info!(self, "IO is not running");
|
||||
return;
|
||||
}
|
||||
|
||||
self.inner.stop_io().await;
|
||||
}
|
||||
|
||||
@@ -275,7 +285,7 @@ impl Context {
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
|
||||
.await;
|
||||
let fingerprint_str = match SignedPublicKey::load_self(self).await {
|
||||
Ok(key) => Key::from(key).fingerprint(),
|
||||
Ok(key) => key.fingerprint().hex(),
|
||||
Err(err) => format!("<key failure: {}>", err),
|
||||
};
|
||||
|
||||
|
||||
@@ -482,15 +482,6 @@ pub struct InvalidEmailError {
|
||||
addr: String,
|
||||
}
|
||||
|
||||
impl InvalidEmailError {
|
||||
fn new(msg: impl Into<String>, addr: impl Into<String>) -> InvalidEmailError {
|
||||
InvalidEmailError {
|
||||
message: msg.into(),
|
||||
addr: addr.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Very simple email address wrapper.
|
||||
///
|
||||
/// Represents an email address, right now just the `name@domain` portion.
|
||||
@@ -530,17 +521,24 @@ impl FromStr for EmailAddress {
|
||||
|
||||
/// Performs a dead-simple parse of an email address.
|
||||
fn from_str(input: &str) -> Result<EmailAddress, InvalidEmailError> {
|
||||
if input.is_empty() {
|
||||
return Err(InvalidEmailError::new("empty string is not valid", input));
|
||||
}
|
||||
let parts: Vec<&str> = input.rsplitn(2, '@').collect();
|
||||
|
||||
let err = |msg: &str| {
|
||||
Err(InvalidEmailError {
|
||||
message: msg.to_string(),
|
||||
addr: input.to_string(),
|
||||
})
|
||||
};
|
||||
if input.is_empty() {
|
||||
return err("empty string is not valid");
|
||||
}
|
||||
let parts: Vec<&str> = input.rsplitn(2, '@').collect();
|
||||
|
||||
if input
|
||||
.chars()
|
||||
.any(|c| c.is_whitespace() || c == '<' || c == '>')
|
||||
{
|
||||
return err("Email must not contain whitespaces, '>' or '<'");
|
||||
}
|
||||
|
||||
match &parts[..] {
|
||||
[domain, local] => {
|
||||
if local.is_empty() {
|
||||
|
||||
101
src/e2ee.rs
101
src/e2ee.rs
@@ -11,7 +11,7 @@ use crate::context::Context;
|
||||
use crate::error::*;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::headerdef::HeaderDefMap;
|
||||
use crate::key::{DcKey, Key, SignedPublicKey, SignedSecretKey};
|
||||
use crate::key::{DcKey, SignedPublicKey, SignedSecretKey};
|
||||
use crate::keyring::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp;
|
||||
@@ -87,30 +87,29 @@ impl EncryptHelper {
|
||||
|
||||
/// Tries to encrypt the passed in `mail`.
|
||||
pub async fn encrypt(
|
||||
&mut self,
|
||||
self,
|
||||
context: &Context,
|
||||
min_verified: PeerstateVerifiedStatus,
|
||||
mail_to_encrypt: lettre_email::PartBuilder,
|
||||
peerstates: &[(Option<Peerstate<'_>>, &str)],
|
||||
peerstates: Vec<(Option<Peerstate<'_>>, &str)>,
|
||||
) -> Result<String> {
|
||||
let mut keyring = Keyring::default();
|
||||
let mut keyring: Keyring<SignedPublicKey> = Keyring::new();
|
||||
|
||||
for (peerstate, addr) in peerstates
|
||||
.iter()
|
||||
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
|
||||
.into_iter()
|
||||
.filter_map(|(state, addr)| state.map(|s| (s, addr)))
|
||||
{
|
||||
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
|
||||
let key = peerstate.take_key(min_verified).ok_or_else(|| {
|
||||
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
|
||||
})?;
|
||||
keyring.add_ref(key);
|
||||
keyring.add(key);
|
||||
}
|
||||
let public_key = Key::from(self.public_key.clone());
|
||||
keyring.add_ref(&public_key);
|
||||
let sign_key = Key::from(SignedSecretKey::load_self(context).await?);
|
||||
keyring.add(self.public_key.clone());
|
||||
let sign_key = SignedSecretKey::load_self(context).await?;
|
||||
|
||||
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
|
||||
|
||||
let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?;
|
||||
let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key)).await?;
|
||||
|
||||
Ok(ctext)
|
||||
}
|
||||
@@ -151,41 +150,33 @@ pub async fn try_decrypt(
|
||||
}
|
||||
|
||||
/* possibly perform decryption */
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut out_mail = None;
|
||||
let private_keyring: Keyring<SignedSecretKey> = Keyring::new_self(context).await?;
|
||||
let mut public_keyring_for_validate: Keyring<SignedPublicKey> = Keyring::new();
|
||||
let mut signatures = HashSet::default();
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).await;
|
||||
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring
|
||||
.load_self_private_for_decrypting(context, self_addr, &context.sql)
|
||||
.await
|
||||
{
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate = Peerstate::from_addr(&context, &from).await;
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate).await?;
|
||||
}
|
||||
if let Some(ref key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
if let Some(ref key) = peerstate.public_key {
|
||||
public_keyring_for_validate.add_ref(key);
|
||||
}
|
||||
}
|
||||
|
||||
out_mail = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
mail,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
)?;
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate = Peerstate::from_addr(&context, &from).await;
|
||||
}
|
||||
if let Some(peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
handle_degrade_event(context, &peerstate).await?;
|
||||
}
|
||||
if let Some(key) = peerstate.gossip_key {
|
||||
public_keyring_for_validate.add(key);
|
||||
}
|
||||
if let Some(key) = peerstate.public_key {
|
||||
public_keyring_for_validate.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
let out_mail = decrypt_if_autocrypt_message(
|
||||
context,
|
||||
mail,
|
||||
private_keyring,
|
||||
public_keyring_for_validate,
|
||||
&mut signatures,
|
||||
)
|
||||
.await?;
|
||||
Ok((out_mail, signatures))
|
||||
}
|
||||
|
||||
@@ -216,11 +207,11 @@ fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail
|
||||
Ok(&mail.subparts[1])
|
||||
}
|
||||
|
||||
fn decrypt_if_autocrypt_message<'a>(
|
||||
async fn decrypt_if_autocrypt_message<'a>(
|
||||
context: &Context,
|
||||
mail: &ParsedMail<'a>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
private_keyring: Keyring<SignedSecretKey>,
|
||||
public_keyring_for_validate: Keyring<SignedPublicKey>,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
// The returned bool is true if we detected an Autocrypt-encrypted
|
||||
@@ -240,20 +231,19 @@ fn decrypt_if_autocrypt_message<'a>(
|
||||
info!(context, "Detected Autocrypt-mime message");
|
||||
|
||||
decrypt_part(
|
||||
context,
|
||||
encrypted_data_part,
|
||||
private_keyring,
|
||||
public_keyring_for_validate,
|
||||
ret_valid_signatures,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns Ok(None) if nothing encrypted was found.
|
||||
fn decrypt_part(
|
||||
_context: &Context,
|
||||
async fn decrypt_part(
|
||||
mail: &ParsedMail<'_>,
|
||||
private_keyring: &Keyring,
|
||||
public_keyring_for_validate: &Keyring,
|
||||
private_keyring: Keyring<SignedSecretKey>,
|
||||
public_keyring_for_validate: Keyring<SignedPublicKey>,
|
||||
ret_valid_signatures: &mut HashSet<String>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let data = mail.get_body_raw()?;
|
||||
@@ -263,11 +253,12 @@ fn decrypt_part(
|
||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||
|
||||
let plain = pgp::pk_decrypt(
|
||||
&data,
|
||||
&private_keyring,
|
||||
&public_keyring_for_validate,
|
||||
data,
|
||||
private_keyring,
|
||||
public_keyring_for_validate,
|
||||
Some(ret_valid_signatures),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||
return Ok(Some(plain));
|
||||
|
||||
130
src/imap/idle.rs
130
src/imap/idle.rs
@@ -34,7 +34,7 @@ impl Imap {
|
||||
self.config.can_idle
|
||||
}
|
||||
|
||||
pub async fn idle(&mut self, context: &Context, watch_folder: Option<String>) -> Result<()> {
|
||||
pub async fn idle(&mut self, context: &Context, watch_folder: Option<String>) -> Result<bool> {
|
||||
use futures::future::FutureExt;
|
||||
|
||||
if !self.can_idle() {
|
||||
@@ -46,6 +46,7 @@ impl Imap {
|
||||
|
||||
let session = self.session.take();
|
||||
let timeout = Duration::from_secs(23 * 60);
|
||||
let mut probe_network = false;
|
||||
|
||||
if let Some(session) = session {
|
||||
let mut handle = session.idle();
|
||||
@@ -55,6 +56,11 @@ impl Imap {
|
||||
|
||||
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
|
||||
|
||||
enum Event {
|
||||
IdleResponse(IdleResponse),
|
||||
Interrupt(bool),
|
||||
}
|
||||
|
||||
if self.skip_next_idle_wait {
|
||||
// interrupt_idle has happened before we
|
||||
// provided self.interrupt
|
||||
@@ -65,23 +71,27 @@ impl Imap {
|
||||
info!(context, "Idle wait was skipped");
|
||||
} else {
|
||||
info!(context, "Idle entering wait-on-remote state");
|
||||
let fut = idle_wait.race(
|
||||
self.idle_interrupt
|
||||
.recv()
|
||||
.map(|_| Ok(IdleResponse::ManualInterrupt)),
|
||||
let fut = idle_wait.map(|ev| ev.map(Event::IdleResponse)).race(
|
||||
self.idle_interrupt.recv().map(|probe_network| {
|
||||
Ok(Event::Interrupt(probe_network.unwrap_or_default()))
|
||||
}),
|
||||
);
|
||||
|
||||
match fut.await {
|
||||
Ok(IdleResponse::NewData(_)) => {
|
||||
Ok(Event::IdleResponse(IdleResponse::NewData(_))) => {
|
||||
info!(context, "Idle has NewData");
|
||||
}
|
||||
// TODO: idle_wait does not distinguish manual interrupts
|
||||
// from Timeouts if we would know it's a Timeout we could bail
|
||||
// directly and reconnect .
|
||||
Ok(IdleResponse::Timeout) => {
|
||||
Ok(Event::IdleResponse(IdleResponse::Timeout)) => {
|
||||
info!(context, "Idle-wait timeout or interruption");
|
||||
}
|
||||
Ok(IdleResponse::ManualInterrupt) => {
|
||||
Ok(Event::IdleResponse(IdleResponse::ManualInterrupt)) => {
|
||||
info!(context, "Idle wait was interrupted");
|
||||
}
|
||||
Ok(Event::Interrupt(probe)) => {
|
||||
probe_network = probe;
|
||||
info!(context, "Idle wait was interrupted");
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -115,16 +125,26 @@ impl Imap {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(probe_network)
|
||||
}
|
||||
|
||||
pub(crate) async fn fake_idle(&mut self, context: &Context, watch_folder: Option<String>) {
|
||||
pub(crate) async fn fake_idle(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
watch_folder: Option<String>,
|
||||
) -> bool {
|
||||
// Idle using polling. This is also needed if we're not yet configured -
|
||||
// in this case, we're waiting for a configure job (and an interrupt).
|
||||
|
||||
let fake_idle_start_time = SystemTime::now();
|
||||
info!(context, "IMAP-fake-IDLEing...");
|
||||
|
||||
// Do not poll, just wait for an interrupt when no folder is passed in.
|
||||
if watch_folder.is_none() {
|
||||
return self.idle_interrupt.recv().await.unwrap_or_default();
|
||||
}
|
||||
|
||||
let mut probe_network = false;
|
||||
if self.skip_next_idle_wait {
|
||||
// interrupt_idle has happened before we
|
||||
// provided self.interrupt
|
||||
@@ -135,53 +155,61 @@ impl Imap {
|
||||
// TODO: grow sleep durations / make them more flexible
|
||||
let mut interval = async_std::stream::interval(Duration::from_secs(60));
|
||||
|
||||
enum Event {
|
||||
Tick,
|
||||
Interrupt(bool),
|
||||
}
|
||||
// loop until we are interrupted or if we fetched something
|
||||
loop {
|
||||
use futures::future::FutureExt;
|
||||
match interval
|
||||
.next()
|
||||
.race(self.idle_interrupt.recv().map(|_| None))
|
||||
.await
|
||||
{
|
||||
Some(_) => {
|
||||
// try to connect with proper login params
|
||||
// (setup_handle_if_needed might not know about them if we
|
||||
// never successfully connected)
|
||||
if let Err(err) = self.connect_configured(context).await {
|
||||
warn!(context, "fake_idle: could not connect: {}", err);
|
||||
continue;
|
||||
}
|
||||
if self.config.can_idle {
|
||||
// we only fake-idled because network was gone during IDLE, probably
|
||||
break;
|
||||
}
|
||||
info!(context, "fake_idle is connected");
|
||||
// we are connected, let's see if fetching messages results
|
||||
// in anything. If so, we behave as if IDLE had data but
|
||||
// will have already fetched the messages so perform_*_fetch
|
||||
// will not find any new.
|
||||
probe_network =
|
||||
loop {
|
||||
use futures::future::FutureExt;
|
||||
match interval
|
||||
.next()
|
||||
.map(|_| Event::Tick)
|
||||
.race(self.idle_interrupt.recv().map(|probe_network| {
|
||||
Event::Interrupt(probe_network.unwrap_or_default())
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Event::Tick => {
|
||||
// try to connect with proper login params
|
||||
// (setup_handle_if_needed might not know about them if we
|
||||
// never successfully connected)
|
||||
if let Err(err) = self.connect_configured(context).await {
|
||||
warn!(context, "fake_idle: could not connect: {}", err);
|
||||
continue;
|
||||
}
|
||||
if self.config.can_idle {
|
||||
// we only fake-idled because network was gone during IDLE, probably
|
||||
break false;
|
||||
}
|
||||
info!(context, "fake_idle is connected");
|
||||
// we are connected, let's see if fetching messages results
|
||||
// in anything. If so, we behave as if IDLE had data but
|
||||
// will have already fetched the messages so perform_*_fetch
|
||||
// will not find any new.
|
||||
|
||||
if let Some(ref watch_folder) = watch_folder {
|
||||
match self.fetch_new_messages(context, watch_folder).await {
|
||||
Ok(res) => {
|
||||
info!(context, "fetch_new_messages returned {:?}", res);
|
||||
if res {
|
||||
break;
|
||||
if let Some(ref watch_folder) = watch_folder {
|
||||
match self.fetch_new_messages(context, watch_folder).await {
|
||||
Ok(res) => {
|
||||
info!(context, "fetch_new_messages returned {:?}", res);
|
||||
if res {
|
||||
break false;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "could not fetch from folder: {}", err);
|
||||
self.trigger_reconnect()
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(context, "could not fetch from folder: {}", err);
|
||||
self.trigger_reconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Interrupt(probe_network) => {
|
||||
// Interrupt
|
||||
break probe_network;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Interrupt
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
info!(
|
||||
@@ -193,5 +221,7 @@ impl Imap {
|
||||
.as_millis() as f64
|
||||
/ 1000.,
|
||||
);
|
||||
|
||||
probe_network
|
||||
}
|
||||
}
|
||||
|
||||
355
src/imap/mod.rs
355
src/imap/mod.rs
@@ -3,7 +3,7 @@
|
||||
//! uses [async-email/async-imap](https://github.com/async-email/async-imap)
|
||||
//! to implement connect, fetch, delete functionality with standard IMAP servers.
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use async_imap::{
|
||||
error::Result as ImapResult,
|
||||
@@ -11,6 +11,7 @@ use async_imap::{
|
||||
};
|
||||
use async_std::prelude::*;
|
||||
use async_std::sync::Receiver;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::constants::*;
|
||||
@@ -108,7 +109,7 @@ const SELECT_ALL: &str = "1:*";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
idle_interrupt: Receiver<()>,
|
||||
idle_interrupt: Receiver<bool>,
|
||||
config: ImapConfig,
|
||||
session: Option<Session>,
|
||||
connected: bool,
|
||||
@@ -180,7 +181,7 @@ impl Default for ImapConfig {
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
pub fn new(idle_interrupt: Receiver<()>) -> Self {
|
||||
pub fn new(idle_interrupt: Receiver<bool>) -> Self {
|
||||
Imap {
|
||||
idle_interrupt,
|
||||
config: Default::default(),
|
||||
@@ -577,164 +578,120 @@ impl Imap {
|
||||
.select_with_uidvalidity(context, folder.as_ref())
|
||||
.await?;
|
||||
|
||||
let mut read_cnt: usize = 0;
|
||||
|
||||
if self.session.is_none() {
|
||||
return Err(Error::NoConnection);
|
||||
}
|
||||
let session = self.session.as_mut().unwrap();
|
||||
|
||||
// fetch messages with larger UID than the last one seen
|
||||
// `(UID FETCH lastseenuid+1:*)`, see RFC 4549
|
||||
let set = format!("{}:*", last_seen_uid + 1);
|
||||
|
||||
info!(context, "fetch_new_messages {:?}", set);
|
||||
let mut list = match session.uid_fetch(&set, PREFETCH_FLAGS).await {
|
||||
Ok(list) => list,
|
||||
Err(err) => {
|
||||
warn!(context, "ERROR: fetch_new_messages {:?} -> {:?}", &set, err);
|
||||
return Err(Error::FetchFailed(err));
|
||||
}
|
||||
};
|
||||
info!(context, "fetch_new_messages {:?} RETURNED", &set);
|
||||
|
||||
let mut msgs = Vec::new();
|
||||
while let Some(fetch) = list.next().await {
|
||||
let fetch = fetch.map_err(|err| Error::Other(err.to_string()))?;
|
||||
msgs.push(fetch);
|
||||
}
|
||||
drop(list);
|
||||
info!(context, "fetch_new_messages got {:?} messsages", msgs.len());
|
||||
|
||||
msgs.sort_unstable_by_key(|msg| msg.uid.unwrap_or_default());
|
||||
let msgs: Vec<_> = msgs
|
||||
.into_iter()
|
||||
.filter(|msg| {
|
||||
let cur_uid = msg.uid.unwrap_or_default();
|
||||
if cur_uid <= last_seen_uid {
|
||||
// If the mailbox is not empty, results always include
|
||||
// at least one UID, even if last_seen_uid+1 is past
|
||||
// the last UID in the mailbox. It happens because
|
||||
// uid+1:* is interpreted the same way as *:uid+1.
|
||||
// See https://tools.ietf.org/html/rfc3501#page-61 for
|
||||
// standard reference. Therefore, sometimes we receive
|
||||
// already seen messages and have to filter them out.
|
||||
info!(
|
||||
context,
|
||||
"fetch_new_messages: ignoring uid {}, last seen was {}",
|
||||
cur_uid,
|
||||
last_seen_uid
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
read_cnt += msgs.len();
|
||||
let msgs = self.fetch_after(context, last_seen_uid).await?;
|
||||
let read_cnt = msgs.len();
|
||||
let folder: &str = folder.as_ref();
|
||||
|
||||
let mut read_errors = 0;
|
||||
|
||||
let mut uids = Vec::with_capacity(msgs.len());
|
||||
let mut new_last_seen_uid = None;
|
||||
|
||||
for fetch in msgs.into_iter() {
|
||||
let folder: &str = folder.as_ref();
|
||||
|
||||
let cur_uid = fetch.uid.unwrap_or_default();
|
||||
let headers = match get_fetch_headers(&fetch) {
|
||||
Ok(h) => h,
|
||||
for (current_uid, msg) in msgs.into_iter() {
|
||||
let (headers, msg_id) = match get_fetch_headers(&msg) {
|
||||
Ok(headers) => {
|
||||
let msg_id = prefetch_get_message_id(&headers).unwrap_or_default();
|
||||
(headers, msg_id)
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "get_fetch_headers error: {}", err);
|
||||
warn!(context, "{}", err);
|
||||
read_errors += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let message_id = prefetch_get_message_id(&headers).unwrap_or_default();
|
||||
let skip = match precheck_imf(context, &message_id, folder, cur_uid).await {
|
||||
Ok(skip) => skip,
|
||||
Err(err) => {
|
||||
warn!(context, "precheck_imf error: {}", err);
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if skip {
|
||||
// we know the message-id already or don't want the message otherwise.
|
||||
info!(
|
||||
context,
|
||||
"Skipping message {} from \"{}\" by precheck.", message_id, folder,
|
||||
);
|
||||
if read_errors == 0 {
|
||||
new_last_seen_uid = Some(cur_uid);
|
||||
}
|
||||
} else {
|
||||
// we do not know the message-id
|
||||
// or the message-id is missing (in this case, we create one in the further process)
|
||||
// or some other error happened
|
||||
let show = match prefetch_should_download(context, &headers, show_emails).await {
|
||||
Ok(show) => show,
|
||||
Err(err) => {
|
||||
warn!(context, "prefetch_should_download error: {}", err);
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if show {
|
||||
uids.push(cur_uid);
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Ignoring new message {} from \"{}\".", message_id, folder,
|
||||
);
|
||||
}
|
||||
if read_errors == 0 {
|
||||
new_last_seen_uid = Some(cur_uid);
|
||||
}
|
||||
if message_needs_processing(
|
||||
context,
|
||||
current_uid,
|
||||
&headers,
|
||||
&msg_id,
|
||||
folder,
|
||||
show_emails,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// Trigger download and processing for this message.
|
||||
uids.push(current_uid);
|
||||
} else if read_errors == 0 {
|
||||
// No errors so far, but this was skipped, so mark as last_seen_uid
|
||||
new_last_seen_uid = Some(current_uid);
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
context,
|
||||
"fetch_many_msgs fetching {} messages in batch",
|
||||
uids.len()
|
||||
);
|
||||
// check passed, go fetch the emails
|
||||
let (new_last_seen_uid_processed, error_cnt) =
|
||||
self.fetch_many_msgs(context, &folder, &uids).await;
|
||||
read_errors += error_cnt;
|
||||
|
||||
// determine which last_seen_uid to use to update to
|
||||
let new_last_seen_uid_processed = new_last_seen_uid_processed.unwrap_or_default();
|
||||
let new_last_seen_uid = new_last_seen_uid.unwrap_or_default();
|
||||
let last_one = new_last_seen_uid.max(new_last_seen_uid_processed);
|
||||
|
||||
if last_one > last_seen_uid {
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid)
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, last_one)
|
||||
.await;
|
||||
}
|
||||
|
||||
read_errors += error_cnt;
|
||||
|
||||
if read_errors > 0 {
|
||||
if read_errors == 0 {
|
||||
info!(context, "{} mails read from \"{}\".", read_cnt, folder,);
|
||||
} else {
|
||||
warn!(
|
||||
context,
|
||||
"{} mails read from \"{}\" with {} errors.",
|
||||
read_cnt,
|
||||
folder.as_ref(),
|
||||
read_errors
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} mails read from \"{}\".",
|
||||
read_cnt,
|
||||
folder.as_ref()
|
||||
"{} mails read from \"{}\" with {} errors.", read_cnt, folder, read_errors
|
||||
);
|
||||
}
|
||||
|
||||
Ok(read_cnt > 0)
|
||||
}
|
||||
|
||||
/// Fetch all uids larger than the passed in. Returns a sorted list of fetch results.
|
||||
async fn fetch_after(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
uid: u32,
|
||||
) -> Result<BTreeMap<u32, async_imap::types::Fetch>> {
|
||||
if self.session.is_none() {
|
||||
return Err(Error::NoConnection);
|
||||
}
|
||||
|
||||
let session = self.session.as_mut().unwrap();
|
||||
|
||||
// fetch messages with larger UID than the last one seen
|
||||
// `(UID FETCH lastseenuid+1:*)`, see RFC 4549
|
||||
let set = format!("{}:*", uid + 1);
|
||||
let mut list = session
|
||||
.uid_fetch(set, PREFETCH_FLAGS)
|
||||
.await
|
||||
.map_err(Error::FetchFailed)?;
|
||||
|
||||
let mut msgs = BTreeMap::new();
|
||||
while let Some(fetch) = list.next().await {
|
||||
let msg = fetch.map_err(|err| Error::Other(err.to_string()))?;
|
||||
if let Some(msg_uid) = msg.uid {
|
||||
msgs.insert(msg_uid, msg);
|
||||
}
|
||||
}
|
||||
drop(list);
|
||||
|
||||
// If the mailbox is not empty, results always include
|
||||
// at least one UID, even if last_seen_uid+1 is past
|
||||
// the last UID in the mailbox. It happens because
|
||||
// uid+1:* is interpreted the same way as *:uid+1.
|
||||
// See https://tools.ietf.org/html/rfc3501#page-61 for
|
||||
// standard reference. Therefore, sometimes we receive
|
||||
// already seen messages and have to filter them out.
|
||||
let new_msgs = msgs.split_off(&(uid + 1));
|
||||
|
||||
for current_uid in msgs.keys() {
|
||||
info!(
|
||||
context,
|
||||
"fetch_new_messages: ignoring uid {}, last seen was {}", current_uid, uid
|
||||
);
|
||||
}
|
||||
|
||||
Ok(new_msgs)
|
||||
}
|
||||
|
||||
async fn set_config_last_seen_uid<S: AsRef<str>>(
|
||||
&self,
|
||||
context: &Context,
|
||||
@@ -766,6 +723,20 @@ impl Imap {
|
||||
return (None, 0);
|
||||
}
|
||||
|
||||
if !self.is_connected() {
|
||||
warn!(context, "Not connected");
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
|
||||
if self.session.is_none() {
|
||||
// we could not get a valid imap session, this should be retried
|
||||
self.trigger_reconnect();
|
||||
warn!(context, "Could not get IMAP session");
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
|
||||
let session = self.session.as_mut().unwrap();
|
||||
|
||||
let set = if server_uids.len() == 1 {
|
||||
server_uids[0].to_string()
|
||||
} else {
|
||||
@@ -775,74 +746,67 @@ impl Imap {
|
||||
format!("{}:{}", first_uid, last_uid)
|
||||
};
|
||||
|
||||
if !self.is_connected() {
|
||||
warn!(context, "Not connected");
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
|
||||
let mut msgs = if let Some(ref mut session) = &mut self.session {
|
||||
match session.uid_fetch(&set, BODY_FLAGS).await {
|
||||
Ok(msgs) => msgs,
|
||||
Err(err) => {
|
||||
// TODO maybe differentiate between IO and input/parsing problems
|
||||
// so we don't reconnect if we have a (rare) input/output parsing problem?
|
||||
self.should_reconnect = true;
|
||||
warn!(
|
||||
context,
|
||||
"Error on fetching messages #{} from folder \"{}\"; error={}.",
|
||||
&set,
|
||||
folder.as_ref(),
|
||||
err
|
||||
);
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
let mut msgs = match session.uid_fetch(&set, BODY_FLAGS).await {
|
||||
Ok(msgs) => msgs,
|
||||
Err(err) => {
|
||||
// TODO: maybe differentiate between IO and input/parsing problems
|
||||
// so we don't reconnect if we have a (rare) input/output parsing problem?
|
||||
self.should_reconnect = true;
|
||||
warn!(
|
||||
context,
|
||||
"Error on fetching messages #{} from folder \"{}\"; error={}.",
|
||||
&set,
|
||||
folder.as_ref(),
|
||||
err
|
||||
);
|
||||
return (None, server_uids.len());
|
||||
}
|
||||
} else {
|
||||
// we could not get a valid imap session, this should be retried
|
||||
self.trigger_reconnect();
|
||||
warn!(context, "Could not get IMAP session");
|
||||
return (None, server_uids.len());
|
||||
};
|
||||
|
||||
let folder = folder.as_ref().to_string();
|
||||
|
||||
let mut read_errors = 0;
|
||||
let mut last_uid = None;
|
||||
let mut count = 0;
|
||||
|
||||
let mut jobs = Vec::with_capacity(server_uids.len());
|
||||
|
||||
let mut tasks = Vec::with_capacity(server_uids.len());
|
||||
while let Some(Ok(msg)) = msgs.next().await {
|
||||
let server_uid = msg.uid.unwrap_or_default();
|
||||
|
||||
if !server_uids.contains(&server_uid) {
|
||||
// skip if there are some in between we are not interested in
|
||||
continue;
|
||||
}
|
||||
count += 1;
|
||||
|
||||
// XXX put flags into a set and pass them to dc_receive_imf
|
||||
let is_deleted = msg.flags().any(|flag| flag == Flag::Deleted);
|
||||
let is_seen = msg.flags().any(|flag| flag == Flag::Seen);
|
||||
if is_deleted || msg.body().is_none() {
|
||||
// No need to process these.
|
||||
continue;
|
||||
}
|
||||
|
||||
if !is_deleted && msg.body().is_some() {
|
||||
let folder = folder.as_ref().to_string();
|
||||
let context = context.clone();
|
||||
let task = async_std::task::spawn(async move {
|
||||
let body = msg.body().unwrap_or_default();
|
||||
// XXX put flags into a set and pass them to dc_receive_imf
|
||||
let context = context.clone();
|
||||
let folder = folder.clone();
|
||||
|
||||
if let Err(err) =
|
||||
dc_receive_imf(&context, &body, &folder, server_uid, is_seen).await
|
||||
{
|
||||
let task = async_std::task::spawn(async move {
|
||||
// safe, as we checked above that there is a body.
|
||||
let body = msg.body().unwrap();
|
||||
let is_seen = msg.flags().any(|flag| flag == Flag::Seen);
|
||||
|
||||
match dc_receive_imf(&context, &body, &folder, server_uid, is_seen).await {
|
||||
Ok(_) => Some(server_uid),
|
||||
Err(err) => {
|
||||
warn!(context, "dc_receive_imf error: {}", err);
|
||||
read_errors += 1;
|
||||
None
|
||||
} else {
|
||||
Some(server_uid)
|
||||
}
|
||||
});
|
||||
jobs.push(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
tasks.push(task);
|
||||
}
|
||||
|
||||
for task in futures::future::join_all(jobs).await {
|
||||
for task in futures::future::join_all(tasks).await {
|
||||
match task {
|
||||
Some(uid) => {
|
||||
last_uid = Some(uid);
|
||||
@@ -1503,3 +1467,50 @@ async fn prefetch_should_download(
|
||||
let show = show && !blocked_contact;
|
||||
Ok(show)
|
||||
}
|
||||
|
||||
async fn message_needs_processing(
|
||||
context: &Context,
|
||||
current_uid: u32,
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
msg_id: &str,
|
||||
folder: &str,
|
||||
show_emails: ShowEmails,
|
||||
) -> bool {
|
||||
let skip = match precheck_imf(context, &msg_id, folder, current_uid).await {
|
||||
Ok(skip) => skip,
|
||||
Err(err) => {
|
||||
warn!(context, "precheck_imf error: {}", err);
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if skip {
|
||||
// we know the message-id already or don't want the message otherwise.
|
||||
info!(
|
||||
context,
|
||||
"Skipping message {} from \"{}\" by precheck.", msg_id, folder,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// we do not know the message-id
|
||||
// or the message-id is missing (in this case, we create one in the further process)
|
||||
// or some other error happened
|
||||
let show = match prefetch_should_download(context, &headers, show_emails).await {
|
||||
Ok(show) => show,
|
||||
Err(err) => {
|
||||
warn!(context, "prefetch_should_download error: {}", err);
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if !show {
|
||||
info!(
|
||||
context,
|
||||
"Ignoring new message {} from \"{}\".", msg_id, folder,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
76
src/imex.rs
76
src/imex.rs
@@ -1,5 +1,6 @@
|
||||
//! # Import/export module
|
||||
|
||||
use std::any::Any;
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use async_std::path::{Path, PathBuf};
|
||||
@@ -16,7 +17,7 @@ use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::*;
|
||||
use crate::events::Event;
|
||||
use crate::key::{self, DcKey, Key, SignedSecretKey};
|
||||
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
|
||||
use crate::message::{Message, MsgId};
|
||||
use crate::mimeparser::SystemMessage;
|
||||
use crate::param::*;
|
||||
@@ -181,13 +182,13 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<St
|
||||
passphrase.len() >= 2,
|
||||
"Passphrase must be at least 2 chars long."
|
||||
);
|
||||
let private_key = Key::from(SignedSecretKey::load_self(context).await?);
|
||||
let private_key = SignedSecretKey::load_self(context).await?;
|
||||
let ac_headers = match context.get_config_bool(Config::E2eeEnabled).await {
|
||||
false => None,
|
||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||
};
|
||||
let private_key_asc = private_key.to_asc(ac_headers);
|
||||
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
||||
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes()).await?;
|
||||
|
||||
let replacement = format!(
|
||||
concat!(
|
||||
@@ -274,7 +275,7 @@ pub async fn continue_key_transfer(
|
||||
if let Some(filename) = msg.get_file(context) {
|
||||
let file = dc_open_file_std(context, filename)?;
|
||||
let sc = normalize_setup_code(setup_code);
|
||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
||||
let armored_key = decrypt_setup_file(&sc, file).await?;
|
||||
set_self_key(context, &armored_key, true, true).await?;
|
||||
maybe_add_bcc_self_device_msg(context).await?;
|
||||
|
||||
@@ -291,12 +292,8 @@ async fn set_self_key(
|
||||
prefer_encrypt_required: bool,
|
||||
) -> Result<()> {
|
||||
// try hard to only modify key-state
|
||||
let keys = Key::from_armored_string(armored, KeyType::Private)
|
||||
.and_then(|(k, h)| if k.verify() { Some((k, h)) } else { None })
|
||||
.and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
|
||||
|
||||
ensure!(keys.is_some(), "Not a valid private key");
|
||||
let (private_key, public_key, header) = keys.unwrap();
|
||||
let (private_key, header) = SignedSecretKey::from_asc(armored)?;
|
||||
let public_key = private_key.split_public_key()?;
|
||||
let preferencrypt = header.get("Autocrypt-Prefer-Encrypt");
|
||||
match preferencrypt.map(|s| s.as_str()) {
|
||||
Some(headerval) => {
|
||||
@@ -322,15 +319,10 @@ async fn set_self_key(
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr).await;
|
||||
ensure!(self_addr.is_some(), "Missing self addr");
|
||||
let addr = EmailAddress::new(&self_addr.unwrap_or_default())?;
|
||||
|
||||
let (public, secret) = match (public_key, private_key) {
|
||||
(Key::Public(p), Key::Secret(s)) => (p, s),
|
||||
_ => bail!("wrong keys unpacked"),
|
||||
};
|
||||
let keypair = pgp::KeyPair {
|
||||
addr,
|
||||
public,
|
||||
secret,
|
||||
public: public_key,
|
||||
secret: private_key,
|
||||
};
|
||||
key::store_self_keypair(
|
||||
context,
|
||||
@@ -345,12 +337,11 @@ async fn set_self_key(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||
_context: &Context,
|
||||
async fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||
passphrase: &str,
|
||||
file: T,
|
||||
) -> Result<String> {
|
||||
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
|
||||
let plain_bytes = pgp::symm_decrypt(passphrase, file).await?;
|
||||
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
||||
|
||||
Ok(plain_text)
|
||||
@@ -697,9 +688,9 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
|
||||
let public_key = SignedPublicKey::from_slice(&public_key_blob);
|
||||
let private_key_blob: Vec<u8> = row.get(2)?;
|
||||
let private_key = Key::from_slice(&private_key_blob, KeyType::Private);
|
||||
let private_key = SignedSecretKey::from_slice(&private_key_blob);
|
||||
let is_default: i32 = row.get(3)?;
|
||||
|
||||
Ok((id, public_key, private_key, is_default))
|
||||
@@ -713,7 +704,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
||||
|
||||
for (id, public_key, private_key, is_default) in keys {
|
||||
let id = Some(id).filter(|_| is_default != 0);
|
||||
if let Some(key) = public_key {
|
||||
if let Ok(key) = public_key {
|
||||
if export_key_to_asc_file(context, &dir, id, &key)
|
||||
.await
|
||||
.is_err()
|
||||
@@ -723,7 +714,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
if let Some(key) = private_key {
|
||||
if let Ok(key) = private_key {
|
||||
if export_key_to_asc_file(context, &dir, id, &key)
|
||||
.await
|
||||
.is_err()
|
||||
@@ -742,22 +733,32 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
||||
/*******************************************************************************
|
||||
* Classic key export
|
||||
******************************************************************************/
|
||||
async fn export_key_to_asc_file(
|
||||
async fn export_key_to_asc_file<T>(
|
||||
context: &Context,
|
||||
dir: impl AsRef<Path>,
|
||||
id: Option<i64>,
|
||||
key: &Key,
|
||||
) -> std::io::Result<()> {
|
||||
key: &T,
|
||||
) -> std::io::Result<()>
|
||||
where
|
||||
T: DcKey + Any,
|
||||
{
|
||||
let file_name = {
|
||||
let kind = if key.is_public() { "public" } else { "private" };
|
||||
let any_key = key as &dyn Any;
|
||||
let kind = if any_key.downcast_ref::<SignedPublicKey>().is_some() {
|
||||
"public"
|
||||
} else if any_key.downcast_ref::<SignedPublicKey>().is_some() {
|
||||
"private"
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
let id = id.map_or("default".into(), |i| i.to_string());
|
||||
|
||||
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
|
||||
};
|
||||
info!(context, "Exporting key {}", file_name.display());
|
||||
dc_delete_file(context, &file_name).await;
|
||||
|
||||
let res = key.write_asc_to_file(&file_name, context).await;
|
||||
let content = key.to_asc(None).into_bytes();
|
||||
let res = dc_write_file(context, &file_name, &content).await;
|
||||
if res.is_err() {
|
||||
error!(context, "Cannot write key to {}", file_name.display());
|
||||
} else {
|
||||
@@ -823,7 +824,7 @@ mod tests {
|
||||
#[async_std::test]
|
||||
async fn test_export_key_to_asc_file() {
|
||||
let context = dummy_context().await;
|
||||
let key = Key::from(alice_keypair().public);
|
||||
let key = alice_keypair().public;
|
||||
let blobdir = "$BLOBDIR";
|
||||
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key)
|
||||
.await
|
||||
@@ -852,9 +853,6 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_split_and_decrypt() {
|
||||
let ctx = dummy_context().await;
|
||||
let context = &ctx.ctx;
|
||||
|
||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||
assert_eq!(typ, BlockType::Message);
|
||||
@@ -864,12 +862,10 @@ mod tests {
|
||||
assert!(!base64.is_empty());
|
||||
|
||||
let setup_file = S_EM_SETUPFILE.to_string();
|
||||
let decrypted = decrypt_setup_file(
|
||||
context,
|
||||
S_EM_SETUPCODE,
|
||||
std::io::Cursor::new(setup_file.as_bytes()),
|
||||
)
|
||||
.unwrap();
|
||||
let decrypted =
|
||||
decrypt_setup_file(S_EM_SETUPCODE, std::io::Cursor::new(setup_file.as_bytes()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||
|
||||
|
||||
70
src/job.rs
70
src/job.rs
@@ -1028,13 +1028,15 @@ pub async fn add(context: &Context, job: Job) {
|
||||
| Action::DeleteMsgOnImap
|
||||
| Action::MarkseenMsgOnImap
|
||||
| Action::MoveMsg => {
|
||||
context.interrupt_inbox().await;
|
||||
info!(context, "interrupt: imap");
|
||||
context.interrupt_inbox(false).await;
|
||||
}
|
||||
Action::MaybeSendLocations
|
||||
| Action::MaybeSendLocationsEnded
|
||||
| Action::SendMdn
|
||||
| Action::SendMsgToSmtp => {
|
||||
context.interrupt_smtp().await;
|
||||
info!(context, "interrupt: smtp");
|
||||
context.interrupt_smtp(false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1088,13 +1090,13 @@ LIMIT 1;
|
||||
.sql
|
||||
.query_row_optional(query, params.clone(), |row| {
|
||||
let job = Job {
|
||||
job_id: row.get(0)?,
|
||||
action: row.get(1)?,
|
||||
foreign_id: row.get(2)?,
|
||||
desired_timestamp: row.get(5)?,
|
||||
added_timestamp: row.get(4)?,
|
||||
tries: row.get(6)?,
|
||||
param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
|
||||
job_id: row.get("id")?,
|
||||
action: row.get("action")?,
|
||||
foreign_id: row.get("foreign_id")?,
|
||||
desired_timestamp: row.get("desired_timestamp")?,
|
||||
added_timestamp: row.get("added_timestamp")?,
|
||||
tries: row.get("tries")?,
|
||||
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
||||
pending_error: None,
|
||||
};
|
||||
|
||||
@@ -1104,8 +1106,9 @@ LIMIT 1;
|
||||
|
||||
match job_res {
|
||||
Ok(job) => break job,
|
||||
Err(_) => {
|
||||
Err(err) => {
|
||||
// Remove invalid job from the DB
|
||||
info!(context, "cleaning up job, because of {}", err);
|
||||
|
||||
// TODO: improve by only doing a single query
|
||||
match context
|
||||
@@ -1116,7 +1119,7 @@ LIMIT 1;
|
||||
Ok(id) => {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE id=?", paramsv![id])
|
||||
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id])
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
@@ -1129,21 +1132,26 @@ LIMIT 1;
|
||||
}
|
||||
};
|
||||
|
||||
if thread == Thread::Imap {
|
||||
if let Some(job) = job {
|
||||
if job.action < Action::DeleteMsgOnImap {
|
||||
load_imap_deletion_job(context)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.or(Some(job))
|
||||
} else {
|
||||
Some(job)
|
||||
}
|
||||
} else {
|
||||
load_imap_deletion_job(context).await.unwrap_or_default()
|
||||
match thread {
|
||||
Thread::Unknown => {
|
||||
error!(context, "unknown thread for job");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
job
|
||||
Thread::Imap => {
|
||||
if let Some(job) = job {
|
||||
if job.action < Action::DeleteMsgOnImap {
|
||||
load_imap_deletion_job(context)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.or(Some(job))
|
||||
} else {
|
||||
Some(job)
|
||||
}
|
||||
} else {
|
||||
load_imap_deletion_job(context).await.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
Thread::Smtp => job,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1175,7 +1183,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job() {
|
||||
async fn test_load_next_job_two() {
|
||||
// We want to ensure that loading jobs skips over jobs which
|
||||
// fails to load from the database instead of failing to load
|
||||
// all jobs.
|
||||
@@ -1188,4 +1196,14 @@ mod tests {
|
||||
let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await;
|
||||
assert!(jobs.is_some());
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job_one() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
insert_job(&t.ctx, 1).await;
|
||||
|
||||
let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await;
|
||||
assert!(jobs.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
489
src/key.rs
489
src/key.rs
@@ -1,9 +1,9 @@
|
||||
//! Cryptographic key module
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
|
||||
use async_std::path::Path;
|
||||
use async_trait::async_trait;
|
||||
use num_traits::FromPrimitive;
|
||||
use pgp::composed::Deserializable;
|
||||
@@ -13,7 +13,7 @@ use pgp::types::{KeyTrait, SecretKeyTrait};
|
||||
use crate::config::Config;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::{dc_write_file, time, EmailAddress, InvalidEmailError};
|
||||
use crate::dc_tools::{time, EmailAddress, InvalidEmailError};
|
||||
use crate::sql;
|
||||
|
||||
// Re-export key types
|
||||
@@ -38,6 +38,8 @@ pub enum Error {
|
||||
NoConfiguredAddr,
|
||||
#[error("Configured address is invalid: {}", _0)]
|
||||
InvalidConfiguredAddr(#[from] InvalidEmailError),
|
||||
#[error("no data provided")]
|
||||
Empty,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -48,8 +50,8 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
/// [SignedSecretKey] types and makes working with them a little
|
||||
/// easier in the deltachat world.
|
||||
#[async_trait]
|
||||
pub trait DcKey: Serialize + Deserializable {
|
||||
type KeyType: Serialize + Deserializable;
|
||||
pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
|
||||
type KeyType: Serialize + Deserializable + KeyTrait + Clone;
|
||||
|
||||
/// Create a key from some bytes.
|
||||
fn from_slice(bytes: &[u8]) -> Result<Self::KeyType> {
|
||||
@@ -66,18 +68,45 @@ pub trait DcKey: Serialize + Deserializable {
|
||||
Self::from_slice(&bytes)
|
||||
}
|
||||
|
||||
/// Create a key from an ASCII-armored string.
|
||||
///
|
||||
/// Returns the key and a map of any headers which might have been set in
|
||||
/// the ASCII-armored representation.
|
||||
fn from_asc(data: &str) -> Result<(Self::KeyType, BTreeMap<String, String>)> {
|
||||
let bytes = data.as_bytes();
|
||||
Self::KeyType::from_armor_single(Cursor::new(bytes)).map_err(Error::Pgp)
|
||||
}
|
||||
|
||||
/// Load the users' default key from the database.
|
||||
async fn load_self(context: &Context) -> Result<Self::KeyType>;
|
||||
|
||||
/// Serialise the key to a base64 string.
|
||||
fn to_base64(&self) -> String {
|
||||
/// Serialise the key as bytes.
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
// Not using Serialize::to_bytes() to make clear *why* it is
|
||||
// safe to ignore this error.
|
||||
// Because we write to a Vec<u8> the io::Write impls never
|
||||
// fail and we can hide this error.
|
||||
let mut buf = Vec::new();
|
||||
self.to_writer(&mut buf).unwrap();
|
||||
base64::encode(&buf)
|
||||
buf
|
||||
}
|
||||
|
||||
/// Serialise the key to a base64 string.
|
||||
fn to_base64(&self) -> String {
|
||||
base64::encode(&DcKey::to_bytes(self))
|
||||
}
|
||||
|
||||
/// Serialise the key to ASCII-armored representation.
|
||||
///
|
||||
/// Each header line must be terminated by `\r\n`. Only allows setting one
|
||||
/// header as a simplification since that's the only way it's used so far.
|
||||
// Since .to_armored_string() are actual methods on SignedPublicKey and
|
||||
// SignedSecretKey we can not generically implement this.
|
||||
fn to_asc(&self, header: Option<(&str, &str)>) -> String;
|
||||
|
||||
/// The fingerprint for the key.
|
||||
fn fingerprint(&self) -> Fingerprint {
|
||||
Fingerprint::new(KeyTrait::fingerprint(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +137,22 @@ impl DcKey for SignedPublicKey {
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_asc(&self, header: Option<(&str, &str)>) -> String {
|
||||
// Not using .to_armored_string() to make clear *why* it is
|
||||
// safe to ignore this error.
|
||||
// Because we write to a Vec<u8> the io::Write impls never
|
||||
// fail and we can hide this error.
|
||||
let headers = header.map(|(key, value)| {
|
||||
let mut m = BTreeMap::new();
|
||||
m.insert(key.to_string(), value.to_string());
|
||||
m
|
||||
});
|
||||
let mut buf = Vec::new();
|
||||
self.to_armored_writer(&mut buf, headers.as_ref())
|
||||
.unwrap_or_default();
|
||||
std::string::String::from_utf8(buf).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -137,6 +182,39 @@ impl DcKey for SignedSecretKey {
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_asc(&self, header: Option<(&str, &str)>) -> String {
|
||||
// Not using .to_armored_string() to make clear *why* it is
|
||||
// safe to do these unwraps.
|
||||
// Because we write to a Vec<u8> the io::Write impls never
|
||||
// fail and we can hide this error. The string is always ASCII.
|
||||
let headers = header.map(|(key, value)| {
|
||||
let mut m = BTreeMap::new();
|
||||
m.insert(key.to_string(), value.to_string());
|
||||
m
|
||||
});
|
||||
let mut buf = Vec::new();
|
||||
self.to_armored_writer(&mut buf, headers.as_ref())
|
||||
.unwrap_or_default();
|
||||
std::string::String::from_utf8(buf).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Deltachat extension trait for secret keys.
|
||||
///
|
||||
/// Provides some convenience wrappers only applicable to [SignedSecretKey].
|
||||
pub trait DcSecretKey {
|
||||
/// Create a public key from a private one.
|
||||
fn split_public_key(&self) -> Result<SignedPublicKey>;
|
||||
}
|
||||
|
||||
impl DcSecretKey for SignedSecretKey {
|
||||
fn split_public_key(&self) -> Result<SignedPublicKey> {
|
||||
self.verify()?;
|
||||
let unsigned_pubkey = SecretKeyTrait::public_key(self);
|
||||
let signed_pubkey = unsigned_pubkey.sign(self, || "".into())?;
|
||||
Ok(signed_pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
@@ -172,7 +250,9 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await)
|
||||
.unwrap_or_default();
|
||||
info!(context, "Generating keypair with type {}", keytype);
|
||||
let keypair = crate::pgp::create_keypair(addr, keytype)?;
|
||||
let keypair =
|
||||
async_std::task::spawn_blocking(move || crate::pgp::create_keypair(addr, keytype))
|
||||
.await?;
|
||||
store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
|
||||
info!(
|
||||
context,
|
||||
@@ -185,198 +265,6 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cryptographic key
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Key {
|
||||
Public(SignedPublicKey),
|
||||
Secret(SignedSecretKey),
|
||||
}
|
||||
|
||||
impl From<SignedPublicKey> for Key {
|
||||
fn from(key: SignedPublicKey) -> Self {
|
||||
Key::Public(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignedSecretKey> for Key {
|
||||
fn from(key: SignedSecretKey) -> Self {
|
||||
Key::Secret(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Key> for SignedSecretKey {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Key::Public(_) => Err(()),
|
||||
Key::Secret(key) => Ok(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &'a Key) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Key::Public(_) => Err(()),
|
||||
Key::Secret(key) => Ok(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Key> for SignedPublicKey {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Key::Public(key) => Ok(key),
|
||||
Key::Secret(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &'a Key) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Key::Public(key) => Ok(key),
|
||||
Key::Secret(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn is_public(&self) -> bool {
|
||||
match self {
|
||||
Key::Public(_) => true,
|
||||
Key::Secret(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_secret(&self) -> bool {
|
||||
!self.is_public()
|
||||
}
|
||||
|
||||
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option<Self> {
|
||||
if bytes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let res: std::result::Result<Key, _> = match key_type {
|
||||
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into),
|
||||
KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into),
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(key) => Some(key),
|
||||
Err(err) => {
|
||||
eprintln!("Invalid key bytes: {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_armored_string(
|
||||
data: &str,
|
||||
key_type: KeyType,
|
||||
) -> Option<(Self, BTreeMap<String, String>)> {
|
||||
let bytes = data.as_bytes();
|
||||
let res: std::result::Result<(Key, _), _> = match key_type {
|
||||
KeyType::Public => SignedPublicKey::from_armor_single(Cursor::new(bytes))
|
||||
.map(|(k, h)| (Into::into(k), h)),
|
||||
KeyType::Private => SignedSecretKey::from_armor_single(Cursor::new(bytes))
|
||||
.map(|(k, h)| (Into::into(k), h)),
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(res) => Some(res),
|
||||
Err(err) => {
|
||||
eprintln!("Invalid key bytes: {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Key::Public(k) => k.to_bytes().unwrap_or_default(),
|
||||
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> bool {
|
||||
match self {
|
||||
Key::Public(k) => k.verify().is_ok(),
|
||||
Key::Secret(k) => k.verify().is_ok(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
let buf = self.to_bytes();
|
||||
base64::encode(&buf)
|
||||
}
|
||||
|
||||
pub fn to_armored_string(
|
||||
&self,
|
||||
headers: Option<&BTreeMap<String, String>>,
|
||||
) -> pgp::errors::Result<String> {
|
||||
match self {
|
||||
Key::Public(k) => k.to_armored_string(headers),
|
||||
Key::Secret(k) => k.to_armored_string(headers),
|
||||
}
|
||||
}
|
||||
|
||||
/// Each header line must be terminated by `\r\n`
|
||||
pub fn to_asc(&self, header: Option<(&str, &str)>) -> String {
|
||||
let headers = header.map(|(key, value)| {
|
||||
let mut m = BTreeMap::new();
|
||||
m.insert(key.to_string(), value.to_string());
|
||||
m
|
||||
});
|
||||
|
||||
self.to_armored_string(headers.as_ref())
|
||||
.expect("failed to serialize key")
|
||||
}
|
||||
|
||||
pub async fn write_asc_to_file(
|
||||
&self,
|
||||
file: impl AsRef<Path>,
|
||||
context: &Context,
|
||||
) -> std::io::Result<()> {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
let res = dc_write_file(context, &file, &file_content).await;
|
||||
if res.is_err() {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
match self {
|
||||
Key::Public(k) => hex::encode_upper(k.fingerprint()),
|
||||
Key::Secret(k) => hex::encode_upper(k.fingerprint()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn formatted_fingerprint(&self) -> String {
|
||||
let rawhex = self.fingerprint();
|
||||
dc_format_fingerprint(&rawhex)
|
||||
}
|
||||
|
||||
pub fn split_key(&self) -> Option<Key> {
|
||||
match self {
|
||||
Key::Public(_) => None,
|
||||
Key::Secret(k) => {
|
||||
let pub_key = k.public_key();
|
||||
pub_key.sign(k, || "".into()).map(Key::Public).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Use of a [KeyPair] for encryption or decryption.
|
||||
///
|
||||
/// This is used by [store_self_keypair] to know what kind of key is
|
||||
@@ -426,14 +314,8 @@ pub async fn store_self_keypair(
|
||||
) -> std::result::Result<(), SaveKeyError> {
|
||||
// Everything should really be one transaction, more refactoring
|
||||
// is needed for that.
|
||||
let public_key = keypair
|
||||
.public
|
||||
.to_bytes()
|
||||
.map_err(|err| SaveKeyError::new("failed to serialise public key", err))?;
|
||||
let secret_key = keypair
|
||||
.secret
|
||||
.to_bytes()
|
||||
.map_err(|err| SaveKeyError::new("failed to serialise secret key", err))?;
|
||||
let public_key = DcKey::to_bytes(&keypair.public);
|
||||
let secret_key = DcKey::to_bytes(&keypair.secret);
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
@@ -471,6 +353,62 @@ pub async fn store_self_keypair(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A key fingerprint
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Fingerprint(Vec<u8>);
|
||||
|
||||
impl Fingerprint {
|
||||
pub fn new(v: Vec<u8>) -> Fingerprint {
|
||||
Fingerprint(v)
|
||||
}
|
||||
|
||||
/// Make a hex string from the fingerprint.
|
||||
///
|
||||
/// Use [std::fmt::Display] or [ToString::to_string] to get a
|
||||
/// human-readable formatted string.
|
||||
pub fn hex(&self) -> String {
|
||||
hex::encode_upper(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a human-readable fingerprint.
|
||||
impl fmt::Display for Fingerprint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Split key into chunks of 4 with space and newline at 20 chars
|
||||
for (i, c) in self.hex().chars().enumerate() {
|
||||
if i > 0 && i % 20 == 0 {
|
||||
writeln!(f)?;
|
||||
} else if i > 0 && i % 4 == 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", c)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a human-readable or otherwise formatted fingerprint.
|
||||
impl std::str::FromStr for Fingerprint {
|
||||
type Err = hex::FromHexError;
|
||||
|
||||
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let hex_repr: String = input
|
||||
.chars()
|
||||
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
|
||||
.collect();
|
||||
let v: Vec<u8> = hex::decode(hex_repr)?;
|
||||
Ok(Fingerprint(v))
|
||||
}
|
||||
}
|
||||
|
||||
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
|
||||
pub fn dc_normalize_fingerprint(fp: &str) -> String {
|
||||
fp.to_uppercase()
|
||||
.chars()
|
||||
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Make a fingerprint human-readable, in hex format.
|
||||
pub fn dc_format_fingerprint(fingerprint: &str) -> String {
|
||||
// split key into chunks of 4 with space, and 20 newline
|
||||
@@ -489,19 +427,10 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
|
||||
res
|
||||
}
|
||||
|
||||
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
|
||||
pub fn dc_normalize_fingerprint(fp: &str) -> String {
|
||||
fp.to_uppercase()
|
||||
.chars()
|
||||
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use async_std::sync::Arc;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -517,9 +446,19 @@ mod tests {
|
||||
assert_eq!(fingerprint, "1234567890ABCDABCDEFABCDEF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_fingerprint() {
|
||||
let fingerprint = dc_format_fingerprint("1234567890ABCDABCDEFABCDEF1234567890ABCD");
|
||||
|
||||
assert_eq!(
|
||||
fingerprint,
|
||||
"1234 5678 90AB CDAB CDEF\nABCD EF12 3456 7890 ABCD"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_armored_string() {
|
||||
let (private_key, _) = Key::from_armored_string(
|
||||
let (private_key, _) = SignedSecretKey::from_asc(
|
||||
"-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
xcLYBF0fgz4BCADnRUV52V4xhSsU56ZaAn3+3oG86MZhXy4X8w14WZZDf0VJGeTh
|
||||
@@ -577,58 +516,64 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
7yPJeQ==
|
||||
=KZk/
|
||||
-----END PGP PRIVATE KEY BLOCK-----",
|
||||
KeyType::Private,
|
||||
)
|
||||
.expect("failed to decode"); // NOTE: if you take out the ===GU1/ part, everything passes!
|
||||
let binary = private_key.to_bytes();
|
||||
Key::from_slice(&binary, KeyType::Private).expect("invalid private key");
|
||||
.expect("failed to decode");
|
||||
let binary = DcKey::to_bytes(&private_key);
|
||||
SignedSecretKey::from_slice(&binary).expect("invalid private key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_fingerprint() {
|
||||
let fingerprint = dc_format_fingerprint("1234567890ABCDABCDEFABCDEF1234567890ABCD");
|
||||
fn test_asc_roundtrip() {
|
||||
let key = KEYPAIR.public.clone();
|
||||
let asc = key.to_asc(Some(("spam", "ham")));
|
||||
let (key2, hdrs) = SignedPublicKey::from_asc(&asc).unwrap();
|
||||
assert_eq!(key, key2);
|
||||
assert_eq!(hdrs.len(), 1);
|
||||
assert_eq!(hdrs.get("spam"), Some(&String::from("ham")));
|
||||
|
||||
assert_eq!(
|
||||
fingerprint,
|
||||
"1234 5678 90AB CDAB CDEF\nABCD EF12 3456 7890 ABCD"
|
||||
);
|
||||
let key = KEYPAIR.secret.clone();
|
||||
let asc = key.to_asc(Some(("spam", "ham")));
|
||||
let (key2, hdrs) = SignedSecretKey::from_asc(&asc).unwrap();
|
||||
assert_eq!(key, key2);
|
||||
assert_eq!(hdrs.len(), 1);
|
||||
assert_eq!(hdrs.get("spam"), Some(&String::from("ham")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_slice_roundtrip() {
|
||||
let public_key = Key::from(KEYPAIR.public.clone());
|
||||
let private_key = Key::from(KEYPAIR.secret.clone());
|
||||
let public_key = KEYPAIR.public.clone();
|
||||
let private_key = KEYPAIR.secret.clone();
|
||||
|
||||
let binary = public_key.to_bytes();
|
||||
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
|
||||
let binary = DcKey::to_bytes(&public_key);
|
||||
let public_key2 = SignedPublicKey::from_slice(&binary).expect("invalid public key");
|
||||
assert_eq!(public_key, public_key2);
|
||||
|
||||
let binary = private_key.to_bytes();
|
||||
let private_key2 = Key::from_slice(&binary, KeyType::Private).expect("invalid private key");
|
||||
let binary = DcKey::to_bytes(&private_key);
|
||||
let private_key2 = SignedSecretKey::from_slice(&binary).expect("invalid private key");
|
||||
assert_eq!(private_key, private_key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_slice_bad_data() {
|
||||
let mut bad_data: [u8; 4096] = [0; 4096];
|
||||
|
||||
for i in 0..4096 {
|
||||
bad_data[i] = (i & 0xff) as u8;
|
||||
}
|
||||
|
||||
for j in 0..(4096 / 40) {
|
||||
let bad_key = Key::from_slice(
|
||||
&bad_data[j..j + 4096 / 2 + j],
|
||||
if 0 != j & 1 {
|
||||
KeyType::Public
|
||||
} else {
|
||||
KeyType::Private
|
||||
},
|
||||
);
|
||||
assert!(bad_key.is_none());
|
||||
let slice = &bad_data[j..j + 4096 / 2 + j];
|
||||
assert!(SignedPublicKey::from_slice(slice).is_err());
|
||||
assert!(SignedSecretKey::from_slice(slice).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base64_roundtrip() {
|
||||
let key = KEYPAIR.public.clone();
|
||||
let base64 = key.to_base64();
|
||||
let key2 = SignedPublicKey::from_base64(&base64).unwrap();
|
||||
assert_eq!(key, key2);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_load_self_existing() {
|
||||
let alice = alice_keypair();
|
||||
@@ -686,29 +631,10 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert_eq!(res0.unwrap(), res1.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ascii_roundtrip() {
|
||||
let public_key = Key::from(KEYPAIR.public.clone());
|
||||
let private_key = Key::from(KEYPAIR.secret.clone());
|
||||
|
||||
let s = public_key.to_armored_string(None).unwrap();
|
||||
let (public_key2, _) =
|
||||
Key::from_armored_string(&s, KeyType::Public).expect("invalid public key");
|
||||
assert_eq!(public_key, public_key2);
|
||||
|
||||
let s = private_key.to_armored_string(None).unwrap();
|
||||
println!("{}", &s);
|
||||
let (private_key2, _) =
|
||||
Key::from_armored_string(&s, KeyType::Private).expect("invalid private key");
|
||||
assert_eq!(private_key, private_key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_key() {
|
||||
let private_key = Key::from(KEYPAIR.secret.clone());
|
||||
let public_wrapped = private_key.split_key().unwrap();
|
||||
let public = SignedPublicKey::try_from(public_wrapped).unwrap();
|
||||
assert_eq!(public.primary_key, KEYPAIR.public.primary_key);
|
||||
let pubkey = KEYPAIR.secret.split_public_key().unwrap();
|
||||
assert_eq!(pubkey.primary_key, KEYPAIR.public.primary_key);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
@@ -756,4 +682,35 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
// )
|
||||
// .unwrap();
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_fingerprint_from_str() {
|
||||
let res = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]);
|
||||
|
||||
let fp: Fingerprint = "0102040810204080FF".parse().unwrap();
|
||||
assert_eq!(fp, res);
|
||||
|
||||
let fp: Fingerprint = "zzzz 0102 0408\n1020 4080 FF zzz".parse().unwrap();
|
||||
assert_eq!(fp, res);
|
||||
|
||||
let err = "1".parse::<Fingerprint>().err().unwrap();
|
||||
assert_eq!(err, hex::FromHexError::OddLength);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fingerprint_hex() {
|
||||
let fp = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]);
|
||||
assert_eq!(fp.hex(), "0102040810204080FF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fingerprint_to_string() {
|
||||
let fp = Fingerprint::new(vec![
|
||||
1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255,
|
||||
]);
|
||||
assert_eq!(
|
||||
fp.to_string(),
|
||||
"0102 0408 1020 4080 FF01\n0204 0810 2040 80FF"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
106
src/keyring.rs
106
src/keyring.rs
@@ -1,46 +1,92 @@
|
||||
use std::borrow::Cow;
|
||||
//! Keyring to perform rpgp operations with.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::constants::KeyType;
|
||||
use crate::context::Context;
|
||||
use crate::key::Key;
|
||||
use crate::sql::Sql;
|
||||
use crate::key::{self, DcKey};
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Keyring<'a> {
|
||||
keys: Vec<Cow<'a, Key>>,
|
||||
/// An in-memory keyring.
|
||||
///
|
||||
/// Instances are usually constructed just for the rpgp operation and
|
||||
/// short-lived.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Keyring<T>
|
||||
where
|
||||
T: DcKey,
|
||||
{
|
||||
keys: Vec<T>,
|
||||
}
|
||||
|
||||
impl<'a> Keyring<'a> {
|
||||
pub fn add_owned(&mut self, key: Key) {
|
||||
self.add(Cow::Owned(key))
|
||||
impl<T> Keyring<T>
|
||||
where
|
||||
T: DcKey<KeyType = T>,
|
||||
{
|
||||
/// New empty keyring.
|
||||
pub fn new() -> Keyring<T> {
|
||||
Keyring { keys: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add_ref(&mut self, key: &'a Key) {
|
||||
self.add(Cow::Borrowed(key))
|
||||
/// Create a new keyring with the the user's secret key loaded.
|
||||
pub async fn new_self(context: &Context) -> Result<Keyring<T>, key::Error> {
|
||||
let mut keyring: Keyring<T> = Keyring::new();
|
||||
keyring.load_self(context).await?;
|
||||
Ok(keyring)
|
||||
}
|
||||
|
||||
fn add(&mut self, key: Cow<'a, Key>) {
|
||||
/// Load the user's key into the keyring.
|
||||
pub async fn load_self(&mut self, context: &Context) -> Result<(), key::Error> {
|
||||
self.add(T::load_self(context).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a key to the keyring.
|
||||
pub fn add(&mut self, key: T) {
|
||||
self.keys.push(key);
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> &[Cow<'a, Key>] {
|
||||
&self.keys
|
||||
pub fn len(&self) -> usize {
|
||||
self.keys.len()
|
||||
}
|
||||
|
||||
pub async fn load_self_private_for_decrypting(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
) -> bool {
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
||||
paramsv![self_addr.as_ref().to_string()],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
|
||||
.map(|key| self.add_owned(key))
|
||||
.is_some()
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.keys.is_empty()
|
||||
}
|
||||
|
||||
/// A vector with reference to all the keys in the keyring.
|
||||
pub fn keys(&self) -> &[T] {
|
||||
&self.keys
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::key::{SignedPublicKey, SignedSecretKey};
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_keyring_add_keys() {
|
||||
let alice = alice_keypair();
|
||||
let mut pub_ring: Keyring<SignedPublicKey> = Keyring::new();
|
||||
pub_ring.add(alice.public.clone());
|
||||
assert_eq!(pub_ring.keys(), [alice.public]);
|
||||
|
||||
let mut sec_ring: Keyring<SignedSecretKey> = Keyring::new();
|
||||
sec_ring.add(alice.secret.clone());
|
||||
assert_eq!(sec_ring.keys(), [alice.secret]);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_keyring_load_self() {
|
||||
// new_self() implies load_self()
|
||||
let t = dummy_context().await;
|
||||
configure_alice_keypair(&t.ctx).await;
|
||||
let alice = alice_keypair();
|
||||
|
||||
let pub_ring: Keyring<SignedPublicKey> = Keyring::new_self(&t.ctx).await.unwrap();
|
||||
assert_eq!(pub_ring.keys(), [alice.public]);
|
||||
|
||||
let sec_ring: Keyring<SignedSecretKey> = Keyring::new_self(&t.ctx).await.unwrap();
|
||||
assert_eq!(sec_ring.keys(), [alice.secret]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let force_plaintext = self.should_force_plaintext();
|
||||
let subject_str = self.subject_str().await;
|
||||
let e2ee_guaranteed = self.is_e2ee_guaranteed();
|
||||
let mut encrypt_helper = EncryptHelper::new(self.context).await?;
|
||||
let encrypt_helper = EncryptHelper::new(self.context).await?;
|
||||
|
||||
let subject = encode_words(&subject_str);
|
||||
|
||||
@@ -560,7 +560,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
let encrypted = encrypt_helper
|
||||
.encrypt(self.context, min_verified, message, &peerstates)
|
||||
.encrypt(self.context, min_verified, message, peerstates)
|
||||
.await?;
|
||||
|
||||
outer_message = outer_message
|
||||
|
||||
@@ -6,9 +6,8 @@ use std::fmt;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::aheader::*;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::key::{Key, SignedPublicKey};
|
||||
use crate::key::{DcKey, SignedPublicKey};
|
||||
use crate::sql::Sql;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -32,12 +31,12 @@ pub struct Peerstate<'a> {
|
||||
pub last_seen: i64,
|
||||
pub last_seen_autocrypt: i64,
|
||||
pub prefer_encrypt: EncryptPreference,
|
||||
pub public_key: Option<Key>,
|
||||
pub public_key: Option<SignedPublicKey>,
|
||||
pub public_key_fingerprint: Option<String>,
|
||||
pub gossip_key: Option<Key>,
|
||||
pub gossip_key: Option<SignedPublicKey>,
|
||||
pub gossip_timestamp: i64,
|
||||
pub gossip_key_fingerprint: Option<String>,
|
||||
pub verified_key: Option<Key>,
|
||||
pub verified_key: Option<SignedPublicKey>,
|
||||
pub verified_key_fingerprint: Option<String>,
|
||||
pub to_save: Option<ToSave>,
|
||||
pub degrade_event: Option<DegradeEvent>,
|
||||
@@ -127,7 +126,7 @@ impl<'a> Peerstate<'a> {
|
||||
res.last_seen_autocrypt = message_time;
|
||||
res.to_save = Some(ToSave::All);
|
||||
res.prefer_encrypt = header.prefer_encrypt;
|
||||
res.public_key = Some(Key::from(header.public_key.clone()));
|
||||
res.public_key = Some(header.public_key.clone());
|
||||
res.recalc_fingerprint();
|
||||
|
||||
res
|
||||
@@ -138,7 +137,7 @@ impl<'a> Peerstate<'a> {
|
||||
|
||||
res.gossip_timestamp = message_time;
|
||||
res.to_save = Some(ToSave::All);
|
||||
res.gossip_key = Some(Key::from(gossip_header.public_key.clone()));
|
||||
res.gossip_key = Some(gossip_header.public_key.clone());
|
||||
res.recalc_fingerprint();
|
||||
|
||||
res
|
||||
@@ -220,15 +219,15 @@ impl<'a> Peerstate<'a> {
|
||||
res.public_key = row
|
||||
.get(4)
|
||||
.ok()
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
||||
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
|
||||
res.gossip_key = row
|
||||
.get(6)
|
||||
.ok()
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
||||
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
|
||||
res.verified_key = row
|
||||
.get(9)
|
||||
.ok()
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
||||
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
|
||||
|
||||
Ok(res)
|
||||
})
|
||||
@@ -239,7 +238,7 @@ impl<'a> Peerstate<'a> {
|
||||
pub fn recalc_fingerprint(&mut self) {
|
||||
if let Some(ref public_key) = self.public_key {
|
||||
let old_public_fingerprint = self.public_key_fingerprint.take();
|
||||
self.public_key_fingerprint = Some(public_key.fingerprint());
|
||||
self.public_key_fingerprint = Some(public_key.fingerprint().hex());
|
||||
|
||||
if old_public_fingerprint.is_none()
|
||||
|| self.public_key_fingerprint.is_none()
|
||||
@@ -254,7 +253,7 @@ impl<'a> Peerstate<'a> {
|
||||
|
||||
if let Some(ref gossip_key) = self.gossip_key {
|
||||
let old_gossip_fingerprint = self.gossip_key_fingerprint.take();
|
||||
self.gossip_key_fingerprint = Some(gossip_key.fingerprint());
|
||||
self.gossip_key_fingerprint = Some(gossip_key.fingerprint().hex());
|
||||
|
||||
if old_gossip_fingerprint.is_none()
|
||||
|| self.gossip_key_fingerprint.is_none()
|
||||
@@ -300,8 +299,8 @@ impl<'a> Peerstate<'a> {
|
||||
self.to_save = Some(ToSave::All)
|
||||
}
|
||||
|
||||
if self.public_key.as_ref() != Some(&Key::from(header.public_key.clone())) {
|
||||
self.public_key = Some(Key::from(header.public_key.clone()));
|
||||
if self.public_key.as_ref() != Some(&header.public_key) {
|
||||
self.public_key = Some(header.public_key.clone());
|
||||
self.recalc_fingerprint();
|
||||
self.to_save = Some(ToSave::All);
|
||||
}
|
||||
@@ -316,9 +315,8 @@ impl<'a> Peerstate<'a> {
|
||||
if message_time > self.gossip_timestamp {
|
||||
self.gossip_timestamp = message_time;
|
||||
self.to_save = Some(ToSave::Timestamps);
|
||||
let hdr_key = Key::from(gossip_header.public_key.clone());
|
||||
if self.gossip_key.as_ref() != Some(&hdr_key) {
|
||||
self.gossip_key = Some(hdr_key);
|
||||
if self.gossip_key.as_ref() != Some(&gossip_header.public_key) {
|
||||
self.gossip_key = Some(gossip_header.public_key.clone());
|
||||
self.recalc_fingerprint();
|
||||
self.to_save = Some(ToSave::All)
|
||||
}
|
||||
@@ -367,7 +365,16 @@ impl<'a> Peerstate<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
|
||||
pub fn take_key(mut self, min_verified: PeerstateVerifiedStatus) -> Option<SignedPublicKey> {
|
||||
match min_verified {
|
||||
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.take(),
|
||||
PeerstateVerifiedStatus::Unverified => {
|
||||
self.public_key.take().or_else(|| self.gossip_key.take())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&SignedPublicKey> {
|
||||
match min_verified {
|
||||
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.as_ref(),
|
||||
PeerstateVerifiedStatus::Unverified => self
|
||||
@@ -486,7 +493,7 @@ mod tests {
|
||||
let ctx = crate::test_utils::dummy_context().await;
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from(alice_keypair().public);
|
||||
let pub_key = alice_keypair().public;
|
||||
|
||||
let mut peerstate = Peerstate {
|
||||
context: &ctx.ctx,
|
||||
@@ -495,12 +502,12 @@ mod tests {
|
||||
last_seen_autocrypt: 11,
|
||||
prefer_encrypt: EncryptPreference::Mutual,
|
||||
public_key: Some(pub_key.clone()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint().hex()),
|
||||
gossip_key: Some(pub_key.clone()),
|
||||
gossip_timestamp: 12,
|
||||
gossip_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
gossip_key_fingerprint: Some(pub_key.fingerprint().hex()),
|
||||
verified_key: Some(pub_key.clone()),
|
||||
verified_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
verified_key_fingerprint: Some(pub_key.fingerprint().hex()),
|
||||
to_save: Some(ToSave::All),
|
||||
degrade_event: None,
|
||||
};
|
||||
@@ -518,7 +525,7 @@ mod tests {
|
||||
peerstate.to_save = None;
|
||||
assert_eq!(peerstate, peerstate_new);
|
||||
let peerstate_new2 =
|
||||
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
|
||||
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint().hex())
|
||||
.await
|
||||
.expect("failed to load peerstate from db");
|
||||
assert_eq!(peerstate, peerstate_new2);
|
||||
@@ -528,7 +535,7 @@ mod tests {
|
||||
async fn test_peerstate_double_create() {
|
||||
let ctx = crate::test_utils::dummy_context().await;
|
||||
let addr = "hello@mail.com";
|
||||
let pub_key = crate::key::Key::from(alice_keypair().public);
|
||||
let pub_key = alice_keypair().public;
|
||||
|
||||
let peerstate = Peerstate {
|
||||
context: &ctx.ctx,
|
||||
@@ -537,7 +544,7 @@ mod tests {
|
||||
last_seen_autocrypt: 11,
|
||||
prefer_encrypt: EncryptPreference::Mutual,
|
||||
public_key: Some(pub_key.clone()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint().hex()),
|
||||
gossip_key: None,
|
||||
gossip_timestamp: 12,
|
||||
gossip_key_fingerprint: None,
|
||||
@@ -562,7 +569,7 @@ mod tests {
|
||||
let ctx = crate::test_utils::dummy_context().await;
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from(alice_keypair().public);
|
||||
let pub_key = alice_keypair().public;
|
||||
|
||||
let mut peerstate = Peerstate {
|
||||
context: &ctx.ctx,
|
||||
@@ -571,7 +578,7 @@ mod tests {
|
||||
last_seen_autocrypt: 11,
|
||||
prefer_encrypt: EncryptPreference::Mutual,
|
||||
public_key: Some(pub_key.clone()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint()),
|
||||
public_key_fingerprint: Some(pub_key.fingerprint().hex()),
|
||||
gossip_key: None,
|
||||
gossip_timestamp: 12,
|
||||
gossip_key_fingerprint: None,
|
||||
|
||||
307
src/pgp.rs
307
src/pgp.rs
@@ -1,7 +1,6 @@
|
||||
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp)
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use std::io;
|
||||
use std::io::Cursor;
|
||||
|
||||
@@ -19,8 +18,8 @@ use rand::{thread_rng, CryptoRng, Rng};
|
||||
use crate::constants::KeyGenType;
|
||||
use crate::dc_tools::EmailAddress;
|
||||
use crate::error::{bail, ensure, format_err, Result};
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
use crate::key::DcKey;
|
||||
use crate::keyring::Keyring;
|
||||
|
||||
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
|
||||
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
|
||||
@@ -238,124 +237,129 @@ fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<SignedPublicKeyOrSu
|
||||
|
||||
/// Encrypts `plain` text using `public_keys_for_encryption`
|
||||
/// and signs it using `private_key_for_signing`.
|
||||
pub fn pk_encrypt(
|
||||
pub async fn pk_encrypt(
|
||||
plain: &[u8],
|
||||
public_keys_for_encryption: &Keyring,
|
||||
private_key_for_signing: Option<&Key>,
|
||||
public_keys_for_encryption: Keyring<SignedPublicKey>,
|
||||
private_key_for_signing: Option<SignedSecretKey>,
|
||||
) -> Result<String> {
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
|
||||
.keys()
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
key.as_ref()
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(select_pk_for_encryption)
|
||||
})
|
||||
.collect();
|
||||
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
async_std::task::spawn_blocking(move || {
|
||||
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
|
||||
.keys()
|
||||
.iter()
|
||||
.filter_map(|key| select_pk_for_encryption(key))
|
||||
.collect();
|
||||
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
|
||||
|
||||
// TODO: measure time
|
||||
let encrypted_msg = if let Some(private_key) = private_key_for_signing {
|
||||
let skey: &SignedSecretKey = private_key
|
||||
.try_into()
|
||||
.map_err(|_| format_err!("Invalid private key"))?;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
lit_msg
|
||||
.sign(skey, || "".into(), Default::default())
|
||||
.and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB))
|
||||
.and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs))
|
||||
} else {
|
||||
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)
|
||||
};
|
||||
// TODO: measure time
|
||||
let encrypted_msg = if let Some(ref skey) = private_key_for_signing {
|
||||
lit_msg
|
||||
.sign(skey, || "".into(), Default::default())
|
||||
.and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB))
|
||||
.and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs))
|
||||
} else {
|
||||
lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)
|
||||
};
|
||||
|
||||
let msg = encrypted_msg?;
|
||||
let encoded_msg = msg.to_armored_string(None)?;
|
||||
let msg = encrypted_msg?;
|
||||
let encoded_msg = msg.to_armored_string(None)?;
|
||||
|
||||
Ok(encoded_msg)
|
||||
Ok(encoded_msg)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::implicit_hasher)]
|
||||
pub fn pk_decrypt(
|
||||
ctext: &[u8],
|
||||
private_keys_for_decryption: &Keyring,
|
||||
public_keys_for_validation: &Keyring,
|
||||
pub async fn pk_decrypt(
|
||||
ctext: Vec<u8>,
|
||||
private_keys_for_decryption: Keyring<SignedSecretKey>,
|
||||
public_keys_for_validation: Keyring<SignedPublicKey>,
|
||||
ret_signature_fingerprints: Option<&mut HashSet<String>>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let (msg, _) = Message::from_armor_single(Cursor::new(ctext))?;
|
||||
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
|
||||
.keys()
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
let k: &Key = &key;
|
||||
k.try_into().ok()
|
||||
})
|
||||
.collect();
|
||||
let msgs = async_std::task::spawn_blocking(move || {
|
||||
let cursor = Cursor::new(ctext);
|
||||
let (msg, _) = Message::from_armor_single(cursor)?;
|
||||
|
||||
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.keys().iter().collect();
|
||||
|
||||
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
|
||||
decryptor.collect::<pgp::errors::Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
|
||||
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
|
||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
||||
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||
|
||||
let dec_msg = &msgs[0];
|
||||
let content = match msgs[0].get_content()? {
|
||||
Some(content) => content,
|
||||
None => bail!("Decrypted message is empty"),
|
||||
};
|
||||
|
||||
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
|
||||
if !public_keys_for_validation.keys().is_empty() {
|
||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation
|
||||
.keys()
|
||||
.iter()
|
||||
.filter_map(|key| {
|
||||
let k: &Key = &key;
|
||||
k.try_into().ok()
|
||||
})
|
||||
.collect();
|
||||
if !public_keys_for_validation.is_empty() {
|
||||
let fingerprints = async_std::task::spawn_blocking(move || {
|
||||
let dec_msg = &msgs[0];
|
||||
|
||||
for pkey in &pkeys {
|
||||
if dec_msg.verify(&pkey.primary_key).is_ok() {
|
||||
let fp = hex::encode_upper(pkey.fingerprint());
|
||||
ret_signature_fingerprints.insert(fp);
|
||||
let pkeys = public_keys_for_validation.keys();
|
||||
|
||||
let mut fingerprints = Vec::new();
|
||||
for pkey in pkeys {
|
||||
if dec_msg.verify(&pkey.primary_key).is_ok() {
|
||||
let fp = DcKey::fingerprint(pkey).hex();
|
||||
fingerprints.push(fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
fingerprints
|
||||
})
|
||||
.await;
|
||||
|
||||
ret_signature_fingerprints.extend(fingerprints);
|
||||
}
|
||||
}
|
||||
|
||||
match dec_msg.get_content()? {
|
||||
Some(content) => Ok(content),
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
/// Symmetric encryption.
|
||||
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
||||
let mut rng = thread_rng();
|
||||
pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
||||
let lit_msg = Message::new_literal_bytes("", plain);
|
||||
let passphrase = passphrase.to_string();
|
||||
|
||||
let s2k = StringToKey::new_default(&mut rng);
|
||||
let msg =
|
||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into())?;
|
||||
async_std::task::spawn_blocking(move || {
|
||||
let mut rng = thread_rng();
|
||||
let s2k = StringToKey::new_default(&mut rng);
|
||||
let msg =
|
||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase)?;
|
||||
|
||||
let encoded_msg = msg.to_armored_string(None)?;
|
||||
let encoded_msg = msg.to_armored_string(None)?;
|
||||
|
||||
Ok(encoded_msg)
|
||||
Ok(encoded_msg)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Symmetric decryption.
|
||||
pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||
pub async fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||
passphrase: &str,
|
||||
ctext: T,
|
||||
) -> Result<Vec<u8>> {
|
||||
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
|
||||
|
||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
||||
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||
let passphrase = passphrase.to_string();
|
||||
async_std::task::spawn_blocking(move || {
|
||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase)?;
|
||||
|
||||
match msgs[0].get_content()? {
|
||||
Some(content) => Ok(content),
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
||||
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||
|
||||
match msgs[0].get_content()? {
|
||||
Some(content) => Ok(content),
|
||||
None => bail!("Decrypted message is empty"),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -408,10 +412,10 @@ mod tests {
|
||||
|
||||
/// [Key] objects to use in tests.
|
||||
struct TestKeys {
|
||||
alice_secret: Key,
|
||||
alice_public: Key,
|
||||
bob_secret: Key,
|
||||
bob_public: Key,
|
||||
alice_secret: SignedSecretKey,
|
||||
alice_public: SignedPublicKey,
|
||||
bob_secret: SignedSecretKey,
|
||||
bob_public: SignedPublicKey,
|
||||
}
|
||||
|
||||
impl TestKeys {
|
||||
@@ -419,10 +423,10 @@ mod tests {
|
||||
let alice = alice_keypair();
|
||||
let bob = bob_keypair();
|
||||
TestKeys {
|
||||
alice_secret: Key::from(alice.secret.clone()),
|
||||
alice_public: Key::from(alice.public.clone()),
|
||||
bob_secret: Key::from(bob.secret.clone()),
|
||||
bob_public: Key::from(bob.public.clone()),
|
||||
alice_secret: alice.secret.clone(),
|
||||
alice_public: alice.public.clone(),
|
||||
bob_secret: bob.secret.clone(),
|
||||
bob_public: bob.public.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,18 +440,18 @@ mod tests {
|
||||
|
||||
/// A cyphertext encrypted to Alice & Bob, signed by Alice.
|
||||
static ref CTEXT_SIGNED: String = {
|
||||
let mut keyring = Keyring::default();
|
||||
keyring.add_owned(KEYS.alice_public.clone());
|
||||
keyring.add_ref(&KEYS.bob_public);
|
||||
pk_encrypt(CLEARTEXT, &keyring, Some(&KEYS.alice_secret)).unwrap()
|
||||
let mut keyring = Keyring::new();
|
||||
keyring.add(KEYS.alice_public.clone());
|
||||
keyring.add(KEYS.bob_public.clone());
|
||||
smol::block_on(pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone()))).unwrap()
|
||||
};
|
||||
|
||||
/// A cyphertext encrypted to Alice & Bob, not signed.
|
||||
static ref CTEXT_UNSIGNED: String = {
|
||||
let mut keyring = Keyring::default();
|
||||
keyring.add_owned(KEYS.alice_public.clone());
|
||||
keyring.add_ref(&KEYS.bob_public);
|
||||
pk_encrypt(CLEARTEXT, &keyring, None).unwrap()
|
||||
let mut keyring = Keyring::new();
|
||||
keyring.add(KEYS.alice_public.clone());
|
||||
keyring.add(KEYS.bob_public.clone());
|
||||
smol::block_on(pk_encrypt(CLEARTEXT, keyring, None)).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -463,110 +467,115 @@ mod tests {
|
||||
assert!(CTEXT_UNSIGNED.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_singed() {
|
||||
#[async_std::test]
|
||||
async fn test_decrypt_singed() {
|
||||
// Check decrypting as Alice
|
||||
let mut decrypt_keyring = Keyring::default();
|
||||
decrypt_keyring.add_ref(&KEYS.alice_secret);
|
||||
let mut sig_check_keyring = Keyring::default();
|
||||
sig_check_keyring.add_ref(&KEYS.alice_public);
|
||||
let mut decrypt_keyring: Keyring<SignedSecretKey> = Keyring::new();
|
||||
decrypt_keyring.add(KEYS.alice_secret.clone());
|
||||
let mut sig_check_keyring: Keyring<SignedPublicKey> = Keyring::new();
|
||||
sig_check_keyring.add(KEYS.alice_public.clone());
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
let plain = pk_decrypt(
|
||||
CTEXT_SIGNED.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||
decrypt_keyring,
|
||||
sig_check_keyring,
|
||||
Some(&mut valid_signatures),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| println!("{:?}", err))
|
||||
.unwrap();
|
||||
assert_eq!(plain, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 1);
|
||||
|
||||
// Check decrypting as Bob
|
||||
let mut decrypt_keyring = Keyring::default();
|
||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
||||
let mut sig_check_keyring = Keyring::default();
|
||||
sig_check_keyring.add_ref(&KEYS.alice_public);
|
||||
let mut decrypt_keyring = Keyring::new();
|
||||
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||
let mut sig_check_keyring = Keyring::new();
|
||||
sig_check_keyring.add(KEYS.alice_public.clone());
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
let plain = pk_decrypt(
|
||||
CTEXT_SIGNED.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||
decrypt_keyring,
|
||||
sig_check_keyring,
|
||||
Some(&mut valid_signatures),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| println!("{:?}", err))
|
||||
.unwrap();
|
||||
assert_eq!(plain, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_no_sig_check() {
|
||||
let mut keyring = Keyring::default();
|
||||
keyring.add_ref(&KEYS.alice_secret);
|
||||
let empty_keyring = Keyring::default();
|
||||
#[async_std::test]
|
||||
async fn test_decrypt_no_sig_check() {
|
||||
let mut keyring = Keyring::new();
|
||||
keyring.add(KEYS.alice_secret.clone());
|
||||
let empty_keyring = Keyring::new();
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
let plain = pk_decrypt(
|
||||
CTEXT_SIGNED.as_bytes(),
|
||||
&keyring,
|
||||
&empty_keyring,
|
||||
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||
keyring,
|
||||
empty_keyring,
|
||||
Some(&mut valid_signatures),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(plain, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_signed_no_key() {
|
||||
#[async_std::test]
|
||||
async fn test_decrypt_signed_no_key() {
|
||||
// The validation does not have the public key of the signer.
|
||||
let mut decrypt_keyring = Keyring::default();
|
||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
||||
let mut sig_check_keyring = Keyring::default();
|
||||
sig_check_keyring.add_ref(&KEYS.bob_public);
|
||||
let mut decrypt_keyring = Keyring::new();
|
||||
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||
let mut sig_check_keyring = Keyring::new();
|
||||
sig_check_keyring.add(KEYS.bob_public.clone());
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
let plain = pk_decrypt(
|
||||
CTEXT_SIGNED.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||
decrypt_keyring,
|
||||
sig_check_keyring,
|
||||
Some(&mut valid_signatures),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(plain, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_unsigned() {
|
||||
let mut decrypt_keyring = Keyring::default();
|
||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
||||
let sig_check_keyring = Keyring::default();
|
||||
decrypt_keyring.add_ref(&KEYS.alice_public);
|
||||
#[async_std::test]
|
||||
async fn test_decrypt_unsigned() {
|
||||
let mut decrypt_keyring = Keyring::new();
|
||||
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||
let sig_check_keyring = Keyring::new();
|
||||
let mut valid_signatures: HashSet<String> = Default::default();
|
||||
let plain = pk_decrypt(
|
||||
CTEXT_UNSIGNED.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
CTEXT_UNSIGNED.as_bytes().to_vec(),
|
||||
decrypt_keyring,
|
||||
sig_check_keyring,
|
||||
Some(&mut valid_signatures),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(plain, CLEARTEXT);
|
||||
assert_eq!(valid_signatures.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decrypt_signed_no_sigret() {
|
||||
#[async_std::test]
|
||||
async fn test_decrypt_signed_no_sigret() {
|
||||
// Check decrypting signed cyphertext without providing the HashSet for signatures.
|
||||
let mut decrypt_keyring = Keyring::default();
|
||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
||||
let mut sig_check_keyring = Keyring::default();
|
||||
sig_check_keyring.add_ref(&KEYS.alice_public);
|
||||
let mut decrypt_keyring = Keyring::new();
|
||||
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||
let mut sig_check_keyring = Keyring::new();
|
||||
sig_check_keyring.add(KEYS.alice_public.clone());
|
||||
let plain = pk_decrypt(
|
||||
CTEXT_SIGNED.as_bytes(),
|
||||
&decrypt_keyring,
|
||||
&sig_check_keyring,
|
||||
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||
decrypt_keyring,
|
||||
sig_check_keyring,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(plain, CLEARTEXT);
|
||||
}
|
||||
|
||||
159
src/scheduler.rs
159
src/scheduler.rs
@@ -2,8 +2,6 @@ use async_std::prelude::*;
|
||||
use async_std::sync::{channel, Receiver, Sender};
|
||||
use async_std::task;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::imap::Imap;
|
||||
use crate::job::{self, Thread};
|
||||
@@ -25,30 +23,45 @@ pub(crate) enum Scheduler {
|
||||
sentbox_handle: Option<task::JoinHandle<()>>,
|
||||
smtp: SmtpConnectionState,
|
||||
smtp_handle: Option<task::JoinHandle<()>>,
|
||||
probe_network: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Indicate that the network likely has come back.
|
||||
pub async fn maybe_network(&self) {
|
||||
self.scheduler.write().await.maybe_network().await;
|
||||
self.scheduler.read().await.maybe_network().await;
|
||||
}
|
||||
|
||||
pub(crate) async fn interrupt_inbox(&self) {
|
||||
self.scheduler.read().await.interrupt_inbox().await;
|
||||
pub(crate) async fn interrupt_inbox(&self, probe_network: bool) {
|
||||
self.scheduler
|
||||
.read()
|
||||
.await
|
||||
.interrupt_inbox(probe_network)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn interrupt_sentbox(&self) {
|
||||
self.scheduler.read().await.interrupt_sentbox().await;
|
||||
pub(crate) async fn interrupt_sentbox(&self, probe_network: bool) {
|
||||
self.scheduler
|
||||
.read()
|
||||
.await
|
||||
.interrupt_sentbox(probe_network)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn interrupt_mvbox(&self) {
|
||||
self.scheduler.read().await.interrupt_mvbox().await;
|
||||
pub(crate) async fn interrupt_mvbox(&self, probe_network: bool) {
|
||||
self.scheduler
|
||||
.read()
|
||||
.await
|
||||
.interrupt_mvbox(probe_network)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn interrupt_smtp(&self) {
|
||||
self.scheduler.read().await.interrupt_smtp().await;
|
||||
pub(crate) async fn interrupt_smtp(&self, probe_network: bool) {
|
||||
self.scheduler
|
||||
.read()
|
||||
.await
|
||||
.interrupt_smtp(probe_network)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,26 +86,23 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
|
||||
|
||||
// track number of continously executed jobs
|
||||
let mut jobs_loaded = 0;
|
||||
let mut probe_network = false;
|
||||
loop {
|
||||
let probe_network = ctx.scheduler.read().await.get_probe_network();
|
||||
match job::load_next(&ctx, Thread::Imap, probe_network)
|
||||
.timeout(Duration::from_millis(200))
|
||||
.await
|
||||
{
|
||||
Ok(Some(job)) if jobs_loaded <= 20 => {
|
||||
match job::load_next(&ctx, Thread::Imap, probe_network).await {
|
||||
Some(job) if jobs_loaded <= 20 => {
|
||||
jobs_loaded += 1;
|
||||
job::perform_job(&ctx, job::Connection::Inbox(&mut connection), job).await;
|
||||
ctx.scheduler.write().await.set_probe_network(false);
|
||||
probe_network = false;
|
||||
}
|
||||
Ok(Some(job)) => {
|
||||
Some(job) => {
|
||||
// Let the fetch run, but return back to the job afterwards.
|
||||
info!(ctx, "postponing imap-job {} to run fetch...", job);
|
||||
jobs_loaded = 0;
|
||||
fetch(&ctx, &mut connection).await;
|
||||
}
|
||||
Ok(None) | Err(async_std::future::TimeoutError { .. }) => {
|
||||
None => {
|
||||
jobs_loaded = 0;
|
||||
fetch_idle(&ctx, &mut connection).await;
|
||||
probe_network = fetch_idle(&ctx, &mut connection).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +136,7 @@ async fn fetch(ctx: &Context, connection: &mut Imap) {
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_idle(ctx: &Context, connection: &mut Imap) {
|
||||
async fn fetch_idle(ctx: &Context, connection: &mut Imap) -> bool {
|
||||
match get_watch_folder(&ctx, "configured_inbox_folder").await {
|
||||
Some(watch_folder) => {
|
||||
// fetch
|
||||
@@ -144,14 +154,15 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap) {
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
error!(ctx, "{}", err);
|
||||
});
|
||||
false
|
||||
})
|
||||
} else {
|
||||
connection.fake_idle(&ctx, Some(watch_folder)).await;
|
||||
connection.fake_idle(&ctx, Some(watch_folder)).await
|
||||
}
|
||||
}
|
||||
None => {
|
||||
warn!(ctx, "Can not watch inbox folder, not set");
|
||||
connection.fake_idle(&ctx, None).await;
|
||||
connection.fake_idle(&ctx, None).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,6 +210,7 @@ async fn simple_imap_loop(
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
error!(ctx, "{}", err);
|
||||
false
|
||||
});
|
||||
} else {
|
||||
connection.fake_idle(&ctx, Some(watch_folder)).await;
|
||||
@@ -210,7 +222,7 @@ async fn simple_imap_loop(
|
||||
"No watch folder found for {}, skipping",
|
||||
folder.as_ref()
|
||||
);
|
||||
connection.fake_idle(&ctx, None).await
|
||||
connection.fake_idle(&ctx, None).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,25 +253,20 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
|
||||
let fut = async move {
|
||||
started.send(()).await;
|
||||
let ctx = ctx1;
|
||||
|
||||
let mut probe_network = false;
|
||||
loop {
|
||||
let probe_network = ctx.scheduler.read().await.get_probe_network();
|
||||
match job::load_next(&ctx, Thread::Smtp, probe_network)
|
||||
.timeout(Duration::from_millis(200))
|
||||
.await
|
||||
{
|
||||
Ok(Some(job)) => {
|
||||
match job::load_next(&ctx, Thread::Smtp, probe_network).await {
|
||||
Some(job) => {
|
||||
info!(ctx, "executing smtp job");
|
||||
job::perform_job(&ctx, job::Connection::Smtp(&mut connection), job).await;
|
||||
ctx.scheduler.write().await.set_probe_network(false);
|
||||
probe_network = false;
|
||||
}
|
||||
Ok(None) | Err(async_std::future::TimeoutError { .. }) => {
|
||||
info!(ctx, "smtp fake idle");
|
||||
None => {
|
||||
// Fake Idle
|
||||
idle_interrupt_receiver
|
||||
.recv()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.await
|
||||
.ok();
|
||||
info!(ctx, "smtp fake idle - started");
|
||||
probe_network = idle_interrupt_receiver.recv().await.unwrap_or_default();
|
||||
info!(ctx, "smtp fake idle - interrupted")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,7 +295,6 @@ impl Scheduler {
|
||||
mvbox,
|
||||
sentbox,
|
||||
smtp,
|
||||
probe_network: false,
|
||||
inbox_handle: None,
|
||||
mvbox_handle: None,
|
||||
sentbox_handle: None,
|
||||
@@ -353,58 +359,39 @@ impl Scheduler {
|
||||
info!(ctx, "scheduler is running");
|
||||
}
|
||||
|
||||
fn set_probe_network(&mut self, val: bool) {
|
||||
match self {
|
||||
Scheduler::Running {
|
||||
ref mut probe_network,
|
||||
..
|
||||
} => {
|
||||
*probe_network = val;
|
||||
}
|
||||
_ => panic!("set_probe_network can only be called when running"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_probe_network(&self) -> bool {
|
||||
match self {
|
||||
Scheduler::Running { probe_network, .. } => *probe_network,
|
||||
_ => panic!("get_probe_network can only be called when running"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn maybe_network(&mut self) {
|
||||
async fn maybe_network(&self) {
|
||||
if !self.is_running() {
|
||||
return;
|
||||
}
|
||||
self.set_probe_network(true);
|
||||
self.interrupt_inbox()
|
||||
.join(self.interrupt_mvbox())
|
||||
.join(self.interrupt_sentbox())
|
||||
.join(self.interrupt_smtp())
|
||||
|
||||
self.interrupt_inbox(true)
|
||||
.join(self.interrupt_mvbox(true))
|
||||
.join(self.interrupt_sentbox(true))
|
||||
.join(self.interrupt_smtp(true))
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn interrupt_inbox(&self) {
|
||||
async fn interrupt_inbox(&self, probe_network: bool) {
|
||||
if let Scheduler::Running { ref inbox, .. } = self {
|
||||
inbox.interrupt().await;
|
||||
inbox.interrupt(probe_network).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn interrupt_mvbox(&self) {
|
||||
async fn interrupt_mvbox(&self, probe_network: bool) {
|
||||
if let Scheduler::Running { ref mvbox, .. } = self {
|
||||
mvbox.interrupt().await;
|
||||
mvbox.interrupt(probe_network).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn interrupt_sentbox(&self) {
|
||||
async fn interrupt_sentbox(&self, probe_network: bool) {
|
||||
if let Scheduler::Running { ref sentbox, .. } = self {
|
||||
sentbox.interrupt().await;
|
||||
sentbox.interrupt(probe_network).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn interrupt_smtp(&self) {
|
||||
async fn interrupt_smtp(&self, probe_network: bool) {
|
||||
if let Scheduler::Running { ref smtp, .. } = self {
|
||||
smtp.interrupt().await;
|
||||
smtp.interrupt(probe_network).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +460,7 @@ struct ConnectionState {
|
||||
/// Channel to interrupt the whole connection.
|
||||
stop_sender: Sender<()>,
|
||||
/// Channel to interrupt idle.
|
||||
idle_interrupt_sender: Sender<()>,
|
||||
idle_interrupt_sender: Sender<bool>,
|
||||
}
|
||||
|
||||
impl ConnectionState {
|
||||
@@ -485,11 +472,9 @@ impl ConnectionState {
|
||||
self.shutdown_receiver.recv().await.ok();
|
||||
}
|
||||
|
||||
async fn interrupt(&self) {
|
||||
if !self.idle_interrupt_sender.is_full() {
|
||||
// Use try_send to avoid blocking on interrupts.
|
||||
self.idle_interrupt_sender.send(()).await;
|
||||
}
|
||||
async fn interrupt(&self, probe_network: bool) {
|
||||
// Use try_send to avoid blocking on interrupts.
|
||||
self.idle_interrupt_sender.try_send(probe_network).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,8 +508,8 @@ impl SmtpConnectionState {
|
||||
}
|
||||
|
||||
/// Interrupt any form of idle.
|
||||
async fn interrupt(&self) {
|
||||
self.state.interrupt().await;
|
||||
async fn interrupt(&self, probe_network: bool) {
|
||||
self.state.interrupt(probe_network).await;
|
||||
}
|
||||
|
||||
/// Shutdown this connection completely.
|
||||
@@ -538,7 +523,7 @@ struct SmtpConnectionHandlers {
|
||||
connection: Smtp,
|
||||
stop_receiver: Receiver<()>,
|
||||
shutdown_sender: Sender<()>,
|
||||
idle_interrupt_receiver: Receiver<()>,
|
||||
idle_interrupt_receiver: Receiver<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -550,8 +535,8 @@ impl ImapConnectionState {
|
||||
/// Construct a new connection.
|
||||
fn new() -> (Self, ImapConnectionHandlers) {
|
||||
let (stop_sender, stop_receiver) = channel(1);
|
||||
let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1);
|
||||
let (shutdown_sender, shutdown_receiver) = channel(1);
|
||||
let (idle_interrupt_sender, idle_interrupt_receiver) = channel(1);
|
||||
|
||||
let handlers = ImapConnectionHandlers {
|
||||
connection: Imap::new(idle_interrupt_receiver),
|
||||
@@ -571,8 +556,8 @@ impl ImapConnectionState {
|
||||
}
|
||||
|
||||
/// Interrupt any form of idle.
|
||||
async fn interrupt(&self) {
|
||||
self.state.interrupt().await;
|
||||
async fn interrupt(&self, probe_network: bool) {
|
||||
self.state.interrupt(probe_network).await;
|
||||
}
|
||||
|
||||
/// Shutdown this connection completely.
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::e2ee::*;
|
||||
use crate::error::{bail, Error};
|
||||
use crate::events::Event;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::key::{dc_normalize_fingerprint, DcKey, Key, SignedPublicKey};
|
||||
use crate::key::{dc_normalize_fingerprint, DcKey, SignedPublicKey};
|
||||
use crate::lot::LotState;
|
||||
use crate::message::Message;
|
||||
use crate::mimeparser::*;
|
||||
@@ -142,7 +142,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
|
||||
|
||||
async fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
match SignedPublicKey::load_self(context).await {
|
||||
Ok(key) => Some(Key::from(key).fingerprint()),
|
||||
Ok(key) => Some(key.fingerprint().hex()),
|
||||
Err(_) => {
|
||||
warn!(context, "get_self_fingerprint(): failed to load key");
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user