Refactor sendEmail function and add support for multiple SMTP auth methods (LOGIN, PLAIN, CRAM-MD5) and TLS verification option, fix syntax error in sendSMTPMessage function

This commit is contained in:
2026-01-22 19:34:05 +01:00
parent 4e61fdf9f4
commit 90d4ff4e9a
11 changed files with 275 additions and 81 deletions

View File

@@ -46,6 +46,8 @@ type SMTPSettings struct {
Password string `json:"password"` Password string `json:"password"`
From string `json:"from"` From string `json:"from"`
UseTLS bool `json:"useTLS"` UseTLS bool `json:"useTLS"`
InsecureSkipVerify bool `json:"insecureSkipVerify"`
AuthMethod string `json:"authMethod"`
} }
// AppSettings holds the main UI settings and Fail2ban configuration // AppSettings holds the main UI settings and Fail2ban configuration
@@ -419,6 +421,8 @@ func applyAppSettingsRecordLocked(rec storage.AppSettingsRecord) {
Password: rec.SMTPPassword, Password: rec.SMTPPassword,
From: rec.SMTPFrom, From: rec.SMTPFrom,
UseTLS: rec.SMTPUseTLS, UseTLS: rec.SMTPUseTLS,
InsecureSkipVerify: rec.SMTPInsecureSkipVerify,
AuthMethod: rec.SMTPAuthMethod,
} }
if rec.AlertCountriesJSON != "" { if rec.AlertCountriesJSON != "" {
@@ -510,6 +514,8 @@ func toAppSettingsRecordLocked() (storage.AppSettingsRecord, error) {
SMTPPassword: currentSettings.SMTP.Password, SMTPPassword: currentSettings.SMTP.Password,
SMTPFrom: currentSettings.SMTP.From, SMTPFrom: currentSettings.SMTP.From,
SMTPUseTLS: currentSettings.SMTP.UseTLS, SMTPUseTLS: currentSettings.SMTP.UseTLS,
SMTPInsecureSkipVerify: currentSettings.SMTP.InsecureSkipVerify,
SMTPAuthMethod: currentSettings.SMTP.AuthMethod,
// Fail2Ban DEFAULT settings // Fail2Ban DEFAULT settings
BantimeIncrement: currentSettings.BantimeIncrement, BantimeIncrement: currentSettings.BantimeIncrement,
DefaultJailEnable: currentSettings.DefaultJailEnable, DefaultJailEnable: currentSettings.DefaultJailEnable,
@@ -654,6 +660,9 @@ func setDefaultsLocked() {
if !currentSettings.SMTP.UseTLS { if !currentSettings.SMTP.UseTLS {
currentSettings.SMTP.UseTLS = true currentSettings.SMTP.UseTLS = true
} }
if currentSettings.SMTP.AuthMethod == "" {
currentSettings.SMTP.AuthMethod = "auto"
}
if len(currentSettings.IgnoreIPs) == 0 { if len(currentSettings.IgnoreIPs) == 0 {
currentSettings.IgnoreIPs = []string{"127.0.0.1/8", "::1"} currentSettings.IgnoreIPs = []string{"127.0.0.1/8", "::1"}
} }

View File

@@ -142,6 +142,16 @@
"settings.smtp_host": "SMTP-Host", "settings.smtp_host": "SMTP-Host",
"settings.smtp_host_placeholder": "z.B. smtp.gmail.com", "settings.smtp_host_placeholder": "z.B. smtp.gmail.com",
"settings.smtp_port": "SMTP-Port", "settings.smtp_port": "SMTP-Port",
"settings.smtp_port_placeholder": "587",
"settings.smtp_port_hint": "Häufige Ports: 25 (unverschlüsselt), 587 (STARTTLS), 465 (SMTPS), 2525 (alternatives STARTTLS)",
"settings.smtp_auth_method": "Authentifizierungsmethode",
"settings.smtp_auth_method_auto": "Auto (LOGIN bevorzugt)",
"settings.smtp_auth_method_login": "LOGIN",
"settings.smtp_auth_method_plain": "PLAIN",
"settings.smtp_auth_method_cram_md5": "CRAM-MD5",
"settings.smtp_auth_method_hint": "LOGIN wird für Office365/Gmail empfohlen. PLAIN ist die Standard-SMTP-Authentifizierung. CRAM-MD5 ist challenge-response-basiert.",
"settings.smtp_insecure_skip_verify": "TLS-Zertifikatsüberprüfung überspringen",
"settings.smtp_insecure_skip_verify_warning": "⚠️ Nicht für Produktion empfohlen",
"settings.smtp_username": "SMTP-Benutzername", "settings.smtp_username": "SMTP-Benutzername",
"settings.smtp_username_placeholder": "z.B. user@example.com", "settings.smtp_username_placeholder": "z.B. user@example.com",
"settings.smtp_password": "SMTP-Passwort", "settings.smtp_password": "SMTP-Passwort",
@@ -150,6 +160,7 @@
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch", "settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "TLS verwenden (empfohlen)", "settings.smtp_tls": "TLS verwenden (empfohlen)",
"settings.send_test_email": "Test-E-Mail senden", "settings.send_test_email": "Test-E-Mail senden",
"settings.send_test_email_hint": "⚠️ Bitte speichern Sie zuerst Ihre SMTP-Einstellungen, bevor Sie eine Test-E-Mail senden.",
"settings.fail2ban": "Globale Standard-Fail2Ban-Konfigurationen", "settings.fail2ban": "Globale Standard-Fail2Ban-Konfigurationen",
"settings.fail2ban.description": "Diese Einstellungen werden auf allen aktivierten Fail2Ban-Servern angewendet und in deren jail.local [DEFAULT]-Abschnitt gespeichert.", "settings.fail2ban.description": "Diese Einstellungen werden auf allen aktivierten Fail2Ban-Servern angewendet und in deren jail.local [DEFAULT]-Abschnitt gespeichert.",
"settings.enable_bantime_increment": "Bantime-Inkrement aktivieren", "settings.enable_bantime_increment": "Bantime-Inkrement aktivieren",

View File

@@ -142,6 +142,16 @@
"settings.smtp_host": "SMTP-Host", "settings.smtp_host": "SMTP-Host",
"settings.smtp_host_placeholder": "z.B. smtp.gmail.com", "settings.smtp_host_placeholder": "z.B. smtp.gmail.com",
"settings.smtp_port": "SMTP-Port", "settings.smtp_port": "SMTP-Port",
"settings.smtp_port_placeholder": "587",
"settings.smtp_port_hint": "Hüfigi Ports: 25 (unverschlüsselt), 587 (STARTTLS), 465 (SMTPS), 2525 (alternativs STARTTLS)",
"settings.smtp_auth_method": "Authentifizierungsmethode",
"settings.smtp_auth_method_auto": "Auto (LOGIN bevorzugt)",
"settings.smtp_auth_method_login": "LOGIN",
"settings.smtp_auth_method_plain": "PLAIN",
"settings.smtp_auth_method_cram_md5": "CRAM-MD5",
"settings.smtp_auth_method_hint": "LOGIN wird für Office365/Gmail empfohle. PLAIN isch d Standard-SMTP-Authentifizierung. CRAM-MD5 isch challenge-response-basiert.",
"settings.smtp_insecure_skip_verify": "TLS-Zertifikatsüberprüfig überspringe",
"settings.smtp_insecure_skip_verify_warning": "⚠️ Nid fürd Produktion empfohle",
"settings.smtp_username": "SMTP-Benutzername", "settings.smtp_username": "SMTP-Benutzername",
"settings.smtp_username_placeholder": "z.B. user@example.com", "settings.smtp_username_placeholder": "z.B. user@example.com",
"settings.smtp_password": "SMTP-Passwort", "settings.smtp_password": "SMTP-Passwort",
@@ -150,6 +160,7 @@
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch", "settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "TLS bruuche (empfohlen)", "settings.smtp_tls": "TLS bruuche (empfohlen)",
"settings.send_test_email": "Test-Email schicke", "settings.send_test_email": "Test-Email schicke",
"settings.send_test_email_hint": "⚠️ Bitte speichere zersch dini SMTP-Iistellige, bevor du e Test-Email schicksch.",
"settings.fail2ban": "Globale Standard-Fail2Ban-Konfiguratione", "settings.fail2ban": "Globale Standard-Fail2Ban-Konfiguratione",
"settings.fail2ban.description": "Die Einstellige werde uf alli aktivierte Fail2Ban-Server aagwändet und i däre jail.local [DEFAULT]-Abschnitt gspeicheret.", "settings.fail2ban.description": "Die Einstellige werde uf alli aktivierte Fail2Ban-Server aagwändet und i däre jail.local [DEFAULT]-Abschnitt gspeicheret.",
"settings.enable_bantime_increment": "Bantime-Inkrement aktivierä", "settings.enable_bantime_increment": "Bantime-Inkrement aktivierä",

View File

@@ -142,6 +142,16 @@
"settings.smtp_host": "SMTP Host", "settings.smtp_host": "SMTP Host",
"settings.smtp_host_placeholder": "e.g., smtp.gmail.com", "settings.smtp_host_placeholder": "e.g., smtp.gmail.com",
"settings.smtp_port": "SMTP Port", "settings.smtp_port": "SMTP Port",
"settings.smtp_port_placeholder": "587",
"settings.smtp_port_hint": "Common ports: 25 (plain), 587 (STARTTLS), 465 (SMTPS), 2525 (alternative STARTTLS)",
"settings.smtp_auth_method": "Authentication Method",
"settings.smtp_auth_method_auto": "Auto (LOGIN preferred)",
"settings.smtp_auth_method_login": "LOGIN",
"settings.smtp_auth_method_plain": "PLAIN",
"settings.smtp_auth_method_cram_md5": "CRAM-MD5",
"settings.smtp_auth_method_hint": "LOGIN is recommended for Office365/Gmail. PLAIN is standard SMTP auth. CRAM-MD5 is challenge-response based.",
"settings.smtp_insecure_skip_verify": "Skip TLS Certificate Verification",
"settings.smtp_insecure_skip_verify_warning": "⚠️ Not recommended for production",
"settings.smtp_username": "SMTP Username", "settings.smtp_username": "SMTP Username",
"settings.smtp_username_placeholder": "e.g., user@example.com", "settings.smtp_username_placeholder": "e.g., user@example.com",
"settings.smtp_password": "SMTP Password", "settings.smtp_password": "SMTP Password",
@@ -150,6 +160,7 @@
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch", "settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Use TLS (Recommended)", "settings.smtp_tls": "Use TLS (Recommended)",
"settings.send_test_email": "Send Test Email", "settings.send_test_email": "Send Test Email",
"settings.send_test_email_hint": "⚠️ Please save your SMTP settings first before sending a test email.",
"settings.fail2ban": "Global Default Fail2Ban Configurations", "settings.fail2ban": "Global Default Fail2Ban Configurations",
"settings.fail2ban.description": "These settings will be applied to all enabled Fail2Ban servers and stored in their jail.local [DEFAULT] section.", "settings.fail2ban.description": "These settings will be applied to all enabled Fail2Ban servers and stored in their jail.local [DEFAULT] section.",
"settings.enable_bantime_increment": "Enable Bantime Increment", "settings.enable_bantime_increment": "Enable Bantime Increment",

View File

@@ -142,6 +142,16 @@
"settings.smtp_host": "Host SMTP", "settings.smtp_host": "Host SMTP",
"settings.smtp_host_placeholder": "p.ej., smtp.gmail.com", "settings.smtp_host_placeholder": "p.ej., smtp.gmail.com",
"settings.smtp_port": "Puerto SMTP", "settings.smtp_port": "Puerto SMTP",
"settings.smtp_port_placeholder": "587",
"settings.smtp_port_hint": "Puertos comunes: 25 (sin cifrar), 587 (STARTTLS), 465 (SMTPS), 2525 (STARTTLS alternativo)",
"settings.smtp_auth_method": "Método de Autenticación",
"settings.smtp_auth_method_auto": "Auto (LOGIN preferido)",
"settings.smtp_auth_method_login": "LOGIN",
"settings.smtp_auth_method_plain": "PLAIN",
"settings.smtp_auth_method_cram_md5": "CRAM-MD5",
"settings.smtp_auth_method_hint": "LOGIN se recomienda para Office365/Gmail. PLAIN es la autenticación SMTP estándar. CRAM-MD5 está basado en challenge-response.",
"settings.smtp_insecure_skip_verify": "Omitir Verificación de Certificado TLS",
"settings.smtp_insecure_skip_verify_warning": "⚠️ No recomendado para producción",
"settings.smtp_username": "Nombre de usuario SMTP", "settings.smtp_username": "Nombre de usuario SMTP",
"settings.smtp_username_placeholder": "p.ej., usuario@example.com", "settings.smtp_username_placeholder": "p.ej., usuario@example.com",
"settings.smtp_password": "Contraseña SMTP", "settings.smtp_password": "Contraseña SMTP",
@@ -150,6 +160,7 @@
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch", "settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Usar TLS (recomendado)", "settings.smtp_tls": "Usar TLS (recomendado)",
"settings.send_test_email": "Enviar correo de prueba", "settings.send_test_email": "Enviar correo de prueba",
"settings.send_test_email_hint": "⚠️ Por favor, guarde primero su configuración SMTP antes de enviar un correo de prueba.",
"settings.fail2ban": "Configuraciones Globales Predeterminadas de Fail2Ban", "settings.fail2ban": "Configuraciones Globales Predeterminadas de Fail2Ban",
"settings.fail2ban.description": "Estas configuraciones se aplicarán a todos los servidores Fail2Ban habilitados y se almacenarán en su sección [DEFAULT] de jail.local.", "settings.fail2ban.description": "Estas configuraciones se aplicarán a todos los servidores Fail2Ban habilitados y se almacenarán en su sección [DEFAULT] de jail.local.",
"settings.enable_bantime_increment": "Habilitar incremento de Bantime", "settings.enable_bantime_increment": "Habilitar incremento de Bantime",

View File

@@ -142,6 +142,16 @@
"settings.smtp_host": "Hôte SMTP", "settings.smtp_host": "Hôte SMTP",
"settings.smtp_host_placeholder": "par exemple, smtp.gmail.com", "settings.smtp_host_placeholder": "par exemple, smtp.gmail.com",
"settings.smtp_port": "Port SMTP", "settings.smtp_port": "Port SMTP",
"settings.smtp_port_placeholder": "587",
"settings.smtp_port_hint": "Ports communs: 25 (non chiffré), 587 (STARTTLS), 465 (SMTPS), 2525 (STARTTLS alternatif)",
"settings.smtp_auth_method": "Méthode d'Authentification",
"settings.smtp_auth_method_auto": "Auto (LOGIN préféré)",
"settings.smtp_auth_method_login": "LOGIN",
"settings.smtp_auth_method_plain": "PLAIN",
"settings.smtp_auth_method_cram_md5": "CRAM-MD5",
"settings.smtp_auth_method_hint": "LOGIN est recommandé pour Office365/Gmail. PLAIN est l'authentification SMTP standard. CRAM-MD5 est basé sur challenge-response.",
"settings.smtp_insecure_skip_verify": "Ignorer la Vérification du Certificat TLS",
"settings.smtp_insecure_skip_verify_warning": "⚠️ Non recommandé pour la production",
"settings.smtp_username": "Nom d'utilisateur SMTP", "settings.smtp_username": "Nom d'utilisateur SMTP",
"settings.smtp_username_placeholder": "par exemple, utilisateur@example.com", "settings.smtp_username_placeholder": "par exemple, utilisateur@example.com",
"settings.smtp_password": "Mot de passe SMTP", "settings.smtp_password": "Mot de passe SMTP",
@@ -150,6 +160,7 @@
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch", "settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Utiliser TLS (recommandé)", "settings.smtp_tls": "Utiliser TLS (recommandé)",
"settings.send_test_email": "Envoyer un email de test", "settings.send_test_email": "Envoyer un email de test",
"settings.send_test_email_hint": "⚠️ Veuillez d'abord enregistrer vos paramètres SMTP avant d'envoyer un email de test.",
"settings.fail2ban": "Configurations Globales par Défaut de Fail2Ban", "settings.fail2ban": "Configurations Globales par Défaut de Fail2Ban",
"settings.fail2ban.description": "Ces paramètres seront appliqués à tous les serveurs Fail2Ban activés et stockés dans leur section [DEFAULT] de jail.local.", "settings.fail2ban.description": "Ces paramètres seront appliqués à tous les serveurs Fail2Ban activés et stockés dans leur section [DEFAULT] de jail.local.",
"settings.enable_bantime_increment": "Activer l'incrémentation du Bantime", "settings.enable_bantime_increment": "Activer l'incrémentation du Bantime",

View File

@@ -142,6 +142,16 @@
"settings.smtp_host": "Host SMTP", "settings.smtp_host": "Host SMTP",
"settings.smtp_host_placeholder": "es. smtp.gmail.com", "settings.smtp_host_placeholder": "es. smtp.gmail.com",
"settings.smtp_port": "Porta SMTP", "settings.smtp_port": "Porta SMTP",
"settings.smtp_port_placeholder": "587",
"settings.smtp_port_hint": "Porte comuni: 25 (non crittografato), 587 (STARTTLS), 465 (SMTPS), 2525 (STARTTLS alternativo)",
"settings.smtp_auth_method": "Metodo di Autenticazione",
"settings.smtp_auth_method_auto": "Auto (LOGIN preferito)",
"settings.smtp_auth_method_login": "LOGIN",
"settings.smtp_auth_method_plain": "PLAIN",
"settings.smtp_auth_method_cram_md5": "CRAM-MD5",
"settings.smtp_auth_method_hint": "LOGIN è raccomandato per Office365/Gmail. PLAIN è l'autenticazione SMTP standard. CRAM-MD5 è basato su challenge-response.",
"settings.smtp_insecure_skip_verify": "Ignora Verifica Certificato TLS",
"settings.smtp_insecure_skip_verify_warning": "⚠️ Non raccomandato per la produzione",
"settings.smtp_username": "Nome utente SMTP", "settings.smtp_username": "Nome utente SMTP",
"settings.smtp_username_placeholder": "es. utente@example.com", "settings.smtp_username_placeholder": "es. utente@example.com",
"settings.smtp_password": "Password SMTP", "settings.smtp_password": "Password SMTP",
@@ -150,6 +160,7 @@
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch", "settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Usa TLS (raccomandato)", "settings.smtp_tls": "Usa TLS (raccomandato)",
"settings.send_test_email": "Invia email di test", "settings.send_test_email": "Invia email di test",
"settings.send_test_email_hint": "⚠️ Si prega di salvare prima le impostazioni SMTP prima di inviare un'email di test.",
"settings.fail2ban": "Configurazioni Globali Predefinite di Fail2Ban", "settings.fail2ban": "Configurazioni Globali Predefinite di Fail2Ban",
"settings.fail2ban.description": "Queste impostazioni verranno applicate a tutti i server Fail2Ban abilitati e memorizzate nella loro sezione [DEFAULT] di jail.local.", "settings.fail2ban.description": "Queste impostazioni verranno applicate a tutti i server Fail2Ban abilitati e memorizzate nella loro sezione [DEFAULT] di jail.local.",
"settings.enable_bantime_increment": "Abilita incremento del Bantime", "settings.enable_bantime_increment": "Abilita incremento del Bantime",

View File

@@ -69,6 +69,8 @@ type AppSettingsRecord struct {
SMTPPassword string SMTPPassword string
SMTPFrom string SMTPFrom string
SMTPUseTLS bool SMTPUseTLS bool
SMTPInsecureSkipVerify bool
SMTPAuthMethod string
// Fail2Ban DEFAULT settings // Fail2Ban DEFAULT settings
BantimeIncrement bool BantimeIncrement bool
DefaultJailEnable bool DefaultJailEnable bool
@@ -192,17 +194,17 @@ func GetAppSettings(ctx context.Context) (AppSettingsRecord, bool, error) {
} }
row := db.QueryRowContext(ctx, ` row := db.QueryRowContext(ctx, `
SELECT language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines, console_output SELECT language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines, console_output, smtp_insecure_skip_verify, smtp_auth_method
FROM app_settings FROM app_settings
WHERE id = 1`) WHERE id = 1`)
var ( var (
lang, callback, callbackSecret, alerts, smtpHost, smtpUser, smtpPass, smtpFrom, ignoreIP, bantime, findtime, destemail, banaction, banactionAllports, advancedActions, geoipProvider, geoipDatabasePath sql.NullString lang, callback, callbackSecret, alerts, smtpHost, smtpUser, smtpPass, smtpFrom, ignoreIP, bantime, findtime, destemail, banaction, banactionAllports, advancedActions, geoipProvider, geoipDatabasePath, smtpAuthMethod sql.NullString
port, smtpPort, maxretry, maxLogLines sql.NullInt64 port, smtpPort, maxretry, maxLogLines sql.NullInt64
debug, restartNeeded, smtpTLS, bantimeInc, defaultJailEn, emailAlertsForBans, emailAlertsForUnbans, consoleOutput sql.NullInt64 debug, restartNeeded, smtpTLS, bantimeInc, defaultJailEn, emailAlertsForBans, emailAlertsForUnbans, consoleOutput, smtpInsecureSkipVerify sql.NullInt64
) )
err := row.Scan(&lang, &port, &debug, &restartNeeded, &callback, &callbackSecret, &alerts, &emailAlertsForBans, &emailAlertsForUnbans, &smtpHost, &smtpPort, &smtpUser, &smtpPass, &smtpFrom, &smtpTLS, &bantimeInc, &defaultJailEn, &ignoreIP, &bantime, &findtime, &maxretry, &destemail, &banaction, &banactionAllports, &advancedActions, &geoipProvider, &geoipDatabasePath, &maxLogLines, &consoleOutput) err := row.Scan(&lang, &port, &debug, &restartNeeded, &callback, &callbackSecret, &alerts, &emailAlertsForBans, &emailAlertsForUnbans, &smtpHost, &smtpPort, &smtpUser, &smtpPass, &smtpFrom, &smtpTLS, &bantimeInc, &defaultJailEn, &ignoreIP, &bantime, &findtime, &maxretry, &destemail, &banaction, &banactionAllports, &advancedActions, &geoipProvider, &geoipDatabasePath, &maxLogLines, &consoleOutput, &smtpInsecureSkipVerify, &smtpAuthMethod)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return AppSettingsRecord{}, false, nil return AppSettingsRecord{}, false, nil
} }
@@ -230,6 +232,8 @@ WHERE id = 1`)
SMTPPassword: stringFromNull(smtpPass), SMTPPassword: stringFromNull(smtpPass),
SMTPFrom: stringFromNull(smtpFrom), SMTPFrom: stringFromNull(smtpFrom),
SMTPUseTLS: intToBool(intFromNull(smtpTLS)), SMTPUseTLS: intToBool(intFromNull(smtpTLS)),
SMTPInsecureSkipVerify: intToBool(intFromNull(smtpInsecureSkipVerify)),
SMTPAuthMethod: stringFromNull(smtpAuthMethod),
// Fail2Ban DEFAULT settings // Fail2Ban DEFAULT settings
BantimeIncrement: intToBool(intFromNull(bantimeInc)), BantimeIncrement: intToBool(intFromNull(bantimeInc)),
DefaultJailEnable: intToBool(intFromNull(defaultJailEn)), DefaultJailEnable: intToBool(intFromNull(defaultJailEn)),
@@ -258,9 +262,9 @@ func SaveAppSettings(ctx context.Context, rec AppSettingsRecord) error {
} }
_, err := db.ExecContext(ctx, ` _, err := db.ExecContext(ctx, `
INSERT INTO app_settings ( INSERT INTO app_settings (
id, language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines, console_output id, language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines, console_output, smtp_insecure_skip_verify, smtp_auth_method
) VALUES ( ) VALUES (
1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
) ON CONFLICT(id) DO UPDATE SET ) ON CONFLICT(id) DO UPDATE SET
language = excluded.language, language = excluded.language,
port = excluded.port, port = excluded.port,
@@ -290,7 +294,9 @@ INSERT INTO app_settings (
geoip_provider = excluded.geoip_provider, geoip_provider = excluded.geoip_provider,
geoip_database_path = excluded.geoip_database_path, geoip_database_path = excluded.geoip_database_path,
max_log_lines = excluded.max_log_lines, max_log_lines = excluded.max_log_lines,
console_output = excluded.console_output console_output = excluded.console_output,
smtp_insecure_skip_verify = excluded.smtp_insecure_skip_verify,
smtp_auth_method = excluded.smtp_auth_method
`, rec.Language, `, rec.Language,
rec.Port, rec.Port,
boolToInt(rec.Debug), boolToInt(rec.Debug),
@@ -319,7 +325,9 @@ INSERT INTO app_settings (
rec.GeoIPProvider, rec.GeoIPProvider,
rec.GeoIPDatabasePath, rec.GeoIPDatabasePath,
rec.MaxLogLines, rec.MaxLogLines,
boolToInt(rec.ConsoleOutput)) boolToInt(rec.ConsoleOutput),
boolToInt(rec.SMTPInsecureSkipVerify),
rec.SMTPAuthMethod)
return err return err
} }
@@ -947,6 +955,18 @@ CREATE INDEX IF NOT EXISTS idx_perm_blocks_status ON permanent_blocks(status);
return err return err
} }
} }
// Migration: Add new SMTP columns if they don't exist
if _, err := db.ExecContext(ctx, `ALTER TABLE app_settings ADD COLUMN smtp_insecure_skip_verify INTEGER DEFAULT 0`); err != nil {
if err != nil && !strings.Contains(strings.ToLower(err.Error()), "duplicate column name") {
return err
}
}
if _, err := db.ExecContext(ctx, `ALTER TABLE app_settings ADD COLUMN smtp_auth_method TEXT DEFAULT 'auto'`); err != nil {
if err != nil && !strings.Contains(strings.ToLower(err.Error()), "duplicate column name") {
return err
}
}
_ = strings.Contains // Keep strings import for migration example above _ = strings.Contains // Keep strings import for migration example above
return nil return nil

View File

@@ -2413,10 +2413,19 @@ func isLOTRModeActive(alertCountries []string) bool {
func sendEmail(to, subject, body string, settings config.AppSettings) error { func sendEmail(to, subject, body string, settings config.AppSettings) error {
// Validate SMTP settings // Validate SMTP settings
if settings.SMTP.Host == "" || settings.SMTP.Username == "" || settings.SMTP.Password == "" || settings.SMTP.From == "" { if settings.SMTP.Host == "" || settings.SMTP.Username == "" || settings.SMTP.Password == "" || settings.SMTP.From == "" {
return errors.New("SMTP settings are incomplete. Please configure all required fields") err := errors.New("SMTP settings are incomplete. Please configure all required fields")
log.Printf("❌ sendEmail validation failed: %v (Host: %q, Username: %q, From: %q)", err, settings.SMTP.Host, settings.SMTP.Username, settings.SMTP.From)
return err
} }
// Format message with **correct HTML headers** // Validate port range
if settings.SMTP.Port <= 0 || settings.SMTP.Port > 65535 {
err := errors.New("SMTP port must be between 1 and 65535")
log.Printf("❌ sendEmail validation failed: %v (Port: %d)", err, settings.SMTP.Port)
return err
}
// Format message with correct HTML headers
message := fmt.Sprintf("From: %s\nTo: %s\nSubject: %s\n"+ message := fmt.Sprintf("From: %s\nTo: %s\nSubject: %s\n"+
"MIME-Version: 1.0\nContent-Type: text/html; charset=\"UTF-8\"\n\n%s", "MIME-Version: 1.0\nContent-Type: text/html; charset=\"UTF-8\"\n\n%s",
settings.SMTP.From, to, subject, body) settings.SMTP.From, to, subject, body)
@@ -2425,60 +2434,91 @@ func sendEmail(to, subject, body string, settings config.AppSettings) error {
// SMTP Connection Config // SMTP Connection Config
smtpHost := settings.SMTP.Host smtpHost := settings.SMTP.Host
smtpPort := settings.SMTP.Port smtpPort := settings.SMTP.Port
auth := LoginAuth(settings.SMTP.Username, settings.SMTP.Password)
smtpAddr := net.JoinHostPort(smtpHost, fmt.Sprintf("%d", smtpPort)) smtpAddr := net.JoinHostPort(smtpHost, fmt.Sprintf("%d", smtpPort))
// **Choose Connection Type** // Determine TLS configuration
switch smtpPort { tlsConfig := &tls.Config{
case 465: ServerName: smtpHost,
// SMTPS (Implicit TLS) - Not supported at the moment. InsecureSkipVerify: settings.SMTP.InsecureSkipVerify,
tlsConfig := &tls.Config{ServerName: smtpHost} }
// Determine authentication method
authMethod := settings.SMTP.AuthMethod
if authMethod == "" {
authMethod = "auto" // Default to auto if not set
}
auth, err := getSMTPAuth(settings.SMTP.Username, settings.SMTP.Password, authMethod, smtpHost)
if err != nil {
log.Printf("❌ sendEmail: failed to create SMTP auth (method: %q): %v", authMethod, err)
return fmt.Errorf("failed to create SMTP auth: %w", err)
}
log.Printf("📧 sendEmail: Using SMTP auth method: %q, host: %s, port: %d, useTLS: %v, insecureSkipVerify: %v", authMethod, smtpHost, smtpPort, settings.SMTP.UseTLS, settings.SMTP.InsecureSkipVerify)
// Determine connection type based on port and UseTLS setting
// Port 465 typically uses implicit TLS (SMTPS)
// Port 587 typically uses STARTTLS
// Other ports: use UseTLS setting to determine behavior
useImplicitTLS := (smtpPort == 465) || (settings.SMTP.UseTLS && smtpPort != 587 && smtpPort != 25)
useSTARTTLS := settings.SMTP.UseTLS && (smtpPort == 587 || (smtpPort != 465 && smtpPort != 25))
var client *smtp.Client
if useImplicitTLS {
// SMTPS (Implicit TLS) - Connect directly with TLS
conn, err := tls.Dial("tcp", smtpAddr, tlsConfig) conn, err := tls.Dial("tcp", smtpAddr, tlsConfig)
if err != nil { if err != nil {
return fmt.Errorf("failed to connect via TLS: %w", err) return fmt.Errorf("failed to connect via TLS: %w", err)
} }
defer conn.Close() defer conn.Close()
client, err := smtp.NewClient(conn, smtpHost) client, err = smtp.NewClient(conn, smtpHost)
if err != nil { if err != nil {
return fmt.Errorf("failed to create SMTP client: %w", err) return fmt.Errorf("failed to create SMTP client: %w", err)
} }
defer client.Quit() } else {
// Plain connection (may upgrade to STARTTLS)
if err := client.Auth(auth); err != nil { conn, err := net.DialTimeout("tcp", smtpAddr, 30*time.Second)
return fmt.Errorf("SMTP authentication failed: %w", err)
}
return sendSMTPMessage(client, settings.SMTP.From, to, msg)
case 587:
// STARTTLS (Explicit TLS)
conn, err := net.Dial("tcp", smtpAddr)
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to SMTP server: %w", err) return fmt.Errorf("failed to connect to SMTP server: %w", err)
} }
defer conn.Close() defer conn.Close()
client, err := smtp.NewClient(conn, smtpHost) client, err = smtp.NewClient(conn, smtpHost)
if err != nil { if err != nil {
return fmt.Errorf("failed to create SMTP client: %w", err) return fmt.Errorf("failed to create SMTP client: %w", err)
} }
defer client.Quit()
// Start TLS Upgrade // Upgrade to STARTTLS if requested
tlsConfig := &tls.Config{ServerName: smtpHost} if useSTARTTLS {
if err := client.StartTLS(tlsConfig); err != nil { if err := client.StartTLS(tlsConfig); err != nil {
return fmt.Errorf("failed to start TLS: %w", err) return fmt.Errorf("failed to start TLS: %w", err)
} }
}
}
// Ensure client is closed
defer func() {
if client != nil {
client.Quit()
}
}()
// Authenticate if credentials are provided
if auth != nil {
if err := client.Auth(auth); err != nil { if err := client.Auth(auth); err != nil {
log.Printf("❌ sendEmail: SMTP authentication failed: %v", err)
return fmt.Errorf("SMTP authentication failed: %w", err) return fmt.Errorf("SMTP authentication failed: %w", err)
} }
log.Printf("📧 sendEmail: SMTP authentication successful")
return sendSMTPMessage(client, settings.SMTP.From, to, msg)
} }
return errors.New("unsupported SMTP port. Use 587 (STARTTLS) or 465 (SMTPS)") err = sendSMTPMessage(client, settings.SMTP.From, to, msg)
if err != nil {
log.Printf("❌ sendEmail: Failed to send message: %v", err)
return err
}
log.Printf("📧 sendEmail: Successfully sent email to %s", to)
return nil
} }
// Helper Function to Send SMTP Message // Helper Function to Send SMTP Message
@@ -3104,8 +3144,37 @@ func TestEmailHandler(c *gin.Context) {
} }
// ******************************************************************* // *******************************************************************
// * Office365 LOGIN Authentication : * // * SMTP Authentication Methods : *
// ******************************************************************* // *******************************************************************
// getSMTPAuth returns the appropriate SMTP authentication mechanism
// based on the authMethod parameter: "auto", "login", "plain", "cram-md5"
func getSMTPAuth(username, password, authMethod, host string) (smtp.Auth, error) {
if username == "" || password == "" {
return nil, nil // No auth if credentials are empty
}
// Normalize auth method
authMethod = strings.ToLower(strings.TrimSpace(authMethod))
if authMethod == "" || authMethod == "auto" {
// Auto-detect: prefer LOGIN for Office365/Gmail, fallback to PLAIN
authMethod = "login"
}
switch authMethod {
case "login":
return LoginAuth(username, password), nil
case "plain":
return smtp.PlainAuth("", username, password, host), nil
case "cram-md5":
return smtp.CRAMMD5Auth(username, password), nil
default:
return nil, fmt.Errorf("unsupported auth method: %s (supported: login, plain, cram-md5)", authMethod)
}
}
// LoginAuth implements the LOGIN authentication mechanism
// Used by Office365, Gmail, and other providers that require LOGIN instead of PLAIN
type loginAuth struct { type loginAuth struct {
username, password string username, password string
} }

View File

@@ -27,7 +27,9 @@ function updateEmailFieldsState() {
document.getElementById('smtpUsername'), document.getElementById('smtpUsername'),
document.getElementById('smtpPassword'), document.getElementById('smtpPassword'),
document.getElementById('smtpFrom'), document.getElementById('smtpFrom'),
document.getElementById('smtpAuthMethod'),
document.getElementById('smtpUseTLS'), document.getElementById('smtpUseTLS'),
document.getElementById('smtpInsecureSkipVerify'),
document.getElementById('sendTestEmailBtn') document.getElementById('sendTestEmailBtn')
]; ];
@@ -143,7 +145,9 @@ function loadSettings() {
document.getElementById('smtpUsername').value = data.smtp.username || ''; document.getElementById('smtpUsername').value = data.smtp.username || '';
document.getElementById('smtpPassword').value = data.smtp.password || ''; document.getElementById('smtpPassword').value = data.smtp.password || '';
document.getElementById('smtpFrom').value = data.smtp.from || ''; document.getElementById('smtpFrom').value = data.smtp.from || '';
document.getElementById('smtpUseTLS').checked = data.smtp.useTLS || false; document.getElementById('smtpUseTLS').checked = data.smtp.useTLS !== undefined ? data.smtp.useTLS : true;
document.getElementById('smtpInsecureSkipVerify').checked = data.smtp.insecureSkipVerify || false;
document.getElementById('smtpAuthMethod').value = data.smtp.authMethod || 'auto';
} }
document.getElementById('bantimeIncrement').checked = data.bantimeIncrement || false; document.getElementById('bantimeIncrement').checked = data.bantimeIncrement || false;
@@ -186,13 +190,22 @@ function saveSettings(event) {
showLoading(true); showLoading(true);
const smtpPort = parseInt(document.getElementById('smtpPort').value, 10);
if (isNaN(smtpPort) || smtpPort < 1 || smtpPort > 65535) {
showToast('SMTP port must be between 1 and 65535', 'error');
showLoading(false);
return;
}
const smtpSettings = { const smtpSettings = {
host: document.getElementById('smtpHost').value.trim(), host: document.getElementById('smtpHost').value.trim(),
port: parseInt(document.getElementById('smtpPort').value, 10) || 587, port: smtpPort,
username: document.getElementById('smtpUsername').value.trim(), username: document.getElementById('smtpUsername').value.trim(),
password: document.getElementById('smtpPassword').value.trim(), password: document.getElementById('smtpPassword').value.trim(),
from: document.getElementById('smtpFrom').value.trim(), from: document.getElementById('smtpFrom').value.trim(),
useTLS: document.getElementById('smtpUseTLS').checked, useTLS: document.getElementById('smtpUseTLS').checked,
insecureSkipVerify: document.getElementById('smtpInsecureSkipVerify').checked,
authMethod: document.getElementById('smtpAuthMethod').value || 'auto',
}; };
const selectedCountries = Array.from(document.getElementById('alertCountries').selectedOptions).map(opt => opt.value); const selectedCountries = Array.from(document.getElementById('alertCountries').selectedOptions).map(opt => opt.value);

View File

@@ -757,10 +757,9 @@
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="smtpPort" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_port">SMTP Port</label> <label for="smtpPort" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_port">SMTP Port</label>
<select id="smtpPort" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"> <input type="number" min="1" max="65535" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed" id="smtpPort"
<option value="587" selected>587 (Recommended - STARTTLS)</option> data-i18n-placeholder="settings.smtp_port_placeholder" placeholder="587" value="587" required />
<option value="465" disabled>465 (Not Supported)</option> <p class="mt-1 text-xs text-gray-500" data-i18n="settings.smtp_port_hint">Common ports: 25 (plain), 587 (STARTTLS), 465 (SMTPS), 2525 (alternative STARTTLS)</p>
</select>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label for="smtpUsername" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_username">SMTP Username</label> <label for="smtpUsername" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_username">SMTP Username</label>
@@ -777,11 +776,29 @@
<input type="email" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed" id="smtpFrom" <input type="email" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed" id="smtpFrom"
data-i18n-placeholder="settings.smtp_sender_placeholder" placeholder="noreply@swissmakers.ch" required /> data-i18n-placeholder="settings.smtp_sender_placeholder" placeholder="noreply@swissmakers.ch" required />
</div> </div>
<div class="mb-4">
<label for="smtpAuthMethod" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.smtp_auth_method">Authentication Method</label>
<select id="smtpAuthMethod" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed">
<option value="auto" selected data-i18n="settings.smtp_auth_method_auto">Auto (LOGIN preferred)</option>
<option value="login" data-i18n="settings.smtp_auth_method_login">LOGIN</option>
<option value="plain" data-i18n="settings.smtp_auth_method_plain">PLAIN</option>
<option value="cram-md5" data-i18n="settings.smtp_auth_method_cram_md5">CRAM-MD5</option>
</select>
<p class="mt-1 text-xs text-gray-500" data-i18n="settings.smtp_auth_method_hint">LOGIN is recommended for Office365/Gmail. PLAIN is standard SMTP auth. CRAM-MD5 is challenge-response based.</p>
</div>
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<input type="checkbox" id="smtpUseTLS" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed"> <input type="checkbox" id="smtpUseTLS" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed">
<label for="smtpUseTLS" class="ml-2 block text-sm text-gray-700" data-i18n="settings.smtp_tls">Use TLS (Recommended)</label> <label for="smtpUseTLS" class="ml-2 block text-sm text-gray-700" data-i18n="settings.smtp_tls">Use TLS (Recommended)</label>
</div> </div>
<div class="flex items-center mb-4">
<input type="checkbox" id="smtpInsecureSkipVerify" class="h-4 w-7 text-blue-600 transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed">
<label for="smtpInsecureSkipVerify" class="ml-2 block text-sm text-gray-700" data-i18n="settings.smtp_insecure_skip_verify">Skip TLS Certificate Verification</label>
<span class="ml-2 text-xs text-red-600" data-i18n="settings.smtp_insecure_skip_verify_warning">⚠️ Not recommended for production</span>
</div>
<div class="mb-4">
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded hover:bg-gray-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed" onclick="sendTestEmail()" id="sendTestEmailBtn" data-i18n="settings.send_test_email">Send Test Email</button> <button type="button" class="bg-gray-600 text-white px-4 py-2 rounded hover:bg-gray-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed" onclick="sendTestEmail()" id="sendTestEmailBtn" data-i18n="settings.send_test_email">Send Test Email</button>
<p class="mt-2 text-xs text-amber-600" data-i18n="settings.send_test_email_hint">⚠️ Please save your SMTP settings first before sending a test email.</p>
</div>
</div> </div>
<!-- Fail2Ban Configuration Group --> <!-- Fail2Ban Configuration Group -->