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