1mod auto_mozilla;
13mod auto_outlook;
14pub(crate) mod server_params;
15
16use anyhow::{Context as _, Result, bail, ensure, format_err};
17use auto_mozilla::moz_autoconfigure;
18use auto_outlook::outlk_autodiscover;
19use deltachat_contact_tools::{EmailAddress, addr_normalize};
20use futures::FutureExt;
21use futures_lite::FutureExt as _;
22use percent_encoding::utf8_percent_encode;
23use server_params::{ServerParams, expand_param_vector};
24use tokio::task;
25
26use crate::config::{self, Config};
27use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT;
28use crate::context::Context;
29use crate::imap::Imap;
30use crate::log::warn;
31use crate::login_param::EnteredCertificateChecks;
32pub use crate::login_param::EnteredLoginParam;
33use crate::message::Message;
34use crate::net::proxy::ProxyConfig;
35use crate::oauth2::get_oauth2_addr;
36use crate::provider::{Protocol, Provider, Socket, UsernamePattern};
37use crate::qr::{login_param_from_account_qr, login_param_from_login_qr};
38use crate::smtp::Smtp;
39use crate::sync::Sync::*;
40use crate::tools::time;
41use crate::transport::{
42 ConfiguredCertificateChecks, ConfiguredLoginParam, ConfiguredServerLoginParam,
43 ConnectionCandidate, send_sync_transports,
44};
45use crate::{EventType, stock_str};
46use crate::{chat, provider};
47
48macro_rules! progress {
49 ($context:tt, $progress:expr, $comment:expr) => {
50 assert!(
51 $progress <= 1000,
52 "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
53 );
54 $context.emit_event($crate::events::EventType::ConfigureProgress {
55 progress: $progress,
56 comment: $comment,
57 });
58 };
59 ($context:tt, $progress:expr) => {
60 progress!($context, $progress, None);
61 };
62}
63
64impl Context {
65 pub async fn is_configured(&self) -> Result<bool> {
67 self.sql.exists("SELECT COUNT(*) FROM transports", ()).await
68 }
69
70 pub async fn configure(&self) -> Result<()> {
75 let mut param = EnteredLoginParam::load(self).await?;
76
77 self.add_transport_inner(&mut param).await
78 }
79
80 pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> {
110 self.stop_io().await;
111 let result = self.add_transport_inner(param).await;
112 if result.is_err() {
113 if let Ok(true) = self.is_configured().await {
114 self.start_io().await;
115 }
116 return result;
117 }
118 self.start_io().await;
119 Ok(())
120 }
121
122 pub(crate) async fn add_transport_inner(&self, param: &mut EnteredLoginParam) -> Result<()> {
123 ensure!(
124 !self.scheduler.is_running().await,
125 "cannot configure, already running"
126 );
127 ensure!(
128 self.sql.is_open().await,
129 "cannot configure, database not opened."
130 );
131 param.addr = addr_normalize(¶m.addr);
132 let cancel_channel = self.alloc_ongoing().await?;
133
134 let res = self
135 .inner_configure(param)
136 .race(cancel_channel.recv().map(|_| Err(format_err!("Canceled"))))
137 .await;
138
139 self.free_ongoing().await;
140
141 if let Err(err) = res.as_ref() {
142 let error_msg = stock_str::configuration_failed(self, &format!("{err:#}")).await;
145 progress!(self, 0, Some(error_msg.clone()));
146 bail!(error_msg);
147 } else {
148 param.save(self).await?;
149 progress!(self, 1000);
150 }
151
152 res
153 }
154
155 pub async fn add_transport_from_qr(&self, qr: &str) -> Result<()> {
159 self.stop_io().await;
160
161 let result = async move {
162 let mut param = match crate::qr::check_qr(self, qr).await? {
163 crate::qr::Qr::Account { .. } => login_param_from_account_qr(self, qr).await?,
164 crate::qr::Qr::Login { address, options } => {
165 login_param_from_login_qr(&address, options)?
166 }
167 _ => bail!("QR code does not contain account"),
168 };
169 self.add_transport_inner(&mut param).await?;
170 Ok(())
171 }
172 .await;
173
174 if result.is_err() {
175 if let Ok(true) = self.is_configured().await {
176 self.start_io().await;
177 }
178 return result;
179 }
180 self.start_io().await;
181 Ok(())
182 }
183
184 pub async fn list_transports(&self) -> Result<Vec<EnteredLoginParam>> {
188 let transports = self
189 .sql
190 .query_map_vec("SELECT entered_param FROM transports", (), |row| {
191 let entered_param: String = row.get(0)?;
192 let transport: EnteredLoginParam = serde_json::from_str(&entered_param)?;
193 Ok(transport)
194 })
195 .await?;
196
197 Ok(transports)
198 }
199
200 pub async fn count_transports(&self) -> Result<usize> {
202 self.sql.count("SELECT COUNT(*) FROM transports", ()).await
203 }
204
205 pub async fn delete_transport(&self, addr: &str) -> Result<()> {
208 let now = time();
209 self.sql
210 .transaction(|transaction| {
211 let primary_addr = transaction.query_row(
212 "SELECT value FROM config WHERE keyname='configured_addr'",
213 (),
214 |row| {
215 let addr: String = row.get(0)?;
216 Ok(addr)
217 },
218 )?;
219
220 if primary_addr == addr {
221 bail!("Cannot delete primary transport");
222 }
223 let (transport_id, add_timestamp) = transaction.query_row(
224 "DELETE FROM transports WHERE addr=? RETURNING id, add_timestamp",
225 (addr,),
226 |row| {
227 let id: u32 = row.get(0)?;
228 let add_timestamp: i64 = row.get(1)?;
229 Ok((id, add_timestamp))
230 },
231 )?;
232 transaction.execute("DELETE FROM imap WHERE transport_id=?", (transport_id,))?;
233 transaction.execute(
234 "DELETE FROM imap_sync WHERE transport_id=?",
235 (transport_id,),
236 )?;
237
238 let remove_timestamp = std::cmp::max(now, add_timestamp);
241
242 transaction.execute(
243 "INSERT INTO removed_transports (addr, remove_timestamp)
244 VALUES (?, ?)
245 ON CONFLICT (addr)
246 DO UPDATE SET remove_timestamp = excluded.remove_timestamp",
247 (addr, remove_timestamp),
248 )?;
249
250 Ok(())
251 })
252 .await?;
253 send_sync_transports(self).await?;
254
255 Ok(())
256 }
257
258 async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
259 info!(self, "Configure ...");
260
261 let old_addr = self.get_config(Config::ConfiguredAddr).await?;
262 if old_addr.is_some()
263 && !self
264 .sql
265 .exists(
266 "SELECT COUNT(*) FROM transports WHERE addr=?",
267 (¶m.addr,),
268 )
269 .await?
270 {
271 if self.get_config(Config::MvboxMove).await?.as_deref() != Some("0") {
272 bail!("Cannot use multi-transport with mvbox_move enabled.");
273 }
274 if self.get_config(Config::OnlyFetchMvbox).await?.as_deref() != Some("0") {
275 bail!("Cannot use multi-transport with only_fetch_mvbox enabled.");
276 }
277 if self.get_config(Config::ShowEmails).await?.as_deref() != Some("2") {
278 bail!("Cannot use multi-transport with disabled fetching of classic emails.");
279 }
280 }
281
282 let provider = configure(self, param).await?;
283 self.set_config_internal(Config::NotifyAboutWrongPw, Some("1"))
284 .await?;
285 on_configure_completed(self, provider).await?;
286 Ok(())
287 }
288}
289
290async fn on_configure_completed(
291 context: &Context,
292 provider: Option<&'static Provider>,
293) -> Result<()> {
294 if let Some(provider) = provider {
295 if let Some(config_defaults) = provider.config_defaults {
296 for def in config_defaults {
297 if !context.config_exists(def.key).await? {
298 info!(context, "apply config_defaults {}={}", def.key, def.value);
299 context
300 .set_config_ex(Nosync, def.key, Some(def.value))
301 .await?;
302 } else {
303 info!(
304 context,
305 "skip already set config_defaults {}={}", def.key, def.value
306 );
307 }
308 }
309 }
310
311 if !provider.after_login_hint.is_empty() {
312 let mut msg = Message::new_text(provider.after_login_hint.to_string());
313 if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg))
314 .await
315 .is_err()
316 {
317 warn!(context, "cannot add after_login_hint as core-provider-info");
318 }
319 }
320 }
321
322 Ok(())
323}
324
325async fn get_configured_param(
328 ctx: &Context,
329 param: &EnteredLoginParam,
330) -> Result<ConfiguredLoginParam> {
331 ensure!(!param.addr.is_empty(), "Missing email address.");
332
333 ensure!(!param.imap.password.is_empty(), "Missing (IMAP) password.");
334
335 let smtp_password = if param.smtp.password.is_empty() {
337 param.imap.password.clone()
338 } else {
339 param.smtp.password.clone()
340 };
341
342 let mut addr = param.addr.clone();
343 if param.oauth2 {
344 progress!(ctx, 10);
347 if let Some(oauth2_addr) = get_oauth2_addr(ctx, ¶m.addr, ¶m.imap.password)
348 .await?
349 .and_then(|e| e.parse().ok())
350 {
351 info!(ctx, "Authorized address is {}", oauth2_addr);
352 addr = oauth2_addr;
353 ctx.sql
354 .set_raw_config("addr", Some(param.addr.as_str()))
355 .await?;
356 }
357 progress!(ctx, 20);
358 }
359 let parsed = EmailAddress::new(¶m.addr).context("Bad email-address")?;
362 let param_domain = parsed.domain;
363
364 progress!(ctx, 200);
365
366 let provider;
367 let param_autoconfig;
368 if param.imap.server.is_empty()
369 && param.imap.port == 0
370 && param.imap.security == Socket::Automatic
371 && param.imap.user.is_empty()
372 && param.smtp.server.is_empty()
373 && param.smtp.port == 0
374 && param.smtp.security == Socket::Automatic
375 && param.smtp.user.is_empty()
376 {
377 info!(
379 ctx,
380 "checking internal provider-info for offline autoconfig"
381 );
382
383 provider = provider::get_provider_info(¶m_domain);
384 if let Some(provider) = provider {
385 if provider.server.is_empty() {
386 info!(ctx, "Offline autoconfig found, but no servers defined.");
387 param_autoconfig = None;
388 } else {
389 info!(ctx, "Offline autoconfig found.");
390 let servers = provider
391 .server
392 .iter()
393 .map(|s| ServerParams {
394 protocol: s.protocol,
395 socket: s.socket,
396 hostname: s.hostname.to_string(),
397 port: s.port,
398 username: match s.username_pattern {
399 UsernamePattern::Email => param.addr.to_string(),
400 UsernamePattern::Emaillocalpart => {
401 if let Some(at) = param.addr.find('@') {
402 param.addr.split_at(at).0.to_string()
403 } else {
404 param.addr.to_string()
405 }
406 }
407 },
408 })
409 .collect();
410
411 param_autoconfig = Some(servers)
412 }
413 } else {
414 info!(ctx, "No offline autoconfig found.");
416 param_autoconfig = get_autoconfig(ctx, param, ¶m_domain).await;
417 }
418 } else {
419 provider = None;
420 param_autoconfig = None;
421 }
422
423 progress!(ctx, 500);
424
425 let mut servers = param_autoconfig.unwrap_or_default();
426 if !servers
427 .iter()
428 .any(|server| server.protocol == Protocol::Imap)
429 {
430 servers.push(ServerParams {
431 protocol: Protocol::Imap,
432 hostname: param.imap.server.clone(),
433 port: param.imap.port,
434 socket: param.imap.security,
435 username: param.imap.user.clone(),
436 })
437 }
438 if !servers
439 .iter()
440 .any(|server| server.protocol == Protocol::Smtp)
441 {
442 servers.push(ServerParams {
443 protocol: Protocol::Smtp,
444 hostname: param.smtp.server.clone(),
445 port: param.smtp.port,
446 socket: param.smtp.security,
447 username: param.smtp.user.clone(),
448 })
449 }
450
451 let servers = expand_param_vector(servers, ¶m.addr, ¶m_domain);
452
453 let configured_login_param = ConfiguredLoginParam {
454 addr,
455 imap: servers
456 .iter()
457 .filter_map(|params| {
458 let Ok(security) = params.socket.try_into() else {
459 return None;
460 };
461 if params.protocol == Protocol::Imap {
462 Some(ConfiguredServerLoginParam {
463 connection: ConnectionCandidate {
464 host: params.hostname.clone(),
465 port: params.port,
466 security,
467 },
468 user: params.username.clone(),
469 })
470 } else {
471 None
472 }
473 })
474 .collect(),
475 imap_user: param.imap.user.clone(),
476 imap_password: param.imap.password.clone(),
477 smtp: servers
478 .iter()
479 .filter_map(|params| {
480 let Ok(security) = params.socket.try_into() else {
481 return None;
482 };
483 if params.protocol == Protocol::Smtp {
484 Some(ConfiguredServerLoginParam {
485 connection: ConnectionCandidate {
486 host: params.hostname.clone(),
487 port: params.port,
488 security,
489 },
490 user: params.username.clone(),
491 })
492 } else {
493 None
494 }
495 })
496 .collect(),
497 smtp_user: param.smtp.user.clone(),
498 smtp_password,
499 provider,
500 certificate_checks: match param.certificate_checks {
501 EnteredCertificateChecks::Automatic => ConfiguredCertificateChecks::Automatic,
502 EnteredCertificateChecks::Strict => ConfiguredCertificateChecks::Strict,
503 EnteredCertificateChecks::AcceptInvalidCertificates
504 | EnteredCertificateChecks::AcceptInvalidCertificates2 => {
505 ConfiguredCertificateChecks::AcceptInvalidCertificates
506 }
507 },
508 oauth2: param.oauth2,
509 };
510 Ok(configured_login_param)
511}
512
513async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'static Provider>> {
514 progress!(ctx, 1);
515
516 let ctx2 = ctx.clone();
517 let update_device_chats_handle = task::spawn(async move { ctx2.update_device_chats().await });
518
519 let configured_param = get_configured_param(ctx, param).await?;
520 let proxy_config = ProxyConfig::load(ctx).await?;
521 let strict_tls = configured_param.strict_tls(proxy_config.is_some());
522
523 progress!(ctx, 550);
524
525 let context_smtp = ctx.clone();
528 let smtp_param = configured_param.smtp.clone();
529 let smtp_password = configured_param.smtp_password.clone();
530 let smtp_addr = configured_param.addr.clone();
531
532 let proxy_config2 = proxy_config.clone();
533 let smtp_config_task = task::spawn(async move {
534 let mut smtp = Smtp::new();
535 smtp.connect(
536 &context_smtp,
537 &smtp_param,
538 &smtp_password,
539 &proxy_config2,
540 &smtp_addr,
541 strict_tls,
542 configured_param.oauth2,
543 )
544 .await?;
545
546 Ok::<(), anyhow::Error>(())
547 });
548
549 progress!(ctx, 600);
550
551 let transport_id = 0;
554 let (_s, r) = async_channel::bounded(1);
555 let mut imap = Imap::new(ctx, transport_id, configured_param.clone(), r).await?;
556 let configuring = true;
557 if let Err(err) = imap.connect(ctx, configuring).await {
558 bail!(
559 "{}",
560 nicer_configuration_error(ctx, format!("{err:#}")).await
561 );
562 };
563
564 progress!(ctx, 850);
565
566 smtp_config_task.await??;
568
569 progress!(ctx, 900);
570
571 let is_configured = ctx.is_configured().await?;
572 if !is_configured {
573 ctx.sql.set_raw_config("mvbox_move", Some("0")).await?;
574 ctx.sql.set_raw_config("only_fetch_mvbox", None).await?;
575 }
576
577 drop(imap);
578
579 progress!(ctx, 910);
580
581 let provider = configured_param.provider;
582 configured_param
583 .clone()
584 .save_to_transports_table(ctx, param, time())
585 .await?;
586 send_sync_transports(ctx).await?;
587
588 ctx.set_config_internal(Config::ConfiguredTimestamp, Some(&time().to_string()))
589 .await?;
590
591 progress!(ctx, 920);
592
593 ctx.set_config_internal(Config::FetchedExistingMsgs, config::from_bool(false))
594 .await?;
595 ctx.scheduler.interrupt_inbox().await;
596
597 progress!(ctx, 940);
598 update_device_chats_handle.await??;
599
600 ctx.sql.set_raw_config_bool("configured", true).await?;
601 ctx.emit_event(EventType::AccountsItemChanged);
602
603 Ok(provider)
604}
605
606async fn get_autoconfig(
611 ctx: &Context,
612 param: &EnteredLoginParam,
613 param_domain: &str,
614) -> Option<Vec<ServerParams>> {
615 let param_addr_urlencoded =
623 utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
624
625 if let Ok(res) = moz_autoconfigure(
626 ctx,
627 &format!(
628 "https://autoconfig.{param_domain}/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
629 ),
630 ¶m.addr,
631 )
632 .await
633 {
634 return Some(res);
635 }
636 progress!(ctx, 300);
637
638 if let Ok(res) = moz_autoconfigure(
639 ctx,
640 &format!(
642 "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
643 ¶m_domain, ¶m_addr_urlencoded
644 ),
645 ¶m.addr,
646 )
647 .await
648 {
649 return Some(res);
650 }
651 progress!(ctx, 310);
652
653 if let Ok(res) = outlk_autodiscover(
655 ctx,
656 format!("https://{}/autodiscover/autodiscover.xml", ¶m_domain),
657 )
658 .await
659 {
660 return Some(res);
661 }
662 progress!(ctx, 320);
663
664 if let Ok(res) = outlk_autodiscover(
665 ctx,
666 format!(
667 "https://autodiscover.{}/autodiscover/autodiscover.xml",
668 ¶m_domain
669 ),
670 )
671 .await
672 {
673 return Some(res);
674 }
675 progress!(ctx, 330);
676
677 if let Ok(res) = moz_autoconfigure(
679 ctx,
680 &format!("https://autoconfig.thunderbird.net/v1.1/{}", ¶m_domain),
681 ¶m.addr,
682 )
683 .await
684 {
685 return Some(res);
686 }
687
688 None
689}
690
691async fn nicer_configuration_error(context: &Context, e: String) -> String {
692 if e.to_lowercase().contains("could not resolve")
693 || e.to_lowercase().contains("connection attempts")
694 || e.to_lowercase()
695 .contains("temporary failure in name resolution")
696 || e.to_lowercase().contains("name or service not known")
697 || e.to_lowercase()
698 .contains("failed to lookup address information")
699 {
700 return stock_str::error_no_network(context).await;
701 }
702
703 e
704}
705
706#[derive(Debug, thiserror::Error)]
707pub enum Error {
708 #[error("Invalid email address: {0:?}")]
709 InvalidEmailAddress(String),
710
711 #[error("XML error at position {position}: {error}")]
712 InvalidXml {
713 position: u64,
714 #[source]
715 error: quick_xml::Error,
716 },
717
718 #[error("Number of redirection is exceeded")]
719 Redirection,
720
721 #[error("{0:#}")]
722 Other(#[from] anyhow::Error),
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728 use crate::config::Config;
729 use crate::login_param::EnteredServerLoginParam;
730 use crate::test_utils::TestContext;
731
732 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
733 async fn test_no_panic_on_bad_credentials() {
734 let t = TestContext::new().await;
735 t.set_config(Config::Addr, Some("probably@unexistant.addr"))
736 .await
737 .unwrap();
738 t.set_config(Config::MailPw, Some("123456")).await.unwrap();
739 assert!(t.configure().await.is_err());
740 }
741
742 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
743 async fn test_get_configured_param() -> Result<()> {
744 let t = &TestContext::new().await;
745 let entered_param = EnteredLoginParam {
746 addr: "alice@example.org".to_string(),
747
748 imap: EnteredServerLoginParam {
749 user: "alice@example.net".to_string(),
750 password: "foobar".to_string(),
751 ..Default::default()
752 },
753
754 ..Default::default()
755 };
756 let configured_param = get_configured_param(t, &entered_param).await?;
757 assert_eq!(configured_param.imap_user, "alice@example.net");
758 assert_eq!(configured_param.smtp_user, "");
759 Ok(())
760 }
761}