Compare commits

..

75 Commits

Author SHA1 Message Date
holger krekel
186512b356 mark 30-times runs tests as ignored by default 2020-06-05 16:43:21 +02:00
Hocuri
be573767f4 Comments 2020-06-05 12:22:39 +02:00
Hocuri
d40754273d Small fixes 2020-06-05 12:22:39 +02:00
Hocuri
6e05039a10 Try to fix it. I dont like the code and it does not work. 2020-06-05 12:22:39 +02:00
Hocuri
76f4b9baa4 Let the python tast recognize when the mvbox is Inbox/DeltaChat instead of DeltaChat 2020-06-05 12:22:38 +02:00
Hocuri
1c936260a0 Rm some debug logs 2020-06-05 12:22:38 +02:00
Hocuri
676ff127c1 typo 2020-06-05 12:22:38 +02:00
Hocuri
a704947546 Next try to reliably mark read 2020-06-05 12:22:37 +02:00
Hocuri
a862484927 Print more info 2020-06-05 12:22:37 +02:00
Hocuri
d66b6acde4 Lint 2020-06-05 12:22:37 +02:00
Hocuri
5677bf3fe5 Put emails into the tmp folder 2020-06-05 12:22:36 +02:00
Hocuri
85a4637b9f update_server_uid() when we see that a messhge was moved 2020-06-05 12:22:36 +02:00
Hocuri
232f1d4f86 Always call print_imap_structure when test fails 2020-06-05 12:22:36 +02:00
Hocuri
3cc5aef016 Add test_mark_bcc_read_on_server 2020-06-05 12:22:36 +02:00
Hocuri
79dac9513f Add print_imap_structure 2020-06-05 12:22:35 +02:00
Hocuri
a8d53301e3 Adapt to new refactorings 2020-06-05 12:22:35 +02:00
Hocuri
a4def9c39c Don't catch 2020-06-05 12:22:35 +02:00
Hocuri
bd59f4914c Add hint where the error comes from 2020-06-05 12:22:35 +02:00
Hocuri
d5e6b6231d Lint 2020-06-05 12:22:34 +02:00
Hocuri
89341c1ceb Add test_mark_read_on_server that reproduces https://github.com/deltachat/deltachat-core-rust/issues/1495 2020-06-05 12:22:34 +02:00
Hocuri
c0003c6726 Use Imaplib instead of ImapClient 2020-06-05 12:22:34 +02:00
Hocuri
1290c191b3 Move direct_imap to deltachat folder 2020-06-05 12:22:33 +02:00
Hocuri
69a28df74d rm unused import 2020-06-05 12:22:33 +02:00
Hocuri
303417f095 Add make_direct_imap() fn. I had to disable PersistentDict because I had problems with importing. 2020-06-05 12:22:33 +02:00
Hocuri
8483b3e1d3 Add holger's ImapConn 2020-06-05 12:22:33 +02:00
Floris Bruynooghe
ca95f25639 Use the Fingerprint type to handle fingerprints
This uses the Fingerprint type more consistenly when handling
fingerprits rather then have various string representations passed
around and sometimes converted back and forth with slight differences
in strictness.

It fixes an important bug in the existing, but until now unused,
parsing behaviour of Fingerprint.  It also adds a default length check
on the fingerprint as that was checked in some existing places.

Fially generating keys is no longer expensive, so let's not ignore
these tests.
2020-06-04 22:46:59 +02:00
Friedel Ziegelmayer
95cde55a7f Merge pull request #1549 from deltachat/fix-reconnect-logic 2020-06-03 16:17:15 +02:00
dignifiedquire
8756c0cbe1 test(python): avoid race condition in unicode test 2020-06-03 16:10:44 +02:00
dignifiedquire
86c6b09814 fix: trigger reconnects when errors occur during idle and fetch 2020-06-03 15:40:54 +02:00
bjoern
6ce27a7f87 Merge pull request #1548 from deltachat/fix-folders
fix(imap): deterministically detect folder meaning
2020-06-02 16:18:49 +02:00
dignifiedquire
7addb15be5 fix(imap): deterministically detect folder meaning 2020-06-02 15:18:24 +02:00
Hocuri
7b3a962498 Sanitize address book 2020-05-31 17:04:25 +02:00
Hocuri
41bba7e780 Fix #880 Don't vary advanced login settings if a user set a particular setting 2020-05-29 20:31:34 +02:00
Friedel Ziegelmayer
419b7d1d5c Merge pull request #1539 from deltachat/sane-configure
refactor(configure): simplify logic and code
2020-05-29 19:32:49 +02:00
dignifiedquire
6d8b4a7ec0 simplify further and apply CR 2020-05-29 19:09:45 +02:00
B. Petersen
84963e198e do autoconfig only when no advanced options are entered
the advanced options are not used anyway later,
but prevent imap/smtp connections from being altered.

nb: we want to stop altering when some advanced options
are entered, however, we want to do this probaby
not depending on autoconfig.
2020-05-29 19:09:45 +02:00
dignifiedquire
408e9946af refactor(configure): cleanup logic 2020-05-29 19:09:45 +02:00
dignifiedquire
43f49f8917 refactor(configure): remove step-counter 2020-05-29 19:09:45 +02:00
Hocuri
b6161c431b Fix #1474 "Sending message to contact with < or > in Recipient gets treated as "Sent" but is not received" (#1476)
Fix #1474 "Sending message to contact with < or > in Recipient gets treated as "Sent" but is not received".

As I was at it, I also extracted the correct name and address from addresses like Mueller, Dave <dave@domain.com>.
2020-05-29 18:14:21 +02:00
Floris Bruynooghe
a236a619ad Finish Key->DcKey refactoring
Migrates .verify() and .split_key() to DcKey.  Removes all remaining
uses of Key.
2020-05-29 11:25:52 +02:00
Floris Bruynooghe
cdbd3d7d84 Move ascii-armored stuff from Key to DcKey
This means all key conversions/serialisation/deserialisation can be
done with DcKey rather than Key.  Also migrate all key conversion
tests to DcKey rather than Key.
2020-05-29 11:25:52 +02:00
Floris Bruynooghe
8efc880b77 Move Keyring and fingerprint to DcKey trait
This moves both the Keyring and the fingerprints to the DcKey trait,
unfortunately I was not able to disentangle these two changes.  The
Keyring now ensures only the right kind of key is added to it.

The keyring now uses the DcKey::load_self method rather than
re-implement the SQL to load keys from the database.  This vastly
simpliefies the use and fixes an error where a failed key load or
unconfigured would result in the message being treated as plain text
and benefits from the in-line key generation path.

For the fingerprint a new type representing it is introduced.  The aim
is to replace more fingerpring uses with this type as now there are
various string representations being passed around and converted
between.  The Display trait is used for the space-separated and
multiline format, which is perhaps not the most obvious but seems
right together with FromStr etc.
2020-05-29 11:25:52 +02:00
jikstra
4bade7e13a Update cargo lock 2020-05-28 20:38:11 +02:00
Jikstra
53099bbfd1 Merge pull request #1537 from deltachat/fix_dc_str_constants
Add missing DC_STR_* constants to deltachat.h
2020-05-28 18:24:11 +02:00
jikstra
7d1d02bf3b Add missing DC_STR_* constants to deltachat.h 2020-05-28 13:41:56 +02:00
bjoern
4f477ec6d2 Merge pull request #1536 from deltachat/prep-1.34
Prep 1.34
2020-05-27 20:33:53 +02:00
B. Petersen
0a4d6fe09b bump version to 1.34.0 2020-05-27 19:33:27 +02:00
B. Petersen
8640bd5ee6 update changelog 2020-05-27 19:31:51 +02:00
bjoern
19a6a30fe2 Merge pull request #1356 from deltachat/feat/async-jobs
asyncify core & remove manual thread handling
2020-05-27 18:17:51 +02:00
dignifiedquire
23b6974386 Merge master 2020-05-27 17:21:39 +02:00
Friedel Ziegelmayer
103ee966f4 Merge pull request #1531 from deltachat/forgiving-start-io 2020-05-27 17:20:35 +02:00
dignifiedquire
6100a23e80 fix: avoid lock for probe_network, avoiding deadlock on startup
Closes #1532
2020-05-27 15:13:29 +02:00
dignifiedquire
9f7f387540 examples: fix blocking the scheduler in the repl 2020-05-27 15:05:29 +02:00
dignifiedquire
307357df70 update simple exaple 2020-05-26 19:40:37 +02:00
dignifiedquire
bd903d8e8f fix: avoid short smtp interruptions 2020-05-26 19:40:30 +02:00
B. Petersen
3db6d5a458 allow calls to start_io when already running and to stop_io when not running, only log a message in these cases 2020-05-26 14:08:47 +02:00
B. Petersen
4330da232c tweak async ffi-docs 2020-05-26 08:20:17 +02:00
dignifiedquire
157dd44df0 refactor: improve structure of fetch_messages
also fixes updating last_seen_uid to the correct value
2020-05-25 17:03:08 +02:00
bjoern
460e60063c Merge pull request #1528 from deltachat/ffi-doc
ffi doc for async-jobs, keep name for data2_str
2020-05-25 14:41:33 +02:00
B. Petersen
ec601a3381 revert renaming of data2_str to data3_str, while this looks clearer at a first glance, it would mean to introduce much noise in the existing bindings and understandings 2020-05-25 12:58:21 +02:00
B. Petersen
2156c6cd7a basic documentation of ffi 2020-05-25 12:36:07 +02:00
dignifiedquire
13811c06ee mark generate_key as blocking 2020-05-25 12:08:16 +02:00
dignifiedquire
230d40daa0 fixup dependency 2020-05-25 01:05:17 +02:00
dignifiedquire
45aba61ac8 update smol fork 2020-05-25 00:56:58 +02:00
dignifiedquire
2adeadfd73 fix: avoid blocking on expensive pgp operations 2020-05-25 00:17:01 +02:00
holger krekel
477e689c74 fix python lint 2020-05-24 22:11:14 +02:00
dignifiedquire
00e8f2271a improve simple example 2020-05-24 21:24:21 +02:00
dignifiedquire
9442df0cf8 fix: restore logic to original in configure 2020-05-24 21:24:13 +02:00
holger krekel
d9de33820f add a stress test 2020-05-24 21:25:13 +02:00
Friedel Ziegelmayer
f13fbe4398 Merge pull request #1527 from deltachat/feat/async-jobs-parallel-fetch 2020-05-24 19:07:08 +02:00
dignifiedquire
811655bc98 update deps, for real 2020-05-24 19:06:22 +02:00
dignifiedquire
0760bfaf7b use patched version of smol to avoid nix dependency 2020-05-24 17:20:04 +02:00
bjoern
177cd52039 Merge pull request #1524 from lupine/add-padfoot
Add telepathy-padfoot to the README
2020-05-24 00:11:16 +02:00
Nick Thomas
1ab6186eaa Add telepathy-padfoot to the README 2020-05-23 20:06:14 +01:00
Hocuri
26b0c43cc4 Remove help for nonexisting open and close commands.
See #1496
2020-05-22 18:45:42 +02:00
36 changed files with 2296 additions and 1963 deletions

View File

@@ -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
View File

@@ -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",
]

View File

@@ -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" }

View File

@@ -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**

View File

@@ -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"

View File

@@ -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
/*
* @}

View File

@@ -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();
}

View File

@@ -146,10 +146,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 +288,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(())
}

View File

@@ -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;
}

View File

@@ -28,7 +28,7 @@ class Account(object):
"""
MissingCredentials = MissingCredentials
def __init__(self, db_path, os_name=None, logging=True):
def __init__(self, db_path, os_name=None, logging=True, logid=None):
""" initialize account object.
:param db_path: a path to the account database. The database
@@ -38,6 +38,7 @@ class Account(object):
# initialize per-account plugin system
self._pm = hookspec.PerAccount._make_plugin_manager()
self._logging = logging
self.logid = logid
self.add_account_plugin(self)
@@ -50,7 +51,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)

View File

@@ -0,0 +1,141 @@
import imaplib
import pathlib
from . import Account
INBOX = "Inbox"
SENT = "Sent"
MVBOX = "DeltaChat"
MVBOX_FALLBBACK = "INBOX/DeltaChat"
DC_CONSTANT_MSG_MOVESTATE_PENDING = 1
DC_CONSTANT_MSG_MOVESTATE_STAY = 2
DC_CONSTANT_MSG_MOVESTATE_MOVING = 3
def db_folder_attr(name):
def fget(s):
return s.db_folder.get(name, 1)
def fset(s, val):
s.db_folder[name] = val
return property(fget, fset, None, None)
class ImapConn():
def __init__(self, foldername, conn_info):
self.foldername = foldername
host, user, pw = conn_info
self.connection = imaplib.IMAP4_SSL(host)
self.connection.login(user, pw)
messages = self.reselect_folder()
try:
self.original_msg_count = int(messages[0])
except IndexError:
self.original_msg_count = 0
def mark_all_read(self):
self.reselect_folder()
# result, data = self.connection.uid('search', None, "(UNSEEN)")
result, data = self.connection.search(None, 'UnSeen')
try:
mails_uid = data[0].split()
print("New mails")
# self.connection.store(data[0].replace(' ',','),'+FLAGS','\Seen')
for e_id in mails_uid:
self.connection.store(e_id, '+FLAGS', '\\Seen')
print("marked:", e_id)
return True
except IndexError:
print("No unread")
return False
def get_unread_cnt(self):
self.reselect_folder()
# result, data = self.connection.uid('search', None, "(UNSEEN)")
result, data = self.connection.search(None, 'UnSeen')
try:
mails_uid = data[0].split()
return len(mails_uid)
except IndexError:
return 0
def get_new_email_cnt(self):
messages = self.reselect_folder()
try:
return int(messages[0]) - self.original_msg_count
except IndexError:
return 0
def reselect_folder(self):
status, messages = self.connection.select(self.foldername)
if status != "OK":
print("Incorrect mail box " + status + str(messages))
raise ConnectionError
# print("(Re-)Selected mailbox: " + status + " " + str(messages))
return messages
def __del__(self):
try:
self.connection.close()
except Exception:
pass
try:
self.connection.logout()
except Exception:
print("Could not logout direct_imap conn")
def make_direct_imap(account, folder):
conn_info = (account.get_config("configured_mail_server"),
account.get_config("addr"), account.get_config("mail_pw"))
# try:
# return ImapConn(folder, conn_info=conn_info)
# except ConnectionError as e:
# if folder == MVBOX:
# account.log("Selecting " + MVBOX_FALLBBACK + " not " + MVBOX + " because connecting to the latter failed")
# return ImapConn(MVBOX_FALLBBACK, conn_info=conn_info)
# else:
# raise e
if folder == MVBOX:
new_folder = account.get_config("configured_mvbox_folder")
else:
new_folder = folder
if new_folder != folder:
account.log("Making connection with " + new_folder + " not " + folder)
return ImapConn(new_folder, conn_info=conn_info)
def print_imap_structure(database, dir="."):
print_imap_structure_ac(Account(database), dir)
def print_imap_structure_ac(ac, dir="."):
acinfo = ac.logid + "-" + ac.get_config("addr")
print("================= ACCOUNT", acinfo, "=================")
print("----------------- CONFIG: -----------------")
print(ac.get_info())
for imapfolder in [INBOX, MVBOX, SENT, MVBOX_FALLBBACK]:
try:
imap = make_direct_imap(ac, imapfolder)
c = imap.connection
typ, data = c.search(None, 'ALL')
c._get_tagged_response
print("-----------------", imapfolder, "-----------------")
for num in data[0].split():
typ, data = c.fetch(num, '(RFC822)')
body = data[0][1]
typ, data = c.fetch(num, '(UID FLAGS)')
info = data[0]
path = pathlib.Path(dir).joinpath("IMAP-MESSAGES", acinfo, imapfolder)
path.mkdir(parents=True, exist_ok=True)
file = path.joinpath(str(info).replace("b'", "").replace("'", "").replace("\\", ""))
file.write_bytes(body)
print("Message", info, "saved as", file)
except Exception:
pass

View File

@@ -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)

View File

@@ -12,7 +12,7 @@ import tempfile
import pytest
import requests
from . import Account, const
from . import Account, const, direct_imap
from .capi import lib
from .events import FFIEventLogger, FFIEventTracker
from _pytest._code import Source
@@ -33,9 +33,6 @@ def pytest_addoption(parser):
def pytest_configure(config):
config.addinivalue_line(
"markers", "ignored: Mark test as bing slow, skipped unless --ignored is used."
)
cfg = config.getoption('--liveconfig')
if not cfg:
cfg = os.getenv('DCC_NEW_TMP_EMAIL')
@@ -228,7 +225,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
acc.disable_logging()
def make_account(self, path, logid, quiet=False):
ac = Account(path, logging=self._logging)
ac = Account(path, logging=self._logging, logid=logid)
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
ac.addr = ac.get_self_contact().addr
if not quiet:
@@ -377,7 +374,10 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
am = AccountMaker()
request.addfinalizer(am.finalize)
return am
yield am
if request.node.rep_call.failed:
for ac in am._accounts:
direct_imap.print_imap_structure_ac(ac, tmpdir)
class BotProcess:
@@ -446,3 +446,15 @@ def lp():
def step(self, msg):
print("-" * 5, "step " + msg, "-" * 5)
return Printer()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
outcome = yield
rep = outcome.get_result()
# set a report attribute for each phase of a call, which can
# be "setup", "call", "teardown"
setattr(item, "rep_" + rep.when, rep)

View File

@@ -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:

View File

@@ -7,6 +7,8 @@ from deltachat import const, Account
from deltachat.message import Message
from deltachat.hookspec import account_hookimpl
from datetime import datetime, timedelta
from deltachat import direct_imap
from deltachat.direct_imap import make_direct_imap
@pytest.mark.parametrize("msgtext,res", [
@@ -635,6 +637,85 @@ class TestOnlineAccount:
ev_msg = ac1_clone._evtracker.wait_next_messages_changed()
assert ev_msg.text == msg_out.text
@pytest.mark.ignored
@pytest.mark.parametrize('i', range(30))
def test_mark_read_on_server(self, acfactory, lp, i):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True)
ac1.wait_configure_finish()
ac1.start_io()
ac2.wait_configure_finish()
ac2.start_io()
imap2 = make_direct_imap(ac2, direct_imap.MVBOX)
# imap2.mark_all_read()
assert imap2.get_unread_cnt() == 0
chat = self.get_chat(ac1, ac2)
chat_on_ac2 = self.get_chat(ac2, ac1)
chat.send_text("Text message")
incoming_on_ac2 = ac2._evtracker.wait_next_incoming_message()
lp.sec("Incoming: "+incoming_on_ac2.text)
assert list(ac2.get_fresh_messages())
for i in range(0, 20):
if imap2.get_unread_cnt() == 1:
break
time.sleep(1) # We might need to wait because Imaplib is slower than DC-Core
assert imap2.get_unread_cnt() == 1
chat_on_ac2.mark_noticed()
incoming_on_ac2.mark_seen()
ac2._evtracker.wait_next_messages_changed()
assert not list(ac2.get_fresh_messages())
# The new messages should be seen now.
for i in range(0, 20):
if imap2.get_unread_cnt() == 0:
break
time.sleep(1) # We might need to wait because Imaplib is slower than DC-Core
assert imap2.get_unread_cnt() == 0
@pytest.mark.ignored
@pytest.mark.parametrize('i', range(30))
def test_mark_bcc_read_on_server(self, acfactory, lp, i):
ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True)
ac2 = acfactory.get_online_configuring_account()
ac1.wait_configure_finish()
ac1.start_io()
ac2.wait_configure_finish()
ac2.start_io()
imap1 = make_direct_imap(ac1, direct_imap.MVBOX)
imap1.mark_all_read()
assert imap1.get_unread_cnt() == 0
chat = self.get_chat(ac1, ac2)
ac1.set_config("bcc_self", "1")
chat.send_text("Text message")
ac1._evtracker.get_matching("DC_EVENT_SMTP_MESSAGE_SENT")
for i in range(0, 20):
if imap1.get_new_email_cnt() == 1:
break
time.sleep(1) # We might need to wait because Imaplib is slower than DC-Core
assert imap1.get_new_email_cnt() == 1
for i in range(0, 20):
if imap1.get_unread_cnt() == 0:
break
time.sleep(1) # We might need to wait because Imaplib is slower than DC-Core
assert imap1.get_unread_cnt() == 0
def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp):
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
@@ -978,15 +1059,15 @@ class TestOnlineAccount:
msg_out = chat.send_text(text1)
assert not msg_out.is_encrypted()
lp.sec("wait for ac2 to receive multi-line non-unicode message")
msg_in = ac2._evtracker.wait_next_incoming_message()
assert msg_in.text == text1
lp.sec("sending multi-line unicode text message from ac1 to ac2")
text2 = "äalis\nthis is ßßÄ"
msg_out = chat.send_text(text2)
assert not msg_out.is_encrypted()
lp.sec("wait for ac2 to receive multi-line non-unicode message")
msg_in = ac2._evtracker.wait_next_incoming_message()
assert msg_in.text == text1
lp.sec("wait for ac2 to receive multi-line unicode message")
msg_in = ac2._evtracker.wait_next_incoming_message()
assert msg_in.text == text2

View File

@@ -71,6 +71,8 @@ norecursedirs = .tox
xfail_strict=true
timeout = 90
timeout_method = thread
markers =
ignored: ignore this test in default test runs, use --ignored to run.
[flake8]
max-line-length = 120

View File

@@ -11,7 +11,7 @@ use crate::dc_tools::*;
use crate::events::Event;
use crate::message::MsgId;
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::stock::StockMessage;
use crate::{scheduler::InterruptInfo, stock::StockMessage};
/// The available configuration keys.
#[derive(
@@ -104,6 +104,9 @@ pub enum Config {
ConfiguredServerFlags,
ConfiguredSendSecurity,
ConfiguredE2EEEnabled,
ConfiguredInboxFolder,
ConfiguredMvboxFolder,
ConfiguredSentboxFolder,
Configured,
#[strum(serialize = "sys.version")]
@@ -137,6 +140,7 @@ impl Context {
// Default values
match key {
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).await.into_owned()),
Config::ConfiguredInboxFolder => Some("INBOX".to_owned()),
_ => key.get_str("default").map(|s| s.to_string()),
}
}
@@ -199,17 +203,18 @@ impl Context {
}
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_inbox().await;
self.interrupt_inbox(InterruptInfo::new(false, None)).await;
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_sentbox().await;
self.interrupt_sentbox(InterruptInfo::new(false, None))
.await;
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_mvbox().await;
self.interrupt_mvbox(InterruptInfo::new(false, None)).await;
ret
}
Config::Selfstatus => {

View File

@@ -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(&param.addr) {
if !was_configured_before {
@@ -141,329 +94,294 @@ 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, &param.addr, &param.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(&param.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, &param) {
// 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, &param_domain, &param_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?;
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, &param.addr, &param.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(&param.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, &param) {
// 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, &param).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, &param).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, &param).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, &param).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, &param).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, &param).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, &param).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, &param).await?,
AutoconfigProvider::Outlook => outlk_autodiscover(ctx, &self.url, &param).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 +397,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 +439,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 +472,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, &param, 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, &param, 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, &param, imap).await.is_ok() {
return Ok(());
}
}
progress!(context, 660 + variation * 30);
param.mail_port = 143;
try_imap_one_param(context, &param, 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 +527,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, &param, &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, &param, &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, &param, &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<()> {

View File

@@ -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());
}
}

View File

@@ -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),
};
@@ -290,13 +300,11 @@ impl Context {
.unwrap_or_default();
let configured_sentbox_folder = self
.sql
.get_raw_config(self, "configured_sentbox_folder")
.get_config(Config::ConfiguredSentboxFolder)
.await
.unwrap_or_else(|| "<unset>".to_string());
let configured_mvbox_folder = self
.sql
.get_raw_config(self, "configured_mvbox_folder")
.get_config(Config::ConfiguredMvboxFolder)
.await
.unwrap_or_else(|| "<unset>".to_string());
@@ -432,33 +440,19 @@ impl Context {
.unwrap_or_default()
}
pub fn is_inbox(&self, folder_name: impl AsRef<str>) -> bool {
folder_name.as_ref() == "INBOX"
pub async fn is_inbox(&self, folder_name: impl AsRef<str>) -> bool {
self.get_config(Config::ConfiguredInboxFolder).await
== Some(folder_name.as_ref().to_string())
}
pub async fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
let sentbox_name = self
.sql
.get_raw_config(self, "configured_sentbox_folder")
.await;
if let Some(name) = sentbox_name {
name == folder_name.as_ref()
} else {
false
}
self.get_config(Config::ConfiguredSentboxFolder).await
== Some(folder_name.as_ref().to_string())
}
pub async fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
let mvbox_name = self
.sql
.get_raw_config(self, "configured_mvbox_folder")
.await;
if let Some(name) = mvbox_name {
name == folder_name.as_ref()
} else {
false
}
self.get_config(Config::ConfiguredMvboxFolder).await
== Some(folder_name.as_ref().to_string())
}
pub async fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) {

View File

@@ -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() {

View File

@@ -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, Fingerprint, 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)
}
@@ -120,7 +119,7 @@ pub async fn try_decrypt(
context: &Context,
mail: &ParsedMail<'_>,
message_time: i64,
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
) -> Result<(Option<Vec<u8>>, HashSet<Fingerprint>)> {
let from = mail
.headers
.get_header(HeaderDef::From_)
@@ -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,12 +207,12 @@ 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,
ret_valid_signatures: &mut HashSet<String>,
private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<Fingerprint>,
) -> Result<Option<Vec<u8>>> {
// The returned bool is true if we detected an Autocrypt-encrypted
// message and successfully decrypted it. Decryption then modifies the
@@ -240,21 +231,20 @@ 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,
ret_valid_signatures: &mut HashSet<String>,
private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<Fingerprint>,
) -> 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));

View File

@@ -4,7 +4,7 @@ use async_imap::extensions::idle::IdleResponse;
use async_std::prelude::*;
use std::time::{Duration, SystemTime};
use crate::context::Context;
use crate::{context::Context, scheduler::InterruptInfo};
use super::select_folder;
use super::session::Session;
@@ -34,7 +34,11 @@ 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<InterruptInfo> {
use futures::future::FutureExt;
if !self.can_idle() {
@@ -46,6 +50,7 @@ impl Imap {
let session = self.session.take();
let timeout = Duration::from_secs(23 * 60);
let mut info = Default::default();
if let Some(session) = session {
let mut handle = session.idle();
@@ -55,6 +60,11 @@ impl Imap {
let (idle_wait, interrupt) = handle.wait_with_timeout(timeout);
enum Event {
IdleResponse(IdleResponse),
Interrupt(InterruptInfo),
}
if self.skip_next_idle_wait {
// interrupt_idle has happened before we
// provided self.interrupt
@@ -65,23 +75,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(i)) => {
info = i;
info!(context, "Idle wait was interrupted");
}
Err(err) => {
@@ -115,16 +129,26 @@ impl Imap {
}
}
Ok(())
Ok(info)
}
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>,
) -> InterruptInfo {
// 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 info: InterruptInfo = Default::default();
if self.skip_next_idle_wait {
// interrupt_idle has happened before we
// provided self.interrupt
@@ -135,53 +159,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(InterruptInfo),
}
// 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.
info =
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 InterruptInfo::new(false, None);
}
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 InterruptInfo::new(false, None);
}
}
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(info) => {
// Interrupt
break info;
}
}
None => {
// Interrupt
break;
}
}
}
};
}
info!(
@@ -193,5 +225,7 @@ impl Imap {
.as_millis() as f64
/ 1000.,
);
info
}
}

View File

@@ -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::*;
@@ -26,7 +27,7 @@ use crate::message::{self, update_server_uid};
use crate::mimeparser;
use crate::oauth2::dc_get_oauth2_access_token;
use crate::param::Params;
use crate::stock::StockMessage;
use crate::{scheduler::InterruptInfo, stock::StockMessage};
mod client;
mod idle;
@@ -108,7 +109,7 @@ const SELECT_ALL: &str = "1:*";
#[derive(Debug)]
pub struct Imap {
idle_interrupt: Receiver<()>,
idle_interrupt: Receiver<InterruptInfo>,
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<InterruptInfo>) -> Self {
Imap {
idle_interrupt,
config: Default::default(),
@@ -294,6 +295,8 @@ impl Imap {
match login_res {
Ok(session) => {
// needs to be set here to ensure it is set on reconnects.
self.connected = true;
self.session = Some(session);
Ok(())
}
@@ -577,164 +580,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 +725,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 +748,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);
@@ -1008,7 +974,7 @@ impl Imap {
uid: u32,
) -> Option<ImapActionResult> {
if uid == 0 {
return Some(ImapActionResult::Failed);
return Some(ImapActionResult::RetryLater);
}
if !self.is_connected() {
// currently jobs are only performed on the INBOX thread
@@ -1176,54 +1142,54 @@ impl Imap {
}
};
let mut delimiter = ".".to_string();
let mut delimiter_is_default = true;
let mut sentbox_folder = None;
let mut mvbox_folder = None;
let mut delimiter = ".".to_string();
if let Some(folder) = folders.next().await {
let folder = folder.map_err(|err| Error::Other(err.to_string()))?;
if let Some(d) = folder.delimiter() {
if !d.is_empty() {
delimiter = d.to_string();
}
}
}
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
let fallback_folder = format!("INBOX{}DeltaChat", delimiter);
let mut fallback_folder = get_fallback_folder(&delimiter);
while let Some(folder) = folders.next().await {
let folder = folder.map_err(|err| Error::Other(err.to_string()))?;
info!(context, "Scanning folder: {:?}", folder);
if mvbox_folder.is_none()
&& (folder.name() == "DeltaChat" || folder.name() == fallback_folder)
{
mvbox_folder = Some(folder.name().to_string());
}
if sentbox_folder.is_none() {
if let FolderMeaning::SentObjects = get_folder_meaning(&folder) {
sentbox_folder = Some(folder);
} else if let FolderMeaning::SentObjects = get_folder_meaning_by_name(&folder) {
sentbox_folder = Some(folder);
// Update the delimiter iff there is a different one, but only once.
if let Some(d) = folder.delimiter() {
if delimiter_is_default && !d.is_empty() && delimiter != d {
delimiter = d.to_string();
fallback_folder = get_fallback_folder(&delimiter);
delimiter_is_default = false;
}
}
if mvbox_folder.is_some() && sentbox_folder.is_some() {
break;
if folder.name() == "DeltaChat" {
// Always takes precendent
mvbox_folder = Some(folder.name().to_string());
} else if folder.name() == fallback_folder {
// only set iff none has been already set
if mvbox_folder.is_none() {
mvbox_folder = Some(folder.name().to_string());
}
} else if let FolderMeaning::SentObjects = get_folder_meaning(&folder) {
// Always takes precedent
sentbox_folder = Some(folder.name().to_string());
} else if let FolderMeaning::SentObjects = get_folder_meaning_by_name(&folder) {
// only set iff none has been already set
if sentbox_folder.is_none() {
sentbox_folder = Some(folder.name().to_string());
}
}
}
info!(context, "sentbox folder is {:?}", sentbox_folder);
drop(folders);
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
info!(context, "sentbox folder is {:?}", sentbox_folder);
if mvbox_folder.is_none() && create_mvbox {
info!(context, "Creating MVBOX-folder \"DeltaChat\"...",);
match session.create("DeltaChat").await {
Ok(_) => {
mvbox_folder = Some("DeltaChat".into());
info!(context, "MVBOX-folder created.",);
}
Err(err) => {
@@ -1257,23 +1223,16 @@ impl Imap {
}
}
context
.sql
.set_raw_config(context, "configured_inbox_folder", Some("INBOX"))
.set_config(Config::ConfiguredInboxFolder, Some("INBOX"))
.await?;
if let Some(ref mvbox_folder) = mvbox_folder {
context
.sql
.set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.set_config(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
.await?;
}
if let Some(ref sentbox_folder) = sentbox_folder {
context
.sql
.set_raw_config(
context,
"configured_sentbox_folder",
Some(sentbox_folder.name()),
)
.set_config(Config::ConfiguredSentboxFolder, Some(sentbox_folder))
.await?;
}
context
@@ -1431,7 +1390,11 @@ async fn precheck_imf(
}
if old_server_folder != server_folder || old_server_uid != server_uid {
update_server_uid(context, &rfc724_mid, server_folder, server_uid).await;
update_server_uid(context, rfc724_mid, server_folder, server_uid).await;
context
.interrupt_inbox(InterruptInfo::new(false, Some(msg_id)))
.await;
info!(context, "Updating server_uid and interrupting")
}
Ok(true)
} else {
@@ -1503,3 +1466,54 @@ 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
}
fn get_fallback_folder(delimiter: &str) -> String {
format!("INBOX{}DeltaChat", delimiter)
}

View File

@@ -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();

View File

@@ -31,7 +31,7 @@ use crate::message::{self, Message, MessageState};
use crate::mimefactory::MimeFactory;
use crate::param::*;
use crate::smtp::Smtp;
use crate::sql;
use crate::{scheduler::InterruptInfo, sql};
// results in ~3 weeks for the last backoff timespan
const JOB_RETRIES: u32 = 17;
@@ -504,10 +504,7 @@ impl Job {
warn!(context, "could not configure folders: {:?}", err);
return Status::RetryLater;
}
let dest_folder = context
.sql
.get_raw_config(context, "configured_mvbox_folder")
.await;
let dest_folder = context.get_config(Config::ConfiguredMvboxFolder).await;
if let Some(dest_folder) = dest_folder {
let server_folder = msg.server_folder.as_ref().unwrap();
@@ -518,7 +515,7 @@ impl Job {
{
ImapActionResult::RetryLater => Status::RetryLater,
ImapActionResult::Success => {
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
// Rust-Imap provides no target uid on mv, so just set it to 0, update again when precheck_imf() is called for the moved message
message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, 0).await;
Status::Finished(Ok(()))
}
@@ -612,11 +609,7 @@ impl Job {
async fn empty_server(&mut self, context: &Context, imap: &mut Imap) -> Status {
if self.foreign_id & DC_EMPTY_MVBOX > 0 {
if let Some(mvbox_folder) = context
.sql
.get_raw_config(context, "configured_mvbox_folder")
.await
{
if let Some(mvbox_folder) = &context.get_config(Config::ConfiguredMvboxFolder).await {
imap.empty_folder(context, &mvbox_folder).await;
}
}
@@ -1028,13 +1021,19 @@ 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(InterruptInfo::new(false, None))
.await;
}
Action::MaybeSendLocations
| Action::MaybeSendLocationsEnded
| Action::SendMdn
| Action::SendMsgToSmtp => {
context.interrupt_smtp().await;
info!(context, "interrupt: smtp");
context
.interrupt_smtp(InterruptInfo::new(false, None))
.await;
}
}
}
@@ -1049,38 +1048,49 @@ pub async fn add(context: &Context, job: Job) {
pub(crate) async fn load_next(
context: &Context,
thread: Thread,
probe_network: bool,
info: &InterruptInfo,
) -> Option<Job> {
info!(context, "loading job for {}-thread", thread);
let query = if !probe_network {
let query;
let params;
let t = time();
let m;
let thread_i = thread as i64;
if let Some(msg_id) = info.msg_id {
query = r#"
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
FROM jobs
WHERE thread=? AND foreign_id=?
ORDER BY action DESC, added_timestamp
LIMIT 1;
"#;
m = msg_id;
params = paramsv![thread_i, m];
} else if !info.probe_network {
// processing for first-try and after backoff-timeouts:
// process jobs in the order they were added.
r#"
query = r#"
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
FROM jobs
WHERE thread=? AND desired_timestamp<=?
ORDER BY action DESC, added_timestamp
LIMIT 1;
"#
"#;
params = paramsv![thread_i, t];
} else {
// processing after call to dc_maybe_network():
// process _all_ pending jobs that failed before
// in the order of their backoff-times.
r#"
query = r#"
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
FROM jobs
WHERE thread=? AND tries>0
ORDER BY desired_timestamp, action DESC
LIMIT 1;
"#
};
let thread_i = thread as i64;
let t = time();
let params = if !probe_network {
paramsv![thread_i, t]
} else {
paramsv![thread_i]
"#;
params = paramsv![thread_i];
};
let job = loop {
@@ -1088,13 +1098,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 +1114,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 +1127,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 +1140,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,17 +1191,42 @@ 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.
let t = dummy_context().await;
insert_job(&t.ctx, -1).await; // This can not be loaded into Job struct.
let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await;
let jobs = load_next(
&t.ctx,
Thread::from(Action::MoveMsg),
&InterruptInfo::new(false, None),
)
.await;
assert!(jobs.is_none());
insert_job(&t.ctx, 1).await;
let jobs = load_next(&t.ctx, Thread::from(Action::MoveMsg), false).await;
let jobs = load_next(
&t.ctx,
Thread::from(Action::MoveMsg),
&InterruptInfo::new(false, None),
)
.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),
&InterruptInfo::new(false, None),
)
.await;
assert!(jobs.is_some());
}
}

View File

@@ -1,19 +1,20 @@
//! 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;
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
use thiserror::Error;
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 +39,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 +51,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 +69,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)).expect("Invalid fingerprint from rpgp")
}
}
@@ -108,6 +138,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 +183,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 +251,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 +266,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 +315,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,37 +354,73 @@ pub async fn store_self_keypair(
Ok(())
}
/// 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
let mut res = String::new();
/// A key fingerprint
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Fingerprint(Vec<u8>);
for (i, c) in fingerprint.chars().enumerate() {
if i > 0 && i % 20 == 0 {
res += "\n";
} else if i > 0 && i % 4 == 0 {
res += " ";
impl Fingerprint {
pub fn new(v: Vec<u8>) -> std::result::Result<Fingerprint, FingerprintError> {
match v.len() {
20 => Ok(Fingerprint(v)),
_ => Err(FingerprintError::WrongLength),
}
res += &c.to_string();
}
res
/// 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)
}
}
/// 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 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 = FingerprintError;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
let hex_repr: String = input
.to_uppercase()
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect();
let v: Vec<u8> = hex::decode(hex_repr)?;
let fp = Fingerprint::new(v)?;
Ok(fp)
}
}
#[derive(Debug, Error)]
pub enum FingerprintError {
#[error("Invalid hex characters")]
NotHex(#[from] hex::FromHexError),
#[error("Incorrect fingerprint lengths")]
WrongLength,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use std::convert::TryFrom;
use std::error::Error;
use async_std::sync::Arc;
use lazy_static::lazy_static;
@@ -510,16 +429,9 @@ mod tests {
static ref KEYPAIR: KeyPair = alice_keypair();
}
#[test]
fn test_normalize_fingerprint() {
let fingerprint = dc_normalize_fingerprint(" 1234 567890 \n AbcD abcdef ABCDEF ");
assert_eq!(fingerprint, "1234567890ABCDABCDEFABCDEF");
}
#[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 +489,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();
@@ -641,7 +559,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
}
#[async_std::test]
#[ignore] // generating keys is expensive
async fn test_load_self_generate_public() {
let t = dummy_context().await;
t.ctx
@@ -653,7 +570,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
}
#[async_std::test]
#[ignore] // generating keys is expensive
async fn test_load_self_generate_secret() {
let t = dummy_context().await;
t.ctx
@@ -665,7 +581,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
}
#[async_std::test]
#[ignore] // generating keys is expensive
async fn test_load_self_generate_concurrent() {
use std::thread;
@@ -686,29 +601,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 +652,49 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
// )
// .unwrap();
// }
#[test]
fn test_fingerprint_from_str() {
let res = Fingerprint::new(vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
])
.unwrap();
let fp: Fingerprint = "0102030405060708090A0B0c0d0e0F1011121314".parse().unwrap();
assert_eq!(fp, res);
let fp: Fingerprint = "zzzz 0102 0304 0506\n0708090a0b0c0D0E0F1011121314 yyy"
.parse()
.unwrap();
assert_eq!(fp, res);
let err = "1".parse::<Fingerprint>().err().unwrap();
match err {
FingerprintError::NotHex(_) => (),
_ => panic!("Wrong error"),
}
let src_err = err.source().unwrap().downcast_ref::<hex::FromHexError>();
assert_eq!(src_err, Some(&hex::FromHexError::OddLength));
}
#[test]
fn test_fingerprint_hex() {
let fp = Fingerprint::new(vec![
1, 2, 4, 8, 16, 32, 64, 128, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
])
.unwrap();
assert_eq!(fp.hex(), "0102040810204080FF0A0B0C0D0E0F1011121314");
}
#[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, 19, 20,
])
.unwrap();
assert_eq!(
fp.to_string(),
"0102 0408 1020 4080 FF01\n0204 0810 2040 80FF 1314"
);
}
}

View File

@@ -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]);
}
}

View File

@@ -1,5 +1,7 @@
use deltachat_derive::{FromSql, ToSql};
use crate::key::Fingerprint;
/// An object containing a set of values.
/// The meaning of the values is defined by the function returning the object.
/// Lot objects are created
@@ -14,7 +16,7 @@ pub struct Lot {
pub(crate) timestamp: i64,
pub(crate) state: LotState,
pub(crate) id: u32,
pub(crate) fingerprint: Option<String>,
pub(crate) fingerprint: Option<Fingerprint>,
pub(crate) invitenumber: Option<String>,
pub(crate) auth: Option<String>,
}

View File

@@ -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

View File

@@ -17,6 +17,7 @@ use crate::e2ee;
use crate::error::{bail, Result};
use crate::events::Event;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::Fingerprint;
use crate::location;
use crate::message;
use crate::param::*;
@@ -44,7 +45,7 @@ pub struct MimeMessage {
pub from: Vec<SingleInfo>,
pub chat_disposition_notification_to: Option<SingleInfo>,
pub decrypting_failed: bool,
pub signatures: HashSet<String>,
pub signatures: HashSet<Fingerprint>,
pub gossipped_addr: HashSet<String>,
pub is_forwarded: bool,
pub is_system_message: SystemMessage,

View File

@@ -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, Fingerprint, SignedPublicKey};
use crate::sql::Sql;
#[derive(Debug)]
@@ -32,13 +31,13 @@ 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_fingerprint: Option<String>,
pub gossip_key: Option<Key>,
pub public_key: Option<SignedPublicKey>,
pub public_key_fingerprint: Option<Fingerprint>,
pub gossip_key: Option<SignedPublicKey>,
pub gossip_timestamp: i64,
pub gossip_key_fingerprint: Option<String>,
pub verified_key: Option<Key>,
pub verified_key_fingerprint: Option<String>,
pub gossip_key_fingerprint: Option<Fingerprint>,
pub verified_key: Option<SignedPublicKey>,
pub verified_key_fingerprint: Option<Fingerprint>,
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
@@ -152,7 +151,7 @@ impl<'a> Peerstate<'a> {
pub async fn from_fingerprint(
context: &'a Context,
_sql: &Sql,
fingerprint: &str,
fingerprint: &Fingerprint,
) -> Option<Peerstate<'a>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
@@ -161,13 +160,8 @@ impl<'a> Peerstate<'a> {
WHERE public_key_fingerprint=? COLLATE NOCASE \
OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;";
Self::from_stmt(
context,
query,
paramsv![fingerprint, fingerprint, fingerprint],
)
.await
let fp = fingerprint.hex();
Self::from_stmt(context, query, paramsv![fp, fp, fp]).await
}
async fn from_stmt(
@@ -190,45 +184,30 @@ impl<'a> Peerstate<'a> {
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
res.gossip_timestamp = row.get(5)?;
res.public_key_fingerprint = row.get(7)?;
if res
.public_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.public_key_fingerprint = None;
}
res.gossip_key_fingerprint = row.get(8)?;
if res
.gossip_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.gossip_key_fingerprint = None;
}
res.verified_key_fingerprint = row.get(10)?;
if res
.verified_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.verified_key_fingerprint = None;
}
res.public_key_fingerprint = row
.get::<_, Option<String>>(7)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.gossip_key_fingerprint = row
.get::<_, Option<String>>(8)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.verified_key_fingerprint = row
.get::<_, Option<String>>(10)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
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)
})
@@ -300,8 +279,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 +295,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 +345,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
@@ -380,7 +367,7 @@ impl<'a> Peerstate<'a> {
pub fn set_verified(
&mut self,
which_key: PeerstateKeyType,
fingerprint: &str,
fingerprint: &Fingerprint,
verified: PeerstateVerifiedStatus,
) -> bool {
if verified == PeerstateVerifiedStatus::BidirectVerified {
@@ -438,10 +425,10 @@ impl<'a> Peerstate<'a> {
self.public_key.as_ref().map(|k| k.to_bytes()),
self.gossip_timestamp,
self.gossip_key.as_ref().map(|k| k.to_bytes()),
self.public_key_fingerprint,
self.gossip_key_fingerprint,
self.public_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key_fingerprint,
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.addr,
],
).await?;
@@ -462,7 +449,7 @@ impl<'a> Peerstate<'a> {
Ok(())
}
pub fn has_verified_key(&self, fingerprints: &HashSet<String>) -> bool {
pub fn has_verified_key(&self, fingerprints: &HashSet<Fingerprint>) -> bool {
if self.verified_key.is_some() && self.verified_key_fingerprint.is_some() {
let vkc = self.verified_key_fingerprint.as_ref().unwrap();
if fingerprints.contains(vkc) {
@@ -474,6 +461,12 @@ impl<'a> Peerstate<'a> {
}
}
impl From<crate::key::FingerprintError> for rusqlite::Error {
fn from(_source: crate::key::FingerprintError) -> Self {
Self::InvalidColumnType(0, "Invalid fingerprint".into(), rusqlite::types::Type::Text)
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -486,7 +479,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,
@@ -528,7 +521,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,
@@ -562,7 +555,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,

View File

@@ -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, Fingerprint};
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,
ret_signature_fingerprints: Option<&mut HashSet<String>>,
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<Fingerprint>>,
) -> 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<Fingerprint> = Vec::new();
for pkey in pkeys {
if dec_msg.verify(&pkey.primary_key).is_ok() {
let fp = DcKey::fingerprint(pkey);
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 valid_signatures: HashSet<String> = Default::default();
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<Fingerprint> = 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 valid_signatures: HashSet<String> = Default::default();
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<Fingerprint> = 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();
let mut valid_signatures: HashSet<String> = Default::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<Fingerprint> = 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 valid_signatures: HashSet<String> = Default::default();
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<Fingerprint> = 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);
let mut valid_signatures: HashSet<String> = Default::default();
#[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<Fingerprint> = 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);
}

View File

@@ -10,8 +10,7 @@ use crate::constants::Blocked;
use crate::contact::*;
use crate::context::Context;
use crate::error::{bail, ensure, format_err, Error};
use crate::key::dc_format_fingerprint;
use crate::key::dc_normalize_fingerprint;
use crate::key::Fingerprint;
use crate::lot::{Lot, LotState};
use crate::param::*;
use crate::peerstate::*;
@@ -80,6 +79,14 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
Some(pair) => pair,
None => (payload, ""),
};
let fingerprint: Fingerprint = match fingerprint.parse() {
Ok(fp) => fp,
Err(err) => {
return Error::new(err)
.context("Failed to parse fingerprint in QR code")
.into()
}
};
// replace & with \n to match expected param format
let fragment = fragment.replace('&', "\n");
@@ -128,13 +135,6 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
None
};
let fingerprint = dc_normalize_fingerprint(fingerprint);
// ensure valid fingerprint
if fingerprint.len() != 40 {
return format_err!("Bad fingerprint length in QR code").into();
}
let mut lot = Lot::new();
// retrieve known state for this fingerprint
@@ -161,7 +161,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)).await;
} else {
lot.state = LotState::QrFprWithoutAddr;
lot.text1 = Some(dc_format_fingerprint(&fingerprint));
lot.text1 = Some(fingerprint.to_string());
}
} else if let Some(addr) = addr {
if grpid.is_some() && grpname.is_some() {

View File

@@ -2,12 +2,10 @@ 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};
use crate::smtp::Smtp;
use crate::{config::Config, message::MsgId, smtp::Smtp};
pub(crate) struct StopToken;
@@ -25,30 +23,29 @@ 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, info: InterruptInfo) {
self.scheduler.read().await.interrupt_inbox(info).await;
}
pub(crate) async fn interrupt_sentbox(&self) {
self.scheduler.read().await.interrupt_sentbox().await;
pub(crate) async fn interrupt_sentbox(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_sentbox(info).await;
}
pub(crate) async fn interrupt_mvbox(&self) {
self.scheduler.read().await.interrupt_mvbox().await;
pub(crate) async fn interrupt_mvbox(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_mvbox(info).await;
}
pub(crate) async fn interrupt_smtp(&self) {
self.scheduler.read().await.interrupt_smtp().await;
pub(crate) async fn interrupt_smtp(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_smtp(info).await;
}
}
@@ -64,35 +61,33 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
let ctx1 = ctx.clone();
let fut = async move {
started.send(()).await;
let ctx = ctx1;
if let Err(err) = connection.connect_configured(&ctx).await {
error!(ctx, "{}", err);
return;
}
started.send(()).await;
// track number of continously executed jobs
let mut jobs_loaded = 0;
let mut jobs_loaded: i32 = 0;
let mut info: InterruptInfo = Default::default();
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, &info).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);
info = Default::default();
}
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;
info = fetch_idle(&ctx, &mut connection, Config::ConfiguredInboxFolder).await;
}
}
}
@@ -109,15 +104,13 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
}
async fn fetch(ctx: &Context, connection: &mut Imap) {
match get_watch_folder(&ctx, "configured_inbox_folder").await {
match ctx.get_config(Config::ConfiguredInboxFolder).await {
Some(watch_folder) => {
// fetch
connection
.fetch(&ctx, &watch_folder)
.await
.unwrap_or_else(|err| {
error!(ctx, "{}", err);
});
if let Err(err) = connection.fetch(&ctx, &watch_folder).await {
connection.trigger_reconnect();
error!(ctx, "{}", err);
}
}
None => {
warn!(ctx, "Can not fetch inbox folder, not set");
@@ -126,16 +119,14 @@ async fn fetch(ctx: &Context, connection: &mut Imap) {
}
}
async fn fetch_idle(ctx: &Context, connection: &mut Imap) {
match get_watch_folder(&ctx, "configured_inbox_folder").await {
async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> InterruptInfo {
match ctx.get_config(folder).await {
Some(watch_folder) => {
// fetch
connection
.fetch(&ctx, &watch_folder)
.await
.unwrap_or_else(|err| {
error!(ctx, "{}", err);
});
if let Err(err) = connection.fetch(&ctx, &watch_folder).await {
connection.trigger_reconnect();
error!(ctx, "{}", err);
}
// idle
if connection.can_idle() {
@@ -143,15 +134,17 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap) {
.idle(&ctx, Some(watch_folder))
.await
.unwrap_or_else(|err| {
connection.trigger_reconnect();
error!(ctx, "{}", err);
});
InterruptInfo::new(false, None)
})
} 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
}
}
}
@@ -160,7 +153,7 @@ async fn simple_imap_loop(
ctx: Context,
started: Sender<()>,
inbox_handlers: ImapConnectionHandlers,
folder: impl AsRef<str>,
folder: Config,
) {
use futures::future::FutureExt;
@@ -174,45 +167,16 @@ async fn simple_imap_loop(
let ctx1 = ctx.clone();
let fut = async move {
started.send(()).await;
let ctx = ctx1;
if let Err(err) = connection.connect_configured(&ctx).await {
error!(ctx, "{}", err);
return;
}
loop {
match get_watch_folder(&ctx, folder.as_ref()).await {
Some(watch_folder) => {
// fetch
connection
.fetch(&ctx, &watch_folder)
.await
.unwrap_or_else(|err| {
error!(ctx, "{}", err);
});
started.send(()).await;
// idle
if connection.can_idle() {
connection
.idle(&ctx, Some(watch_folder))
.await
.unwrap_or_else(|err| {
error!(ctx, "{}", err);
});
} else {
connection.fake_idle(&ctx, Some(watch_folder)).await;
}
}
None => {
warn!(
&ctx,
"No watch folder found for {}, skipping",
folder.as_ref()
);
connection.fake_idle(&ctx, None).await
}
}
loop {
fetch_idle(&ctx, &mut connection, folder).await;
}
};
@@ -241,25 +205,20 @@ async fn smtp_loop(ctx: Context, started: Sender<()>, smtp_handlers: SmtpConnect
let fut = async move {
started.send(()).await;
let ctx = ctx1;
let mut interrupt_info = Default::default();
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, &interrupt_info).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);
interrupt_info = Default::default();
}
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");
interrupt_info = idle_interrupt_receiver.recv().await.unwrap_or_default();
info!(ctx, "smtp fake idle - interrupted")
}
}
}
@@ -288,7 +247,6 @@ impl Scheduler {
mvbox,
sentbox,
smtp,
probe_network: false,
inbox_handle: None,
mvbox_handle: None,
sentbox_handle: None,
@@ -311,7 +269,7 @@ impl Scheduler {
ctx1,
mvbox_start_send,
mvbox_handlers,
"configured_mvbox_folder",
Config::ConfiguredMvboxFolder,
)
.await
}));
@@ -325,7 +283,7 @@ impl Scheduler {
ctx1,
sentbox_start_send,
sentbox_handlers,
"configured_sentbox_folder",
Config::ConfiguredSentboxFolder,
)
.await
}));
@@ -353,58 +311,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(InterruptInfo::new(true, None))
.join(self.interrupt_mvbox(InterruptInfo::new(true, None)))
.join(self.interrupt_sentbox(InterruptInfo::new(true, None)))
.join(self.interrupt_smtp(InterruptInfo::new(true, None)))
.await;
}
async fn interrupt_inbox(&self) {
async fn interrupt_inbox(&self, info: InterruptInfo) {
if let Scheduler::Running { ref inbox, .. } = self {
inbox.interrupt().await;
inbox.interrupt(info).await;
}
}
async fn interrupt_mvbox(&self) {
async fn interrupt_mvbox(&self, info: InterruptInfo) {
if let Scheduler::Running { ref mvbox, .. } = self {
mvbox.interrupt().await;
mvbox.interrupt(info).await;
}
}
async fn interrupt_sentbox(&self) {
async fn interrupt_sentbox(&self, info: InterruptInfo) {
if let Scheduler::Running { ref sentbox, .. } = self {
sentbox.interrupt().await;
sentbox.interrupt(info).await;
}
}
async fn interrupt_smtp(&self) {
async fn interrupt_smtp(&self, info: InterruptInfo) {
if let Scheduler::Running { ref smtp, .. } = self {
smtp.interrupt().await;
smtp.interrupt(info).await;
}
}
@@ -473,7 +412,7 @@ struct ConnectionState {
/// Channel to interrupt the whole connection.
stop_sender: Sender<()>,
/// Channel to interrupt idle.
idle_interrupt_sender: Sender<()>,
idle_interrupt_sender: Sender<InterruptInfo>,
}
impl ConnectionState {
@@ -485,11 +424,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, info: InterruptInfo) {
// Use try_send to avoid blocking on interrupts.
self.idle_interrupt_sender.try_send(info).ok();
}
}
@@ -523,8 +460,8 @@ impl SmtpConnectionState {
}
/// Interrupt any form of idle.
async fn interrupt(&self) {
self.state.interrupt().await;
async fn interrupt(&self, info: InterruptInfo) {
self.state.interrupt(info).await;
}
/// Shutdown this connection completely.
@@ -538,7 +475,7 @@ struct SmtpConnectionHandlers {
connection: Smtp,
stop_receiver: Receiver<()>,
shutdown_sender: Sender<()>,
idle_interrupt_receiver: Receiver<()>,
idle_interrupt_receiver: Receiver<InterruptInfo>,
}
#[derive(Debug)]
@@ -550,8 +487,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 +508,8 @@ impl ImapConnectionState {
}
/// Interrupt any form of idle.
async fn interrupt(&self) {
self.state.interrupt().await;
async fn interrupt(&self, info: InterruptInfo) {
self.state.interrupt(info).await;
}
/// Shutdown this connection completely.
@@ -588,20 +525,17 @@ struct ImapConnectionHandlers {
shutdown_sender: Sender<()>,
}
async fn get_watch_folder(context: &Context, config_name: impl AsRef<str>) -> Option<String> {
match context
.sql
.get_raw_config(context, config_name.as_ref())
.await
{
Some(name) => Some(name),
None => {
if config_name.as_ref() == "configured_inbox_folder" {
// initialized with old version, so has not set configured_inbox_folder
Some("INBOX".to_string())
} else {
None
}
#[derive(Default, Debug)]
pub struct InterruptInfo {
pub probe_network: bool,
pub msg_id: Option<MsgId>,
}
impl InterruptInfo {
pub fn new(probe_network: bool, msg_id: Option<MsgId>) -> Self {
Self {
probe_network,
msg_id,
}
}
}

View File

@@ -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::{DcKey, Fingerprint, SignedPublicKey};
use crate::lot::LotState;
use crate::message::Message;
use crate::mimeparser::*;
@@ -73,8 +73,6 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
==== Step 1 in "Setup verified contact" protocol ====
=======================================================*/
let fingerprint: String;
ensure_secret_key_exists(context).await.ok();
// invitenumber will be used to allow starting the handshake,
@@ -95,7 +93,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
.await
.unwrap_or_default();
fingerprint = match get_self_fingerprint(context).await {
let fingerprint: Fingerprint = match get_self_fingerprint(context).await {
Some(fp) => fp,
None => {
return None;
@@ -140,9 +138,9 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
qr
}
async fn get_self_fingerprint(context: &Context) -> Option<String> {
async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
match SignedPublicKey::load_self(context).await {
Ok(key) => Some(Key::from(key).fingerprint()),
Ok(key) => Some(key.fingerprint()),
Err(_) => {
warn!(context, "get_self_fingerprint(): failed to load key");
None
@@ -249,7 +247,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
chat_id_2_contact_id(context, contact_chat_id).await,
400
);
let own_fingerprint = get_self_fingerprint(context).await.unwrap_or_default();
let own_fingerprint = get_self_fingerprint(context).await;
// Bob -> Alice
if let Err(err) = send_handshake_msg(
@@ -261,7 +259,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
"vc-request-with-auth"
},
get_qr_attr!(context, auth).to_string(),
Some(own_fingerprint),
own_fingerprint,
if join_vg {
get_qr_attr!(context, text2).to_string()
} else {
@@ -311,7 +309,7 @@ async fn send_handshake_msg(
contact_chat_id: ChatId,
step: &str,
param2: impl AsRef<str>,
fingerprint: Option<String>,
fingerprint: Option<Fingerprint>,
grpid: impl AsRef<str>,
) -> Result<(), HandshakeError> {
let mut msg = Message::default();
@@ -328,7 +326,7 @@ async fn send_handshake_msg(
msg.param.set(Param::Arg2, param2);
}
if let Some(fp) = fingerprint {
msg.param.set(Param::Arg3, fp);
msg.param.set(Param::Arg3, fp.hex());
}
if !grpid.as_ref().is_empty() {
msg.param.set(Param::Arg4, grpid.as_ref());
@@ -360,7 +358,7 @@ async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32
async fn fingerprint_equals_sender(
context: &Context,
fingerprint: impl AsRef<str>,
fingerprint: &Fingerprint,
contact_chat_id: ChatId,
) -> bool {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await;
@@ -368,9 +366,8 @@ async fn fingerprint_equals_sender(
if contacts.len() == 1 {
if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await {
if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await {
let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref());
if peerstate.public_key_fingerprint.is_some()
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
&& fingerprint == peerstate.public_key_fingerprint.as_ref().unwrap()
{
return true;
}
@@ -397,6 +394,8 @@ pub(crate) enum HandshakeError {
NoSelfAddr,
#[error("Failed to send message")]
MsgSendFailed(#[source] Error),
#[error("Failed to parse fingerprint")]
BadFingerprint(#[from] crate::key::FingerprintError),
}
/// What to do with a Secure-Join handshake message after it was handled.
@@ -516,10 +515,11 @@ pub(crate) async fn handle_securejoin_handshake(
// no error, just aborted somehow or a mail from another handshake
return Ok(HandshakeMessage::Ignore);
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let scanned_fingerprint_of_alice: Fingerprint =
get_qr_attr!(context, fingerprint).clone();
let auth = get_qr_attr!(context, auth).to_string();
if !encrypted_and_signed(context, mime_message, &scanned_fingerprint_of_alice) {
if !encrypted_and_signed(context, mime_message, Some(&scanned_fingerprint_of_alice)) {
could_not_establish_secure_connection(
context,
contact_chat_id,
@@ -576,8 +576,9 @@ pub(crate) async fn handle_securejoin_handshake(
==========================================================*/
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) {
Some(fp) => fp,
let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
{
Some(fp) => fp.parse()?,
None => {
could_not_establish_secure_connection(
context,
@@ -588,7 +589,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore);
}
};
if !encrypted_and_signed(context, mime_message, &fingerprint) {
if !encrypted_and_signed(context, mime_message, Some(&fingerprint)) {
could_not_establish_secure_connection(
context,
contact_chat_id,
@@ -625,7 +626,7 @@ pub(crate) async fn handle_securejoin_handshake(
.await;
return Ok(HandshakeMessage::Ignore);
}
if mark_peer_as_verified(context, fingerprint).await.is_err() {
if mark_peer_as_verified(context, &fingerprint).await.is_err() {
could_not_establish_secure_connection(
context,
contact_chat_id,
@@ -673,7 +674,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id,
"vc-contact-confirm",
"",
Some(fingerprint.clone()),
Some(fingerprint),
"",
)
.await?;
@@ -709,7 +710,8 @@ pub(crate) async fn handle_securejoin_handshake(
);
return Ok(abort_retval);
}
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string();
let scanned_fingerprint_of_alice: Fingerprint =
get_qr_attr!(context, fingerprint).clone();
let vg_expect_encrypted = if join_vg {
let group_id = get_qr_attr!(context, text2).to_string();
@@ -731,7 +733,7 @@ pub(crate) async fn handle_securejoin_handshake(
true
};
if vg_expect_encrypted
&& !encrypted_and_signed(context, mime_message, &scanned_fingerprint_of_alice)
&& !encrypted_and_signed(context, mime_message, Some(&scanned_fingerprint_of_alice))
{
could_not_establish_secure_connection(
context,
@@ -888,7 +890,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
if !encrypted_and_signed(
context,
mime_message,
get_self_fingerprint(context).await.unwrap_or_default(),
get_self_fingerprint(context).await.as_ref(),
) {
could_not_establish_secure_connection(
context,
@@ -898,8 +900,9 @@ pub(crate) async fn observe_securejoin_on_other_device(
.await;
return Ok(HandshakeMessage::Ignore);
}
let fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) {
Some(fp) => fp,
let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
{
Some(fp) => fp.parse()?,
None => {
could_not_establish_secure_connection(
context,
@@ -910,7 +913,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
return Ok(HandshakeMessage::Ignore);
}
};
if mark_peer_as_verified(context, fingerprint).await.is_err() {
if mark_peer_as_verified(context, &fingerprint).await.is_err() {
could_not_establish_secure_connection(
context,
contact_chat_id,
@@ -967,16 +970,13 @@ async fn could_not_establish_secure_connection(
error!(context, "{} ({})", &msg, details);
}
async fn mark_peer_as_verified(
context: &Context,
fingerprint: impl AsRef<str>,
) -> Result<(), Error> {
async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> {
if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()).await
Peerstate::from_fingerprint(context, &context.sql, fingerprint).await
{
if peerstate.set_verified(
PeerstateKeyType::PublicKey,
fingerprint.as_ref(),
fingerprint,
PeerstateVerifiedStatus::BidirectVerified,
) {
peerstate.prefer_encrypt = EncryptPreference::Mutual;
@@ -990,7 +990,7 @@ async fn mark_peer_as_verified(
}
bail!(
"could not mark peer as verified for fingerprint {}",
fingerprint.as_ref()
fingerprint.hex()
);
}
@@ -1001,7 +1001,7 @@ async fn mark_peer_as_verified(
fn encrypted_and_signed(
context: &Context,
mimeparser: &MimeMessage,
expected_fingerprint: impl AsRef<str>,
expected_fingerprint: Option<&Fingerprint>,
) -> bool {
if !mimeparser.was_encrypted() {
warn!(context, "Message not encrypted.",);
@@ -1009,17 +1009,17 @@ fn encrypted_and_signed(
} else if mimeparser.signatures.is_empty() {
warn!(context, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {
warn!(context, "Fingerprint for comparison missing.",);
} else if expected_fingerprint.is_none() {
warn!(context, "Fingerprint for comparison missing.");
false
} else if !mimeparser
.signatures
.contains(expected_fingerprint.as_ref())
.contains(expected_fingerprint.unwrap())
{
warn!(
context,
"Message does not match expected fingerprint {}.",
expected_fingerprint.as_ref(),
expected_fingerprint.unwrap(),
);
false
} else {