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:
parent
ccccd3d93b
commit
a345c16425
@ -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
|
- 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
|
- UCi config updated with scoring weights, sensitivity, whitelist, decay options
|
||||||
- Enables fine-tuning of auto-ban sensitivity for production traffic
|
- 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
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Work In Progress (Claude)
|
# 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
|
> **Architecture Reference**: SecuBox Fanzine v3 — Les 4 Couches
|
||||||
|
|
||||||
@ -10,6 +10,17 @@ _Last updated: 2026-03-16 (WAF Auto-Ban Tuning)_
|
|||||||
|
|
||||||
### 2026-03-16
|
### 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)**
|
- **WAF Auto-Ban Tuning System (Complete)**
|
||||||
- Configurable scoring weights via UCI `scoring` section
|
- Configurable scoring weights via UCI `scoring` section
|
||||||
- Sensitivity presets: low (0.7x), medium (1.0x), high (1.3x), custom
|
- 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
|
### Backlog
|
||||||
|
|
||||||
- SSMTP / mail host / MX record management (v2)
|
- Advanced mail features: webmail integration, DKIM signing, multiple recipients (v2)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -27,10 +27,33 @@ get_alert_setting() {
|
|||||||
echo "$value"
|
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 notification
|
||||||
send_email() {
|
send_email() {
|
||||||
local subject="$1"
|
local subject="$1"
|
||||||
local body="$2"
|
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 recipient=""
|
||||||
local smtp_server=""
|
local smtp_server=""
|
||||||
local smtp_port=""
|
local smtp_port=""
|
||||||
|
|||||||
30
package/secubox/luci-app-smtp-relay/Makefile
Normal file
30
package/secubox/luci-app-smtp-relay/Makefile
Normal 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))
|
||||||
@ -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
|
||||||
|
});
|
||||||
@ -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
|
||||||
@ -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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,47 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# SecuBox Reporter - Email Integration
|
# 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
|
. /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 via email
|
||||||
send_report_email() {
|
send_report_email() {
|
||||||
local report_type="$1"
|
local report_type="$1"
|
||||||
local html_content="$2"
|
local html_content="$2"
|
||||||
local recipient="$3"
|
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_server=""
|
||||||
local smtp_port=""
|
local smtp_port=""
|
||||||
local smtp_user=""
|
local smtp_user=""
|
||||||
@ -28,19 +60,6 @@ send_report_email() {
|
|||||||
return 1
|
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
|
# Build MIME multipart email
|
||||||
local boundary="SecuBox_Report_$(date +%s)_$$"
|
local boundary="SecuBox_Report_$(date +%s)_$$"
|
||||||
|
|
||||||
@ -105,6 +124,15 @@ EOF
|
|||||||
# Test email configuration
|
# Test email configuration
|
||||||
test_email() {
|
test_email() {
|
||||||
local recipient="$1"
|
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" ] && {
|
[ -z "$recipient" ] && {
|
||||||
config_load secubox-reporter
|
config_load secubox-reporter
|
||||||
config_get recipient email recipient ""
|
config_get recipient email recipient ""
|
||||||
@ -115,7 +143,8 @@ test_email() {
|
|||||||
return 1
|
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>"
|
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
|
if send_report_email "test" "$test_body" "$recipient"; then
|
||||||
|
|||||||
46
package/secubox/secubox-app-smtp-relay/Makefile
Normal file
46
package/secubox/secubox-app-smtp-relay/Makefile
Normal 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))
|
||||||
@ -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'
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user