1use std::fmt;
12use std::pin::Pin;
13
14use anyhow::{Context as _, Result, bail, format_err};
15use deltachat_contact_tools::{EmailAddress, addr_normalize};
16use serde::{Deserialize, Serialize};
17
18use crate::config::Config;
19use crate::configure::server_params::{ServerParams, expand_param_vector};
20use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
21use crate::context::Context;
22use crate::events::EventType;
23use crate::login_param::EnteredLoginParam;
24use crate::net::load_connection_timestamp;
25use crate::provider::{Protocol, Provider, Socket, UsernamePattern, get_provider_by_id};
26use crate::sql::Sql;
27use crate::sync::{RemovedTransportData, SyncData, TransportData};
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub(crate) enum ConnectionSecurity {
31 Tls,
33
34 Starttls,
36
37 Plain,
39}
40
41impl fmt::Display for ConnectionSecurity {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::Tls => write!(f, "tls")?,
45 Self::Starttls => write!(f, "starttls")?,
46 Self::Plain => write!(f, "plain")?,
47 }
48 Ok(())
49 }
50}
51
52impl TryFrom<Socket> for ConnectionSecurity {
53 type Error = anyhow::Error;
54
55 fn try_from(socket: Socket) -> Result<Self> {
56 match socket {
57 Socket::Automatic => Err(format_err!("Socket security is not configured")),
58 Socket::Ssl => Ok(Self::Tls),
59 Socket::Starttls => Ok(Self::Starttls),
60 Socket::Plain => Ok(Self::Plain),
61 }
62 }
63}
64
65#[derive(
67 Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize, Deserialize,
68)]
69#[repr(u32)]
70#[strum(serialize_all = "snake_case")]
71pub(crate) enum ConfiguredCertificateChecks {
72 OldAutomatic = 0,
84
85 Strict = 1,
87
88 AcceptInvalidCertificates = 2,
91
92 AcceptInvalidCertificates2 = 3,
97
98 Automatic = 4,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
105pub(crate) struct ConnectionCandidate {
106 pub host: String,
108
109 pub port: u16,
111
112 pub security: ConnectionSecurity,
114}
115
116impl fmt::Display for ConnectionCandidate {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}:{}:{}", &self.host, self.port, self.security)?;
119 Ok(())
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub(crate) struct ConfiguredServerLoginParam {
125 pub connection: ConnectionCandidate,
126
127 pub user: String,
129}
130
131impl fmt::Display for ConfiguredServerLoginParam {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(f, "{}:{}", self.connection, &self.user)?;
134 Ok(())
135 }
136}
137
138pub(crate) async fn prioritize_server_login_params(
139 sql: &Sql,
140 params: &[ConfiguredServerLoginParam],
141 alpn: &str,
142) -> Result<Vec<ConfiguredServerLoginParam>> {
143 let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
144 for param in params {
145 let timestamp = load_connection_timestamp(
146 sql,
147 alpn,
148 ¶m.connection.host,
149 param.connection.port,
150 None,
151 )
152 .await?;
153 res.push((timestamp, param.clone()));
154 }
155 res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
156 Ok(res.into_iter().map(|(_ts, param)| param).collect())
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
162pub(crate) struct ConfiguredLoginParam {
163 pub addr: String,
165
166 pub imap: Vec<ConfiguredServerLoginParam>,
167
168 pub imap_user: String,
173
174 pub imap_password: String,
175
176 pub smtp: Vec<ConfiguredServerLoginParam>,
177
178 pub smtp_user: String,
183
184 pub smtp_password: String,
185
186 pub provider: Option<&'static Provider>,
187
188 pub certificate_checks: ConfiguredCertificateChecks,
191
192 pub oauth2: bool,
194}
195
196#[derive(Debug, Serialize, Deserialize)]
199pub(crate) struct ConfiguredLoginParamJson {
200 pub addr: String,
201 pub imap: Vec<ConfiguredServerLoginParam>,
202 pub imap_user: String,
203 pub imap_password: String,
204 pub smtp: Vec<ConfiguredServerLoginParam>,
205 pub smtp_user: String,
206 pub smtp_password: String,
207 pub provider_id: Option<String>,
208 pub certificate_checks: ConfiguredCertificateChecks,
209 pub oauth2: bool,
210}
211
212impl fmt::Display for ConfiguredLoginParam {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 let addr = &self.addr;
215 let provider_id = match self.provider {
216 Some(provider) => provider.id,
217 None => "none",
218 };
219 let certificate_checks = self.certificate_checks;
220 write!(f, "{addr} imap:[")?;
221 let mut first = true;
222 for imap in &self.imap {
223 if !first {
224 write!(f, ", ")?;
225 }
226 write!(f, "{imap}")?;
227 first = false;
228 }
229 write!(f, "] smtp:[")?;
230 let mut first = true;
231 for smtp in &self.smtp {
232 if !first {
233 write!(f, ", ")?;
234 }
235 write!(f, "{smtp}")?;
236 first = false;
237 }
238 write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
239 Ok(())
240 }
241}
242
243impl ConfiguredLoginParam {
244 pub(crate) async fn load(context: &Context) -> Result<Option<(u32, Self)>> {
250 let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
251 return Ok(None);
252 };
253
254 let Some((id, json)) = context
255 .sql
256 .query_row_optional(
257 "SELECT id, configured_param FROM transports WHERE addr=?",
258 (&self_addr,),
259 |row| {
260 let id: u32 = row.get(0)?;
261 let json: String = row.get(1)?;
262 Ok((id, json))
263 },
264 )
265 .await?
266 else {
267 bail!("Self address {self_addr} doesn't have a corresponding transport");
268 };
269 Ok(Some((id, Self::from_json(&json)?)))
270 }
271
272 pub(crate) async fn load_all(context: &Context) -> Result<Vec<(u32, Self)>> {
277 context
278 .sql
279 .query_map_vec("SELECT id, configured_param FROM transports", (), |row| {
280 let id: u32 = row.get(0)?;
281 let json: String = row.get(1)?;
282 let param = Self::from_json(&json)?;
283 Ok((id, param))
284 })
285 .await
286 }
287
288 pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
290 if !context.get_config_bool(Config::Configured).await? {
291 return Ok(None);
292 }
293
294 let addr = context
295 .get_config(Config::ConfiguredAddr)
296 .await?
297 .unwrap_or_default()
298 .trim()
299 .to_string();
300
301 let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
302 context
303 .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
304 .await?
305 {
306 num_traits::FromPrimitive::from_i32(certificate_checks)
307 .context("Invalid configured_imap_certificate_checks value")?
308 } else {
309 ConfiguredCertificateChecks::OldAutomatic
312 };
313
314 let send_pw = context
315 .get_config(Config::ConfiguredSendPw)
316 .await?
317 .context("SMTP password is not configured")?;
318 let mail_pw = context
319 .get_config(Config::ConfiguredMailPw)
320 .await?
321 .context("IMAP password is not configured")?;
322
323 let server_flags = context
324 .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
325 .await?
326 .unwrap_or_default();
327 let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
328
329 let provider = context
330 .get_config(Config::ConfiguredProvider)
331 .await?
332 .and_then(|cfg| get_provider_by_id(&cfg));
333
334 let imap;
335 let smtp;
336
337 let mail_user = context
338 .get_config(Config::ConfiguredMailUser)
339 .await?
340 .unwrap_or_default();
341 let send_user = context
342 .get_config(Config::ConfiguredSendUser)
343 .await?
344 .unwrap_or_default();
345
346 if let Some(provider) = provider {
347 let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
348 let addr_localpart = parsed_addr.local;
349
350 if provider.server.is_empty() {
351 let servers = vec![
352 ServerParams {
353 protocol: Protocol::Imap,
354 hostname: context
355 .get_config(Config::ConfiguredMailServer)
356 .await?
357 .unwrap_or_default(),
358 port: context
359 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
360 .await?
361 .unwrap_or_default(),
362 socket: context
363 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
364 .await?
365 .and_then(num_traits::FromPrimitive::from_i32)
366 .unwrap_or_default(),
367 username: mail_user.clone(),
368 },
369 ServerParams {
370 protocol: Protocol::Smtp,
371 hostname: context
372 .get_config(Config::ConfiguredSendServer)
373 .await?
374 .unwrap_or_default(),
375 port: context
376 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
377 .await?
378 .unwrap_or_default(),
379 socket: context
380 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
381 .await?
382 .and_then(num_traits::FromPrimitive::from_i32)
383 .unwrap_or_default(),
384 username: send_user.clone(),
385 },
386 ];
387 let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
388 imap = servers
389 .iter()
390 .filter_map(|params| {
391 let Ok(security) = params.socket.try_into() else {
392 return None;
393 };
394 if params.protocol == Protocol::Imap {
395 Some(ConfiguredServerLoginParam {
396 connection: ConnectionCandidate {
397 host: params.hostname.clone(),
398 port: params.port,
399 security,
400 },
401 user: params.username.clone(),
402 })
403 } else {
404 None
405 }
406 })
407 .collect();
408 smtp = servers
409 .iter()
410 .filter_map(|params| {
411 let Ok(security) = params.socket.try_into() else {
412 return None;
413 };
414 if params.protocol == Protocol::Smtp {
415 Some(ConfiguredServerLoginParam {
416 connection: ConnectionCandidate {
417 host: params.hostname.clone(),
418 port: params.port,
419 security,
420 },
421 user: params.username.clone(),
422 })
423 } else {
424 None
425 }
426 })
427 .collect();
428 } else {
429 imap = provider
430 .server
431 .iter()
432 .filter_map(|server| {
433 if server.protocol != Protocol::Imap {
434 return None;
435 }
436
437 let Ok(security) = server.socket.try_into() else {
438 return None;
439 };
440
441 Some(ConfiguredServerLoginParam {
442 connection: ConnectionCandidate {
443 host: server.hostname.to_string(),
444 port: server.port,
445 security,
446 },
447 user: if !mail_user.is_empty() {
448 mail_user.clone()
449 } else {
450 match server.username_pattern {
451 UsernamePattern::Email => addr.to_string(),
452 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
453 }
454 },
455 })
456 })
457 .collect();
458 smtp = provider
459 .server
460 .iter()
461 .filter_map(|server| {
462 if server.protocol != Protocol::Smtp {
463 return None;
464 }
465
466 let Ok(security) = server.socket.try_into() else {
467 return None;
468 };
469
470 Some(ConfiguredServerLoginParam {
471 connection: ConnectionCandidate {
472 host: server.hostname.to_string(),
473 port: server.port,
474 security,
475 },
476 user: if !send_user.is_empty() {
477 send_user.clone()
478 } else {
479 match server.username_pattern {
480 UsernamePattern::Email => addr.to_string(),
481 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
482 }
483 },
484 })
485 })
486 .collect();
487 }
488 } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
489 context.get_config(Config::ConfiguredImapServers).await?,
490 context.get_config(Config::ConfiguredSmtpServers).await?,
491 ) {
492 imap = serde_json::from_str(&configured_mail_servers)
493 .context("Failed to parse configured IMAP servers")?;
494 smtp = serde_json::from_str(&configured_send_servers)
495 .context("Failed to parse configured SMTP servers")?;
496 } else {
497 let mail_server = context
499 .get_config(Config::ConfiguredMailServer)
500 .await?
501 .unwrap_or_default();
502 let mail_port = context
503 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
504 .await?
505 .unwrap_or_default();
506
507 let mail_security: Socket = context
508 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
509 .await?
510 .and_then(num_traits::FromPrimitive::from_i32)
511 .unwrap_or_default();
512
513 let send_server = context
514 .get_config(Config::ConfiguredSendServer)
515 .await?
516 .context("SMTP server is not configured")?;
517 let send_port = context
518 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
519 .await?
520 .unwrap_or_default();
521 let send_security: Socket = context
522 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
523 .await?
524 .and_then(num_traits::FromPrimitive::from_i32)
525 .unwrap_or_default();
526
527 imap = vec![ConfiguredServerLoginParam {
528 connection: ConnectionCandidate {
529 host: mail_server,
530 port: mail_port,
531 security: mail_security.try_into()?,
532 },
533 user: mail_user.clone(),
534 }];
535 smtp = vec![ConfiguredServerLoginParam {
536 connection: ConnectionCandidate {
537 host: send_server,
538 port: send_port,
539 security: send_security.try_into()?,
540 },
541 user: send_user.clone(),
542 }];
543 }
544
545 Ok(Some(ConfiguredLoginParam {
546 addr,
547 imap,
548 imap_user: mail_user,
549 imap_password: mail_pw,
550 smtp,
551 smtp_user: send_user,
552 smtp_password: send_pw,
553 certificate_checks,
554 provider,
555 oauth2,
556 }))
557 }
558
559 pub(crate) async fn save_to_transports_table(
560 self,
561 context: &Context,
562 entered_param: &EnteredLoginParam,
563 timestamp: i64,
564 ) -> Result<()> {
565 let is_published = true;
566 save_transport(
567 context,
568 entered_param,
569 &self.into(),
570 timestamp,
571 is_published,
572 )
573 .await?;
574 Ok(())
575 }
576
577 pub(crate) fn from_json(json: &str) -> Result<Self> {
578 let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
579
580 let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
581
582 Ok(ConfiguredLoginParam {
583 addr: json.addr,
584 imap: json.imap,
585 imap_user: json.imap_user,
586 imap_password: json.imap_password,
587 smtp: json.smtp,
588 smtp_user: json.smtp_user,
589 smtp_password: json.smtp_password,
590 provider,
591 certificate_checks: json.certificate_checks,
592 oauth2: json.oauth2,
593 })
594 }
595
596 pub(crate) fn into_json(self) -> Result<String> {
597 let json: ConfiguredLoginParamJson = self.into();
598 Ok(serde_json::to_string(&json)?)
599 }
600
601 pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
602 let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
603 match self.certificate_checks {
604 ConfiguredCertificateChecks::OldAutomatic => {
605 provider_strict_tls.unwrap_or(connected_through_proxy)
606 }
607 ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
608 ConfiguredCertificateChecks::Strict => true,
609 ConfiguredCertificateChecks::AcceptInvalidCertificates
610 | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
611 }
612 }
613}
614
615impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
616 fn from(configured_login_param: ConfiguredLoginParam) -> Self {
617 Self {
618 addr: configured_login_param.addr,
619 imap: configured_login_param.imap,
620 imap_user: configured_login_param.imap_user,
621 imap_password: configured_login_param.imap_password,
622 smtp: configured_login_param.smtp,
623 smtp_user: configured_login_param.smtp_user,
624 smtp_password: configured_login_param.smtp_password,
625 provider_id: configured_login_param.provider.map(|p| p.id.to_string()),
626 certificate_checks: configured_login_param.certificate_checks,
627 oauth2: configured_login_param.oauth2,
628 }
629 }
630}
631
632pub(crate) async fn save_transport(
635 context: &Context,
636 entered_param: &EnteredLoginParam,
637 configured: &ConfiguredLoginParamJson,
638 add_timestamp: i64,
639 is_published: bool,
640) -> Result<bool> {
641 let addr = addr_normalize(&configured.addr);
642 let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
643
644 let mut modified = context
645 .sql
646 .execute(
647 "INSERT INTO transports (addr, entered_param, configured_param, add_timestamp, is_published)
648 VALUES (?, ?, ?, ?, ?)
649 ON CONFLICT (addr)
650 DO UPDATE SET entered_param=excluded.entered_param,
651 configured_param=excluded.configured_param,
652 add_timestamp=excluded.add_timestamp,
653 is_published=excluded.is_published
654 WHERE entered_param != excluded.entered_param
655 OR configured_param != excluded.configured_param
656 OR add_timestamp < excluded.add_timestamp
657 OR is_published != excluded.is_published",
658 (
659 &addr,
660 serde_json::to_string(entered_param)?,
661 serde_json::to_string(configured)?,
662 add_timestamp,
663 is_published,
664 ),
665 )
666 .await?
667 > 0;
668
669 if configured_addr.is_none() {
670 context
672 .sql
673 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
674 .await?;
675 modified = true;
676 }
677 Ok(modified)
678}
679
680pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
682 info!(context, "Sending transport synchronization message.");
683
684 context.self_public_key.lock().await.take();
686
687 let transports = context
698 .sql
699 .query_map_vec(
700 "SELECT entered_param, configured_param, add_timestamp, is_published
701 FROM transports WHERE id>1",
702 (),
703 |row| {
704 let entered_json: String = row.get(0)?;
705 let entered: EnteredLoginParam = serde_json::from_str(&entered_json)?;
706 let configured_json: String = row.get(1)?;
707 let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
708 let timestamp: i64 = row.get(2)?;
709 let is_published: bool = row.get(3)?;
710 Ok(TransportData {
711 configured,
712 entered,
713 timestamp,
714 is_published,
715 })
716 },
717 )
718 .await?;
719 let removed_transports = context
720 .sql
721 .query_map_vec(
722 "SELECT addr, remove_timestamp FROM removed_transports",
723 (),
724 |row| {
725 let addr: String = row.get(0)?;
726 let timestamp: i64 = row.get(1)?;
727 Ok(RemovedTransportData { addr, timestamp })
728 },
729 )
730 .await?;
731 context
732 .add_sync_item(SyncData::Transports {
733 transports,
734 removed_transports,
735 })
736 .await?;
737 context.scheduler.interrupt_smtp().await;
738
739 Ok(())
740}
741
742pub(crate) async fn sync_transports(
744 context: &Context,
745 transports: &[TransportData],
746 removed_transports: &[RemovedTransportData],
747) -> Result<()> {
748 let mut modified = false;
749 for TransportData {
750 configured,
751 entered,
752 timestamp,
753 is_published,
754 } in transports
755 {
756 modified |= save_transport(context, entered, configured, *timestamp, *is_published).await?;
757 }
758
759 context
760 .sql
761 .transaction(|transaction| {
762 for RemovedTransportData { addr, timestamp } in removed_transports {
763 modified |= transaction.execute(
764 "DELETE FROM transports
765 WHERE addr=? AND add_timestamp<=?",
766 (addr, timestamp),
767 )? > 0;
768 transaction.execute(
769 "INSERT INTO removed_transports (addr, remove_timestamp)
770 VALUES (?, ?)
771 ON CONFLICT (addr) DO
772 UPDATE SET remove_timestamp = excluded.remove_timestamp
773 WHERE excluded.remove_timestamp > remove_timestamp",
774 (addr, timestamp),
775 )?;
776 }
777 Ok(())
778 })
779 .await?;
780
781 if modified {
782 context.self_public_key.lock().await.take();
783 tokio::task::spawn(restart_io_if_running_boxed(context.clone()));
784 context.emit_event(EventType::TransportsModified);
785 }
786 Ok(())
787}
788
789fn restart_io_if_running_boxed(context: Context) -> Pin<Box<dyn Future<Output = ()> + Send>> {
792 Box::pin(async move { context.restart_io_if_running().await })
793}
794
795pub(crate) async fn add_pseudo_transport(context: &Context, addr: &str) -> Result<()> {
797 context.sql
798 .execute(
799 "INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
800 (
801 addr,
802 serde_json::to_string(&EnteredLoginParam{addr: addr.to_string(), ..Default::default()})?,
803 format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
804 ),
805 )
806 .await?;
807 Ok(())
808}
809
810#[cfg(test)]
811mod transport_tests;