/* * Copyright (C) 2026 Mateusz GruszczyƄski @linuxiarz.pl * Source-Available Code / Dual-Licensed. * * Free for non-commercial and evaluation use under terms of BSL/GPLv3. * Commercial or production use requires a valid paid license. * See LICENSE file in repository root for details. */ use lettre::message::Mailbox; use crate::state::{SmtpConfig, SmtpSecurity}; use super::values::ConfigValues; pub(super) fn load_smtp( values: &ConfigValues, ) -> Result, Box> { let Some(host) = values.optional("SMTP_HOST") else { return Ok(None); }; let port = values.get("SMTP_PORT", "587").parse()?; let security = match values.optional("SMTP_SECURITY") { Some(value) => parse_smtp_security(&value)?, None => smtp_security_for_port(port), }; Ok(Some(SmtpConfig { host, port, security, username: values.get("SMTP_USERNAME", ""), password: values.get("SMTP_PASSWORD", ""), from: normalize_smtp_from(values.required("SMTP_FROM", "SMTP_HOST is set")?)?, public_url: values.required("PUBLIC_URL", "SMTP_HOST is set")?, })) } fn smtp_security_for_port(port: u16) -> SmtpSecurity { match port { 465 => SmtpSecurity::Tls, 587 => SmtpSecurity::StartTls, _ => SmtpSecurity::None, } } fn parse_smtp_security(value: &str) -> Result> { match value.trim().to_ascii_lowercase().as_str() { "none" | "plain" => Ok(SmtpSecurity::None), "starttls" => Ok(SmtpSecurity::StartTls), "tls" | "ssl" | "smtps" => Ok(SmtpSecurity::Tls), _ => Err("SMTP_SECURITY must be one of: none, starttls, tls".into()), } } fn normalize_smtp_from(value: String) -> Result> { let trimmed = value.trim(); if trimmed.is_empty() { return Err("SMTP_FROM cannot be empty".into()); } if trimmed.parse::().is_ok() { return Ok(trimmed.to_owned()); } let unquoted = match (trimmed.as_bytes().first(), trimmed.as_bytes().last()) { (Some(b'"'), Some(b'"')) | (Some(b'\''), Some(b'\'')) if trimmed.len() >= 2 => { trimmed[1..trimmed.len() - 1].trim() } _ => return Err(format!("SMTP_FROM is not a valid mailbox: {trimmed}").into()), }; if unquoted.is_empty() { return Err("SMTP_FROM cannot be empty".into()); } unquoted .parse::() .map_err(|error| format!("SMTP_FROM is not a valid mailbox: {error}"))?; Ok(unquoted.to_owned()) } #[cfg(test)] #[path = "../tests/config_smtp.rs"] mod tests;