add dns_prefill attribute to entered login params and implement parsing

for it
This commit is contained in:
Simon Laux
2026-01-16 22:42:42 +01:00
parent 659d21aa9d
commit 252fc8480e
5 changed files with 65 additions and 1 deletions

View File

@@ -54,6 +54,9 @@ pub struct EnteredLoginParam {
/// If true, login via OAUTH2 (not recommended anymore). /// If true, login via OAUTH2 (not recommended anymore).
/// Default: false /// Default: false
pub oauth2: Option<bool>, pub oauth2: Option<bool>,
/// IP addresses for prefilling DNS
pub dns_prefill: Vec<String>,
} }
impl From<dc::EnteredLoginParam> for EnteredLoginParam { impl From<dc::EnteredLoginParam> for EnteredLoginParam {
@@ -75,6 +78,7 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
smtp_password: param.smtp.password.into_option(), smtp_password: param.smtp.password.into_option(),
certificate_checks: certificate_checks.into_option(), certificate_checks: certificate_checks.into_option(),
oauth2: param.oauth2.into_option(), oauth2: param.oauth2.into_option(),
dns_prefill: param.dns_prefill,
} }
} }
} }
@@ -101,6 +105,7 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
}, },
certificate_checks: param.certificate_checks.unwrap_or_default().into(), certificate_checks: param.certificate_checks.unwrap_or_default().into(),
oauth2: param.oauth2.unwrap_or_default(), oauth2: param.oauth2.unwrap_or_default(),
dns_prefill: param.dns_prefill,
}) })
} }
} }

View File

@@ -97,6 +97,9 @@ pub struct EnteredLoginParam {
/// If true, login via OAUTH2 (not recommended anymore) /// If true, login via OAUTH2 (not recommended anymore)
pub oauth2: bool, pub oauth2: bool,
/// IP addresses for prefilling DNS
pub dns_prefill: Vec<String>,
} }
impl EnteredLoginParam { impl EnteredLoginParam {
@@ -191,6 +194,7 @@ impl EnteredLoginParam {
}, },
certificate_checks, certificate_checks,
oauth2, oauth2,
dns_prefill: Default::default(),
}) })
} }
@@ -360,6 +364,7 @@ mod tests {
}, },
certificate_checks: Default::default(), certificate_checks: Default::default(),
oauth2: false, oauth2: false,
dns_prefill: Default::default(),
}; };
param.save(&t).await?; param.save(&t).await?;
assert_eq!( assert_eq!(

View File

@@ -12,6 +12,7 @@ use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, percent_encode};
use rand::TryRngCore as _; use rand::TryRngCore as _;
use rand::distr::{Alphanumeric, SampleString}; use rand::distr::{Alphanumeric, SampleString};
use serde::Deserialize; use serde::Deserialize;
use url::Url;
use crate::config::Config; use crate::config::Config;
use crate::contact::{Contact, ContactId, Origin}; use crate::contact::{Contact, ContactId, Origin};
@@ -656,6 +657,7 @@ async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result<
/// scheme: `DCACCOUNT:example.org` /// scheme: `DCACCOUNT:example.org`
/// or `DCACCOUNT:https://example.org/new` /// or `DCACCOUNT:https://example.org/new`
/// or `DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3` /// 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> { fn decode_account(qr: &str) -> Result<Qr> {
let payload = qr let payload = qr
.get(DCACCOUNT_SCHEME.len()..) .get(DCACCOUNT_SCHEME.len()..)
@@ -784,9 +786,33 @@ pub(crate) async fn login_param_from_account_qr(
if !payload.starts_with(HTTPS_SCHEME) { if !payload.starts_with(HTTPS_SCHEME) {
let rng = &mut rand::rngs::OsRng.unwrap_err(); let rng = &mut rand::rngs::OsRng.unwrap_err();
let username = Alphanumeric.sample_string(rng, 9); 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 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 { let param = EnteredLoginParam {
addr, addr,
imap: EnteredServerLoginParam { imap: EnteredServerLoginParam {
@@ -796,6 +822,7 @@ pub(crate) async fn login_param_from_account_qr(
smtp: Default::default(), smtp: Default::default(),
certificate_checks: EnteredCertificateChecks::Strict, certificate_checks: EnteredCertificateChecks::Strict,
oauth2: false, oauth2: false,
dns_prefill,
}; };
return Ok(param); return Ok(param);
} }
@@ -816,6 +843,7 @@ pub(crate) async fn login_param_from_account_qr(
smtp: Default::default(), smtp: Default::default(),
certificate_checks: EnteredCertificateChecks::Strict, certificate_checks: EnteredCertificateChecks::Strict,
oauth2: false, oauth2: false,
dns_prefill: Default::default(),
}; };
Ok(param) Ok(param)

View File

@@ -191,6 +191,7 @@ pub(crate) fn login_param_from_login_qr(
}, },
certificate_checks: certificate_checks.unwrap_or_default(), certificate_checks: certificate_checks.unwrap_or_default(),
oauth2: false, oauth2: false,
dns_prefill: Default::default(),
}; };
Ok(param) Ok(param)
} }

View File

@@ -711,6 +711,31 @@ async fn test_decode_account() -> Result<()> {
Ok(()) 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)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decode_tg_socks_proxy() -> Result<()> { async fn test_decode_tg_socks_proxy() -> Result<()> {
let t = TestContext::new().await; let t = TestContext::new().await;