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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-03-15 16:01:12 +01:00
parent ccccd3d93b
commit a345c16425
13 changed files with 1327 additions and 17 deletions

View File

@ -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

View File

@ -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)
---

View File

@ -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=""

View File

@ -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 <contact@secubox.io>
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))

View File

@ -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 = '<span style="color: #4caf50;">✓ ' + _('Test email sent successfully!') + '</span>';
ui.addNotification(null, E('p', _('Test email sent to %s').format(recipient)), 'info');
} else {
resultDiv.innerHTML = '<span style="color: #f44336;">✗ ' + (res.error || _('Failed to send')) + '</span>';
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 = '<span style="color: #f44336;">✗ ' + err.message + '</span>';
});
},
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 = '<div style="display: flex; gap: 20px; flex-wrap: wrap;">';
// Enabled status
statusHtml += '<div><strong>' + _('Relay:') + '</strong> ';
if (status.enabled) {
statusHtml += '<span style="color: #4caf50;">●</span> ' + _('Enabled');
} else {
statusHtml += '<span style="color: #f44336;">●</span> ' + _('Disabled');
}
statusHtml += '</div>';
// Mode
statusHtml += '<div><strong>' + _('Mode:') + '</strong> ' + (modeText[status.mode] || status.mode || '-') + '</div>';
// Server
if (status.server) {
statusHtml += '<div><strong>' + _('Server:') + '</strong> ' + status.server + ':' + status.port + '</div>';
}
// Transport
statusHtml += '<div><strong>' + _('Transport:') + '</strong> ';
if (status.msmtp_available) {
statusHtml += '<span style="color: #4caf50;">msmtp</span>';
} else if (status.sendmail_available) {
statusHtml += '<span style="color: #ff9800;">sendmail</span>';
} else {
statusHtml += '<span style="color: #f44336;">' + _('None') + '</span>';
}
statusHtml += '</div>';
// Local mailserver
if (localDetect.detected) {
statusHtml += '<div><strong>' + _('Local Mail:') + '</strong> ';
if (localDetect.responding) {
statusHtml += '<span style="color: #4caf50;">● ' + _('Available') + '</span>';
} else {
statusHtml += '<span style="color: #ff9800;">● ' + _('Not responding') + '</span>';
}
statusHtml += '</div>';
}
statusHtml += '</div>';
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 = '<span style="color: #4caf50;">✓ ' + _('Test email sent!') + '</span>';
} else {
resultDiv.innerHTML = '<span style="color: #f44336;">✗ ' + (res.error || _('Failed')) + '</span>';
}
}).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
});

View File

@ -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

View File

@ -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}
}
}
}

View File

@ -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"]
}
}
}

View File

@ -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="<html><body><h1>SecuBox Email Test</h1><p>This is a test email from <strong>$hostname</strong>.</p><p>Generated: $(date)</p></body></html>"
if send_report_email "test" "$test_body" "$recipient"; then

View File

@ -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 <contact@secubox.io>
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))

View File

@ -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'

View File

@ -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" "<h1>HTML Body</h1>"
. /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 '<html|<body|<div|<p>|<h[1-6]>|<table'; then
content_type="text/html"
else
content_type="text/plain"
fi
fi
# Build email headers
local date_rfc
date_rfc=$(date -R 2>/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
}

View File

@ -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 <recipient> <subject> [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 <mode>"
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 <email>"
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 <command> [options]
Commands:
status Show SMTP configuration status
test [recipient] Send test email
send <to> <subject> [body] Send email (body from stdin if omitted)
configure <mode> 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