From a345c16425fd093ef9263c86fcf4683f888e9849 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sun, 15 Mar 2026 16:01:12 +0100 Subject: [PATCH] feat(smtp-relay): Add unified SMTP relay configuration - New secubox-app-smtp-relay package with centralized SMTP config - Shared library with send_mail(), send_html_mail(), send_text_mail() - CLI: smtp-relayctl with status/test/send/configure/admin commands - RPCD: 5 methods for LuCI integration - LuCI settings page with mode selection and test button - Modes: external (SMTP server), local (auto-detect mailserver), direct - Migrated reporter and bandwidth-manager to use shared library - Backwards-compatible fallback to legacy per-app config Co-Authored-By: Claude Opus 4.5 --- .claude/HISTORY.md | 12 + .claude/WIP.md | 15 +- .../root/usr/share/secubox/lib/alerts.sh | 23 ++ package/secubox/luci-app-smtp-relay/Makefile | 30 ++ .../resources/view/smtp-relay/settings.js | 281 ++++++++++++++ .../root/usr/libexec/rpcd/luci.smtp-relay | 169 +++++++++ .../luci/menu.d/luci-app-smtp-relay.json | 14 + .../share/rpcd/acl.d/luci-app-smtp-relay.json | 24 ++ .../usr/share/secubox-reporter/lib/mailer.sh | 59 ++- .../secubox/secubox-app-smtp-relay/Makefile | 46 +++ .../files/etc/config/smtp-relay | 41 ++ .../files/usr/lib/secubox/mail/smtp-relay.sh | 280 ++++++++++++++ .../files/usr/sbin/smtp-relayctl | 350 ++++++++++++++++++ 13 files changed, 1327 insertions(+), 17 deletions(-) create mode 100644 package/secubox/luci-app-smtp-relay/Makefile create mode 100644 package/secubox/luci-app-smtp-relay/htdocs/luci-static/resources/view/smtp-relay/settings.js create mode 100644 package/secubox/luci-app-smtp-relay/root/usr/libexec/rpcd/luci.smtp-relay create mode 100644 package/secubox/luci-app-smtp-relay/root/usr/share/luci/menu.d/luci-app-smtp-relay.json create mode 100644 package/secubox/luci-app-smtp-relay/root/usr/share/rpcd/acl.d/luci-app-smtp-relay.json create mode 100644 package/secubox/secubox-app-smtp-relay/Makefile create mode 100644 package/secubox/secubox-app-smtp-relay/files/etc/config/smtp-relay create mode 100644 package/secubox/secubox-app-smtp-relay/files/usr/lib/secubox/mail/smtp-relay.sh create mode 100644 package/secubox/secubox-app-smtp-relay/files/usr/sbin/smtp-relayctl diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 44ba8ec2..a027a2b7 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -5270,3 +5270,15 @@ git checkout HEAD -- index.html - 6 new RPCD methods: get_tuning, set_tuning, whitelist_add, whitelist_remove, whitelist_list, reset_reputation - UCi config updated with scoring weights, sensitivity, whitelist, decay options - Enables fine-tuning of auto-ban sensitivity for production traffic + +- **Unified SMTP Relay Configuration (2026-03-16)** + - New `secubox-app-smtp-relay` package: centralized SMTP config for all SecuBox apps + - Shared library with `send_mail()`, `send_html_mail()`, `send_text_mail()` functions + - CLI tool: `smtp-relayctl status|test|send|configure|admin|enable|disable` + - RPCD backend with 5 methods: get_status, get_config, test_email, send_email, detect_local + - LuCI settings page with mode selection, provider presets, connection test + - Three modes: external (SMTP server), local (auto-detect mailserver), direct (MTA) + - Provider presets: Gmail, SendGrid, Mailgun, Amazon SES, Mailjet + - Migrated secubox-reporter and bandwidth-manager to use shared library + - Backwards-compatible fallback to legacy per-app SMTP settings + - Eliminates duplicated SMTP configuration across SecuBox apps diff --git a/.claude/WIP.md b/.claude/WIP.md index 113df621..2c0b9fff 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -1,6 +1,6 @@ # Work In Progress (Claude) -_Last updated: 2026-03-16 (WAF Auto-Ban Tuning)_ +_Last updated: 2026-03-16 (Unified SMTP Relay)_ > **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches @@ -10,6 +10,17 @@ _Last updated: 2026-03-16 (WAF Auto-Ban Tuning)_ ### 2026-03-16 +- **Unified SMTP Relay Configuration (Complete)** + - New `secubox-app-smtp-relay` package with centralized SMTP config + - Shared library `/usr/lib/secubox/mail/smtp-relay.sh` with `send_mail()` function + - CLI: `smtp-relayctl status|test|send|configure|admin|enable|disable` + - RPCD: get_status, get_config, test_email, send_email, detect_local + - LuCI settings page with mode selection, provider config, test button + - Modes: external (Gmail, SendGrid, etc.), local (auto-detect mailserver), direct + - Migrated `secubox-reporter` and `bandwidth-manager` to use shared library + - Backwards-compatible fallback to legacy per-app SMTP settings + - Eliminates duplicated SMTP configuration across SecuBox apps + - **WAF Auto-Ban Tuning System (Complete)** - Configurable scoring weights via UCI `scoring` section - Sensitivity presets: low (0.7x), medium (1.0x), high (1.3x), custom @@ -672,7 +683,7 @@ All core features complete. Optional polish tasks remain. ### Backlog -- SSMTP / mail host / MX record management (v2) +- Advanced mail features: webmail integration, DKIM signing, multiple recipients (v2) --- diff --git a/package/secubox/luci-app-bandwidth-manager/root/usr/share/secubox/lib/alerts.sh b/package/secubox/luci-app-bandwidth-manager/root/usr/share/secubox/lib/alerts.sh index 2ae59ad4..bd9d5da6 100644 --- a/package/secubox/luci-app-bandwidth-manager/root/usr/share/secubox/lib/alerts.sh +++ b/package/secubox/luci-app-bandwidth-manager/root/usr/share/secubox/lib/alerts.sh @@ -27,10 +27,33 @@ get_alert_setting() { echo "$value" } +# Check if centralized SMTP relay is available and enabled +_use_smtp_relay() { + [ -f /usr/lib/secubox/mail/smtp-relay.sh ] || return 1 + local enabled + enabled=$(uci -q get smtp-relay.main.enabled) + [ "$enabled" = "1" ] +} + # Send email notification send_email() { local subject="$1" local body="$2" + + # Use centralized SMTP relay if available + if _use_smtp_relay; then + . /usr/lib/secubox/mail/smtp-relay.sh + local recipient + recipient=$(uci -q get smtp-relay.recipients.admin) + [ -z "$recipient" ] && { + config_load bandwidth + config_get recipient email recipient "" + } + send_text_mail "$recipient" "$subject" "$body" + return $? + fi + + # Legacy fallback: use per-app config local recipient="" local smtp_server="" local smtp_port="" diff --git a/package/secubox/luci-app-smtp-relay/Makefile b/package/secubox/luci-app-smtp-relay/Makefile new file mode 100644 index 00000000..da6587c7 --- /dev/null +++ b/package/secubox/luci-app-smtp-relay/Makefile @@ -0,0 +1,30 @@ +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI SMTP Relay Configuration +LUCI_DEPENDS:=+secubox-app-smtp-relay +luci-base +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-smtp-relay +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=SecuBox Team +PKG_LICENSE:=GPL-3.0 + +include $(TOPDIR)/feeds/luci/luci.mk + +define Package/luci-app-smtp-relay/install + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.smtp-relay $(1)/usr/libexec/rpcd/luci.smtp-relay + + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-smtp-relay.json $(1)/usr/share/rpcd/acl.d/luci-app-smtp-relay.json + + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-smtp-relay.json $(1)/usr/share/luci/menu.d/luci-app-smtp-relay.json + + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/smtp-relay + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/smtp-relay/settings.js $(1)/www/luci-static/resources/view/smtp-relay/settings.js +endef + +$(eval $(call BuildPackage,luci-app-smtp-relay)) diff --git a/package/secubox/luci-app-smtp-relay/htdocs/luci-static/resources/view/smtp-relay/settings.js b/package/secubox/luci-app-smtp-relay/htdocs/luci-static/resources/view/smtp-relay/settings.js new file mode 100644 index 00000000..17599c3d --- /dev/null +++ b/package/secubox/luci-app-smtp-relay/htdocs/luci-static/resources/view/smtp-relay/settings.js @@ -0,0 +1,281 @@ +'use strict'; +'require view'; +'require form'; +'require ui'; +'require uci'; +'require rpc'; + +var callGetStatus = rpc.declare({ + object: 'luci.smtp-relay', + method: 'get_status', + expect: {} +}); + +var callGetConfig = rpc.declare({ + object: 'luci.smtp-relay', + method: 'get_config', + expect: {} +}); + +var callTestEmail = rpc.declare({ + object: 'luci.smtp-relay', + method: 'test_email', + params: ['recipient'], + expect: {} +}); + +var callDetectLocal = rpc.declare({ + object: 'luci.smtp-relay', + method: 'detect_local', + expect: {} +}); + +return view.extend({ + load: function() { + return Promise.all([ + callGetStatus().catch(function() { return {}; }), + callGetConfig().catch(function() { return {}; }), + callDetectLocal().catch(function() { return {}; }), + uci.load('smtp-relay') + ]); + }, + + handleTestEmail: function(ev) { + var recipient = document.getElementById('test_recipient').value; + var btn = ev.target; + var resultDiv = document.getElementById('test_result'); + + if (!recipient) { + recipient = uci.get('smtp-relay', 'recipients', 'admin') || ''; + } + + if (!recipient) { + ui.addNotification(null, E('p', _('Please enter a recipient email address')), 'warning'); + return; + } + + btn.disabled = true; + btn.textContent = _('Sending...'); + resultDiv.innerHTML = ''; + + callTestEmail(recipient).then(function(res) { + btn.disabled = false; + btn.textContent = _('Send Test Email'); + + if (res.success) { + resultDiv.innerHTML = '✓ ' + _('Test email sent successfully!') + ''; + ui.addNotification(null, E('p', _('Test email sent to %s').format(recipient)), 'info'); + } else { + resultDiv.innerHTML = '✗ ' + (res.error || _('Failed to send')) + ''; + ui.addNotification(null, E('p', res.error || _('Failed to send test email')), 'error'); + } + }).catch(function(err) { + btn.disabled = false; + btn.textContent = _('Send Test Email'); + resultDiv.innerHTML = '✗ ' + err.message + ''; + }); + }, + + render: function(data) { + var status = data[0] || {}; + var config = data[1] || {}; + var localDetect = data[2] || {}; + var m, s, o; + + m = new form.Map('smtp-relay', _('SMTP Relay'), + _('Centralized outbound email configuration for all SecuBox services. Configure once, use everywhere.')); + + // Status section + s = m.section(form.NamedSection, 'main', 'smtp_relay', _('Status')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_status', _('Current Status')); + o.rawhtml = true; + o.cfgvalue = function() { + var modeText = { + 'external': _('External SMTP Server'), + 'local': _('Local Mailserver'), + 'direct': _('Direct Delivery') + }; + var statusHtml = '
'; + + // Enabled status + statusHtml += '
' + _('Relay:') + ' '; + if (status.enabled) { + statusHtml += ' ' + _('Enabled'); + } else { + statusHtml += ' ' + _('Disabled'); + } + statusHtml += '
'; + + // Mode + statusHtml += '
' + _('Mode:') + ' ' + (modeText[status.mode] || status.mode || '-') + '
'; + + // Server + if (status.server) { + statusHtml += '
' + _('Server:') + ' ' + status.server + ':' + status.port + '
'; + } + + // Transport + statusHtml += '
' + _('Transport:') + ' '; + if (status.msmtp_available) { + statusHtml += 'msmtp'; + } else if (status.sendmail_available) { + statusHtml += 'sendmail'; + } else { + statusHtml += '' + _('None') + ''; + } + statusHtml += '
'; + + // Local mailserver + if (localDetect.detected) { + statusHtml += '
' + _('Local Mail:') + ' '; + if (localDetect.responding) { + statusHtml += '● ' + _('Available') + ''; + } else { + statusHtml += '● ' + _('Not responding') + ''; + } + statusHtml += '
'; + } + + statusHtml += '
'; + return statusHtml; + }; + + // Main settings + s = m.section(form.NamedSection, 'main', 'smtp_relay', _('General Settings')); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', _('Enable SMTP Relay')); + o.default = '0'; + o.rmempty = false; + + o = s.option(form.ListValue, 'mode', _('Relay Mode')); + o.value('external', _('External SMTP Server')); + o.value('local', _('Local Mailserver')); + o.value('direct', _('Direct Delivery (MTA)')); + o.default = 'external'; + o.description = _('External: Use Gmail, SendGrid, etc. Local: Use secubox-app-mailserver. Direct: Send directly (requires port 25).'); + + o = s.option(form.Flag, 'auto_detect', _('Auto-detect Local Mailserver')); + o.description = _('Automatically use local mailserver if available and responding'); + o.default = '1'; + o.depends('mode', 'external'); + + // External SMTP section + s = m.section(form.NamedSection, 'external', 'external', _('External SMTP Settings')); + s.anonymous = true; + + o = s.option(form.ListValue, '_preset', _('Provider Preset')); + o.value('', _('-- Custom --')); + o.value('gmail', 'Gmail / Google Workspace'); + o.value('sendgrid', 'SendGrid'); + o.value('mailgun', 'Mailgun'); + o.value('ses', 'Amazon SES (us-east-1)'); + o.value('mailjet', 'Mailjet'); + o.rmempty = true; + o.write = function() {}; // Don't save, just triggers onchange + + o = s.option(form.Value, 'server', _('SMTP Server')); + o.placeholder = 'smtp.example.com'; + o.rmempty = true; + + o = s.option(form.Value, 'port', _('Port')); + o.datatype = 'port'; + o.default = '587'; + o.placeholder = '587'; + + o = s.option(form.Flag, 'tls', _('Use STARTTLS')); + o.default = '1'; + o.description = _('Use STARTTLS encryption (recommended for port 587)'); + + o = s.option(form.Flag, 'ssl', _('Use SSL/TLS')); + o.default = '0'; + o.description = _('Use implicit SSL/TLS (for port 465)'); + + o = s.option(form.Flag, 'auth', _('Authentication Required')); + o.default = '1'; + + o = s.option(form.Value, 'user', _('Username')); + o.depends('auth', '1'); + o.rmempty = true; + + o = s.option(form.Value, 'password', _('Password')); + o.password = true; + o.depends('auth', '1'); + o.rmempty = true; + + o = s.option(form.Value, 'from', _('From Address')); + o.placeholder = 'secubox@example.com'; + o.datatype = 'email'; + o.rmempty = true; + + o = s.option(form.Value, 'from_name', _('From Name')); + o.placeholder = 'SecuBox'; + o.default = 'SecuBox'; + o.rmempty = true; + + // Recipients section + s = m.section(form.NamedSection, 'recipients', 'recipients', _('Default Recipients')); + s.anonymous = true; + + o = s.option(form.Value, 'admin', _('Admin Email')); + o.datatype = 'email'; + o.description = _('Default recipient for system notifications and test emails'); + + // Test section + s = m.section(form.NamedSection, 'main', 'smtp_relay', _('Connection Test')); + s.anonymous = true; + + o = s.option(form.DummyValue, '_test', ' '); + o.rawhtml = true; + o.cfgvalue = function() { + var adminEmail = uci.get('smtp-relay', 'recipients', 'admin') || ''; + return E('div', { 'style': 'display: flex; gap: 10px; align-items: center; flex-wrap: wrap;' }, [ + E('input', { + 'type': 'email', + 'id': 'test_recipient', + 'placeholder': adminEmail || _('recipient@example.com'), + 'value': adminEmail, + 'style': 'flex: 1; min-width: 200px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;' + }), + E('button', { + 'class': 'cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, function(ev) { + var view = document.querySelector('[data-page="smtp-relay/settings"]'); + if (view && view.handleTestEmail) { + view.handleTestEmail(ev); + } else { + // Fallback + var recipient = document.getElementById('test_recipient').value; + var btn = ev.target; + btn.disabled = true; + btn.textContent = _('Sending...'); + + callTestEmail(recipient).then(function(res) { + btn.disabled = false; + btn.textContent = _('Send Test Email'); + var resultDiv = document.getElementById('test_result'); + if (res.success) { + resultDiv.innerHTML = '✓ ' + _('Test email sent!') + ''; + } else { + resultDiv.innerHTML = '✗ ' + (res.error || _('Failed')) + ''; + } + }).catch(function(err) { + btn.disabled = false; + btn.textContent = _('Send Test Email'); + }); + } + }) + }, _('Send Test Email')), + E('div', { 'id': 'test_result', 'style': 'margin-left: 10px;' }) + ]); + }; + + return m.render(); + }, + + handleSave: null, + handleSaveApply: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-smtp-relay/root/usr/libexec/rpcd/luci.smtp-relay b/package/secubox/luci-app-smtp-relay/root/usr/libexec/rpcd/luci.smtp-relay new file mode 100644 index 00000000..2981d5d5 --- /dev/null +++ b/package/secubox/luci-app-smtp-relay/root/usr/libexec/rpcd/luci.smtp-relay @@ -0,0 +1,169 @@ +#!/bin/sh +# RPCD backend for SecuBox SMTP Relay + +. /usr/share/libubox/jshn.sh +. /lib/functions.sh + +CONFIG="smtp-relay" + +# Source shared library if available +[ -f /usr/lib/secubox/mail/smtp-relay.sh ] && . /usr/lib/secubox/mail/smtp-relay.sh + +case "$1" in + list) + cat << 'EOF' +{ + "get_status": {}, + "get_config": {}, + "test_email": {"recipient": "string"}, + "send_email": {"recipient": "string", "subject": "string", "body": "string"}, + "detect_local": {} +} +EOF + ;; + + call) + case "$2" in + get_status) + if [ -f /usr/lib/secubox/mail/smtp-relay.sh ]; then + smtp_relay_status + else + echo '{"error": "smtp-relay library not installed"}' + fi + ;; + + get_config) + json_init + + local enabled mode auto_detect + enabled=$(uci -q get ${CONFIG}.main.enabled) + mode=$(uci -q get ${CONFIG}.main.mode) + auto_detect=$(uci -q get ${CONFIG}.main.auto_detect) + + json_add_boolean "enabled" "${enabled:-0}" + json_add_string "mode" "${mode:-external}" + json_add_boolean "auto_detect" "${auto_detect:-1}" + + # External settings + json_add_object "external" + json_add_string "server" "$(uci -q get ${CONFIG}.external.server)" + json_add_int "port" "$(uci -q get ${CONFIG}.external.port || echo 587)" + json_add_boolean "tls" "$(uci -q get ${CONFIG}.external.tls || echo 1)" + json_add_boolean "ssl" "$(uci -q get ${CONFIG}.external.ssl || echo 0)" + json_add_boolean "auth" "$(uci -q get ${CONFIG}.external.auth || echo 1)" + json_add_string "user" "$(uci -q get ${CONFIG}.external.user)" + local pwd_set=0 + [ -n "$(uci -q get ${CONFIG}.external.password)" ] && pwd_set=1 + json_add_boolean "password_set" "$pwd_set" + json_add_string "from" "$(uci -q get ${CONFIG}.external.from)" + json_add_string "from_name" "$(uci -q get ${CONFIG}.external.from_name)" + json_close_object + + # Local settings + json_add_object "local" + json_add_string "server" "$(uci -q get ${CONFIG}.local.server)" + json_add_int "port" "$(uci -q get ${CONFIG}.local.port || echo 25)" + json_close_object + + # Recipients + json_add_string "admin" "$(uci -q get ${CONFIG}.recipients.admin)" + + json_dump + ;; + + test_email) + read -r input + local recipient + recipient=$(echo "$input" | jsonfilter -e '@.recipient' 2>/dev/null) + + json_init + + if [ -f /usr/lib/secubox/mail/smtp-relay.sh ]; then + local output + output=$(smtp_relay_test "$recipient" 2>&1) + local code=$? + + if [ $code -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Test email sent successfully" + else + json_add_boolean "success" 0 + json_add_string "error" "$output" + fi + else + json_add_boolean "success" 0 + json_add_string "error" "smtp-relay library not installed" + fi + + json_dump + ;; + + send_email) + read -r input + local recipient subject body + recipient=$(echo "$input" | jsonfilter -e '@.recipient' 2>/dev/null) + subject=$(echo "$input" | jsonfilter -e '@.subject' 2>/dev/null) + body=$(echo "$input" | jsonfilter -e '@.body' 2>/dev/null) + + json_init + + if [ -z "$recipient" ] || [ -z "$subject" ]; then + json_add_boolean "success" 0 + json_add_string "error" "recipient and subject are required" + json_dump + exit 0 + fi + + if [ -f /usr/lib/secubox/mail/smtp-relay.sh ]; then + local output + output=$(send_mail "$recipient" "$subject" "$body" 2>&1) + local code=$? + + if [ $code -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Email sent" + else + json_add_boolean "success" 0 + json_add_string "error" "$output" + fi + else + json_add_boolean "success" 0 + json_add_string "error" "smtp-relay library not installed" + fi + + json_dump + ;; + + detect_local) + json_init + + local mailserver_enabled mailserver_ip detected responding + mailserver_enabled=$(uci -q get mailserver.main.enabled) + mailserver_ip=$(uci -q get mailserver.server.ip_address) + [ -z "$mailserver_ip" ] && mailserver_ip=$(uci -q get mailserver.main.ip_address) + + detected=0 + responding=0 + + if [ "$mailserver_enabled" = "1" ] && [ -n "$mailserver_ip" ]; then + detected=1 + if nc -z "$mailserver_ip" 25 2>/dev/null; then + responding=1 + fi + fi + + json_add_boolean "detected" "$detected" + json_add_boolean "responding" "$responding" + json_add_string "ip" "${mailserver_ip:-}" + + json_dump + ;; + + *) + echo '{"error": "Unknown method"}' + ;; + esac + ;; +esac + +exit 0 diff --git a/package/secubox/luci-app-smtp-relay/root/usr/share/luci/menu.d/luci-app-smtp-relay.json b/package/secubox/luci-app-smtp-relay/root/usr/share/luci/menu.d/luci-app-smtp-relay.json new file mode 100644 index 00000000..0c3331bc --- /dev/null +++ b/package/secubox/luci-app-smtp-relay/root/usr/share/luci/menu.d/luci-app-smtp-relay.json @@ -0,0 +1,14 @@ +{ + "admin/secubox/system/smtp-relay": { + "title": "SMTP Relay", + "order": 55, + "action": { + "type": "view", + "path": "smtp-relay/settings" + }, + "depends": { + "acl": ["luci-app-smtp-relay"], + "uci": {"smtp-relay": true} + } + } +} diff --git a/package/secubox/luci-app-smtp-relay/root/usr/share/rpcd/acl.d/luci-app-smtp-relay.json b/package/secubox/luci-app-smtp-relay/root/usr/share/rpcd/acl.d/luci-app-smtp-relay.json new file mode 100644 index 00000000..dbf0cdd0 --- /dev/null +++ b/package/secubox/luci-app-smtp-relay/root/usr/share/rpcd/acl.d/luci-app-smtp-relay.json @@ -0,0 +1,24 @@ +{ + "luci-app-smtp-relay": { + "description": "Grant access to SMTP Relay configuration", + "read": { + "ubus": { + "luci.smtp-relay": [ + "get_status", + "get_config", + "detect_local" + ] + }, + "uci": ["smtp-relay"] + }, + "write": { + "ubus": { + "luci.smtp-relay": [ + "test_email", + "send_email" + ] + }, + "uci": ["smtp-relay"] + } + } +} diff --git a/package/secubox/secubox-app-reporter/files/usr/share/secubox-reporter/lib/mailer.sh b/package/secubox/secubox-app-reporter/files/usr/share/secubox-reporter/lib/mailer.sh index dfc4a55a..b64c89a2 100644 --- a/package/secubox/secubox-app-reporter/files/usr/share/secubox-reporter/lib/mailer.sh +++ b/package/secubox/secubox-app-reporter/files/usr/share/secubox-reporter/lib/mailer.sh @@ -1,15 +1,47 @@ #!/bin/sh # SecuBox Reporter - Email Integration -# Sends reports via msmtp or sendmail +# Uses centralized SMTP relay (secubox-app-smtp-relay) or falls back to legacy config . /lib/functions.sh +# Check if centralized SMTP relay is available and enabled +_use_smtp_relay() { + [ -f /usr/lib/secubox/mail/smtp-relay.sh ] || return 1 + local enabled + enabled=$(uci -q get smtp-relay.main.enabled) + [ "$enabled" = "1" ] +} + # Send report via email send_report_email() { local report_type="$1" local html_content="$2" local recipient="$3" + local hostname + hostname=$(uci -q get system.@system[0].hostname || hostname) + local date_str + date_str=$(date '+%Y-%m-%d') + + # Format report type for subject + local report_name="Status" + case "$report_type" in + dev) report_name="Development Status" ;; + services) report_name="Services Distribution" ;; + system) report_name="System Hardware" ;; + all) report_name="Full Status" ;; + esac + + local subject="[SecuBox] $report_name Report - $hostname - $date_str" + + # Use centralized SMTP relay if available + if _use_smtp_relay; then + . /usr/lib/secubox/mail/smtp-relay.sh + send_html_mail "$recipient" "$subject" "$html_content" + return $? + fi + + # Legacy fallback: use per-app config local smtp_server="" local smtp_port="" local smtp_user="" @@ -28,19 +60,6 @@ send_report_email() { return 1 } - local hostname=$(uci -q get system.@system[0].hostname || hostname) - local date_str=$(date '+%Y-%m-%d') - - # Format report type for subject - local report_name="Status" - case "$report_type" in - dev) report_name="Development Status" ;; - services) report_name="Services Distribution" ;; - all) report_name="Full Status" ;; - esac - - local subject="[SecuBox] $report_name Report - $hostname - $date_str" - # Build MIME multipart email local boundary="SecuBox_Report_$(date +%s)_$$" @@ -105,6 +124,15 @@ EOF # Test email configuration test_email() { local recipient="$1" + + # Use centralized SMTP relay if available + if _use_smtp_relay; then + . /usr/lib/secubox/mail/smtp-relay.sh + smtp_relay_test "$recipient" + return $? + fi + + # Legacy fallback [ -z "$recipient" ] && { config_load secubox-reporter config_get recipient email recipient "" @@ -115,7 +143,8 @@ test_email() { return 1 } - local hostname=$(uci -q get system.@system[0].hostname || hostname) + local hostname + hostname=$(uci -q get system.@system[0].hostname || hostname) local test_body="

SecuBox Email Test

This is a test email from $hostname.

Generated: $(date)

" if send_report_email "test" "$test_body" "$recipient"; then diff --git a/package/secubox/secubox-app-smtp-relay/Makefile b/package/secubox/secubox-app-smtp-relay/Makefile new file mode 100644 index 00000000..e21b398a --- /dev/null +++ b/package/secubox/secubox-app-smtp-relay/Makefile @@ -0,0 +1,46 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=secubox-app-smtp-relay +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=SecuBox Team +PKG_LICENSE:=GPL-3.0 + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-app-smtp-relay + SECTION:=secubox + CATEGORY:=SecuBox + SUBMENU:=System + TITLE:=SecuBox Unified SMTP Relay + DEPENDS:=+msmtp + PKGARCH:=all +endef + +define Package/secubox-app-smtp-relay/description + Centralized SMTP relay configuration for all SecuBox services. + Provides a shared mail library and CLI tool for sending emails. + Supports external SMTP servers, local mailserver auto-detection, + and direct delivery modes. +endef + +define Package/secubox-app-smtp-relay/conffiles +/etc/config/smtp-relay +endef + +define Build/Compile +endef + +define Package/secubox-app-smtp-relay/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/smtp-relay $(1)/etc/config/smtp-relay + + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./files/usr/sbin/smtp-relayctl $(1)/usr/sbin/smtp-relayctl + + $(INSTALL_DIR) $(1)/usr/lib/secubox/mail + $(INSTALL_DATA) ./files/usr/lib/secubox/mail/smtp-relay.sh $(1)/usr/lib/secubox/mail/smtp-relay.sh +endef + +$(eval $(call BuildPackage,secubox-app-smtp-relay)) diff --git a/package/secubox/secubox-app-smtp-relay/files/etc/config/smtp-relay b/package/secubox/secubox-app-smtp-relay/files/etc/config/smtp-relay new file mode 100644 index 00000000..cebd2131 --- /dev/null +++ b/package/secubox/secubox-app-smtp-relay/files/etc/config/smtp-relay @@ -0,0 +1,41 @@ +# SecuBox SMTP Relay Configuration +# Centralized outbound email settings for all SecuBox services + +config smtp_relay 'main' + option enabled '0' + # Mode: external (configured SMTP), local (auto-detected mailserver), direct (MTA) + option mode 'external' + # Auto-detect local mailserver (secubox-app-mailserver) if running + option auto_detect '1' + +# External SMTP provider settings +config external 'external' + option server '' + option port '587' + # TLS mode: 0=none, 1=STARTTLS + option tls '1' + # SSL mode: 0=STARTTLS, 1=implicit SSL (port 465) + option ssl '0' + option auth '1' + option user '' + option password '' + option from '' + option from_name 'SecuBox' + +# Local mailserver settings (auto-populated from secubox-app-mailserver) +config local 'local' + option server '127.0.0.1' + option port '25' + option tls '0' + option auth '0' + option from '' + +# Direct delivery settings (when port 25 is open) +config direct 'direct' + option helo_domain '' + +# Default recipients for system notifications +config recipients 'recipients' + option admin '' + # Additional notification recipients (list) + # list notify 'user@example.com' diff --git a/package/secubox/secubox-app-smtp-relay/files/usr/lib/secubox/mail/smtp-relay.sh b/package/secubox/secubox-app-smtp-relay/files/usr/lib/secubox/mail/smtp-relay.sh new file mode 100644 index 00000000..1f29db7d --- /dev/null +++ b/package/secubox/secubox-app-smtp-relay/files/usr/lib/secubox/mail/smtp-relay.sh @@ -0,0 +1,280 @@ +#!/bin/sh +# SecuBox SMTP Relay - Shared Mail Library +# Source this file to use send_mail() in any SecuBox app +# +# Usage: +# . /usr/lib/secubox/mail/smtp-relay.sh +# send_mail "user@example.com" "Subject" "Body text" +# send_html_mail "user@example.com" "Subject" "

HTML Body

" + +. /lib/functions.sh + +SMTP_CONFIG="smtp-relay" + +# Global variables set by smtp_relay_load_config +smtp_enabled="" +smtp_mode="" +smtp_server="" +smtp_port="" +smtp_tls="" +smtp_ssl="" +smtp_auth="" +smtp_user="" +smtp_password="" +smtp_from="" +smtp_from_name="" +smtp_admin="" +smtp_helo="" + +# Load SMTP configuration from UCI +smtp_relay_load_config() { + config_load "$SMTP_CONFIG" + + config_get smtp_enabled main enabled '0' + config_get smtp_mode main mode 'external' + config_get auto_detect main auto_detect '1' + + # Auto-detect local mailserver if enabled + if [ "$auto_detect" = "1" ] && [ "$smtp_mode" = "external" ]; then + local mailserver_enabled mailserver_ip + mailserver_enabled=$(uci -q get mailserver.main.enabled) + mailserver_ip=$(uci -q get mailserver.server.ip_address) + [ -z "$mailserver_ip" ] && mailserver_ip=$(uci -q get mailserver.main.ip_address) + [ -z "$mailserver_ip" ] && mailserver_ip="127.0.0.1" + + if [ "$mailserver_enabled" = "1" ]; then + # Check if mailserver is responsive on SMTP port + if nc -z "$mailserver_ip" 25 2>/dev/null; then + smtp_mode="local" + fi + fi + fi + + # Load mode-specific settings + case "$smtp_mode" in + external) + config_get smtp_server external server '' + config_get smtp_port external port '587' + config_get smtp_tls external tls '1' + config_get smtp_ssl external ssl '0' + config_get smtp_auth external auth '1' + config_get smtp_user external user '' + config_get smtp_password external password '' + config_get smtp_from external from '' + config_get smtp_from_name external from_name 'SecuBox' + ;; + local) + config_get smtp_server local server '127.0.0.1' + config_get smtp_port local port '25' + config_get smtp_tls local tls '0' + config_get smtp_ssl local ssl '0' + config_get smtp_auth local auth '0' + config_get smtp_user local user '' + config_get smtp_password local password '' + config_get smtp_from local from '' + smtp_from_name="SecuBox" + ;; + direct) + smtp_server="" + smtp_port="25" + smtp_tls="0" + smtp_ssl="0" + smtp_auth="0" + config_get smtp_helo direct helo_domain '' + ;; + esac + + # Load default recipients + config_get smtp_admin recipients admin '' +} + +# Build msmtp configuration file +# Arguments: $1 - output file path (optional) +# Returns: path to temp config file +smtp_relay_build_msmtp_config() { + local conf_file="${1:-/tmp/msmtp-relay-$$.conf}" + local hostname + hostname=$(uci -q get system.@system[0].hostname || hostname) + + local tls_mode="off" + local tls_starttls="off" + + if [ "$smtp_ssl" = "1" ]; then + tls_mode="on" + tls_starttls="off" + elif [ "$smtp_tls" = "1" ]; then + tls_mode="on" + tls_starttls="on" + fi + + cat > "$conf_file" << EOF +account default +host ${smtp_server} +port ${smtp_port} +auth $([ "$smtp_auth" = "1" ] && echo "on" || echo "off") +user ${smtp_user} +password ${smtp_password} +tls ${tls_mode} +tls_starttls ${tls_starttls} +tls_certcheck off +from ${smtp_from:-secubox@$hostname} +EOF + chmod 600 "$conf_file" + echo "$conf_file" +} + +# Send email via SMTP relay +# Arguments: +# $1 - recipient email +# $2 - subject +# $3 - body (plain text or HTML) +# $4 - content type (optional: "text/plain" or "text/html", default: auto-detect) +# Returns: 0 on success, 1 on failure +send_mail() { + local recipient="$1" + local subject="$2" + local body="$3" + local content_type="${4:-}" + + # Load configuration + smtp_relay_load_config + + [ "$smtp_enabled" != "1" ] && { + echo "ERROR: SMTP relay is disabled" >&2 + return 1 + } + + # Use admin email if no recipient specified + [ -z "$recipient" ] && recipient="$smtp_admin" + + [ -z "$recipient" ] && { + echo "ERROR: No recipient specified" >&2 + return 1 + } + + local hostname + hostname=$(uci -q get system.@system[0].hostname || hostname) + local from_addr="${smtp_from:-secubox@$hostname}" + local from_header="${smtp_from_name} <${from_addr}>" + + # Auto-detect content type from body + if [ -z "$content_type" ]; then + if echo "$body" | grep -qE '||/dev/null || date) + + local email_content + email_content="Date: ${date_rfc} +From: ${from_header} +To: ${recipient} +Subject: ${subject} +MIME-Version: 1.0 +Content-Type: ${content_type}; charset=utf-8 +X-Mailer: SecuBox SMTP Relay 1.0 + +${body}" + + # Try msmtp first (for external and local modes) + if command -v msmtp >/dev/null 2>&1 && [ "$smtp_mode" != "direct" ]; then + local msmtp_conf + msmtp_conf=$(smtp_relay_build_msmtp_config) + echo "$email_content" | msmtp -C "$msmtp_conf" "$recipient" 2>&1 + local result=$? + rm -f "$msmtp_conf" + return $result + fi + + # Fallback to sendmail (for direct mode or if msmtp unavailable) + if command -v sendmail >/dev/null 2>&1; then + echo "$email_content" | sendmail -t 2>&1 + return $? + fi + + echo "ERROR: No mail transport available (msmtp or sendmail required)" >&2 + return 1 +} + +# Send HTML email (convenience wrapper) +send_html_mail() { + send_mail "$1" "$2" "$3" "text/html" +} + +# Send plain text email (convenience wrapper) +send_text_mail() { + send_mail "$1" "$2" "$3" "text/plain" +} + +# Test SMTP configuration by sending a test email +# Arguments: $1 - recipient (optional, uses admin if not specified) +# Returns: 0 on success, 1 on failure +smtp_relay_test() { + local recipient="${1:-}" + + smtp_relay_load_config + + [ -z "$recipient" ] && recipient="$smtp_admin" + [ -z "$recipient" ] && { + echo "ERROR: No test recipient available (set admin email in config)" >&2 + return 1 + } + + local hostname + hostname=$(uci -q get system.@system[0].hostname || hostname) + + local test_body="This is a test email from SecuBox SMTP Relay. + +Hostname: ${hostname} +Mode: ${smtp_mode} +Server: ${smtp_server:-direct delivery} +Port: ${smtp_port} +TLS: $([ "$smtp_tls" = "1" ] && echo "Yes" || echo "No") +Time: $(date) + +If you received this message, your SMTP configuration is working correctly." + + send_mail "$recipient" "[SecuBox] SMTP Test - $hostname" "$test_body" +} + +# Get SMTP relay status as JSON +smtp_relay_status() { + smtp_relay_load_config + + local mailserver_detected="false" + local mailserver_ip + mailserver_ip=$(uci -q get mailserver.server.ip_address) + [ -z "$mailserver_ip" ] && mailserver_ip=$(uci -q get mailserver.main.ip_address) + + if [ -n "$mailserver_ip" ] && nc -z "$mailserver_ip" 25 2>/dev/null; then + mailserver_detected="true" + fi + + local msmtp_available="false" + local sendmail_available="false" + command -v msmtp >/dev/null 2>&1 && msmtp_available="true" + command -v sendmail >/dev/null 2>&1 && sendmail_available="true" + + cat << EOF +{ + "enabled": $([ "$smtp_enabled" = "1" ] && echo "true" || echo "false"), + "mode": "${smtp_mode}", + "server": "${smtp_server:-}", + "port": ${smtp_port:-587}, + "tls": $([ "$smtp_tls" = "1" ] && echo "true" || echo "false"), + "ssl": $([ "$smtp_ssl" = "1" ] && echo "true" || echo "false"), + "auth": $([ "$smtp_auth" = "1" ] && echo "true" || echo "false"), + "from": "${smtp_from:-}", + "from_name": "${smtp_from_name:-SecuBox}", + "admin_recipient": "${smtp_admin:-}", + "local_mailserver_detected": ${mailserver_detected}, + "msmtp_available": ${msmtp_available}, + "sendmail_available": ${sendmail_available} +} +EOF +} diff --git a/package/secubox/secubox-app-smtp-relay/files/usr/sbin/smtp-relayctl b/package/secubox/secubox-app-smtp-relay/files/usr/sbin/smtp-relayctl new file mode 100644 index 00000000..ff25ceae --- /dev/null +++ b/package/secubox/secubox-app-smtp-relay/files/usr/sbin/smtp-relayctl @@ -0,0 +1,350 @@ +#!/bin/sh +# SecuBox SMTP Relay Controller +# CLI tool for managing centralized SMTP configuration + +VERSION="1.0.0" +CONFIG="smtp-relay" + +# Source shared library +. /usr/lib/secubox/mail/smtp-relay.sh + +# Colors (disabled if not terminal) +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + CYAN='\033[0;36m' + BOLD='\033[1m' + NC='\033[0m' +else + RED='' GREEN='' YELLOW='' CYAN='' BOLD='' NC='' +fi + +log() { echo -e "${GREEN}[SMTP]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } + +# Show current status +cmd_status() { + smtp_relay_load_config + + echo "" + echo -e "${BOLD}========================================" + echo -e " SecuBox SMTP Relay v$VERSION" + echo -e "========================================${NC}" + echo "" + echo -e " Enabled: $([ "$smtp_enabled" = "1" ] && echo -e "${GREEN}Yes${NC}" || echo -e "${RED}No${NC}")" + echo -e " Mode: ${CYAN}$smtp_mode${NC}" + echo "" + + case "$smtp_mode" in + external) + echo " Server: ${smtp_server:-${YELLOW}not configured${NC}}" + echo " Port: ${smtp_port}" + echo " TLS: $([ "$smtp_tls" = "1" ] && echo "STARTTLS" || ([ "$smtp_ssl" = "1" ] && echo "SSL/TLS" || echo "None"))" + echo " Auth: $([ "$smtp_auth" = "1" ] && echo "Yes (user: $smtp_user)" || echo "No")" + echo " From: ${smtp_from:-auto}" + ;; + local) + echo " Server: ${smtp_server} (local mailserver)" + echo " Port: ${smtp_port}" + ;; + direct) + echo " Mode: Direct MTA delivery" + echo " HELO: ${smtp_helo:-auto}" + ;; + esac + + echo "" + echo -e " Admin Email: ${smtp_admin:-${YELLOW}not set${NC}}" + echo "" + + # Transport availability + echo -e "${BOLD}Transport:${NC}" + if command -v msmtp >/dev/null 2>&1; then + echo -e " msmtp: ${GREEN}Available${NC}" + else + echo -e " msmtp: ${RED}Not installed${NC}" + fi + + if command -v sendmail >/dev/null 2>&1; then + echo -e " sendmail: ${GREEN}Available${NC}" + else + echo -e " sendmail: ${YELLOW}Not available${NC}" + fi + + # Check local mailserver + local mailserver_ip + mailserver_ip=$(uci -q get mailserver.server.ip_address) + [ -z "$mailserver_ip" ] && mailserver_ip=$(uci -q get mailserver.main.ip_address) + + if [ -n "$mailserver_ip" ]; then + if nc -z "$mailserver_ip" 25 2>/dev/null; then + echo -e " Local Mail: ${GREEN}Running ($mailserver_ip)${NC}" + else + echo -e " Local Mail: ${YELLOW}Not responding ($mailserver_ip)${NC}" + fi + else + echo -e " Local Mail: ${YELLOW}Not configured${NC}" + fi + + echo "" +} + +# Send test email +cmd_test() { + local recipient="$1" + + log "Testing SMTP configuration..." + + if smtp_relay_test "$recipient"; then + log "Test email sent successfully!" + return 0 + else + error "Failed to send test email" + return 1 + fi +} + +# Send email from CLI +cmd_send() { + local recipient="$1" + local subject="$2" + local body="$3" + + if [ -z "$recipient" ] || [ -z "$subject" ]; then + echo "Usage: smtp-relayctl send [body]" + echo " If body is omitted, reads from stdin" + return 1 + fi + + [ -z "$body" ] && body=$(cat) + + if send_mail "$recipient" "$subject" "$body"; then + log "Email sent to $recipient" + return 0 + else + error "Failed to send email" + return 1 + fi +} + +# Interactive configuration +cmd_configure() { + local mode="$1" + + case "$mode" in + external) + echo -e "${BOLD}Configuring External SMTP${NC}" + echo "" + + printf "SMTP Server: " + read -r server + [ -z "$server" ] && { error "Server is required"; return 1; } + + printf "Port [587]: " + read -r port + port=${port:-587} + + printf "Use STARTTLS? [Y/n]: " + read -r tls_yn + tls_yn=${tls_yn:-Y} + + printf "Authentication required? [Y/n]: " + read -r auth_yn + auth_yn=${auth_yn:-Y} + + local user="" password="" + if [ "$auth_yn" != "n" ] && [ "$auth_yn" != "N" ]; then + printf "Username: " + read -r user + + printf "Password: " + stty -echo 2>/dev/null + read -r password + stty echo 2>/dev/null + echo "" + fi + + printf "From email address: " + read -r from_email + + printf "From name [SecuBox]: " + read -r from_name + from_name=${from_name:-SecuBox} + + # Save configuration + uci set ${CONFIG}.main.mode='external' + uci set ${CONFIG}.main.enabled='1' + uci set ${CONFIG}.external.server="$server" + uci set ${CONFIG}.external.port="$port" + uci set ${CONFIG}.external.tls=$([ "$tls_yn" = "n" ] || [ "$tls_yn" = "N" ] && echo "0" || echo "1") + uci set ${CONFIG}.external.auth=$([ "$auth_yn" = "n" ] || [ "$auth_yn" = "N" ] && echo "0" || echo "1") + uci set ${CONFIG}.external.user="$user" + uci set ${CONFIG}.external.password="$password" + uci set ${CONFIG}.external.from="$from_email" + uci set ${CONFIG}.external.from_name="$from_name" + uci commit ${CONFIG} + + log "External SMTP configured successfully" + echo "" + echo "Test with: smtp-relayctl test your@email.com" + ;; + + local) + local mailserver_ip + mailserver_ip=$(uci -q get mailserver.server.ip_address) + [ -z "$mailserver_ip" ] && mailserver_ip=$(uci -q get mailserver.main.ip_address) + + if [ -z "$mailserver_ip" ]; then + error "Local mailserver not configured" + echo "Install secubox-app-mailserver first" + return 1 + fi + + if ! nc -z "$mailserver_ip" 25 2>/dev/null; then + warn "Local mailserver not responding on $mailserver_ip:25" + printf "Continue anyway? [y/N]: " + read -r cont + [ "$cont" != "y" ] && [ "$cont" != "Y" ] && return 1 + fi + + uci set ${CONFIG}.main.mode='local' + uci set ${CONFIG}.main.enabled='1' + uci set ${CONFIG}.local.server="$mailserver_ip" + uci commit ${CONFIG} + + log "Local mailserver configured ($mailserver_ip)" + ;; + + direct) + warn "Direct delivery requires port 25 to be open to the internet" + printf "HELO domain (leave empty for auto): " + read -r helo + + uci set ${CONFIG}.main.mode='direct' + uci set ${CONFIG}.main.enabled='1' + [ -n "$helo" ] && uci set ${CONFIG}.direct.helo_domain="$helo" + uci commit ${CONFIG} + + log "Direct delivery mode configured" + ;; + + "") + echo "Usage: smtp-relayctl configure " + echo "" + echo "Modes:" + echo " external - Use external SMTP server (Gmail, SendGrid, etc.)" + echo " local - Use local mailserver (secubox-app-mailserver)" + echo " direct - Direct MTA delivery (requires port 25 open)" + return 1 + ;; + + *) + error "Unknown mode: $mode" + return 1 + ;; + esac +} + +# Set admin email +cmd_admin() { + local email="$1" + + if [ -z "$email" ]; then + local current + current=$(uci -q get ${CONFIG}.recipients.admin) + if [ -n "$current" ]; then + echo "Current admin email: $current" + else + echo "No admin email configured" + fi + echo "" + echo "Usage: smtp-relayctl admin " + return 0 + fi + + uci set ${CONFIG}.recipients.admin="$email" + uci commit ${CONFIG} + log "Admin email set to: $email" +} + +# Enable/disable relay +cmd_enable() { + uci set ${CONFIG}.main.enabled='1' + uci commit ${CONFIG} + log "SMTP relay enabled" +} + +cmd_disable() { + uci set ${CONFIG}.main.enabled='0' + uci commit ${CONFIG} + log "SMTP relay disabled" +} + +# Output JSON status +cmd_json() { + smtp_relay_status +} + +# Show help +show_help() { + cat << EOF +SecuBox SMTP Relay v$VERSION + +Centralized SMTP configuration for all SecuBox services. + +Usage: smtp-relayctl [options] + +Commands: + status Show SMTP configuration status + test [recipient] Send test email + send [body] Send email (body from stdin if omitted) + configure Interactive configuration + admin [email] Show/set admin email address + enable Enable SMTP relay + disable Disable SMTP relay + json Output status as JSON + +Configuration modes: + external Use external SMTP server (Gmail, SendGrid, etc.) + local Use local mailserver (secubox-app-mailserver) + direct Direct MTA delivery (requires port 25 open) + +Examples: + smtp-relayctl status + smtp-relayctl configure external + smtp-relayctl test admin@example.com + smtp-relayctl admin notifications@mydomain.com + echo "Hello" | smtp-relayctl send user@example.com "Test Subject" + +Configuration: /etc/config/smtp-relay + +EOF +} + +# Main command dispatcher +case "${1:-}" in + status) shift; cmd_status "$@" ;; + test) shift; cmd_test "$@" ;; + send) shift; cmd_send "$@" ;; + configure) shift; cmd_configure "$@" ;; + admin) shift; cmd_admin "$@" ;; + enable) shift; cmd_enable "$@" ;; + disable) shift; cmd_disable "$@" ;; + json) shift; cmd_json "$@" ;; + help|--help|-h) + show_help + ;; + '') + show_help + ;; + *) + error "Unknown command: $1" + echo "" + show_help >&2 + exit 1 + ;; +esac + +exit 0