rework smtp

This commit is contained in:
Matt Hill
2026-02-23 14:25:51 -07:00
parent 804560d43c
commit e9b9925c0e
14 changed files with 333 additions and 107 deletions

View File

@@ -3124,7 +3124,7 @@ help.arg.smtp-from:
fr_FR: "Adresse de l'expéditeur"
pl_PL: "Adres nadawcy e-mail"
help.arg.smtp-login:
help.arg.smtp-username:
en_US: "SMTP authentication username"
de_DE: "SMTP-Authentifizierungsbenutzername"
es_ES: "Nombre de usuario de autenticación SMTP"
@@ -3145,13 +3145,20 @@ help.arg.smtp-port:
fr_FR: "Port du serveur SMTP"
pl_PL: "Port serwera SMTP"
help.arg.smtp-server:
help.arg.smtp-host:
en_US: "SMTP server hostname"
de_DE: "SMTP-Server-Hostname"
es_ES: "Nombre de host del servidor SMTP"
fr_FR: "Nom d'hôte du serveur SMTP"
pl_PL: "Nazwa hosta serwera SMTP"
help.arg.smtp-security:
en_US: "Connection security mode (starttls or tls)"
de_DE: "Verbindungssicherheitsmodus (starttls oder tls)"
es_ES: "Modo de seguridad de conexión (starttls o tls)"
fr_FR: "Mode de sécurité de connexion (starttls ou tls)"
pl_PL: "Tryb zabezpieczeń połączenia (starttls lub tls)"
help.arg.smtp-to:
en_US: "Email recipient address"
de_DE: "E-Mail-Empfängeradresse"

View File

@@ -1049,20 +1049,36 @@ async fn get_disk_info() -> Result<MetricsDisk, Error> {
})
}
#[derive(
Debug, Clone, Copy, Default, serde::Serialize, serde::Deserialize, TS, clap::ValueEnum,
)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum SmtpSecurity {
#[default]
Starttls,
Tls,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SmtpValue {
#[arg(long, help = "help.arg.smtp-server")]
pub server: String,
#[arg(long, help = "help.arg.smtp-host")]
#[serde(alias = "server")]
pub host: String,
#[arg(long, help = "help.arg.smtp-port")]
pub port: u16,
#[arg(long, help = "help.arg.smtp-from")]
pub from: String,
#[arg(long, help = "help.arg.smtp-login")]
pub login: String,
#[arg(long, help = "help.arg.smtp-username")]
#[serde(alias = "login")]
pub username: String,
#[arg(long, help = "help.arg.smtp-password")]
pub password: Option<String>,
#[arg(long, help = "help.arg.smtp-security")]
#[serde(default)]
pub security: SmtpSecurity,
}
pub async fn set_system_smtp(ctx: RpcContext, smtp: SmtpValue) -> Result<(), Error> {
let smtp = Some(smtp);
@@ -1121,47 +1137,63 @@ pub async fn set_ifconfig_url(
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct TestSmtpParams {
#[arg(long, help = "help.arg.smtp-server")]
pub server: String,
#[arg(long, help = "help.arg.smtp-host")]
pub host: String,
#[arg(long, help = "help.arg.smtp-port")]
pub port: u16,
#[arg(long, help = "help.arg.smtp-from")]
pub from: String,
#[arg(long, help = "help.arg.smtp-to")]
pub to: String,
#[arg(long, help = "help.arg.smtp-login")]
pub login: String,
#[arg(long, help = "help.arg.smtp-username")]
pub username: String,
#[arg(long, help = "help.arg.smtp-password")]
pub password: String,
#[arg(long, help = "help.arg.smtp-security")]
#[serde(default)]
pub security: SmtpSecurity,
}
pub async fn test_smtp(
_: RpcContext,
TestSmtpParams {
server,
host,
port,
from,
to,
login,
username,
password,
security,
}: TestSmtpParams,
) -> Result<(), Error> {
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use lettre::transport::smtp::client::{Tls, TlsParameters};
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
AsyncSmtpTransport::<Tokio1Executor>::relay(&server)?
.port(port)
.credentials(Credentials::new(login, password))
.build()
.send(
Message::builder()
.from(from.parse()?)
.to(to.parse()?)
.subject("StartOS Test Email")
.header(ContentType::TEXT_PLAIN)
.body("This is a test email sent from your StartOS Server".to_owned())?,
)
.await?;
let creds = Credentials::new(username, password);
let message = Message::builder()
.from(from.parse()?)
.to(to.parse()?)
.subject("StartOS Test Email")
.header(ContentType::TEXT_PLAIN)
.body("This is a test email sent from your StartOS Server".to_owned())?;
let transport = match security {
SmtpSecurity::Starttls => AsyncSmtpTransport::<Tokio1Executor>::relay(&host)?
.port(port)
.credentials(creds)
.build(),
SmtpSecurity::Tls => {
let tls = TlsParameters::new(host.clone())?;
AsyncSmtpTransport::<Tokio1Executor>::relay(&host)?
.port(port)
.tls(Tls::Wrapper(tls))
.credentials(creds)
.build()
}
};
transport.send(message).await?;
Ok(())
}

View File

@@ -166,6 +166,9 @@ impl VersionT for Version {
// Rebuild from actual assigned ports in all bindings
migrate_available_ports(db);
// Migrate SMTP: rename server->host, login->username, add security field
migrate_smtp(db);
Ok(migration_data)
}
@@ -242,6 +245,25 @@ fn migrate_available_ports(db: &mut Value) {
}
}
fn migrate_smtp(db: &mut Value) {
if let Some(smtp) = db
.get_mut("public")
.and_then(|p| p.get_mut("serverInfo"))
.and_then(|s| s.get_mut("smtp"))
.and_then(|s| s.as_object_mut())
{
if let Some(server) = smtp.remove("server") {
smtp.insert("host".into(), server);
}
if let Some(login) = smtp.remove("login") {
smtp.insert("username".into(), login);
}
if !smtp.contains_key("security") {
smtp.insert("security".into(), json!("starttls"));
}
}
}
fn migrate_host(host: Option<&mut Value>) {
let Some(host) = host.and_then(|h| h.as_object_mut()) else {
return;