84 lines
2.6 KiB
Rust
84 lines
2.6 KiB
Rust
/*
|
|
* 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<Option<SmtpConfig>, Box<dyn std::error::Error>> {
|
|
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<SmtpSecurity, Box<dyn std::error::Error>> {
|
|
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<String, Box<dyn std::error::Error>> {
|
|
let trimmed = value.trim();
|
|
if trimmed.is_empty() {
|
|
return Err("SMTP_FROM cannot be empty".into());
|
|
}
|
|
if trimmed.parse::<Mailbox>().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::<Mailbox>()
|
|
.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;
|