Compare commits

..

40 Commits

Author SHA1 Message Date
holger krekel
7e454ede4c add env 2019-11-12 18:39:38 +01:00
holger krekel
029f60df27 new try 2019-11-12 18:35:37 +01:00
holger krekel
20c82b324a try again 2019-11-12 18:21:54 +01:00
dignifiedquire
e7ebb40cd1 do not cancel on warnings 2019-11-12 18:15:41 +01:00
dignifiedquire
bca8094fcb try to fix python action 2019-11-12 18:15:12 +01:00
holger krekel
7ab5d36b5b try 2019-11-12 18:11:18 +01:00
dignifiedquire
58ad14d9c3 better github action 2019-11-12 16:21:49 +01:00
dignifiedquire
e539bddc3b github actions: nightly only 2019-11-12 16:08:44 +01:00
dignifiedquire
2fc1d21959 update github actions 2019-11-12 16:04:37 +01:00
holger krekel
b43d9d2ffe shortcut fetch/idle on mvbox/sentbox if we don't know the folder and prevent busy-looping 2019-11-12 16:03:19 +01:00
holger krekel
5b73951b9b steramline some teardown decision code, and add webpki_roots for cert-checking 2019-11-12 16:03:19 +01:00
holger krekel
8595b92fcf also make smtp respect CertificateChecks setting roughly 2019-11-12 16:03:19 +01:00
holger krekel
6054b90975 rough integration of async-tls CertChecks (strict and automatic but not more finegrained work) 2019-11-12 16:03:19 +01:00
dignifiedquire
b8427ab56e update to released versions 2019-11-12 16:03:19 +01:00
holger krekel
02f72eea61 use AtomicBool for skip_next_idle_wait 2019-11-12 16:03:19 +01:00
holger krekel
a5a20078f0 actually this fixes the double import issue 2019-11-12 16:03:19 +01:00
holger krekel
7383094b33 fix tests for failed logins 2019-11-12 16:03:19 +01:00
holger krekel
e225a6fb17 * fix interrupt_idle by signalling "skip_next_idle_wait" to the potentially concurrently "fn idle" function 2019-11-12 16:03:19 +01:00
holger krekel
6bdc207277 make select_folder return ImapActionResult's and early-return from idle if there is no selected folder 2019-11-12 16:03:19 +01:00
dignifiedquire
101141c67a remove rustup install 2019-11-12 16:03:19 +01:00
dignifiedquire
d07afe5bd6 try use a different rust version 2019-11-12 16:03:19 +01:00
dignifiedquire
00e22d4339 update deps 2019-11-12 16:03:19 +01:00
dignifiedquire
91c8f48c21 bust ci cache 2019-11-12 16:03:19 +01:00
dignifiedquire
3d76d21925 refactor: drop native-tls 2019-11-12 16:03:19 +01:00
dignifiedquire
3349c0e9dc update docker image 2019-11-12 16:03:19 +01:00
dignifiedquire
7b35104b83 cleanup imap impl 2019-11-12 16:03:19 +01:00
dignifiedquire
22b6e8f6e2 update async-imap 2019-11-12 16:03:19 +01:00
dignifiedquire
ad118aa0df implement idle again 2019-11-12 16:03:19 +01:00
dignifiedquire
9044b80b9f remove local dependency 2019-11-12 16:03:19 +01:00
dignifiedquire
b948e973c5 it compiles with async-imap 2019-11-12 16:03:19 +01:00
Friedel Ziegelmayer
d330d890c0 setup minimal github action
Just so we can experiment with it on branches
2019-11-12 14:31:38 +01:00
Alexander Krotov
c6369b1c5a Implement TryFrom instead of TryInto
TryInto is derived automatically and its documentation recommends implementing TryFrom.
2019-11-10 15:38:58 +00:00
Alexander Krotov
bfa0f9d911 Use the first subkey for encryption instead of the primary key 2019-11-10 16:32:09 +01:00
B. Petersen
154cb2db83 add missing DC_STR_DEVICE_MESSAGES 2019-11-10 16:31:12 +01:00
B. Petersen
37ecfa6b67 fix gm2local offset calculations 2019-11-09 20:26:00 +01:00
B. Petersen
99ba2fb358 let dc_timestamp_to_str() print the local time, not UTC times. the function is used for outputs directly shown to the user, eg. dc_get_msg_info() 2019-11-09 20:26:00 +01:00
B. Petersen
85ebde29dc do not resort chatlist on draft changes
resorting the chatlist on changing drafts has some ux issues.
eg. when the chatlist is visible together with the input field,
if may come to flickering resorting during input
or to a resorting just when the user leave the chat
as this might trigger set_draft().

but also on mobiles, the resorting is visible and a bit unexpected.
also it is unclear what happens when a chat with a draft is entered
and left without modifications.

the solution proposed here is to ignore draft on sorting
while still showing them in the chatlist
if they're newer as the last message.

a possible disadvantage is
that the date for the chat with a draft does not follow the ordering
(the ordering is by the last message),
however, the date is not shown as a "primary sort" criterion or so,
so it might be that this is completely okay.
also, of course, it affects only draft :)
2019-11-09 19:32:13 +01:00
björn petersen
0876f45503 Merge pull request #810 from deltachat/sync-config-param
restore config-param if configure() fails
2019-11-08 19:36:43 +01:00
B. Petersen
2fae6890c2 the new MsgId type comes with a formatter that makes special formatting in the repl tool superfluous 2019-11-07 13:19:38 +01:00
B. Petersen
34f9961857 restore config-param if configure() fails 2019-11-06 22:59:14 +01:00
25 changed files with 1997 additions and 1319 deletions

View File

@@ -15,7 +15,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -44,7 +44,7 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
@@ -60,7 +60,7 @@ jobs:
paths:
- crate
- save_cache:
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -121,7 +121,7 @@ jobs:
steps:
- checkout
- run: bash ci_scripts/run-doxygen.sh
- run: mkdir -p workspace/c-docs
- run: mkdir -p workspace/c-docs
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
- persist_to_workspace:
root: workspace
@@ -189,7 +189,7 @@ workflows:
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_doxygen
- build_doxygen
- rustfmt:
requires:
- cargo_fetch

30
.github/workflows/rust.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: CI
on:
pull_request:
push:
env:
RUSTFLAGS: -Dwarnings
jobs:
build:
name: 3.7 python tests against core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- name: Setup python
uses: actions/setup-python@v1
with:
python-version: 3.x
architecture: x64
- run: bash ci_scripts/run-python.sh

954
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,12 +15,13 @@ hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
reqwest = "0.9.15"
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
num-derive = "0.2.5"
num-traits = "0.2.6"
native-tls = "0.2.3"
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
imap = { git = "https://github.com/deltachat/rust-imap", branch = "master" }
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
async-imap = "0.1"
async-tls = "0.6"
async-std = { version = "1.0", features = ["unstable"] }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
@@ -49,6 +50,10 @@ bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
sanitize-filename = "0.2.1"
stop-token = { version = "0.1.1", features = ["unstable"] }
rustls = "0.16.0"
webpki-roots = "0.18.0"
webpki = "0.21.0"
[dev-dependencies]
tempfile = "3.0"
@@ -74,6 +79,6 @@ path = "examples/repl/main.rs"
[features]
default = ["nightly", "ringbuf"]
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
vendored = []
nightly = ["pgp/nightly"]
ringbuf = ["pgp/ringbuf"]

View File

@@ -5,16 +5,6 @@ RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
# Install a recent Perl, needed to install OpenSSL
ADD deps/build_perl.sh /builder/build_perl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
@@ -23,3 +13,12 @@ RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
ADD deps/build_openssl.sh /builder/build_openssl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_openssl.sh && cd .. && rm -r tmp1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1

View File

@@ -1,11 +1,11 @@
#!/bin/bash
PERL_VERSION=5.28.0
PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
./Configure -de
make

View File

@@ -1,11 +1,8 @@
#!/bin/bash
set -e -x
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-09-12 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/

View File

@@ -13,7 +13,6 @@ export TOXWORKDIR=.docker-tox
export PATH=/root/.cargo/bin:$PATH
cargo build --release -p deltachat_ffi
# cargo test --all --all-features
# Statically link against libdeltachat.a.
export DCC_RS_DEV=$(pwd)
@@ -22,36 +21,27 @@ export DCC_RS_DEV=$(pwd)
# needed by tox below.
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd /bin
ln -s /opt/python/cp27-cp27m/bin/python2.7
ln -s /opt/python/cp36-cp36m/bin/python3.6
ln -s /opt/python/cp37-cp37m/bin/python3.7
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
#tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
#tox --workdir "$TOXWORKDIR" -e auditwheels
popd
if [ -n "$TESTS" ]; then
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels
popd
fi
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs

View File

@@ -501,15 +501,9 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
* To interrupt a configuration prematurely, use dc_stop_ongoing_process();
* this is not needed if #DC_EVENT_CONFIGURE_PROGRESS reports success.
*
* On a successfull configuration,
* the core makes a copy of the parameters mentioned above:
* the original parameters as are never modified by the core.
*
* UI-implementors should keep this in mind -
* eg. if the UI wants to prefill a configure-edit-dialog with these parameters,
* the UI should reset them if the user cancels the dialog
* after a configure-attempts has failed.
* Otherwise the parameters may not reflect the current configuation.
* If #DC_EVENT_CONFIGURE_PROGRESS reports failure,
* the core continues to use the last working configuration
* and parameters as `addr`, `mail_pw` etc. are set to that.
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
@@ -4473,7 +4467,8 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_STR_MSGLOCATIONDISABLED 65
#define DC_STR_LOCATION 66
#define DC_STR_STICKER 67
#define DC_STR_COUNT 67
#define DC_STR_DEVICE_MESSAGES 68
#define DC_STR_COUNT 68
/*
* @}

View File

@@ -192,7 +192,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let msgtext = msg.get_text();
info!(
context,
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
prefix.as_ref(),
msg.get_id(),
if msg.get_showpadlock() { "🔒" } else { "" },
@@ -240,7 +240,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
lines_out += 1
}
let msg = Message::load_from_db(context, msg_id)?;
log_msg(context, "Msg", &msg);
log_msg(context, "", &msg);
}
}
if lines_out > 0 {
@@ -426,12 +426,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
if msg.is_setupmessage() {
let setupcodebegin = msg.get_setupcodebegin(context);
println!(
"The setup code for setup message Msg#{} starts with: {}",
"The setup code for setup message {} starts with: {}",
msg_id,
setupcodebegin.unwrap_or_default(),
);
} else {
bail!("Msg#{} is no setup message.", msg_id,);
bail!("{} is no setup message.", msg_id,);
}
}
"continue-key-transfer" => {
@@ -726,7 +726,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let marker = location.marker.as_ref().unwrap_or(&default_marker);
info!(
context,
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
location.location_id,
dc_timestamp_to_str(location.timestamp),
location.latitude,
@@ -852,9 +852,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{} images or videos: ", images.len());
for (i, data) in images.iter().enumerate() {
if 0 == i {
print!("Msg#{}", data);
print!("{}", data);
} else {
print!(", Msg#{}", data);
print!(", {}", data);
}
}
print!("\n");

View File

@@ -642,7 +642,7 @@ class TestOnlineAccount:
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online_all_twice(self, acfactory, tmpdir, lp):
def test_import_export_online_all(self, acfactory, tmpdir, lp):
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
@@ -895,7 +895,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
assert "cannot login" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_user(self, acfactory):
@@ -904,7 +904,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "authentication failed" in ev1[2].lower()
assert "cannot login" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_domain(self, acfactory):

View File

@@ -1 +1 @@
nightly-2019-08-13
nightly-2019-11-06

View File

@@ -126,7 +126,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
@@ -149,7 +149,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.archived=1",
@@ -175,7 +175,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.name LIKE ?",
@@ -198,7 +198,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.archived=0",
@@ -294,7 +294,7 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
@@ -306,6 +306,16 @@ impl Chatlist {
None
};
if let Ok(draft) = get_draft(context, chat.id) {
if draft.is_some()
&& (lastmsg.is_none()
|| draft.as_ref().unwrap().timestamp_sort
> lastmsg.as_ref().unwrap().timestamp_sort)
{
lastmsg = draft;
}
}
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED

View File

@@ -421,6 +421,16 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &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(context, "").save_to_database(context, "configured_raw_");
} else {
LoginParam::from_database(context, "configured_raw_").save_to_database(context, "");
}
context.free_ongoing();
progress!(context, if success { 1000 } else { 0 });
}
@@ -559,7 +569,7 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if imap.is_connected() {
if async_std::task::block_on(async move { imap.is_connected().await }) {
ret_connected = 1
} else if !context.sql.get_raw_config_bool(context, "configured") {
warn!(context, "Not configured, cannot connect.",);

View File

@@ -154,13 +154,15 @@ pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
******************************************************************************/
pub fn dc_timestamp_to_str(wanted: i64) -> String {
let ts = chrono::Utc.timestamp(wanted, 0);
let ts = Local.timestamp(wanted, 0);
ts.format("%Y.%m.%d %H:%M:%S").to_string()
}
pub(crate) fn dc_gm2local_offset() -> i64 {
/* returns the offset that must be _added_ to an UTC/GMT-time to create the localtime.
the function may return negative values. */
let lt = Local::now();
((lt.offset().local_minus_utc() / (60 * 60)) * 100) as i64
lt.offset().local_minus_utc() as i64
}
/* timesmearing */

View File

@@ -498,7 +498,7 @@ fn decrypt_if_autocrypt_message(
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
ret_gossip_headers: *mut *mut mailimf_fields,
) -> Result<(bool)> {
) -> Result<bool> {
/* The returned bool is true if we detected an Autocrypt-encrypted
message and successfully decrypted it. Decryption then modifies the
passed in mime structure in place. The returned bool is false

File diff suppressed because it is too large Load Diff

294
src/imap_client.rs Normal file
View File

@@ -0,0 +1,294 @@
use async_imap::{
error::{Error as ImapError, Result as ImapResult},
extensions::idle::Handle as ImapIdleHandle,
types::{Capabilities, Fetch, Mailbox, Name},
Client as ImapClient, Session as ImapSession,
};
use async_std::net::{self, TcpStream};
use async_std::prelude::*;
use async_std::sync::Arc;
use async_tls::client::TlsStream;
use crate::login_param::{dc_build_tls_config, CertificateChecks};
const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
#[derive(Debug)]
pub(crate) enum Client {
Secure(ImapClient<TlsStream<TcpStream>>),
Insecure(ImapClient<TcpStream>),
}
#[derive(Debug)]
pub(crate) enum Session {
Secure(ImapSession<TlsStream<TcpStream>>),
Insecure(ImapSession<TcpStream>),
}
#[derive(Debug)]
pub(crate) enum IdleHandle {
Secure(ImapIdleHandle<TlsStream<TcpStream>>),
Insecure(ImapIdleHandle<TcpStream>),
}
impl Client {
pub async fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
certificate_checks: CertificateChecks,
) -> ImapResult<Self> {
let stream = TcpStream::connect(addr).await?;
let tls_config = dc_build_tls_config(certificate_checks);
let tls_connector: async_tls::TlsConnector = Arc::new(tls_config).into();
let tls_stream = tls_connector.connect(domain.as_ref(), stream)?.await?;
let mut client = ImapClient::new(tls_stream);
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
.read_response()
.await
.expect("failed to read greeting");
Ok(Client::Secure(client))
}
pub async fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> ImapResult<Self> {
let stream = TcpStream::connect(addr).await?;
let mut client = ImapClient::new(stream);
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
.read_response()
.await
.expect("failed to read greeting");
Ok(Client::Insecure(client))
}
pub async fn secure<S: AsRef<str>>(
self,
domain: S,
_certificate_checks: CertificateChecks,
) -> ImapResult<Client> {
match self {
Client::Insecure(client) => {
let tls = async_tls::TlsConnector::new();
let client_sec = client.secure(domain, &tls).await?;
Ok(Client::Secure(client_sec))
}
// Nothing to do
Client::Secure(_) => Ok(self),
}
}
pub async fn authenticate<A: async_imap::Authenticator, S: AsRef<str>>(
self,
auth_type: S,
authenticator: &A,
) -> Result<Session, (ImapError, Client)> {
match self {
Client::Secure(i) => match i.authenticate(auth_type, authenticator).await {
Ok(session) => Ok(Session::Secure(session)),
Err((err, c)) => Err((err, Client::Secure(c))),
},
Client::Insecure(i) => match i.authenticate(auth_type, authenticator).await {
Ok(session) => Ok(Session::Insecure(session)),
Err((err, c)) => Err((err, Client::Insecure(c))),
},
}
}
pub async fn login<U: AsRef<str>, P: AsRef<str>>(
self,
username: U,
password: P,
) -> Result<Session, (ImapError, Client)> {
match self {
Client::Secure(i) => match i.login(username, password).await {
Ok(session) => Ok(Session::Secure(session)),
Err((err, c)) => Err((err, Client::Secure(c))),
},
Client::Insecure(i) => match i.login(username, password).await {
Ok(session) => Ok(Session::Insecure(session)),
Err((err, c)) => Err((err, Client::Insecure(c))),
},
}
}
}
impl Session {
pub async fn capabilities(&mut self) -> ImapResult<Capabilities> {
let res = match self {
Session::Secure(i) => i.capabilities().await?,
Session::Insecure(i) => i.capabilities().await?,
};
Ok(res)
}
pub async fn list(
&mut self,
reference_name: Option<&str>,
mailbox_pattern: Option<&str>,
) -> ImapResult<Vec<Name>> {
let res = match self {
Session::Secure(i) => {
i.list(reference_name, mailbox_pattern)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.list(reference_name, mailbox_pattern)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> ImapResult<()> {
match self {
Session::Secure(i) => i.create(mailbox_name).await?,
Session::Insecure(i) => i.create(mailbox_name).await?,
}
Ok(())
}
pub async fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> ImapResult<()> {
match self {
Session::Secure(i) => i.subscribe(mailbox).await?,
Session::Insecure(i) => i.subscribe(mailbox).await?,
}
Ok(())
}
pub async fn close(&mut self) -> ImapResult<()> {
match self {
Session::Secure(i) => i.close().await?,
Session::Insecure(i) => i.close().await?,
}
Ok(())
}
pub async fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> ImapResult<Mailbox> {
let mbox = match self {
Session::Secure(i) => i.select(mailbox_name).await?,
Session::Insecure(i) => i.select(mailbox_name).await?,
};
Ok(mbox)
}
pub async fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.fetch(sequence_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.fetch(sequence_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.uid_fetch(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.uid_fetch(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub fn idle(self) -> IdleHandle {
match self {
Session::Secure(i) => {
let h = i.idle();
IdleHandle::Secure(h)
}
Session::Insecure(i) => {
let h = i.idle();
IdleHandle::Insecure(h)
}
}
}
pub async fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.uid_store(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.uid_store(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
&mut self,
uid_set: S1,
mailbox_name: S2,
) -> ImapResult<()> {
match self {
Session::Secure(i) => i.uid_mv(uid_set, mailbox_name).await?,
Session::Insecure(i) => i.uid_mv(uid_set, mailbox_name).await?,
}
Ok(())
}
pub async fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
&mut self,
uid_set: S1,
mailbox_name: S2,
) -> ImapResult<()> {
match self {
Session::Secure(i) => i.uid_copy(uid_set, mailbox_name).await?,
Session::Insecure(i) => i.uid_copy(uid_set, mailbox_name).await?,
}
Ok(())
}
}

View File

@@ -245,10 +245,10 @@ impl Job {
&dest_folder,
&mut dest_uid,
) {
ImapResult::RetryLater => {
ImapActionResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::Success => {
ImapActionResult::Success => {
message::update_server_uid(
context,
&msg.rfc724_mid,
@@ -256,7 +256,7 @@ impl Job {
dest_uid,
);
}
ImapResult::Failed | ImapResult::AlreadyDone => {}
ImapActionResult::Failed | ImapActionResult::AlreadyDone => {}
}
}
}
@@ -280,7 +280,7 @@ impl Job {
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
if res == ImapResult::RetryLater {
if res == ImapActionResult::RetryLater {
self.try_again_later(-1i32, None);
return;
}
@@ -313,11 +313,11 @@ impl Job {
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, folder, msg.server_uid) {
ImapResult::RetryLater => {
ImapActionResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapResult::AlreadyDone => {}
ImapResult::Success | ImapResult::Failed => {
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
// XXX the message might just have been moved
// we want to send out an MDN anyway
// The job will not be retried so locally
@@ -343,7 +343,7 @@ impl Job {
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let inbox = context.inbox.read().unwrap();
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
if inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
self.try_again_later(3i32, None);
return;
}
@@ -361,7 +361,7 @@ impl Job {
.get_raw_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapResult::RetryLater
if ImapActionResult::RetryLater
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);

View File

@@ -107,12 +107,17 @@ impl JobThread {
}
fn connect_to_imap(&self, context: &Context) -> bool {
if self.imap.is_connected() {
if async_std::task::block_on(async move { self.imap.is_connected().await }) {
return true;
}
let watch_folder_name = match context.sql.get_raw_config(context, self.folder_config_name) {
Some(name) => name,
None => {
return false;
}
};
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
let ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
if ret_connected {
if context
.sql
@@ -123,12 +128,7 @@ impl JobThread {
self.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);
ret_connected = false;
}
self.imap.set_watch_folder(watch_folder_name);
}
ret_connected
@@ -170,10 +170,18 @@ impl JobThread {
}
}
self.connect_to_imap(context);
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
if self.connect_to_imap(context) {
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
} else {
// It's probably wrong that the thread even runs
// but let's call fake_idle and tell it to not try network at all.
// (once we move to rust-managed threads this problem goes away)
info!(context, "{}-IDLE not connected, fake-idling", self.name);
async_std::task::block_on(async move { self.imap.fake_idle(context, false).await });
info!(context, "{}-IDLE fake-idling finished", self.name);
}
self.state.0.lock().unwrap().using_handle = false;
}

View File

@@ -29,44 +29,44 @@ impl From<SignedSecretKey> for Key {
}
}
impl std::convert::TryInto<SignedSecretKey> for Key {
impl std::convert::TryFrom<Key> for SignedSecretKey {
type Error = ();
fn try_into(self) -> Result<SignedSecretKey, Self::Error> {
match self {
fn try_from(value: Key) -> Result<Self, Self::Error> {
match value {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl<'a> std::convert::TryInto<&'a SignedSecretKey> for &'a Key {
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
type Error = ();
fn try_into(self) -> Result<&'a SignedSecretKey, Self::Error> {
match self {
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl std::convert::TryInto<SignedPublicKey> for Key {
impl std::convert::TryFrom<Key> for SignedPublicKey {
type Error = ();
fn try_into(self) -> Result<SignedPublicKey, Self::Error> {
match self {
fn try_from(value: Key) -> Result<Self, Self::Error> {
match value {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}
}
}
impl<'a> std::convert::TryInto<&'a SignedPublicKey> for &'a Key {
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
type Error = ();
fn try_into(self) -> Result<&'a SignedPublicKey, Self::Error> {
match self {
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}

View File

@@ -40,6 +40,7 @@ pub mod contact;
pub mod context;
mod e2ee;
mod imap;
mod imap_client;
pub mod imex;
pub mod job;
mod job_thread;

View File

@@ -3,6 +3,10 @@ use std::fmt;
use crate::context::Context;
use crate::error::Error;
use async_std::sync::Arc;
use rustls;
use webpki;
use webpki_roots;
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
#[repr(i32)]
@@ -251,27 +255,49 @@ fn get_readable_flags(flags: i32) -> String {
res
}
pub fn dc_build_tls(
certificate_checks: CertificateChecks,
) -> Result<native_tls::TlsConnector, native_tls::Error> {
let mut tls_builder = native_tls::TlsConnector::builder();
pub struct NoCertificateVerification {}
impl rustls::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: webpki::DNSNameRef<'_>,
_ocsp: &[u8],
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
pub fn dc_build_tls_config(certificate_checks: CertificateChecks) -> rustls::ClientConfig {
let mut config = rustls::ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
match certificate_checks {
CertificateChecks::Strict => {}
CertificateChecks::Automatic => {
// Same as AcceptInvalidCertificates for now.
// TODO: use provider database when it becomes available
tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
}
CertificateChecks::AcceptInvalidCertificates => {
// TODO: only accept invalid certs
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
}
CertificateChecks::Strict => &mut tls_builder,
CertificateChecks::AcceptInvalidHostnames => {
tls_builder.danger_accept_invalid_hostnames(true)
// TODO: only accept invalid hostnames
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
}
CertificateChecks::AcceptInvalidCertificates => tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true),
}
.build()
config
}
#[cfg(test)]

View File

@@ -5,7 +5,7 @@ use std::io::Cursor;
use pgp::armor::BlockType;
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
SignedSecretKey, SubkeyParamsBuilder,
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder,
};
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
@@ -97,18 +97,29 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
Some((Key::Public(public_key), Key::Secret(private_key)))
}
/// Select subkey of the public key to use for encryption.
///
/// Currently the first subkey is selected.
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
key.public_subkeys.iter().find(|_k|
// TODO: check if it is an encryption subkey
true)
}
pub fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Result<String, Error> {
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
.keys()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
key.as_ref()
.try_into()
.ok()
.and_then(select_pk_for_encryption)
})
.collect();

View File

@@ -5,7 +5,7 @@ use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
use crate::login_param::{dc_build_tls, LoginParam};
use crate::login_param::{dc_build_tls_config, LoginParam};
use crate::oauth2::*;
#[derive(DebugStub)]
@@ -65,8 +65,8 @@ impl Smtp {
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
let tls_config = dc_build_tls_config(lp.smtp_certificate_checks);
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_config);
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2