haraka-plugin-rspamd
Advanced tools
| name: CI | ||
| on: [ push ] | ||
| on: [ push, pull_request ] | ||
@@ -5,0 +5,0 @@ env: |
+9
-0
| ### Unreleased | ||
| ### [1.3.0] - 2023-02-23 | ||
| - add: defer options, similar to spamassassin.js #32 | ||
| - es6: replace Object.keys().forEach with for...of | ||
| - fix: wrap milter header adds in try/catch, fixes #28 | ||
| ### [1.2.0] - 2022-10-14 | ||
@@ -68,1 +75,3 @@ | ||
| [1.1.9]: https://github.com/haraka/haraka-plugin-rspamd/releases/tag/1.1.9 | ||
| [1.2.0]: https://github.com/haraka/haraka-plugin-rspamd/releases/tag/1.2.0 | ||
| [1.3.0]: https://github.com/haraka/haraka-plugin-rspamd/releases/tag/1.3.0 |
+94
-94
@@ -29,2 +29,4 @@ 'use strict'; | ||
| '+smtp_message.enabled', | ||
| '-defer.error', | ||
| '-defer.timeout', | ||
| ], | ||
@@ -35,28 +37,28 @@ }, () => { | ||
| if (!plugin.cfg.reject.message) { | ||
| plugin.cfg.reject.message = 'Detected as spam'; | ||
| if (!this.cfg.reject.message) { | ||
| this.cfg.reject.message = 'Detected as spam'; | ||
| } | ||
| if (!plugin.cfg.soft_reject.message) { | ||
| plugin.cfg.soft_reject.message = 'Deferred by policy'; | ||
| if (!this.cfg.soft_reject.message) { | ||
| this.cfg.soft_reject.message = 'Deferred by policy'; | ||
| } | ||
| if (!plugin.cfg.spambar) { | ||
| plugin.cfg.spambar = { positive: '+', negative: '-', neutral: '/' }; | ||
| if (!this.cfg.spambar) { | ||
| this.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 (!this.cfg.main.port) this.cfg.main.port = 11333; | ||
| if (!this.cfg.main.host) this.cfg.main.host = 'localhost'; | ||
| if (!plugin.cfg.main.add_headers) { | ||
| if (plugin.cfg.main.always_add_headers === true) { | ||
| plugin.cfg.main.add_headers = 'always'; | ||
| if (!this.cfg.main.add_headers) { | ||
| if (this.cfg.main.always_add_headers === true) { | ||
| this.cfg.main.add_headers = 'always'; | ||
| } | ||
| else { | ||
| plugin.cfg.main.add_headers = 'sometimes'; | ||
| this.cfg.main.add_headers = 'sometimes'; | ||
| } | ||
| } | ||
| if (!plugin.cfg.subject) { | ||
| plugin.cfg.subject = "[SPAM] %s"; | ||
| if (!this.cfg.subject) { | ||
| this.cfg.subject = "[SPAM] %s"; | ||
| } | ||
@@ -66,3 +68,2 @@ } | ||
| exports.get_options = function (connection) { | ||
| const plugin = this; | ||
@@ -77,8 +78,8 @@ // https://rspamd.com/doc/architecture/protocol.html | ||
| if (plugin.cfg.main.unix_socket) { | ||
| options.socketPath = plugin.cfg.main.unix_socket; | ||
| if (this.cfg.main.unix_socket) { | ||
| options.socketPath = this.cfg.main.unix_socket; | ||
| } | ||
| else { | ||
| options.port = plugin.cfg.main.port; | ||
| options.host = plugin.cfg.main.host; | ||
| options.port = this.cfg.main.port; | ||
| options.host = this.cfg.main.host; | ||
| } | ||
@@ -125,4 +126,4 @@ | ||
| options.headers.Rcpt = []; | ||
| for (let i=0; i < rcpts.length; i++) { | ||
| options.headers.Rcpt.push(rcpts[i].address()); | ||
| for (const rcpt of rcpts) { | ||
| options.headers.Rcpt.push(rcpt.address()); | ||
| } | ||
@@ -148,5 +149,4 @@ | ||
| exports.get_smtp_message = function (r) { | ||
| const plugin = this; | ||
| if (!plugin.cfg.smtp_message.enabled || !r.data.messages) return; | ||
| if (!this.cfg.smtp_message.enabled || !r.data.messages) return; | ||
| if (typeof(r.data.messages) !== 'object') return; | ||
@@ -159,8 +159,7 @@ if (!r.data.messages.smtp_message) return; | ||
| exports.do_rewrite = function (connection, data) { | ||
| const plugin = this; | ||
| if (!plugin.cfg.rewrite_subject.enabled) return false; | ||
| if (!this.cfg.rewrite_subject.enabled) return false; | ||
| if (data.action !== 'rewrite subject') return false; | ||
| const rspamd_subject = data.subject || plugin.cfg.subject; | ||
| const rspamd_subject = data.subject || this.cfg.subject; | ||
| const old_subject = connection.transaction.header.get('Subject') || ''; | ||
@@ -174,5 +173,4 @@ const new_subject = rspamd_subject.replace('%s', old_subject); | ||
| exports.add_dkim_header = function (connection, data) { | ||
| const plugin = this; | ||
| if (!plugin.cfg.dkim.enabled) return; | ||
| if (!this.cfg.dkim.enabled) return; | ||
| if (!data['dkim-signature']) return; | ||
@@ -184,36 +182,40 @@ | ||
| exports.do_milter_headers = function (connection, data) { | ||
| const plugin = this; | ||
| if (!plugin.cfg.rmilter_headers.enabled) return; | ||
| if (!this.cfg.rmilter_headers.enabled) return; | ||
| if (!data.milter) return; | ||
| if (data.milter.remove_headers) { | ||
| Object.keys(data.milter.remove_headers).forEach((key) => { | ||
| for (const key of Object.keys(data.milter.remove_headers)) { | ||
| connection.transaction.remove_header(key); | ||
| }) | ||
| } | ||
| } | ||
| if (data.milter.add_headers) { | ||
| connection.logdebug(`milter.add_headers: ${JSON.stringify(data.milter.add_headers)}`, plugin); | ||
| Object.keys(data.milter.add_headers).forEach((key) => { | ||
| const header_values = data.milter.add_headers[key]; | ||
| if (!header_values) return; | ||
| try { | ||
| connection.logdebug(this, `milter.add_headers: ${JSON.stringify(data.milter.add_headers)}`); | ||
| for (const key of Object.keys(data.milter.add_headers)) { | ||
| const header_values = data.milter.add_headers[key]; | ||
| if (!header_values) return; | ||
| if (Object.prototype.toString.call(header_values) == '[object Array]') { | ||
| header_values.forEach(function (header_value, header_index) { | ||
| if (typeof header_value === 'object') { | ||
| connection.transaction.add_header(key, header_value.value); | ||
| } | ||
| else { | ||
| connection.transaction.add_header(key, header_value); | ||
| } | ||
| }); | ||
| if (Object.prototype.toString.call(header_values) == '[object Array]') { | ||
| header_values.forEach(function (header_value, header_index) { | ||
| if (typeof header_value === 'object') { | ||
| connection.transaction.add_header(key, header_value.value); | ||
| } | ||
| else { | ||
| connection.transaction.add_header(key, header_value); | ||
| } | ||
| }); | ||
| } | ||
| else if (typeof header_values === 'object') { | ||
| connection.transaction.add_header(key, header_values.value); | ||
| } | ||
| else { | ||
| connection.transaction.add_header(key, header_values); | ||
| } | ||
| } | ||
| else if (typeof header_values === 'object') { | ||
| connection.transaction.add_header(key, header_values.value); | ||
| } | ||
| else { | ||
| connection.transaction.add_header(key, header_values); | ||
| } | ||
| }) | ||
| } | ||
| catch (err) { | ||
| connection.errorlog(this, `milter.addheaders error: ${err}`) | ||
| } | ||
| } | ||
@@ -236,2 +238,3 @@ } | ||
| calledNext=true; | ||
| if (!connection?.transaction) return; | ||
| next(code, msg); | ||
@@ -241,5 +244,5 @@ } | ||
| timer = setTimeout(() => { | ||
| if (!connection) return; | ||
| if (!connection.transaction) return; | ||
| if (!connection?.transaction) return; | ||
| connection.transaction.results.add(plugin, {err: 'timeout'}); | ||
| if (plugin.cfg.defer.timeout) return nextOnce(DENYSOFT, 'Rspamd scan timeout'); | ||
| nextOnce(); | ||
@@ -256,4 +259,9 @@ }, timeout * 1000); | ||
| res.on('end', () => { | ||
| if (!connection.transaction) return nextOnce(); //client gone | ||
| const r = plugin.parse_response(rawData, connection); | ||
| if (!r || !r.data || !r.log) return nextOnce(); | ||
| if (!r || !r.data || !r.log) { | ||
| if (plugin.cfg.defer.error) return nextOnce(DENYSOFT, 'Rspamd scan error'); | ||
| return nextOnce(); | ||
| } | ||
@@ -263,4 +271,2 @@ r.log.emit = true; // spit out a log entry | ||
| if (!connection.transaction) return nextOnce(); | ||
| connection.transaction.results.add(plugin, r.log); | ||
@@ -290,4 +296,5 @@ if (r.data.symbols) connection.transaction.results.add(plugin, { symbols: r.data.symbols }); | ||
| req.on('error', (err) => { | ||
| if (!connection || !connection.transaction) return; | ||
| if (!connection?.transaction) return nextOnce(); // client gone | ||
| connection.transaction.results.add(plugin, { err: err.message}); | ||
| if (plugin.cfg.defer.error) return nextOnce(DENYSOFT, 'Rspamd scan error'); | ||
| nextOnce(); | ||
@@ -301,27 +308,26 @@ }); | ||
| exports.should_check = function (connection) { | ||
| const plugin = this; | ||
| let result = true; // default | ||
| if (plugin.cfg.check.authenticated == false && connection.notes.auth_user) { | ||
| connection.transaction.results.add(plugin, { skip: 'authed'}); | ||
| if (this.cfg.check.authenticated == false && connection.notes.auth_user) { | ||
| connection.transaction.results.add(this, { skip: 'authed'}); | ||
| result = false; | ||
| } | ||
| if (plugin.cfg.check.relay == false && connection.relaying) { | ||
| connection.transaction.results.add(plugin, { skip: 'relay'}); | ||
| if (this.cfg.check.relay == false && connection.relaying) { | ||
| connection.transaction.results.add(this, { skip: 'relay'}); | ||
| result = false; | ||
| } | ||
| if (plugin.cfg.check.local_ip == false && connection.remote.is_local) { | ||
| connection.transaction.results.add(plugin, { skip: 'local_ip'}); | ||
| if (this.cfg.check.local_ip == false && connection.remote.is_local) { | ||
| connection.transaction.results.add(this, { 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) { | ||
| if (this.cfg.check.private_ip == false && connection.remote.is_private) { | ||
| if (this.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'}); | ||
| connection.transaction.results.add(this, { skip: 'private_ip'}); | ||
| result = false; | ||
@@ -335,3 +341,2 @@ } | ||
| exports.wants_reject = function (connection, data) { | ||
| const plugin = this; | ||
@@ -341,6 +346,6 @@ if (data.action !== 'reject') return false; | ||
| if (connection.notes.auth_user) { | ||
| if (plugin.cfg.reject.authenticated == false) return false; | ||
| if (this.cfg.reject.authenticated == false) return false; | ||
| } | ||
| else { | ||
| if (plugin.cfg.reject.spam == false) return false; | ||
| if (this.cfg.reject.spam == false) return false; | ||
| } | ||
@@ -352,6 +357,5 @@ | ||
| exports.wants_headers_added = function (rspamd_data) { | ||
| const plugin = this; | ||
| if (plugin.cfg.main.add_headers === 'never') return false; | ||
| if (plugin.cfg.main.add_headers === 'always') return true; | ||
| if (this.cfg.main.add_headers === 'never') return false; | ||
| if (this.cfg.main.add_headers === 'always') return true; | ||
@@ -364,3 +368,2 @@ // implicit add_headers=sometimes, based on rspamd response | ||
| exports.get_clean = function (data, connection) { | ||
| const plugin = this; | ||
| const clean = { symbols: {} }; | ||
@@ -377,3 +380,3 @@ | ||
| // unhandled type | ||
| connection.logerror(plugin, a); | ||
| connection.logerror(this, a); | ||
| }) | ||
@@ -383,3 +386,4 @@ } | ||
| // objects that may exist | ||
| ['action', 'is_skipped', 'required_score', 'score'].forEach((key) => { | ||
| const skip_keys = ['action', 'is_skipped', 'required_score', 'score']; | ||
| for (const key of skip_keys) { | ||
| switch (typeof data[key]) { | ||
@@ -392,8 +396,9 @@ case 'boolean': | ||
| default: | ||
| connection.loginfo(plugin, `skipping unhandled: ${ typeof data[key]}`); | ||
| connection.loginfo(this, `skipping unhandled: ${typeof data[key]}`); | ||
| } | ||
| }); | ||
| } | ||
| // arrays which might be present | ||
| ['urls', 'emails', 'messages'].forEach(b => { | ||
| const arrays = ['urls', 'emails', 'messages']; | ||
| for (const b of arrays) { | ||
| // collapse to comma separated string, so values get logged | ||
@@ -413,3 +418,3 @@ if (!data[b]) return; | ||
| } | ||
| }); | ||
| } | ||
@@ -420,4 +425,2 @@ return clean; | ||
| exports.parse_response = function (rawData, connection) { | ||
| const plugin = this; | ||
| if (!rawData) return; | ||
@@ -430,4 +433,4 @@ | ||
| catch (err) { | ||
| connection.transaction.results.add(plugin, { | ||
| err: `parse failure: ${ err.message}` | ||
| connection.transaction.results.add(this, { | ||
| err: `parse failure: ${err.message}` | ||
| }); | ||
@@ -440,5 +443,3 @@ return; | ||
| if (Object.keys(data).length === 1 && data.error) { | ||
| connection.transaction.results.add(plugin, { | ||
| err: data.error | ||
| }); | ||
| connection.transaction.results.add(this, { err: data.error }); | ||
| return; | ||
@@ -449,3 +450,3 @@ } | ||
| data, | ||
| 'log' : plugin.get_clean(data, connection), | ||
| 'log' : this.get_clean(data, connection), | ||
| }; | ||
@@ -455,6 +456,5 @@ } | ||
| exports.add_headers = function (connection, data) { | ||
| const plugin = this; | ||
| const cfg = plugin.cfg; | ||
| const cfg = this.cfg; | ||
| if (!plugin.wants_headers_added(data)) return; | ||
| if (!this.wants_headers_added(data)) return; | ||
@@ -484,3 +484,3 @@ if (cfg.header && cfg.header.bar) { | ||
| if (data.symbols[k].score) { | ||
| prettySymbols.push(`${data.symbols[k].name }(${ data.symbols[k].score })`); | ||
| prettySymbols.push(`${data.symbols[k].name}(${data.symbols[k].score})`); | ||
| } | ||
@@ -495,4 +495,4 @@ } | ||
| connection.transaction.remove_header(cfg.header.score); | ||
| connection.transaction.add_header(cfg.header.score, `${ data.score}`); | ||
| connection.transaction.add_header(cfg.header.score, `${data.score}`); | ||
| } | ||
| } |
+1
-1
| { | ||
| "name": "haraka-plugin-rspamd", | ||
| "version": "1.2.0", | ||
| "version": "1.3.0", | ||
| "description": "Haraka plugin for rspamd", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
30574
1.76%587
0.17%