UNPKG

haraka-plugin-dcc

Version:

Haraka plugin that scans messages with DCC

210 lines (181 loc) 4.88 kB
// dcc client // http://www.dcc-servers.net/dcc/dcc-tree/dccifd.html const net = require('net') exports.register = function () { this.load_dcc_ini() } exports.load_dcc_ini = function () { const plugin = this plugin.cfg = plugin.config.get('dcc.ini', () => { plugin.load_dcc_ini() }) } exports.get_host = function (host) { switch (host) { case 'Unknown': case 'NXDOMAIN': case 'DNSERROR': case undefined: return undefined default: return host } } exports.should_train = function (txn) { if (txn.notes.training_mode && txn.notes.training_mode === 'spam') return ' spam' return '' } exports.human_result = function (code) { switch (code) { case 'A': return 'Accept' case 'G': return 'Greylist' case 'R': return 'Reject' case 'S': return 'Accept some' case 'T': return 'Temp fail' } } exports.get_result = function (c, result) { const plugin = this // Get result code switch (result) { case 'A': // Accept, fall through case 'G': // Greylist, fall through case 'R': // Reject, fall through case 'S': // Accept for some recipients, fall through case 'T': // Temporary failure break default: c.logerror(plugin, 'invalid result: ' + result) break } return result } exports.human_disposition = function (code) { switch (code) { case 'A': return 'Accept' case 'G': return 'Greylist/Discard' case 'R': return 'Reject' } } exports.get_disposition = function (c, disposition) { const plugin = this switch (disposition) { case 'A': // Deliver the message case 'G': // Discard the message during greylist embargo case 'R': // Discard the message as spam break default: c.logerror(plugin, 'invalid disposition: ' + disposition) break } return disposition } exports.get_request_headers = function (conn, training) { const plugin = this const txn = conn.transaction const host = plugin.get_host(conn.remote.host) const headers = [ 'header' + training, conn.remote.ip + (host ? '\r' + host : ''), conn.hello.host, txn.mail_from.address(), txn.rcpt_to .map((rcpt) => { return rcpt.address() }) .join('\r'), ].join('\n') conn.logdebug(plugin, 'sending protocol headers: ' + headers) return headers + '\n\n' } exports.get_response_headers = function (c, rl) { // Read headers const headers = [] for (let i = 0; i < rl.length; i++) { if (/^\s/.test(rl[i]) && headers.length) { // Continuation headers[headers.length - 1] += rl[i] } else { if (rl[i]) headers.push(rl[i]) } } c.logdebug(this, 'found ' + headers.length + ' headers') for (let h = 0; h < headers.length; h++) { const header = headers[h].toString('utf8').trim() let match if ((match = /^([^: ]+):\s*((?:.|[\r\n])+)/.exec(header))) { c.transaction.add_header(match[1], match[2]) } else { c.logerror(this, 'header did not match regexp: ' + header) } } return headers } exports.hook_data_post = function (next, connection) { const plugin = this // Fix-up rDNS for DCC const training = plugin.should_train(connection.transaction) let response = '' let client function onConnect() { connection.logdebug(plugin, 'connected to dcc') this.write(plugin.get_request_headers(connection, training), () => { connection.transaction.message_stream.pipe(client) }) } const c = plugin.cfg.dccifd if (c.path) { client = net.createConnection(c.path, onConnect) } else { client = net.createConnection(c.port, c.host, onConnect) } client .on('error', function (err) { connection.logerror(plugin, err.message) return next() }) .on('data', function (chunk) { response += chunk.toString('utf8') }) .on('end', function () { const rl = response.split('\n') if (rl.length < 2) { connection.logwarn( plugin, 'invalid response: ' + response + 'length=' + rl.length, ) return next() } connection.logdebug(plugin, 'got response: ' + response) const result = plugin.get_result(connection, rl.shift()) const disposition = plugin.get_disposition(connection, rl.shift()) const headers = plugin.get_response_headers(connection, rl) connection.transaction.results.add(plugin, { training: training ? true : false, result: plugin.human_result(result), disposition: plugin.human_disposition(disposition), headers, }) connection.loginfo( plugin, 'training=' + (training ? 'Y' : 'N') + ` result=${result} disposition=${disposition} headers=${headers.length}`, ) return next() }) }