Add detection patterns for latest actively exploited vulnerabilities: - CVE-2025-55182 (React2Shell, CVSS 10.0) - CVE-2025-8110 (Gogs RCE), CVE-2025-53770 (SharePoint) - CVE-2025-52691 (SmarterMail), CVE-2025-40551 (SolarWinds) - CVE-2024-47575 (FortiManager), CVE-2024-21887 (Ivanti) - CVE-2024-3400, CVE-2024-0012, CVE-2024-9474 (PAN-OS) New attack categories based on OWASP Top 10 2025: - HTTP Request Smuggling (TE.CL/CL.TE conflicts) - AI/LLM Prompt Injection (ChatML, instruction markers) - WAF Bypass techniques (Unicode normalization, double encoding) - Supply Chain attacks (CI/CD poisoning, dependency confusion) - Extended SSTI (Jinja2, Freemarker, Velocity, Thymeleaf) - API Abuse (BOLA/IDOR, mass assignment) CrowdSec scenarios split into 11 separate files for reliability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
577 lines
16 KiB
JavaScript
577 lines
16 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require rpc';
|
|
'require ui';
|
|
'require form';
|
|
'require uci';
|
|
'require secubox/kiss-theme';
|
|
|
|
var callStatus = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'status',
|
|
expect: {}
|
|
});
|
|
|
|
var callUserList = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'user_list',
|
|
expect: {}
|
|
});
|
|
|
|
var callAliasList = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'alias_list',
|
|
expect: {}
|
|
});
|
|
|
|
var callWebmailStatus = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'webmail_status',
|
|
expect: {}
|
|
});
|
|
|
|
var callInstall = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'install',
|
|
expect: {}
|
|
});
|
|
|
|
var callStart = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'start',
|
|
expect: {}
|
|
});
|
|
|
|
var callStop = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'stop',
|
|
expect: {}
|
|
});
|
|
|
|
var callUserAdd = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'user_add',
|
|
params: ['email', 'password'],
|
|
expect: {}
|
|
});
|
|
|
|
var callUserDel = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'user_del',
|
|
params: ['email'],
|
|
expect: {}
|
|
});
|
|
|
|
var callAliasAdd = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'alias_add',
|
|
params: ['alias', 'target'],
|
|
expect: {}
|
|
});
|
|
|
|
var callUserPasswd = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'user_passwd',
|
|
params: ['email', 'password'],
|
|
expect: {}
|
|
});
|
|
|
|
var callDnsSetup = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'dns_setup',
|
|
expect: {}
|
|
});
|
|
|
|
var callSslSetup = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'ssl_setup',
|
|
expect: {}
|
|
});
|
|
|
|
var callWebmailConfigure = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'webmail_configure',
|
|
expect: {}
|
|
});
|
|
|
|
var callMeshBackup = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'mesh_backup',
|
|
expect: {}
|
|
});
|
|
|
|
var callFixPorts = rpc.declare({
|
|
object: 'luci.mailserver',
|
|
method: 'fix_ports',
|
|
expect: {}
|
|
});
|
|
|
|
return view.extend({
|
|
load: function() {
|
|
return Promise.all([
|
|
callStatus(),
|
|
callUserList(),
|
|
callAliasList(),
|
|
callWebmailStatus()
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var status = data[0] || {};
|
|
var users = (data[1] || {}).users || [];
|
|
var aliases = (data[2] || {}).aliases || [];
|
|
var webmail = data[3] || {};
|
|
|
|
var view = E('div', { 'class': 'cbi-map' }, [
|
|
E('h2', {}, 'Mail Server'),
|
|
|
|
// Status Section
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Server Status'),
|
|
E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td', 'style': 'width:150px' }, 'Status'),
|
|
E('td', { 'class': 'td' }, [
|
|
E('span', { 'style': status.state === 'running' ? 'color:green' : 'color:red' },
|
|
status.state === 'running' ? '● Running' : '○ Stopped')
|
|
])
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Domain'),
|
|
E('td', { 'class': 'td' }, status.fqdn || 'Not configured')
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Users'),
|
|
E('td', { 'class': 'td' }, String(status.users || 0))
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Storage'),
|
|
E('td', { 'class': 'td' }, status.storage || '0')
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'SSL'),
|
|
E('td', { 'class': 'td' }, status.ssl_valid ? '✓ Valid' : '✗ Not configured')
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Webmail'),
|
|
E('td', { 'class': 'td' }, webmail.running ? '● Running (port ' + webmail.port + ')' : '○ Stopped')
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, 'Mesh Backup'),
|
|
E('td', { 'class': 'td' }, status.mesh_enabled ? '● Enabled' : '○ Disabled')
|
|
])
|
|
]),
|
|
|
|
// Port Status
|
|
E('h4', { 'style': 'margin-top:15px' }, 'Ports'),
|
|
this.renderPortStatus(status.ports || {})
|
|
]),
|
|
|
|
// Quick Actions
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Quick Actions'),
|
|
E('div', { 'style': 'display:flex;gap:10px;flex-wrap:wrap' }, [
|
|
status.state !== 'running' ?
|
|
E('button', {
|
|
'class': 'btn cbi-button-action',
|
|
'click': ui.createHandlerFn(this, this.doStart)
|
|
}, 'Start Server') :
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, this.doStop)
|
|
}, 'Stop Server'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, this.doDnsSetup)
|
|
}, 'Setup DNS'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, this.doSslSetup)
|
|
}, 'Setup SSL'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, this.doWebmailConfigure)
|
|
}, 'Configure Webmail'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, this.doMeshBackup)
|
|
}, 'Mesh Backup'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'click': ui.createHandlerFn(this, this.doFixPorts)
|
|
}, 'Fix Ports')
|
|
])
|
|
]),
|
|
|
|
// Users Section
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Mail Users'),
|
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button-add',
|
|
'click': ui.createHandlerFn(this, this.showAddUserModal)
|
|
}, 'Add User')
|
|
]),
|
|
this.renderUserTable(users)
|
|
]),
|
|
|
|
// Aliases Section
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, 'Email Aliases'),
|
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button-add',
|
|
'click': ui.createHandlerFn(this, this.showAddAliasModal)
|
|
}, 'Add Alias')
|
|
]),
|
|
this.renderAliasTable(aliases)
|
|
])
|
|
]);
|
|
|
|
return KissTheme.wrap([view], 'admin/services/mailserver');
|
|
},
|
|
|
|
renderPortStatus: function(ports) {
|
|
var portList = [
|
|
{ port: '25', name: 'SMTP' },
|
|
{ port: '587', name: 'Submission' },
|
|
{ port: '465', name: 'SMTPS' },
|
|
{ port: '993', name: 'IMAPS' },
|
|
{ port: '995', name: 'POP3S' }
|
|
];
|
|
|
|
return E('div', { 'style': 'display:flex;gap:15px;flex-wrap:wrap' },
|
|
portList.map(function(p) {
|
|
var isOpen = ports[p.port];
|
|
return E('span', {
|
|
'style': 'padding:5px 10px;border-radius:4px;background:' + (isOpen ? '#d4edda' : '#f8d7da')
|
|
}, p.name + ' (' + p.port + '): ' + (isOpen ? '✓' : '✗'));
|
|
})
|
|
);
|
|
},
|
|
|
|
renderUserTable: function(users) {
|
|
if (!users || users.length === 0) {
|
|
return E('p', { 'class': 'cbi-value-description' }, 'No mail users configured.');
|
|
}
|
|
|
|
var rows = users.map(L.bind(function(u) {
|
|
return E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, u.email),
|
|
E('td', { 'class': 'td' }, u.size || '0'),
|
|
E('td', { 'class': 'td' }, String(u.messages || 0)),
|
|
E('td', { 'class': 'td' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button-neutral',
|
|
'style': 'padding:2px 8px;font-size:12px;margin-right:5px',
|
|
'click': ui.createHandlerFn(this, this.showResetPasswordModal, u.email)
|
|
}, 'Reset Password'),
|
|
E('button', {
|
|
'class': 'btn cbi-button-remove',
|
|
'style': 'padding:2px 8px;font-size:12px',
|
|
'click': ui.createHandlerFn(this, this.doDeleteUser, u.email)
|
|
}, 'Delete')
|
|
])
|
|
]);
|
|
}, this));
|
|
|
|
return E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, 'Email'),
|
|
E('th', { 'class': 'th' }, 'Size'),
|
|
E('th', { 'class': 'th' }, 'Messages'),
|
|
E('th', { 'class': 'th' }, 'Actions')
|
|
])
|
|
].concat(rows));
|
|
},
|
|
|
|
renderAliasTable: function(aliases) {
|
|
if (!aliases || aliases.length === 0) {
|
|
return E('p', { 'class': 'cbi-value-description' }, 'No aliases configured.');
|
|
}
|
|
|
|
var rows = aliases.map(function(a) {
|
|
return E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, a.alias),
|
|
E('td', { 'class': 'td' }, '→'),
|
|
E('td', { 'class': 'td' }, a.target)
|
|
]);
|
|
});
|
|
|
|
return E('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, 'Alias'),
|
|
E('th', { 'class': 'th' }, ''),
|
|
E('th', { 'class': 'th' }, 'Target')
|
|
])
|
|
].concat(rows));
|
|
},
|
|
|
|
showAddUserModal: function() {
|
|
var emailInput, passwordInput;
|
|
|
|
ui.showModal('Add Mail User', [
|
|
E('p', {}, 'Enter email address and password for the new user.'),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, 'Email'),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
emailInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'user@domain.com' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, 'Password'),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
passwordInput = E('input', { 'type': 'password', 'class': 'cbi-input-text' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', {
|
|
'class': 'btn',
|
|
'click': ui.hideModal
|
|
}, 'Cancel'),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button-action',
|
|
'click': ui.createHandlerFn(this, function() {
|
|
var email = emailInput.value;
|
|
var password = passwordInput.value;
|
|
if (!email || !password) {
|
|
ui.addNotification(null, E('p', 'Email and password required'), 'error');
|
|
return;
|
|
}
|
|
ui.hideModal();
|
|
return this.doAddUser(email, password);
|
|
})
|
|
}, 'Add User')
|
|
])
|
|
]);
|
|
},
|
|
|
|
showAddAliasModal: function() {
|
|
var aliasInput, targetInput;
|
|
|
|
ui.showModal('Add Email Alias', [
|
|
E('p', {}, 'Create an alias that forwards to another address.'),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, 'Alias'),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
aliasInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'info@domain.com' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, 'Forward To'),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
targetInput = E('input', { 'type': 'email', 'class': 'cbi-input-text', 'placeholder': 'user@domain.com' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', {
|
|
'class': 'btn',
|
|
'click': ui.hideModal
|
|
}, 'Cancel'),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button-action',
|
|
'click': ui.createHandlerFn(this, function() {
|
|
var alias = aliasInput.value;
|
|
var target = targetInput.value;
|
|
if (!alias || !target) {
|
|
ui.addNotification(null, E('p', 'Alias and target required'), 'error');
|
|
return;
|
|
}
|
|
ui.hideModal();
|
|
return this.doAddAlias(alias, target);
|
|
})
|
|
}, 'Add Alias')
|
|
])
|
|
]);
|
|
},
|
|
|
|
showResetPasswordModal: function(email) {
|
|
var passwordInput, confirmInput;
|
|
|
|
ui.showModal('Reset Password', [
|
|
E('p', {}, 'Enter new password for: ' + email),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, 'New Password'),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
passwordInput = E('input', { 'type': 'password', 'class': 'cbi-input-text' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, 'Confirm'),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
confirmInput = E('input', { 'type': 'password', 'class': 'cbi-input-text' })
|
|
])
|
|
]),
|
|
E('div', { 'class': 'right' }, [
|
|
E('button', {
|
|
'class': 'btn',
|
|
'click': ui.hideModal
|
|
}, 'Cancel'),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button-action',
|
|
'click': ui.createHandlerFn(this, function() {
|
|
var password = passwordInput.value;
|
|
var confirm = confirmInput.value;
|
|
if (!password) {
|
|
ui.addNotification(null, E('p', 'Password required'), 'error');
|
|
return;
|
|
}
|
|
if (password !== confirm) {
|
|
ui.addNotification(null, E('p', 'Passwords do not match'), 'error');
|
|
return;
|
|
}
|
|
ui.hideModal();
|
|
return this.doResetPassword(email, password);
|
|
})
|
|
}, 'Reset Password')
|
|
])
|
|
]);
|
|
},
|
|
|
|
doStart: function() {
|
|
ui.showModal('Starting Server', [
|
|
E('p', { 'class': 'spinning' }, 'Starting mail server...')
|
|
]);
|
|
return callStart().then(function() {
|
|
ui.hideModal();
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
doStop: function() {
|
|
ui.showModal('Stopping Server', [
|
|
E('p', { 'class': 'spinning' }, 'Stopping mail server...')
|
|
]);
|
|
return callStop().then(function() {
|
|
ui.hideModal();
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
doAddUser: function(email, password) {
|
|
ui.showModal('Adding User', [
|
|
E('p', { 'class': 'spinning' }, 'Adding user: ' + email)
|
|
]);
|
|
return callUserAdd(email, password).then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'User added: ' + email), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', 'Failed: ' + res.output), 'error');
|
|
}
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
doDeleteUser: function(email) {
|
|
if (!confirm('Delete user ' + email + '?')) return;
|
|
|
|
ui.showModal('Deleting User', [
|
|
E('p', { 'class': 'spinning' }, 'Deleting user: ' + email)
|
|
]);
|
|
return callUserDel(email).then(function() {
|
|
ui.hideModal();
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
doAddAlias: function(alias, target) {
|
|
ui.showModal('Adding Alias', [
|
|
E('p', { 'class': 'spinning' }, 'Adding alias: ' + alias)
|
|
]);
|
|
return callAliasAdd(alias, target).then(function() {
|
|
ui.hideModal();
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
doResetPassword: function(email, password) {
|
|
ui.showModal('Resetting Password', [
|
|
E('p', { 'class': 'spinning' }, 'Resetting password for: ' + email)
|
|
]);
|
|
return callUserPasswd(email, password).then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'Password reset for: ' + email), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', 'Failed: ' + (res.error || res.output)), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
doDnsSetup: function() {
|
|
ui.showModal('DNS Setup', [
|
|
E('p', { 'class': 'spinning' }, 'Creating MX, SPF, DMARC records...')
|
|
]);
|
|
return callDnsSetup().then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'DNS records created'), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', res.output), 'warning');
|
|
}
|
|
});
|
|
},
|
|
|
|
doSslSetup: function() {
|
|
ui.showModal('SSL Setup', [
|
|
E('p', { 'class': 'spinning' }, 'Obtaining SSL certificate via DNS-01...')
|
|
]);
|
|
return callSslSetup().then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'SSL certificate installed'), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', res.output), 'error');
|
|
}
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
doWebmailConfigure: function() {
|
|
ui.showModal('Configuring Webmail', [
|
|
E('p', { 'class': 'spinning' }, 'Configuring Roundcube...')
|
|
]);
|
|
return callWebmailConfigure().then(function(res) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', 'Webmail configured. Restart webmail container to apply.'), 'success');
|
|
});
|
|
},
|
|
|
|
doMeshBackup: function() {
|
|
ui.showModal('Mesh Backup', [
|
|
E('p', { 'class': 'spinning' }, 'Creating backup for mesh sync...')
|
|
]);
|
|
return callMeshBackup().then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'Backup created'), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', res.output), 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
doFixPorts: function() {
|
|
ui.showModal('Fixing Ports', [
|
|
E('p', { 'class': 'spinning' }, 'Enabling submission (587), smtps (465), and POP3S (995) ports...')
|
|
]);
|
|
return callFixPorts().then(function(res) {
|
|
ui.hideModal();
|
|
if (res.code === 0) {
|
|
ui.addNotification(null, E('p', 'Ports enabled successfully'), 'success');
|
|
} else {
|
|
ui.addNotification(null, E('p', res.output || 'Some ports may still not be listening'), 'warning');
|
|
}
|
|
window.location.reload();
|
|
});
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|