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::Config;
27use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT;
28use crate::context::Context;
29use crate::imap::Imap;
30use crate::log::warn;
31pub use crate::login_param::EnteredLoginParam;
32use crate::login_param::{EnteredCertificateChecks, TransportListEntry};
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
48pub(crate) const MAX_TRANSPORT_RELAYS: usize = 5;
51
52macro_rules! progress {
53 ($context:tt, $progress:expr, $comment:expr) => {
54 assert!(
55 $progress <= 1000,
56 "value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
57 );
58 $context.emit_event($crate::events::EventType::ConfigureProgress {
59 progress: $progress,
60 comment: $comment,
61 });
62 };
63 ($context:tt, $progress:expr) => {
64 progress!($context, $progress, None);
65 };
66}
67
68impl Context {
69 pub async fn is_configured(&self) -> Result<bool> {
71 self.sql.exists("SELECT COUNT(*) FROM transports", ()).await
72 }
73
74 pub async fn configure(&self) -> Result<()> {
79 let mut param = EnteredLoginParam::load_legacy(self).await?;
80
81 self.add_transport_inner(&mut param).await
82 }
83
84 pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> {
115 self.stop_io().await;
116 let result = self.add_transport_inner(param).await;
117 if result.is_err() {
118 if let Ok(true) = self.is_configured().await {
119 self.start_io().await;
120 }
121 return result;
122 }
123 self.start_io().await;
124 Ok(())
125 }
126
127 pub(crate) async fn add_transport_inner(&self, param: &mut EnteredLoginParam) -> Result<()> {
128 ensure!(
129 !self.scheduler.is_running().await,
130 "cannot configure, already running"
131 );
132 ensure!(
133 self.sql.is_open().await,
134 "cannot configure, database not opened."
135 );
136 param.addr = addr_normalize(¶m.addr);
137 let cancel_channel = self.alloc_ongoing().await?;
138
139 let res = self
140 .inner_configure(param)
141 .race(cancel_channel.recv().map(|_| Err(format_err!("Canceled"))))
142 .await;
143
144 self.free_ongoing().await;
145
146 if let Err(err) = res.as_ref() {
147 let error_msg = stock_str::configuration_failed(self, &format!("{err:#}"));
150 progress!(self, 0, Some(error_msg.clone()));
151 bail!(error_msg);
152 } else {
153 param.save_legacy(self).await?;
154 progress!(self, 1000);
155 }
156
157 res
158 }
159
160 pub async fn add_transport_from_qr(&self, qr: &str) -> Result<()> {
164 self.stop_io().await;
165
166 let result = async move {
167 let mut param = match crate::qr::check_qr(self, qr).await? {
168 crate::qr::Qr::Account { .. } => login_param_from_account_qr(self, qr).await?,
169 crate::qr::Qr::Login { address, options } => {
170 login_param_from_login_qr(&address, options)?
171 }
172 _ => bail!("QR code does not contain account"),
173 };
174 self.add_transport_inner(&mut param).await?;
175 Ok(())
176 }
177 .await;
178
179 if result.is_err() {
180 if let Ok(true) = self.is_configured().await {
181 self.start_io().await;
182 }
183 return result;
184 }
185 self.start_io().await;
186 Ok(())
187 }
188
189 pub async fn list_transports(&self) -> Result<Vec<TransportListEntry>> {
193 let transports = self
194 .sql
195 .query_map_vec(
196 "SELECT entered_param, is_published FROM transports",
197 (),
198 |row| {
199 let param: String = row.get(0)?;
200 let param: EnteredLoginParam = serde_json::from_str(¶m)?;
201 let is_published: bool = row.get(1)?;
202 Ok(TransportListEntry {
203 param,
204 is_unpublished: !is_published,
205 })
206 },
207 )
208 .await?;
209
210 Ok(transports)
211 }
212
213 pub async fn count_transports(&self) -> Result<usize> {
215 self.sql.count("SELECT COUNT(*) FROM transports", ()).await
216 }
217
218 pub async fn delete_transport(&self, addr: &str) -> Result<()> {
221 let now = time();
222 let removed_transport_id = self
223 .sql
224 .transaction(|transaction| {
225 let primary_addr = transaction.query_row(
226 "SELECT value FROM config WHERE keyname='configured_addr'",
227 (),
228 |row| {
229 let addr: String = row.get(0)?;
230 Ok(addr)
231 },
232 )?;
233
234 if primary_addr == addr {
235 bail!("Cannot delete primary transport");
236 }
237 let (transport_id, add_timestamp) = transaction.query_row(
238 "DELETE FROM transports WHERE addr=? RETURNING id, add_timestamp",
239 (addr,),
240 |row| {
241 let id: u32 = row.get(0)?;
242 let add_timestamp: i64 = row.get(1)?;
243 Ok((id, add_timestamp))
244 },
245 )?;
246
247 let remove_timestamp = std::cmp::max(now, add_timestamp);
250
251 transaction.execute(
252 "INSERT INTO removed_transports (addr, remove_timestamp)
253 VALUES (?, ?)
254 ON CONFLICT (addr)
255 DO UPDATE SET remove_timestamp = excluded.remove_timestamp",
256 (addr, remove_timestamp),
257 )?;
258
259 Ok(transport_id)
260 })
261 .await?;
262 send_sync_transports(self).await?;
263 self.quota.write().await.remove(&removed_transport_id);
264 self.restart_io_if_running().await;
265
266 Ok(())
267 }
268
269 pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> {
280 self.sql
281 .transaction(|trans| {
282 let primary_addr: String = trans
283 .query_row(
284 "SELECT value FROM config WHERE keyname='configured_addr'",
285 (),
286 |row| row.get(0),
287 )
288 .context("Select primary address")?;
289 if primary_addr == addr && unpublished {
290 bail!("Can't set primary relay as unpublished");
291 }
292 trans
295 .execute(
296 "UPDATE transports SET is_published=?, add_timestamp=? WHERE addr=? AND is_published!=?1",
297 (!unpublished, time(), addr),
298 )
299 .context("Update transports")?;
300 Ok(())
301 })
302 .await?;
303 send_sync_transports(self).await?;
304 Ok(())
305 }
306
307 async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
308 info!(self, "Configure ...");
309
310 let old_addr = self.get_config(Config::ConfiguredAddr).await?;
311 if old_addr.is_some()
312 && !self
313 .sql
314 .exists(
315 "SELECT COUNT(*) FROM transports WHERE addr=?",
316 (¶m.addr,),
317 )
318 .await?
319 && self
320 .sql
321 .count("SELECT COUNT(*) FROM transports", ())
322 .await?
323 >= MAX_TRANSPORT_RELAYS
324 {
325 bail!(
326 "You have reached the maximum number of relays ({}).",
327 MAX_TRANSPORT_RELAYS
328 )
329 }
330
331 let provider = match configure(self, param).await {
332 Err(error) => {
333 let configured_param = get_configured_param(self, param).await;
335 warn!(
336 self,
337 "configure failed: Entered params: {}. Used params: {}. Error: {error}.",
338 param.to_string(),
339 configured_param
340 .map(|param| param.to_string())
341 .unwrap_or("error".to_owned())
342 );
343 return Err(error);
344 }
345 Ok(provider) => provider,
346 };
347 self.set_config_internal(Config::NotifyAboutWrongPw, Some("1"))
348 .await?;
349 on_configure_completed(self, provider).await?;
350 Ok(())
351 }
352}
353
354async fn on_configure_completed(
355 context: &Context,
356 provider: Option<&'static Provider>,
357) -> Result<()> {
358 if let Some(provider) = provider {
359 if let Some(config_defaults) = provider.config_defaults {
360 for def in config_defaults {
361 if !context.config_exists(def.key).await? {
362 info!(context, "apply config_defaults {}={}", def.key, def.value);
363 context
364 .set_config_ex(Nosync, def.key, Some(def.value))
365 .await?;
366 } else {
367 info!(
368 context,
369 "skip already set config_defaults {}={}", def.key, def.value
370 );
371 }
372 }
373 }
374
375 if !provider.after_login_hint.is_empty() {
376 let mut msg = Message::new_text(provider.after_login_hint.to_string());
377 if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg))
378 .await
379 .is_err()
380 {
381 warn!(context, "cannot add after_login_hint as core-provider-info");
382 }
383 }
384 }
385
386 Ok(())
387}
388
389async fn get_configured_param(
392 ctx: &Context,
393 param: &EnteredLoginParam,
394) -> Result<ConfiguredLoginParam> {
395 ensure!(!param.addr.is_empty(), "Missing email address.");
396
397 ensure!(!param.imap.password.is_empty(), "Missing (IMAP) password.");
398
399 let smtp_password = if param.smtp.password.is_empty() {
401 param.imap.password.clone()
402 } else {
403 param.smtp.password.clone()
404 };
405
406 let mut addr = param.addr.clone();
407 if param.oauth2 {
408 progress!(ctx, 10);
411 if let Some(oauth2_addr) = get_oauth2_addr(ctx, ¶m.addr, ¶m.imap.password)
412 .await?
413 .and_then(|e| e.parse().ok())
414 {
415 info!(ctx, "Authorized address is {}", oauth2_addr);
416 addr = oauth2_addr;
417 ctx.sql
418 .set_raw_config("addr", Some(param.addr.as_str()))
419 .await?;
420 }
421 progress!(ctx, 20);
422 }
423 let parsed = EmailAddress::new(¶m.addr).context("Bad email-address")?;
426 let param_domain = parsed.domain;
427
428 progress!(ctx, 200);
429
430 let provider;
431 let param_autoconfig;
432 if param.imap.server.is_empty()
433 && param.imap.port == 0
434 && param.imap.security == Socket::Automatic
435 && param.imap.user.is_empty()
436 && param.smtp.server.is_empty()
437 && param.smtp.port == 0
438 && param.smtp.security == Socket::Automatic
439 && param.smtp.user.is_empty()
440 {
441 info!(
443 ctx,
444 "checking internal provider-info for offline autoconfig"
445 );
446
447 provider = provider::get_provider_info(¶m_domain);
448 if let Some(provider) = provider {
449 if provider.server.is_empty() {
450 info!(ctx, "Offline autoconfig found, but no servers defined.");
451 param_autoconfig = None;
452 } else {
453 info!(ctx, "Offline autoconfig found.");
454 let servers = provider
455 .server
456 .iter()
457 .map(|s| ServerParams {
458 protocol: s.protocol,
459 socket: s.socket,
460 hostname: s.hostname.to_string(),
461 port: s.port,
462 username: match s.username_pattern {
463 UsernamePattern::Email => param.addr.to_string(),
464 UsernamePattern::Emaillocalpart => {
465 if let Some(at) = param.addr.find('@') {
466 param.addr.split_at(at).0.to_string()
467 } else {
468 param.addr.to_string()
469 }
470 }
471 },
472 })
473 .collect();
474
475 param_autoconfig = Some(servers)
476 }
477 } else {
478 info!(ctx, "No offline autoconfig found.");
480 param_autoconfig = get_autoconfig(ctx, param, ¶m_domain).await;
481 }
482 } else {
483 provider = None;
484 param_autoconfig = None;
485 }
486
487 progress!(ctx, 500);
488
489 let mut servers = param_autoconfig.unwrap_or_default();
490 if !servers
491 .iter()
492 .any(|server| server.protocol == Protocol::Imap)
493 {
494 servers.push(ServerParams {
495 protocol: Protocol::Imap,
496 hostname: param.imap.server.clone(),
497 port: param.imap.port,
498 socket: param.imap.security,
499 username: param.imap.user.clone(),
500 })
501 }
502 if !servers
503 .iter()
504 .any(|server| server.protocol == Protocol::Smtp)
505 {
506 servers.push(ServerParams {
507 protocol: Protocol::Smtp,
508 hostname: param.smtp.server.clone(),
509 port: param.smtp.port,
510 socket: param.smtp.security,
511 username: param.smtp.user.clone(),
512 })
513 }
514
515 let servers = expand_param_vector(servers, ¶m.addr, ¶m_domain);
516
517 let configured_login_param = ConfiguredLoginParam {
518 addr,
519 imap: servers
520 .iter()
521 .filter_map(|params| {
522 let Ok(security) = params.socket.try_into() else {
523 return None;
524 };
525 if params.protocol == Protocol::Imap {
526 Some(ConfiguredServerLoginParam {
527 connection: ConnectionCandidate {
528 host: params.hostname.clone(),
529 port: params.port,
530 security,
531 },
532 user: params.username.clone(),
533 })
534 } else {
535 None
536 }
537 })
538 .collect(),
539 imap_user: param.imap.user.clone(),
540 imap_password: param.imap.password.clone(),
541 imap_folder: Some(param.imap.folder.clone()).filter(|folder| !folder.is_empty()),
542 smtp: servers
543 .iter()
544 .filter_map(|params| {
545 let Ok(security) = params.socket.try_into() else {
546 return None;
547 };
548 if params.protocol == Protocol::Smtp {
549 Some(ConfiguredServerLoginParam {
550 connection: ConnectionCandidate {
551 host: params.hostname.clone(),
552 port: params.port,
553 security,
554 },
555 user: params.username.clone(),
556 })
557 } else {
558 None
559 }
560 })
561 .collect(),
562 smtp_user: param.smtp.user.clone(),
563 smtp_password,
564 provider,
565 certificate_checks: match param.certificate_checks {
566 EnteredCertificateChecks::Automatic => ConfiguredCertificateChecks::Automatic,
567 EnteredCertificateChecks::Strict => ConfiguredCertificateChecks::Strict,
568 EnteredCertificateChecks::AcceptInvalidCertificates
569 | EnteredCertificateChecks::AcceptInvalidCertificates2 => {
570 ConfiguredCertificateChecks::AcceptInvalidCertificates
571 }
572 },
573 oauth2: param.oauth2,
574 };
575 Ok(configured_login_param)
576}
577
578async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'static Provider>> {
579 progress!(ctx, 1);
580
581 let configured_param = get_configured_param(ctx, param).await?;
582 let proxy_config = ProxyConfig::load(ctx).await?;
583 let strict_tls = configured_param.strict_tls(proxy_config.is_some());
584
585 progress!(ctx, 550);
586
587 let context_smtp = ctx.clone();
590 let smtp_param = configured_param.smtp.clone();
591 let smtp_password = configured_param.smtp_password.clone();
592 let smtp_addr = configured_param.addr.clone();
593
594 let proxy_config2 = proxy_config.clone();
595 let smtp_config_task = task::spawn(async move {
596 let mut smtp = Smtp::new();
597 smtp.connect(
598 &context_smtp,
599 &smtp_param,
600 &smtp_password,
601 &proxy_config2,
602 &smtp_addr,
603 strict_tls,
604 configured_param.oauth2,
605 )
606 .await?;
607
608 Ok::<(), anyhow::Error>(())
609 });
610
611 progress!(ctx, 600);
612
613 let transport_id = 0;
616 let (_s, r) = async_channel::bounded(1);
617 let mut imap = Imap::new(ctx, transport_id, configured_param.clone(), r).await?;
618 let configuring = true;
619 let imap_session = match imap.connect(ctx, configuring).await {
620 Ok(imap_session) => imap_session,
621 Err(err) => {
622 bail!("{}", nicer_configuration_error(ctx, format!("{err:#}")));
623 }
624 };
625
626 progress!(ctx, 850);
627
628 smtp_config_task.await??;
630
631 progress!(ctx, 900);
632
633 let is_configured = ctx.is_configured().await?;
634 if !ctx.get_config_bool(Config::FixIsChatmail).await? {
635 if imap_session.is_chatmail() {
636 ctx.sql.set_raw_config("is_chatmail", Some("1")).await?;
637 } else if !is_configured {
638 ctx.sql.set_raw_config("is_chatmail", Some("0")).await?;
641 }
642 }
643
644 drop(imap_session);
645 drop(imap);
646
647 progress!(ctx, 910);
648
649 let provider = configured_param.provider;
650 configured_param
651 .clone()
652 .save_to_transports_table(ctx, param, time())
653 .await?;
654 send_sync_transports(ctx).await?;
655
656 ctx.set_config_internal(Config::ConfiguredTimestamp, Some(&time().to_string()))
657 .await?;
658
659 progress!(ctx, 920);
660
661 ctx.scheduler.interrupt_inbox().await;
662
663 progress!(ctx, 940);
664 ctx.update_device_chats()
665 .await
666 .context("Failed to update device chats")?;
667
668 ctx.sql.set_raw_config_bool("configured", true).await?;
669 ctx.emit_event(EventType::AccountsItemChanged);
670
671 Ok(provider)
672}
673
674async fn get_autoconfig(
679 ctx: &Context,
680 param: &EnteredLoginParam,
681 param_domain: &str,
682) -> Option<Vec<ServerParams>> {
683 let param_addr_urlencoded =
691 utf8_percent_encode(¶m.addr, NON_ALPHANUMERIC_WITHOUT_DOT).to_string();
692
693 if let Ok(res) = moz_autoconfigure(
694 ctx,
695 &format!(
696 "https://autoconfig.{param_domain}/mail/config-v1.1.xml?emailaddress={param_addr_urlencoded}"
697 ),
698 ¶m.addr,
699 )
700 .await
701 {
702 return Some(res);
703 }
704 progress!(ctx, 300);
705
706 if let Ok(res) = moz_autoconfigure(
707 ctx,
708 &format!(
710 "https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
711 ¶m_domain, ¶m_addr_urlencoded
712 ),
713 ¶m.addr,
714 )
715 .await
716 {
717 return Some(res);
718 }
719 progress!(ctx, 310);
720
721 if let Ok(res) = outlk_autodiscover(
723 ctx,
724 format!("https://{}/autodiscover/autodiscover.xml", ¶m_domain),
725 )
726 .await
727 {
728 return Some(res);
729 }
730 progress!(ctx, 320);
731
732 if let Ok(res) = outlk_autodiscover(
733 ctx,
734 format!(
735 "https://autodiscover.{}/autodiscover/autodiscover.xml",
736 ¶m_domain
737 ),
738 )
739 .await
740 {
741 return Some(res);
742 }
743 progress!(ctx, 330);
744
745 if let Ok(res) = moz_autoconfigure(
747 ctx,
748 &format!("https://autoconfig.thunderbird.net/v1.1/{}", ¶m_domain),
749 ¶m.addr,
750 )
751 .await
752 {
753 return Some(res);
754 }
755
756 None
757}
758
759fn nicer_configuration_error(context: &Context, e: String) -> String {
760 if e.to_lowercase().contains("could not resolve")
761 || e.to_lowercase().contains("connection attempts")
762 || e.to_lowercase()
763 .contains("temporary failure in name resolution")
764 || e.to_lowercase().contains("name or service not known")
765 || e.to_lowercase()
766 .contains("failed to lookup address information")
767 {
768 return stock_str::error_no_network(context);
769 }
770
771 e
772}
773
774#[derive(Debug, thiserror::Error)]
775pub enum Error {
776 #[error("Invalid email address: {0:?}")]
777 InvalidEmailAddress(String),
778
779 #[error("XML error at position {position}: {error}")]
780 InvalidXml {
781 position: u64,
782 #[source]
783 error: quick_xml::Error,
784 },
785
786 #[error("Number of redirection is exceeded")]
787 Redirection,
788
789 #[error("{0:#}")]
790 Other(#[from] anyhow::Error),
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796 use crate::config::Config;
797 use crate::login_param::EnteredImapLoginParam;
798 use crate::test_utils::TestContext;
799
800 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
801 async fn test_no_panic_on_bad_credentials() {
802 let t = TestContext::new().await;
803 t.set_config(Config::Addr, Some("probably@unexistant.addr"))
804 .await
805 .unwrap();
806 t.set_config(Config::MailPw, Some("123456")).await.unwrap();
807 assert!(t.configure().await.is_err());
808 }
809
810 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
811 async fn test_get_configured_param() -> Result<()> {
812 let t = &TestContext::new().await;
813 let entered_param = EnteredLoginParam {
814 addr: "alice@example.org".to_string(),
815
816 imap: EnteredImapLoginParam {
817 user: "alice@example.net".to_string(),
818 password: "foobar".to_string(),
819 ..Default::default()
820 },
821
822 ..Default::default()
823 };
824 let configured_param = get_configured_param(t, &entered_param).await?;
825 assert_eq!(configured_param.imap_user, "alice@example.net");
826 assert_eq!(configured_param.smtp_user, "");
827 Ok(())
828 }
829}