haraka-plugin-rspamd
Advanced tools
Comparing version 1.1.6 to 1.1.7
@@ -11,3 +11,1 @@ Fixes # | ||
- [ ] Changes.md updated | ||
- [ ] package.json.version bumped | ||
- [ ] published to NPM (will be done by @core) |
@@ -0,1 +1,10 @@ | ||
### Unreleased | ||
## 1.1.7 - 2022-06-05 | ||
- ci: replace travis & appveyor with GitHub actions | ||
- test: replace nodeunit with mocha | ||
- test: update header checks against lower cased header names | ||
## 1.1.6 - 2020-02-29 | ||
@@ -5,2 +14,3 @@ | ||
## 1.1.5 - 2019-04-01 | ||
@@ -10,2 +20,3 @@ | ||
## 1.1.4 - 2019-01-28 | ||
@@ -15,2 +26,3 @@ | ||
## 1.1.3 - 2018-12-19 | ||
@@ -20,2 +32,3 @@ | ||
## 1.1.2 - 2018-11-03 | ||
@@ -25,2 +38,3 @@ | ||
## 1.1.1 - 2018-05-10 | ||
@@ -27,0 +41,0 @@ |
649
index.js
@@ -10,450 +10,449 @@ 'use strict'; | ||
exports.register = function () { | ||
this.load_rspamd_ini(); | ||
this.load_rspamd_ini(); | ||
} | ||
exports.load_rspamd_ini = function () { | ||
const plugin = this; | ||
const plugin = this; | ||
plugin.cfg = plugin.config.get('rspamd.ini', { | ||
booleans: [ | ||
'-check.authenticated', | ||
'+dkim.enabled', | ||
'-check.private_ip', | ||
'-check.local_ip', | ||
'-check.relay', | ||
'+reject.spam', | ||
'-reject.authenticated', | ||
'+rewrite_subject.enabled', | ||
'+rmilter_headers.enabled', | ||
'+soft_reject.enabled', | ||
'+smtp_message.enabled', | ||
], | ||
}, () => { | ||
plugin.load_rspamd_ini(); | ||
}); | ||
plugin.cfg = plugin.config.get('rspamd.ini', { | ||
booleans: [ | ||
'-check.authenticated', | ||
'+dkim.enabled', | ||
'-check.private_ip', | ||
'-check.local_ip', | ||
'-check.relay', | ||
'+reject.spam', | ||
'-reject.authenticated', | ||
'+rewrite_subject.enabled', | ||
'+rmilter_headers.enabled', | ||
'+soft_reject.enabled', | ||
'+smtp_message.enabled', | ||
], | ||
}, () => { | ||
plugin.load_rspamd_ini(); | ||
}); | ||
if (!plugin.cfg.reject.message) { | ||
plugin.cfg.reject.message = 'Detected as spam'; | ||
} | ||
if (!plugin.cfg.reject.message) { | ||
plugin.cfg.reject.message = 'Detected as spam'; | ||
} | ||
if (!plugin.cfg.soft_reject.message) { | ||
plugin.cfg.soft_reject.message = 'Deferred by policy'; | ||
} | ||
if (!plugin.cfg.soft_reject.message) { | ||
plugin.cfg.soft_reject.message = 'Deferred by policy'; | ||
} | ||
if (!plugin.cfg.spambar) { | ||
plugin.cfg.spambar = { positive: '+', negative: '-', neutral: '/' }; | ||
} | ||
if (!plugin.cfg.spambar) { | ||
plugin.cfg.spambar = { positive: '+', negative: '-', neutral: '/' }; | ||
} | ||
if (!plugin.cfg.main.port) plugin.cfg.main.port = 11333; | ||
if (!plugin.cfg.main.host) plugin.cfg.main.host = 'localhost'; | ||
if (!plugin.cfg.main.port) plugin.cfg.main.port = 11333; | ||
if (!plugin.cfg.main.host) plugin.cfg.main.host = 'localhost'; | ||
if (!plugin.cfg.main.add_headers) { | ||
if (plugin.cfg.main.always_add_headers === true) { | ||
plugin.cfg.main.add_headers = 'always'; | ||
} | ||
else { | ||
plugin.cfg.main.add_headers = 'sometimes'; | ||
} | ||
if (!plugin.cfg.main.add_headers) { | ||
if (plugin.cfg.main.always_add_headers === true) { | ||
plugin.cfg.main.add_headers = 'always'; | ||
} | ||
else { | ||
plugin.cfg.main.add_headers = 'sometimes'; | ||
} | ||
} | ||
if (!plugin.cfg.subject) { | ||
plugin.cfg.subject = "[SPAM] %s"; | ||
} | ||
if (!plugin.cfg.subject) { | ||
plugin.cfg.subject = "[SPAM] %s"; | ||
} | ||
} | ||
exports.get_options = function (connection) { | ||
const plugin = this; | ||
const plugin = this; | ||
// https://rspamd.com/doc/architecture/protocol.html | ||
// https://github.com/vstakhov/rspamd/blob/master/rules/http_headers.lua | ||
const options = { | ||
headers: {}, | ||
path: '/checkv2', | ||
method: 'POST', | ||
}; | ||
// https://rspamd.com/doc/architecture/protocol.html | ||
// https://github.com/vstakhov/rspamd/blob/master/rules/http_headers.lua | ||
const options = { | ||
headers: {}, | ||
path: '/checkv2', | ||
method: 'POST', | ||
}; | ||
if (plugin.cfg.main.unix_socket) { | ||
options.socketPath = plugin.cfg.main.unix_socket; | ||
} | ||
else { | ||
options.port = plugin.cfg.main.port; | ||
options.host = plugin.cfg.main.host; | ||
} | ||
if (plugin.cfg.main.unix_socket) { | ||
options.socketPath = plugin.cfg.main.unix_socket; | ||
} | ||
else { | ||
options.port = plugin.cfg.main.port; | ||
options.host = plugin.cfg.main.host; | ||
} | ||
if (connection.notes.auth_user) { | ||
options.headers.User = connection.notes.auth_user; | ||
} | ||
if (connection.notes.auth_user) { | ||
options.headers.User = connection.notes.auth_user; | ||
} | ||
if (connection.remote.ip) options.headers.IP = connection.remote.ip; | ||
if (connection.remote.ip) options.headers.IP = connection.remote.ip; | ||
const fcrdns = connection.results.get('fcrdns'); | ||
if (fcrdns && fcrdns.fcrdns && fcrdns.fcrdns[0]) { | ||
options.headers.Hostname = fcrdns.fcrdns[0]; | ||
const fcrdns = connection.results.get('fcrdns'); | ||
if (fcrdns && fcrdns.fcrdns && fcrdns.fcrdns[0]) { | ||
options.headers.Hostname = fcrdns.fcrdns[0]; | ||
} | ||
else { | ||
if (connection.remote.host) { | ||
options.headers.Hostname = connection.remote.host; | ||
} | ||
else { | ||
if (connection.remote.host) { | ||
options.headers.Hostname = connection.remote.host; | ||
} | ||
} | ||
} | ||
if (connection.hello.host) options.headers.Helo = connection.hello.host; | ||
if (connection.hello.host) options.headers.Helo = connection.hello.host; | ||
let spf = connection.transaction.results.get('spf'); | ||
let spf = connection.transaction.results.get('spf'); | ||
if (spf && spf.result) { | ||
options.headers.SPF = { result: spf.result.toLowerCase() }; | ||
} | ||
else { | ||
spf = connection.results.get('spf'); | ||
if (spf && spf.result) { | ||
options.headers.SPF = { result: spf.result.toLowerCase() }; | ||
options.headers.SPF = { result: spf.result.toLowerCase() }; | ||
} | ||
else { | ||
spf = connection.results.get('spf'); | ||
if (spf && spf.result) { | ||
options.headers.SPF = { result: spf.result.toLowerCase() }; | ||
} | ||
} | ||
if (connection.transaction.mail_from) { | ||
const mfaddr = connection.transaction.mail_from.address().toString(); | ||
if (mfaddr) { | ||
options.headers.From = mfaddr; | ||
} | ||
} | ||
if (connection.transaction.mail_from) { | ||
const mfaddr = connection.transaction.mail_from.address().toString(); | ||
if (mfaddr) { | ||
options.headers.From = mfaddr; | ||
} | ||
const rcpts = connection.transaction.rcpt_to; | ||
if (rcpts) { | ||
options.headers.Rcpt = []; | ||
for (let i=0; i < rcpts.length; i++) { | ||
options.headers.Rcpt.push(rcpts[i].address()); | ||
} | ||
const rcpts = connection.transaction.rcpt_to; | ||
if (rcpts) { | ||
options.headers.Rcpt = []; | ||
for (let i=0; i < rcpts.length; i++) { | ||
options.headers.Rcpt.push(rcpts[i].address()); | ||
} | ||
// for per-user options | ||
if (rcpts.length === 1) { | ||
options.headers['Deliver-To'] = options.headers.Rcpt[0]; | ||
} | ||
// for per-user options | ||
if (rcpts.length === 1) { | ||
options.headers['Deliver-To'] = options.headers.Rcpt[0]; | ||
} | ||
} | ||
if (connection.transaction.uuid) | ||
options.headers['Queue-Id'] = connection.transaction.uuid; | ||
if (connection.transaction.uuid) | ||
options.headers['Queue-Id'] = connection.transaction.uuid; | ||
if (connection.tls.enabled) { | ||
options.headers['TLS-Cipher'] = connection.tls.cipher.name; | ||
options.headers['TLS-Version'] = connection.tls.cipher.version; | ||
} | ||
if (connection.tls.enabled) { | ||
options.headers['TLS-Cipher'] = connection.tls.cipher.name; | ||
options.headers['TLS-Version'] = connection.tls.cipher.version; | ||
} | ||
return options; | ||
return options; | ||
} | ||
exports.get_smtp_message = function (r) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (!plugin.cfg.smtp_message.enabled || !r.data.messages) return; | ||
if (typeof(r.data.messages) !== 'object') return; | ||
if (!r.data.messages.smtp_message) return; | ||
if (!plugin.cfg.smtp_message.enabled || !r.data.messages) return; | ||
if (typeof(r.data.messages) !== 'object') return; | ||
if (!r.data.messages.smtp_message) return; | ||
return r.data.messages.smtp_message; | ||
return r.data.messages.smtp_message; | ||
} | ||
exports.do_rewrite = function (connection, data) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (!plugin.cfg.rewrite_subject.enabled) return false; | ||
if (data.action !== 'rewrite subject') return false; | ||
if (!plugin.cfg.rewrite_subject.enabled) return false; | ||
if (data.action !== 'rewrite subject') return false; | ||
const rspamd_subject = data.subject || plugin.cfg.subject; | ||
const old_subject = connection.transaction.header.get('Subject') || ''; | ||
const new_subject = rspamd_subject.replace('%s', old_subject); | ||
const rspamd_subject = data.subject || plugin.cfg.subject; | ||
const old_subject = connection.transaction.header.get('Subject') || ''; | ||
const new_subject = rspamd_subject.replace('%s', old_subject); | ||
connection.transaction.remove_header('Subject'); | ||
connection.transaction.add_header('Subject', new_subject); | ||
connection.transaction.remove_header('Subject'); | ||
connection.transaction.add_header('Subject', new_subject); | ||
} | ||
exports.add_dkim_header = function (connection, data) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (!plugin.cfg.dkim.enabled) return; | ||
if (!data['dkim-signature']) return; | ||
if (!plugin.cfg.dkim.enabled) return; | ||
if (!data['dkim-signature']) return; | ||
connection.transaction.add_header('DKIM-Signature', data['dkim-signature']); | ||
connection.transaction.add_header('DKIM-Signature', data['dkim-signature']); | ||
} | ||
exports.do_milter_headers = function (connection, data) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (!plugin.cfg.rmilter_headers.enabled) return; | ||
if (!data.milter) return; | ||
if (!plugin.cfg.rmilter_headers.enabled) return; | ||
if (!data.milter) return; | ||
if (data.milter.remove_headers) { | ||
Object.keys(data.milter.remove_headers).forEach((key) => { | ||
connection.transaction.remove_header(key); | ||
}) | ||
} | ||
if (data.milter.remove_headers) { | ||
Object.keys(data.milter.remove_headers).forEach((key) => { | ||
connection.transaction.remove_header(key); | ||
}) | ||
} | ||
if (data.milter.add_headers) { | ||
Object.keys(data.milter.add_headers).forEach((key) => { | ||
const header_value = data.milter.add_headers[key]; | ||
if (!header_value) return; | ||
if (data.milter.add_headers) { | ||
Object.keys(data.milter.add_headers).forEach((key) => { | ||
const header_value = data.milter.add_headers[key]; | ||
if (!header_value) return; | ||
if (typeof header_value === 'object') { | ||
connection.transaction.add_header(key, header_value.value); | ||
} | ||
else { | ||
connection.transaction.add_header(key, header_value); | ||
} | ||
}) | ||
} | ||
if (typeof header_value === 'object') { | ||
connection.transaction.add_header(key, header_value.value); | ||
} | ||
else { | ||
connection.transaction.add_header(key, header_value); | ||
} | ||
}) | ||
} | ||
} | ||
exports.hook_data_post = function (next, connection) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (!connection.transaction) return next(); | ||
if (!plugin.should_check(connection)) return next(); | ||
if (!connection.transaction) return next(); | ||
if (!plugin.should_check(connection)) return next(); | ||
let timer; | ||
const timeout = plugin.cfg.main.timeout || plugin.timeout - 1; | ||
let timer; | ||
const timeout = plugin.cfg.main.timeout || plugin.timeout - 1; | ||
let calledNext=false; | ||
function nextOnce (code, msg) { | ||
clearTimeout(timer); | ||
if (calledNext) return; | ||
calledNext=true; | ||
next(code, msg); | ||
} | ||
let calledNext=false; | ||
function nextOnce (code, msg) { | ||
clearTimeout(timer); | ||
if (calledNext) return; | ||
calledNext=true; | ||
next(code, msg); | ||
} | ||
timer = setTimeout(() => { | ||
if (!connection) return; | ||
if (!connection.transaction) return; | ||
connection.transaction.results.add(plugin, {err: 'timeout'}); | ||
nextOnce(); | ||
}, timeout * 1000); | ||
timer = setTimeout(() => { | ||
if (!connection) return; | ||
if (!connection.transaction) return; | ||
connection.transaction.results.add(plugin, {err: 'timeout'}); | ||
nextOnce(); | ||
}, timeout * 1000); | ||
const start = Date.now(); | ||
const start = Date.now(); | ||
const req = http.request(plugin.get_options(connection), (res) => { | ||
let rawData = ''; | ||
const req = http.request(plugin.get_options(connection), (res) => { | ||
let rawData = ''; | ||
res.on('data', (chunk) => { rawData += chunk; }); | ||
res.on('data', (chunk) => { rawData += chunk; }); | ||
res.on('end', () => { | ||
const r = plugin.parse_response(rawData, connection); | ||
if (!r || !r.data || !r.log) return nextOnce(); | ||
res.on('end', () => { | ||
const r = plugin.parse_response(rawData, connection); | ||
if (!r || !r.data || !r.log) return nextOnce(); | ||
r.log.emit = true; // spit out a log entry | ||
r.log.time = (Date.now() - start)/1000; | ||
r.log.emit = true; // spit out a log entry | ||
r.log.time = (Date.now() - start)/1000; | ||
if (!connection.transaction) return nextOnce(); | ||
if (!connection.transaction) return nextOnce(); | ||
connection.transaction.results.add(plugin, r.log); | ||
if (r.data.symbols) connection.transaction.results.add(plugin, { symbols: r.data.symbols }); | ||
connection.transaction.results.add(plugin, r.log); | ||
if (r.data.symbols) connection.transaction.results.add(plugin, { symbols: r.data.symbols }); | ||
const smtp_message = plugin.get_smtp_message(r); | ||
const smtp_message = plugin.get_smtp_message(r); | ||
plugin.do_rewrite(connection, r.data); | ||
plugin.do_rewrite(connection, r.data); | ||
if (plugin.cfg.soft_reject.enabled && r.data.action === 'soft reject') { | ||
nextOnce(DENYSOFT, DSN.sec_unauthorized(smtp_message || plugin.cfg.soft_reject.message, 451)); | ||
} | ||
else if (plugin.wants_reject(connection, r.data)) { | ||
nextOnce(DENY, smtp_message || plugin.cfg.reject.message); | ||
} | ||
else { | ||
plugin.add_dkim_header(connection, r.data); | ||
plugin.do_milter_headers(connection, r.data); | ||
plugin.add_headers(connection, r.data); | ||
if (plugin.cfg.soft_reject.enabled && r.data.action === 'soft reject') { | ||
nextOnce(DENYSOFT, DSN.sec_unauthorized(smtp_message || plugin.cfg.soft_reject.message, 451)); | ||
} | ||
else if (plugin.wants_reject(connection, r.data)) { | ||
nextOnce(DENY, smtp_message || plugin.cfg.reject.message); | ||
} | ||
else { | ||
plugin.add_dkim_header(connection, r.data); | ||
plugin.do_milter_headers(connection, r.data); | ||
plugin.add_headers(connection, r.data); | ||
nextOnce(); | ||
} | ||
}); | ||
}) | ||
req.on('error', (err) => { | ||
if (!connection || !connection.transaction) return; | ||
connection.transaction.results.add(plugin, { err: err.message}); | ||
nextOnce(); | ||
} | ||
}); | ||
}) | ||
connection.transaction.message_stream.pipe(req); | ||
// pipe calls req.end() asynchronously | ||
req.on('error', (err) => { | ||
if (!connection || !connection.transaction) return; | ||
connection.transaction.results.add(plugin, { err: err.message}); | ||
nextOnce(); | ||
}); | ||
connection.transaction.message_stream.pipe(req); | ||
// pipe calls req.end() asynchronously | ||
} | ||
exports.should_check = function (connection) { | ||
const plugin = this; | ||
const plugin = this; | ||
let result = true; // default | ||
let result = true; // default | ||
if (plugin.cfg.check.authenticated == false && connection.notes.auth_user) { | ||
connection.transaction.results.add(plugin, { skip: 'authed'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.authenticated == false && connection.notes.auth_user) { | ||
connection.transaction.results.add(plugin, { skip: 'authed'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.relay == false && connection.relaying) { | ||
connection.transaction.results.add(plugin, { skip: 'relay'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.relay == false && connection.relaying) { | ||
connection.transaction.results.add(plugin, { skip: 'relay'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.local_ip == false && connection.remote.is_local) { | ||
connection.transaction.results.add(plugin, { skip: 'local_ip'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.local_ip == false && connection.remote.is_local) { | ||
connection.transaction.results.add(plugin, { skip: 'local_ip'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.private_ip == false && connection.remote.is_private) { | ||
if (plugin.cfg.check.local_ip == true && connection.remote.is_local) { | ||
// local IPs are included in private IPs | ||
} | ||
else { | ||
connection.transaction.results.add(plugin, { skip: 'private_ip'}); | ||
result = false; | ||
} | ||
if (plugin.cfg.check.private_ip == false && connection.remote.is_private) { | ||
if (plugin.cfg.check.local_ip == true && connection.remote.is_local) { | ||
// local IPs are included in private IPs | ||
} | ||
else { | ||
connection.transaction.results.add(plugin, { skip: 'private_ip'}); | ||
result = false; | ||
} | ||
} | ||
return result; | ||
return result; | ||
} | ||
exports.wants_reject = function (connection, data) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (data.action !== 'reject') return false; | ||
if (data.action !== 'reject') return false; | ||
if (connection.notes.auth_user) { | ||
if (plugin.cfg.reject.authenticated == false) return false; | ||
} | ||
else { | ||
if (plugin.cfg.reject.spam == false) return false; | ||
} | ||
if (connection.notes.auth_user) { | ||
if (plugin.cfg.reject.authenticated == false) return false; | ||
} | ||
else { | ||
if (plugin.cfg.reject.spam == false) return false; | ||
} | ||
return true; | ||
return true; | ||
} | ||
exports.wants_headers_added = function (rspamd_data) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (plugin.cfg.main.add_headers === 'never') return false; | ||
if (plugin.cfg.main.add_headers === 'always') return true; | ||
if (plugin.cfg.main.add_headers === 'never') return false; | ||
if (plugin.cfg.main.add_headers === 'always') return true; | ||
// implicit add_headers=sometimes, based on rspamd response | ||
if (rspamd_data.action === 'add header') return true; | ||
return false; | ||
// implicit add_headers=sometimes, based on rspamd response | ||
if (rspamd_data.action === 'add header') return true; | ||
return false; | ||
} | ||
exports.get_clean = function (data, connection) { | ||
const plugin = this; | ||
const clean = { symbols: {} }; | ||
const plugin = this; | ||
const clean = { symbols: {} }; | ||
if (data.symbols) { | ||
Object.keys(data.symbols).forEach(key => { | ||
const a = data.symbols[key]; | ||
// transform { name: KEY, score: VAL } -> { KEY: VAL } | ||
if (a.name && a.score !== undefined) { | ||
clean.symbols[ a.name ] = a.score; | ||
return; | ||
} | ||
// unhandled type | ||
connection.logerror(plugin, a); | ||
}) | ||
if (data.symbols) { | ||
Object.keys(data.symbols).forEach(key => { | ||
const a = data.symbols[key]; | ||
// transform { name: KEY, score: VAL } -> { KEY: VAL } | ||
if (a.name && a.score !== undefined) { | ||
clean.symbols[ a.name ] = a.score; | ||
return; | ||
} | ||
// unhandled type | ||
connection.logerror(plugin, a); | ||
}) | ||
} | ||
// objects that may exist | ||
['action', 'is_skipped', 'required_score', 'score'].forEach((key) => { | ||
switch (typeof data[key]) { | ||
case 'boolean': | ||
case 'number': | ||
case 'string': | ||
clean[key] = data[key]; | ||
break; | ||
default: | ||
connection.loginfo(plugin, `skipping unhandled: ${ typeof data[key]}`); | ||
} | ||
}); | ||
// objects that may exist | ||
['action', 'is_skipped', 'required_score', 'score'].forEach((key) => { | ||
switch (typeof data[key]) { | ||
case 'boolean': | ||
case 'number': | ||
case 'string': | ||
clean[key] = data[key]; | ||
break; | ||
default: | ||
connection.loginfo(plugin, "skipping unhandled: " + typeof data[key]); | ||
} | ||
}); | ||
// arrays which might be present | ||
['urls', 'emails', 'messages'].forEach(b => { | ||
// collapse to comma separated string, so values get logged | ||
if (!data[b]) return; | ||
// arrays which might be present | ||
['urls', 'emails', 'messages'].forEach(b => { | ||
// collapse to comma separated string, so values get logged | ||
if (!data[b]) return; | ||
if (data[b].length) { | ||
clean[b] = data[b].join(','); | ||
return; | ||
} | ||
if (data[b].length) { | ||
clean[b] = data[b].join(','); | ||
return; | ||
} | ||
if (typeof(data[b]) == 'object') { | ||
// 'messages' is probably a dictionary | ||
Object.keys(data[b]).map((k) => { | ||
return `${k} : ${data[b][k]}`; | ||
}).join(','); | ||
} | ||
}); | ||
if (typeof(data[b]) == 'object') { | ||
// 'messages' is probably a dictionary | ||
Object.keys(data[b]).map((k) => { | ||
return `${k} : ${data[b][k]}`; | ||
}).join(','); | ||
} | ||
}); | ||
return clean; | ||
return clean; | ||
} | ||
exports.parse_response = function (rawData, connection) { | ||
const plugin = this; | ||
const plugin = this; | ||
if (!rawData) return; | ||
if (!rawData) return; | ||
let data; | ||
try { | ||
data = JSON.parse(rawData); | ||
} | ||
catch (err) { | ||
connection.transaction.results.add(plugin, { | ||
err: 'parse failure: ' + err.message | ||
}); | ||
return; | ||
} | ||
let data; | ||
try { | ||
data = JSON.parse(rawData); | ||
} | ||
catch (err) { | ||
connection.transaction.results.add(plugin, { | ||
err: `parse failure: ${ err.message}` | ||
}); | ||
return; | ||
} | ||
if (Object.keys(data).length === 0) return; | ||
if (Object.keys(data).length === 0) return; | ||
if (Object.keys(data).length === 1 && data.error) { | ||
connection.transaction.results.add(plugin, { | ||
err: data.error | ||
}); | ||
return; | ||
} | ||
if (Object.keys(data).length === 1 && data.error) { | ||
connection.transaction.results.add(plugin, { | ||
err: data.error | ||
}); | ||
return; | ||
} | ||
return { | ||
'data' : data, | ||
'log' : plugin.get_clean(data, connection), | ||
}; | ||
return { | ||
data, | ||
'log' : plugin.get_clean(data, connection), | ||
}; | ||
} | ||
exports.add_headers = function (connection, data) { | ||
const plugin = this; | ||
const cfg = plugin.cfg; | ||
const plugin = this; | ||
const cfg = plugin.cfg; | ||
if (!plugin.wants_headers_added(data)) return; | ||
if (!plugin.wants_headers_added(data)) return; | ||
if (cfg.header && cfg.header.bar) { | ||
let spamBar = ''; | ||
let spamBarScore = 1; | ||
let spamBarChar = cfg.spambar.neutral || '/'; | ||
if (data.score >= 1) { | ||
spamBarScore = Math.floor(data.score); | ||
spamBarChar = cfg.spambar.positive || '+'; | ||
} | ||
else if (data.score <= -1) { | ||
spamBarScore = Math.floor(data.score * -1); | ||
spamBarChar = cfg.spambar.negative || '-'; | ||
} | ||
for (let i = 0; i < spamBarScore; i++) { | ||
spamBar += spamBarChar; | ||
} | ||
connection.transaction.remove_header(cfg.header.bar); | ||
connection.transaction.add_header(cfg.header.bar, spamBar); | ||
if (cfg.header && cfg.header.bar) { | ||
let spamBar = ''; | ||
let spamBarScore = 1; | ||
let spamBarChar = cfg.spambar.neutral || '/'; | ||
if (data.score >= 1) { | ||
spamBarScore = Math.floor(data.score); | ||
spamBarChar = cfg.spambar.positive || '+'; | ||
} | ||
else if (data.score <= -1) { | ||
spamBarScore = Math.floor(data.score * -1); | ||
spamBarChar = cfg.spambar.negative || '-'; | ||
} | ||
for (let i = 0; i < spamBarScore; i++) { | ||
spamBar += spamBarChar; | ||
} | ||
connection.transaction.remove_header(cfg.header.bar); | ||
connection.transaction.add_header(cfg.header.bar, spamBar); | ||
} | ||
if (cfg.header && cfg.header.report) { | ||
const prettySymbols = []; | ||
for (const k in data.symbols) { | ||
if (data.symbols[k].score) { | ||
prettySymbols.push(data.symbols[k].name + | ||
'(' + data.symbols[k].score + ')'); | ||
} | ||
} | ||
connection.transaction.remove_header(cfg.header.report); | ||
connection.transaction.add_header(cfg.header.report, | ||
prettySymbols.join(' ')); | ||
if (cfg.header && cfg.header.report) { | ||
const prettySymbols = []; | ||
for (const k in data.symbols) { | ||
if (data.symbols[k].score) { | ||
prettySymbols.push(`${data.symbols[k].name }(${ data.symbols[k].score })`); | ||
} | ||
} | ||
connection.transaction.remove_header(cfg.header.report); | ||
connection.transaction.add_header(cfg.header.report, | ||
prettySymbols.join(' ')); | ||
} | ||
if (cfg.header && cfg.header.score) { | ||
connection.transaction.remove_header(cfg.header.score); | ||
connection.transaction.add_header(cfg.header.score, '' + data.score); | ||
} | ||
if (cfg.header && cfg.header.score) { | ||
connection.transaction.remove_header(cfg.header.score); | ||
connection.transaction.add_header(cfg.header.score, `${ data.score}`); | ||
} | ||
} |
{ | ||
"name": "haraka-plugin-rspamd", | ||
"version": "1.1.6", | ||
"version": "1.1.7", | ||
"description": "Haraka plugin for rspamd", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "./node_modules/.bin/eslint *.js test/**/*.js", | ||
"lintfix": "./node_modules/.bin/eslint --fix *.js test/**/*.js", | ||
"cover": "./node_modules/.bin/istanbul cover ./node_modules/.bin/nodeunit", | ||
"test": "./node_modules/.bin/nodeunit" | ||
"lint": "npx eslint *.js test", | ||
"lintfix": "npx eslint --fix *.js test", | ||
"test": "npx _mocha" | ||
}, | ||
@@ -28,6 +27,6 @@ "repository": { | ||
"devDependencies": { | ||
"eslint": ">=3", | ||
"eslint": ">=8", | ||
"eslint-plugin-haraka": "*", | ||
"haraka-test-fixtures": "*", | ||
"nodeunit": "*" | ||
"haraka-test-fixtures": "^1.0.35", | ||
"mocha": ">=9" | ||
}, | ||
@@ -34,0 +33,0 @@ "dependencies": { |
'use strict'; | ||
// var Address = require('address-rfc2821'); | ||
const assert = require('assert') | ||
const fixtures = require('haraka-test-fixtures'); | ||
const connection = fixtures.connection; | ||
const _set_up = function (done) { | ||
function _set_up (done) { | ||
this.plugin = new fixtures.plugin('rspamd'); | ||
this.plugin.register(); | ||
this.connection = connection.createConnection(); | ||
this.connection.transaction = fixtures.transaction.createTransaction() | ||
// this.connection.init_transaction(); | ||
this.plugin = new fixtures.plugin('rspamd'); | ||
done(); | ||
} | ||
describe('register', function () { | ||
beforeEach(_set_up) | ||
it('loads the rspamd plugin', function (done) { | ||
assert.equal('rspamd', this.plugin.name); | ||
done(); | ||
}) | ||
it('register loads rspamd.ini', function (done) { | ||
this.plugin.register(); | ||
this.connection = connection.createConnection(); | ||
this.connection.init_transaction(); | ||
assert.ok(this.plugin.cfg); | ||
assert.equal(true, this.plugin.cfg.reject.spam); | ||
assert.ok(this.plugin.cfg.header.bar); | ||
done(); | ||
}) | ||
}) | ||
describe('add_headers', function () { | ||
beforeEach(_set_up) | ||
it('add_headers exists as function', function (done) { | ||
// console.log(this.plugin.cfg); | ||
assert.equal('function', typeof this.plugin.add_headers); | ||
done(); | ||
} | ||
}) | ||
exports.register = { | ||
setUp : _set_up, | ||
'loads the rspamd plugin': function (test) { | ||
test.expect(1); | ||
test.equal('rspamd', this.plugin.name); | ||
test.done(); | ||
}, | ||
'register loads rspamd.ini': function (test) { | ||
test.expect(2); | ||
this.plugin.register(); | ||
test.ok(this.plugin.cfg); | ||
test.equal(true, this.plugin.cfg.reject.spam); | ||
test.done(); | ||
}, | ||
} | ||
it('adds a header to a message with positive score', function (done) { | ||
const test_data = { | ||
score: 1.1, | ||
symbols: { | ||
FOO: { | ||
name: 'FOO', | ||
score: 0.100000, | ||
description: 'foo', | ||
options: ['foo', 'bar'], | ||
}, | ||
BAR: { | ||
name: 'BAR', | ||
score: 1.0, | ||
description: 'bar', | ||
} | ||
} | ||
}; | ||
this.plugin.cfg.main.add_headers = 'always'; | ||
this.plugin.add_headers(this.connection, test_data); | ||
assert.deepEqual(this.connection.transaction.header.headers['x-rspamd-score'], [ '1.1' ]); | ||
assert.deepEqual(this.connection.transaction.header.headers['x-rspamd-bar'], ['+']); | ||
assert.deepEqual(this.connection.transaction.header.headers['x-rspamd-report'], ['FOO(0.1) BAR(1)']); | ||
done(); | ||
}) | ||
exports.load_rspamd_ini = { | ||
setUp : _set_up, | ||
'loads rspamd.ini': function (test) { | ||
test.expect(1); | ||
this.plugin.load_rspamd_ini(); | ||
test.ok(this.plugin.cfg.header.bar); | ||
test.done(); | ||
}, | ||
} | ||
it('adds a header to a message with negative score', function (done) { | ||
const test_data = { | ||
score: -1 | ||
}; | ||
this.plugin.cfg.main.add_headers = 'always'; | ||
this.plugin.add_headers(this.connection, test_data); | ||
// console.log(this.connection.transaction.header); | ||
assert.deepEqual(this.connection.transaction.header.headers['x-rspamd-score'], ['-1']); | ||
assert.deepEqual(this.connection.transaction.header.headers['x-rspamd-bar'], ['-']); | ||
done(); | ||
}) | ||
}) | ||
exports.add_headers = { | ||
setUp : _set_up, | ||
'add_headers exists as function': function (test) { | ||
test.expect(1); | ||
// console.log(this.plugin.cfg); | ||
test.equal('function', typeof this.plugin.add_headers); | ||
// test.ok(!this.plugin.score_too_high(this.connection, {score: 5})); | ||
test.done(); | ||
}, | ||
'adds a header to a message with positive score': function (test) { | ||
test.expect(3); | ||
const test_data = { | ||
score: 1.1, | ||
symbols: { | ||
FOO: { | ||
name: 'FOO', | ||
score: 0.100000, | ||
description: 'foo', | ||
options: ['foo', 'bar'], | ||
}, | ||
BAR: { | ||
name: 'BAR', | ||
score: 1.0, | ||
description: 'bar', | ||
} | ||
} | ||
}; | ||
this.plugin.cfg.main.add_headers = 'always'; | ||
this.plugin.add_headers(this.connection, test_data); | ||
test.equal(this.connection.transaction.header.headers['X-Rspamd-Score'], '1.1'); | ||
test.equal(this.connection.transaction.header.headers['X-Rspamd-Bar'], '+'); | ||
test.equal(this.connection.transaction.header.headers['X-Rspamd-Report'], 'FOO(0.1) BAR(1)'); | ||
test.done(); | ||
}, | ||
'adds a header to a message with negative score': function (test) { | ||
test.expect(2); | ||
const test_data = { | ||
score: -1 | ||
}; | ||
this.plugin.cfg.main.add_headers = 'always'; | ||
this.plugin.add_headers(this.connection, test_data); | ||
// console.log(this.connection.transaction.header); | ||
test.equal(this.connection.transaction.header.headers['X-Rspamd-Score'], '-1'); | ||
test.equal(this.connection.transaction.header.headers['X-Rspamd-Bar'], '-'); | ||
test.done(); | ||
} | ||
} | ||
exports.wants_headers_added = { | ||
setUp : _set_up, | ||
'wants no headers when add_headers=never': function (test) { | ||
test.expect(1); | ||
this.plugin.cfg.main.add_headers='never'; | ||
test.equal( | ||
this.plugin.wants_headers_added({ action: 'add header' }), | ||
false | ||
); | ||
test.done(); | ||
}, | ||
'always wants no headers when add_headers=always': function (test) { | ||
test.expect(1); | ||
this.plugin.cfg.main.add_headers='always'; | ||
test.equal( | ||
this.plugin.wants_headers_added({ action: 'beat it' }), | ||
true | ||
); | ||
test.done(); | ||
}, | ||
'wants headers when rspamd response indicates, add_headers=sometimes': function (test) { | ||
test.expect(2); | ||
this.plugin.cfg.main.add_headers='sometimes'; | ||
test.equal( | ||
this.plugin.wants_headers_added({ action: 'add header' }), | ||
true | ||
); | ||
test.equal( | ||
this.plugin.wants_headers_added({ action: 'brownlist' }), | ||
false | ||
); | ||
test.done(); | ||
} | ||
} | ||
describe('wants_headers_added', function () { | ||
exports.parse_response = { | ||
setUp : _set_up, | ||
'returns undef on empty string': function (test) { | ||
test.expect(1); | ||
// console.log(this.connection.transaction); | ||
test.equal( | ||
this.plugin.parse_response('', this.connection), | ||
undefined | ||
); | ||
test.done(); | ||
}, | ||
'returns undef on empty object': function (test) { | ||
test.expect(1); | ||
test.equal( | ||
this.plugin.parse_response('{}', this.connection), | ||
undefined | ||
); | ||
test.done(); | ||
}, | ||
} | ||
beforeEach(_set_up) | ||
function _check_setup (done) { | ||
it('wants no headers when add_headers=never', function (done) { | ||
this.plugin.cfg.main.add_headers='never'; | ||
assert.equal( | ||
this.plugin.wants_headers_added({ action: 'add header' }), | ||
false | ||
); | ||
done(); | ||
}) | ||
it('always wants no headers when add_headers=always', function (done) { | ||
this.plugin.cfg.main.add_headers='always'; | ||
assert.equal( | ||
this.plugin.wants_headers_added({ action: 'beat it' }), | ||
true | ||
); | ||
done(); | ||
}) | ||
it('wants headers when rspamd response indicates, add_headers=sometimes', function (done) { | ||
this.plugin.cfg.main.add_headers='sometimes'; | ||
assert.equal( | ||
this.plugin.wants_headers_added({ action: 'add header' }), | ||
true | ||
); | ||
assert.equal( | ||
this.plugin.wants_headers_added({ action: 'brownlist' }), | ||
false | ||
); | ||
done(); | ||
}) | ||
}) | ||
describe('parse_response', function () { | ||
beforeEach(_set_up) | ||
it('returns undef on empty string', function (done) { | ||
// console.log(this.connection.transaction); | ||
assert.equal( | ||
this.plugin.parse_response('', this.connection), | ||
undefined | ||
); | ||
done(); | ||
}) | ||
it('returns undef on empty object', function (done) { | ||
assert.equal( | ||
this.plugin.parse_response('{}', this.connection), | ||
undefined | ||
); | ||
done(); | ||
}) | ||
}) | ||
describe('should_check', function () { | ||
beforeEach(function (done) { | ||
this.plugin = new fixtures.plugin('rspamd'); | ||
@@ -165,105 +161,91 @@ this.plugin.register(); | ||
done() | ||
} | ||
}) | ||
exports.should_check = { | ||
setUp : _check_setup, | ||
'checks authenticated': function (test) { | ||
this.connection.notes.auth_user = "username"; | ||
this.plugin.cfg.check.authenticated = true; | ||
it('checks authenticated', function (done) { | ||
this.connection.notes.auth_user = "username"; | ||
this.plugin.cfg.check.authenticated = true; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
'skips authenticated': function (test) { | ||
this.connection.notes.auth_user = "username"; | ||
this.plugin.cfg.check.authenticated = false; | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
it('skips authenticated', function (done) { | ||
this.connection.notes.auth_user = "username"; | ||
this.plugin.cfg.check.authenticated = false; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), false); | ||
test.done(); | ||
}, | ||
'skips relaying': function (test) { | ||
this.connection.relaying = true; | ||
this.plugin.cfg.check.relay = false; | ||
assert.equal(this.plugin.should_check(this.connection), false); | ||
done(); | ||
}) | ||
it('skips relaying', function (done) { | ||
this.connection.relaying = true; | ||
this.plugin.cfg.check.relay = false; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), false); | ||
test.done(); | ||
}, | ||
'checks not relaying': function (test) { | ||
this.connection.relaying = false; | ||
this.plugin.cfg.check.relay = false; | ||
assert.equal(this.plugin.should_check(this.connection), false); | ||
done(); | ||
}) | ||
it('checks not relaying', function (done) { | ||
this.connection.relaying = false; | ||
this.plugin.cfg.check.relay = false; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
'checks relaying when enabled': function (test) { | ||
this.connection.relaying = true; | ||
this.plugin.cfg.check.relay = true; | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
it('checks relaying when enabled', function (done) { | ||
this.connection.relaying = true; | ||
this.plugin.cfg.check.relay = true; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
'checks local IP': function (test) { | ||
this.connection.remote.is_local = true; | ||
this.plugin.cfg.check.local_ip = true; | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
it('checks local IP', function (done) { | ||
this.connection.remote.is_local = true; | ||
this.plugin.cfg.check.local_ip = true; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
'skips local IP': function (test) { | ||
this.connection.remote.is_local = true; | ||
this.plugin.cfg.check.local_ip = false; | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
it('skips local IP', function (done) { | ||
this.connection.remote.is_local = true; | ||
this.plugin.cfg.check.local_ip = false; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), false); | ||
test.done(); | ||
}, | ||
'checks private IP': function (test) { | ||
this.connection.remote.is_private = true; | ||
this.plugin.cfg.check.private_ip = true; | ||
assert.equal(this.plugin.should_check(this.connection), false); | ||
done(); | ||
}) | ||
it('checks private IP', function (done) { | ||
this.connection.remote.is_private = true; | ||
this.plugin.cfg.check.private_ip = true; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
'skips private IP': function (test) { | ||
this.connection.remote.is_private = true; | ||
this.plugin.cfg.check.private_ip = false; | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
it('skips private IP', function (done) { | ||
this.connection.remote.is_private = true; | ||
this.plugin.cfg.check.private_ip = false; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), false); | ||
test.done(); | ||
}, | ||
'checks public ip': function (test) { | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
'skip localhost if check.local_ip = false and check.private_ip = true': function (test) { | ||
this.connection.remote.is_local = true; | ||
this.connection.remote.is_private = true; | ||
assert.equal(this.plugin.should_check(this.connection), false); | ||
done(); | ||
}) | ||
it('checks public ip', function (done) { | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
it('skip localhost if check.local_ip = false and check.private_ip = true', function (done) { | ||
this.connection.remote.is_local = true; | ||
this.connection.remote.is_private = true; | ||
this.plugin.cfg.check.local_ip = false; | ||
this.plugin.cfg.check.private_ip = true; | ||
this.plugin.cfg.check.local_ip = false; | ||
this.plugin.cfg.check.private_ip = true; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), false); | ||
test.done(); | ||
}, | ||
'checks localhost if check.local_ip = true and check.private_ip = false': function (test) { | ||
this.connection.remote.is_local = true; | ||
this.connection.remote.is_private = true; | ||
assert.equal(this.plugin.should_check(this.connection), false); | ||
done(); | ||
}) | ||
it('checks localhost if check.local_ip = true and check.private_ip = false', function (done) { | ||
this.connection.remote.is_local = true; | ||
this.connection.remote.is_private = true; | ||
this.plugin.cfg.check.local_ip = true; | ||
this.plugin.cfg.check.private_ip = false; | ||
this.plugin.cfg.check.local_ip = true; | ||
this.plugin.cfg.check.private_ip = false; | ||
test.expect(1); | ||
test.equal(this.plugin.should_check(this.connection), true); | ||
test.done(); | ||
}, | ||
} | ||
assert.equal(this.plugin.should_check(this.connection), true); | ||
done(); | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16
29495
575