Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions deltachat-jsonrpc/src/api/types/login_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub struct EnteredLoginParam {
/// If true, login via OAUTH2 (not recommended anymore).
/// Default: false
pub oauth2: Option<bool>,

/// IP addresses for prefilling DNS
pub dns_prefill: Vec<String>,
}

impl From<dc::EnteredLoginParam> for EnteredLoginParam {
Expand All @@ -75,6 +78,7 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
smtp_password: param.smtp.password.into_option(),
certificate_checks: certificate_checks.into_option(),
oauth2: param.oauth2.into_option(),
dns_prefill: param.dns_prefill,
}
}
}
Expand All @@ -101,6 +105,7 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
},
certificate_checks: param.certificate_checks.unwrap_or_default().into(),
oauth2: param.oauth2.unwrap_or_default(),
dns_prefill: param.dns_prefill,
})
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ mod auto_mozilla;
mod auto_outlook;
pub(crate) mod server_params;

use std::net::IpAddr;

use anyhow::{Context as _, Result, bail, ensure, format_err};
use auto_mozilla::moz_autoconfigure;
use auto_outlook::outlk_autodiscover;
Expand Down Expand Up @@ -557,6 +559,25 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
let ctx2 = ctx.clone();
let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await });

if !param.dns_prefill.is_empty() {
let mut ips: Vec<IpAddr> = Vec::new();
for ip in &param.dns_prefill {
match ip.parse::<IpAddr>() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also already parse the IP addresses while parsing the QR code in login_param_from_account_qr.

Also, there is the question we should ignore invalid IP aggresses or if we should fail parsing the QR code if one address is invalid. Currently, it just emits an error event, but it continues with the rest of the IP addresses. Explicit error during parsing may be better?

Ok(ip) => ips.push(ip),
Err(err) => {
error!(
ctx,
"IP address prefill failed: parsing of address '{ip}' failed: {err}"
);
}
}
}
ctx.dns_memory_cache
.write()
.await
.insert(param.imap.server.clone(), ips);
}

let configured_param = get_configured_param(ctx, param).await?;
let proxy_config = ProxyConfig::load(ctx).await?;
let strict_tls = configured_param.strict_tls(proxy_config.is_some());
Expand Down
6 changes: 6 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@ pub struct InnerContext {
) -> mail_builder::mime::MimePart<'a>,
>,
>,

/// Short lived DNS cache which only lives in memory.
/// Used for configuration from `dcaccount` links with ip address.
/// Like `dcaccount:example.org?a=127.0.0.1,[::1]`
pub(crate) dns_memory_cache: Arc<RwLock<HashMap<String, Vec<std::net::IpAddr>>>>,
}

/// The state of ongoing process.
Expand Down Expand Up @@ -494,6 +499,7 @@ impl Context {
self_fingerprint: OnceLock::new(),
connectivities: parking_lot::Mutex::new(Vec::new()),
pre_encrypt_mime_hook: None.into(),
dns_memory_cache: Arc::new(RwLock::new(HashMap::new())),
};

let ctx = Context {
Expand Down
5 changes: 5 additions & 0 deletions src/login_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ pub struct EnteredLoginParam {

/// If true, login via OAUTH2 (not recommended anymore)
pub oauth2: bool,

/// IP addresses for prefilling DNS
pub dns_prefill: Vec<String>,
}

impl EnteredLoginParam {
Expand Down Expand Up @@ -191,6 +194,7 @@ impl EnteredLoginParam {
},
certificate_checks,
oauth2,
dns_prefill: Default::default(),
})
}

Expand Down Expand Up @@ -360,6 +364,7 @@ mod tests {
},
certificate_checks: Default::default(),
oauth2: false,
dns_prefill: Default::default(),
};
param.save(&t).await?;
assert_eq!(
Expand Down
8 changes: 8 additions & 0 deletions src/net/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,14 @@ pub(crate) async fn lookup_host_with_cache(
}
}
}
if let Some(ips) = context.dns_memory_cache.read().await.get(hostname) {
for ip in ips {
let addr = SocketAddr::new(*ip, port);
if !cache.contains(&addr) {
cache.push(addr);
}
}
}

merge_with_cache(resolved_addrs, cache)
} else {
Expand Down
30 changes: 29 additions & 1 deletion src/qr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, percent_encode};
use rand::TryRngCore as _;
use rand::distr::{Alphanumeric, SampleString};
use serde::Deserialize;
use url::Url;

use crate::config::Config;
use crate::contact::{Contact, ContactId, Origin};
Expand Down Expand Up @@ -656,6 +657,7 @@ async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result<
/// scheme: `DCACCOUNT:example.org`
/// or `DCACCOUNT:https://example.org/new`
/// or `DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3`
/// or `dcaccount:example.org?a=127.0.0.1,[::1]`
fn decode_account(qr: &str) -> Result<Qr> {
let payload = qr
.get(DCACCOUNT_SCHEME.len()..)
Expand Down Expand Up @@ -784,9 +786,33 @@ pub(crate) async fn login_param_from_account_qr(
if !payload.starts_with(HTTPS_SCHEME) {
let rng = &mut rand::rngs::OsRng.unwrap_err();
let username = Alphanumeric.sample_string(rng, 9);
let addr = username + "@" + payload;
let host = if let Some(start_of_query) = payload.find("?") {
payload
.get(..start_of_query)
.context("failed to ignore query part")?
} else {
payload
};
let addr = username + "@" + host;
let password = Alphanumeric.sample_string(rng, 50);

let dns_prefill: Vec<String> = match Url::parse(qr) {
Ok(url) => {
let options = url.query_pairs();
let parameter_map: BTreeMap<String, String> = options
.map(|(key, value)| (key.into_owned(), value.into_owned()))
.collect();
parameter_map
.get("a")
.map(|ips| ips.split(",").map(|s| s.to_owned()).collect())
.unwrap_or_default()
}
Err(err) => {
error!(context, "error parsing parameter of account url: {err}");
Default::default()
}
};

let param = EnteredLoginParam {
addr,
imap: EnteredServerLoginParam {
Expand All @@ -796,6 +822,7 @@ pub(crate) async fn login_param_from_account_qr(
smtp: Default::default(),
certificate_checks: EnteredCertificateChecks::Strict,
oauth2: false,
dns_prefill,
};
return Ok(param);
}
Expand All @@ -816,6 +843,7 @@ pub(crate) async fn login_param_from_account_qr(
smtp: Default::default(),
certificate_checks: EnteredCertificateChecks::Strict,
oauth2: false,
dns_prefill: Default::default(),
};

Ok(param)
Expand Down
1 change: 1 addition & 0 deletions src/qr/dclogin_scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ pub(crate) fn login_param_from_login_qr(
},
certificate_checks: certificate_checks.unwrap_or_default(),
oauth2: false,
dns_prefill: Default::default(),
};
Ok(param)
}
Expand Down
25 changes: 25 additions & 0 deletions src/qr/qr_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,31 @@ async fn test_decode_account() -> Result<()> {
Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decode_account_with_dns_prefill() -> Result<()> {
let ctx = &TestContext::new().await;

for (qr, prefill_ips) in [
(
"dcaccount:example.org?a=127.0.0.1,[::1]",
vec!["127.0.0.1", "[::1]"],
),
(
"DCACCOUNT:example.org?a=127.0.0.1,[::1]",
vec!["127.0.0.1", "[::1]"],
),
("dcaccount:example.org?a=[::1]", vec!["[::1]"]),
("DCACCOUNT:example.org?a=127.0.0.1", vec!["127.0.0.1"]),
] {
let param = login_param_from_account_qr(ctx, qr).await?;
println!("addr {}", param.addr);
assert!(param.addr.ends_with("example.org"));
assert_eq!(param.dns_prefill, prefill_ips);
}

Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decode_tg_socks_proxy() -> Result<()> {
let t = TestContext::new().await;
Expand Down
Loading