From ec70eec2628bc370cf8a1587623e07589832452f Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 31 Mar 2017 10:33:53 -0700 Subject: [PATCH] remove legacy code collapse dkim sign/verify to a single field --- Changes.md | 45 ++++--- README.md | 28 ++++- config/watch.ini | 10 ++ html/client.js | 17 ++- index.js | 321 +++++++++++++---------------------------------- package.json | 2 +- 6 files changed, 166 insertions(+), 257 deletions(-) create mode 100644 config/watch.ini diff --git a/Changes.md b/Changes.md index 1a7a19c..e9db214 100644 --- a/Changes.md +++ b/Changes.md @@ -1,32 +1,43 @@ -1.0.6 - 2017-01-25 +### 1.0.7 - 2017-03-31 - - plugins w/o pass results are now light grey (was light green) +- add default config/watch.ini file +- add config section to README +- add repo badges +- remove legacy "walk the connection/transaction to find results" code +- replace some function () { calls with arrow functions +- handle smtp_forward recipient validation results +- collapse dkim sign/verify to a single field -1.0.5 - 2017-01-24 +### 1.0.6 - 2017-01-25 - - added plugin known-senders - - removed grunt +- plugins w/o pass results are now light grey (was light green) -1.0.4 - 2017-01-01 - - lint fixes +### 1.0.5 - 2017-01-24 -1.0.3 - 2016-10-25 +- added plugin known-senders +- removed grunt - - set logs URLs to open in new browser window +### 1.0.4 - 2017-01-01 -Oct 23 22:23:57 2016 +- lint fixes - - add new names of plugins published to npm +### 1.0.3 - 2016-10-25 -Oct 10 20:26:45 2016 +- set logs URLs to open in new browser window - - enable more data plugins by default +### Oct 23 22:23:57 2016 -Oct 10 19:33:41 2016 +- add new names of plugins published to npm - - add start of test suite +### Oct 10 20:26:45 2016 -Oct 8 23:28:07 2016 +- enable more data plugins by default - - initial import +### Oct 10 19:33:41 2016 + +- add start of test suite + +### Oct 8 23:28:07 2016 + +- initial import diff --git a/README.md b/README.md index 2e87a90..5971dc5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # watch -[![Greenkeeper badge](https://badges.greenkeeper.io/haraka/haraka-plugin-watch.svg)](https://greenkeeper.io/) +[![Build Status][ci-img]][ci-url] +[![Build status][ci-win-img]][ci-win-url] +[![Greenkeeper badge][gk-img]][gk-url] +[![NPM][npm-img]][npm-url] + Watch live SMTP traffic in a web interface. @@ -33,3 +37,25 @@ details. * Light red: tests failed, but no rejection * Red: tests failed causing rejection +## Config + +Config options are set in watch.ini. + +* sampling: boolean, limit display connections to one-per-second +* wss.url: specify the WSS url (default: same scheme, host, port as http) +* wss.htdocs: an alternate docroot (default ./html) + +## Troubleshooting + +* If you aren't getting activity, make sure your web browser is able to establish the websockets connection. Either use straight http (only) or have a valid signed TLS certificate. The security for websockets connections is more strict than plain HTTP(s). + + +[ci-img]: https://travis-ci.org/haraka/haraka-plugin-watch.svg?branch=master +[ci-url]: https://travis-ci.org/haraka/haraka-plugin-watch +[ci-win-img]: https://ci.appveyor.com/api/projects/status/yxjfxu5mb4n94ho3?svg=true +[ci-win-url]: https://ci.appveyor.com/project/msimerson/haraka-plugin-watch +[gk-img]: https://badges.greenkeeper.io/haraka/haraka-plugin-watch.svg +[gk-url]: https://greenkeeper.io/ +[npm-img]: https://nodei.co/npm/haraka-plugin-watch.png +[npm-url]: https://www.npmjs.com/package/haraka-plugin-watch + diff --git a/config/watch.ini b/config/watch.ini new file mode 100644 index 0000000..4796c8a --- /dev/null +++ b/config/watch.ini @@ -0,0 +1,10 @@ + +;sampling=false + +[wss] +;url=ws://mail.example.com/ +;url=wss://mail.example.com/ +; +; the default htdocs is ./html. If you customize the interface, you +; can specify the alternate location here. +;htdocs=/var/www/html diff --git a/html/client.js b/html/client.js index accbf3d..d91d185 100644 --- a/html/client.js +++ b/html/client.js @@ -11,14 +11,17 @@ var total_cols; var cxn_cols; var txn_cols; -var connect_plugins = ['geoip','asn','p0f','dnsbl', 'early_talker', 'fcrdns']; +var connect_plugins = ['geoip','asn','p0f','dnsbl', 'access', 'fcrdns']; var helo_plugins = ['helo.checks', 'tls', 'auth', 'relay', 'spf']; var mail_from_plugins= ['spf', 'mail_from.is_resolvable', 'known-senders']; -var rcpt_to_plugins = ['access', 'rcpt_to.in_host_list', 'rcpt_to.qmail_deliverable']; +var rcpt_to_plugins = [ + 'queue/smtp_forward', + 'rcpt_to.in_host_list', + 'rcpt_to.qmail_deliverable' +]; var data_plugins = [ - 'bounce','data.headers','karma','spamassassin','rspamd', - 'clamd','avg','data.uribl','limit','dkim_sign','dkim_verify', - 'attachment' + 'early_talker', 'bounce','data.headers','karma','spamassassin','rspamd', + 'clamd','avg','data.uribl','limit','dkim','attachment' ]; // 'seen' plugins are ones we've seen data reported for. When data from a new // plugin arrives, it gets added to one of the sections above and the table is @@ -364,8 +367,10 @@ function shorten_pi (name) { 'rcpt_to.qmail_deliverable': 'qmd', 'rcpt_to.in_host_list': 'host_list', 'mail_from.is_resolvable': 'dns', - 'dkim_verify' : 'dkim', 'known-senders' : 'known', + 'queue/smtp_forward': 'forward', + 'smtp_forward': 'forward', + 'attachment': 'attach' }; if (trims[name]) return trims[name]; diff --git a/index.js b/index.js index 9859558..aac39a4 100644 --- a/index.js +++ b/index.js @@ -13,17 +13,9 @@ exports.register = function () { plugin.register_hook('init_master', 'redis_subscribe_all_results'); plugin.register_hook('init_child', 'redis_subscribe_all_results'); - // [ - // 'lookup_rdns', 'connect', 'helo', 'ehlo', 'mail', 'rcpt', 'rcpt_ok', - // 'data', 'data_post', 'reset_transaction' - // ] - // .forEach(function (hook) { - // plugin.register_hook(hook, 'get_incremental_results'); - // }); - // plugin.register_hook('queue_ok', 'queue_ok'); - // plugin.register_hook('deny', 'w_deny'); - // plugin.register_hook('disconnect', 'disconnect'); -}; + plugin.register_hook('deny', 'w_deny'); + plugin.register_hook('queue_ok', 'queue_ok'); +} exports.load_watch_ini = function () { var plugin = this; @@ -33,7 +25,7 @@ exports.load_watch_ini = function () { function () { plugin.load_watch_ini(); }); -}; +} exports.hook_init_http = function (next, server) { var plugin = this; @@ -46,7 +38,7 @@ exports.hook_init_http = function (next, server) { client.wss_url = plugin.cfg.wss.url; } res.end(JSON.stringify(client)); - }); + }) var htdocs = __dirname + '/html'; if (plugin.cfg.wss && plugin.cfg.wss.htdocs) { @@ -56,7 +48,7 @@ exports.hook_init_http = function (next, server) { plugin.loginfo('watch init_http done'); return next(); -}; +} exports.hook_init_wss = function (next, server) { var plugin = this; @@ -64,12 +56,13 @@ exports.hook_init_wss = function (next, server) { wss = server.http.wss; - wss.on('error', function (error) { + wss.on('error', (error) => { plugin.loginfo("server error: " + error); - }); + }) - wss.on('connection', function (ws) { + wss.on('connection', (ws) => { watchers++; + // broadcast updated watcher count wss.broadcast({ watchers: watchers }); @@ -78,36 +71,78 @@ exports.hook_init_wss = function (next, server) { // send message to just this websocket // ws.send('welcome!'); - ws.on('error', function (error) { + ws.on('error', (error) => { plugin.logdebug("client error: " + error); - }); + }) - ws.on('close', function (code, message) { + ws.on('close', (code, message) => { plugin.logdebug("client closed: " + message + '('+code+')'); watchers--; - }); + }) - ws.on('message', function (message) { + ws.on('message', (message) => { plugin.logdebug("received from client: " + message); - }); - }); + }) + }) wss.broadcast = function (data) { var f = JSON.stringify(data); for (var i in this.clients) { this.clients[i].send(f); } - }; + } plugin.loginfo('watch init_wss done'); return next(); +} + +exports.w_deny = function (next, connection, params) { + var plugin = this; + var pi_code = params[0]; + // var pi_msg = params[1]; + var pi_name = params[2]; + // var pi_function = params[3]; + // var pi_params = params[4]; + var pi_hook = params[5]; + + connection.logdebug(this, "watch deny saw: " + pi_name + + ' deny from ' + pi_hook); + + var req = { + uuid: connection.transaction ? connection.transaction.uuid + : connection.uuid, + local_port: { classy: 'bg_white', title: 'disconnected' }, + remote_host: get_remote_host(connection), + }; + + connection.logdebug(this, "watch sending dark red to " + pi_name); + var bg_class = pi_code === DENYSOFT ? 'bg_dyellow' : 'bg_dred'; + var report_as = plugin.get_plugin_name(pi_name); + if (req[report_as]) req[report_as].classy = bg_class; + if (!req[report_as]) req[report_as] = { classy: bg_class }; + + wss.broadcast(req); + return next(); +}; + +exports.queue_ok = function (next, connection, msg) { + // ok 1390590369 qp 634 (F82E2DD5-9238-41DC-BC95-9C3A02716AD2.1) + // required b/c outbound doesn't emit results - 2017-03 + wss.broadcast({ + uuid: connection.transaction.uuid, + queue: { + classy: 'bg_green', + title: msg, + }, + }); + next(); }; exports.redis_subscribe_all_results = function (next) { var plugin = this; - plugin.redis_subscribe_pattern('result-*', function () { - plugin.redis.on('pmessage', function (pattern, channel, message) { + plugin.redis_subscribe_pattern('result-*', () => { + plugin.redis.on('pmessage', (pattern, channel, message) => { var match = /result-([A-F0-9\-\.]+)$/.exec(channel); // uuid if (!match) { plugin.logerror('pattern: ' + pattern); @@ -209,148 +244,23 @@ exports.redis_subscribe_all_results = function (next) { if (m.result.fail == 'UA') return; } break; + case 'queue/smtp_forward': + if (m.result.pass) + wss.broadcast({ + uuid: match[1], + 'queue/smtp_forward': { classy: 'bg_green' } + }); + return; } var req = { uuid : match[1] }; req[ plugin.get_plugin_name(m.plugin) ] = plugin.format_any(m.plugin, m.result); wss.broadcast(req); - }); + }) next(); - }); -}; - -exports.get_incremental_results = function (next, connection) { - var plugin = this; - - plugin.get_connection_results(connection); - if (connection.transaction) { - plugin.get_transaction_results(connection.transaction); - } - - return next(); -}; - -exports.queue_ok = function (next, connection) { - // queue_ok arguments: next, connection, msg - // ok 1390590369 qp 634 (F82E2DD5-9238-41DC-BC95-9C3A02716AD2.1) - - this.get_incremental_results(function () { - wss.broadcast({ - uuid: connection.transaction.uuid, - queue: { classy: 'bg_green' }, - }); - next(); - }, - connection); -}; - -exports.w_deny = function (next, connection, params) { - var plugin = this; - // this.loginfo(this, params); - var pi_code = params[0]; // deny code? - // var pi_msg = params[1]; // deny error - var pi_name = params[2]; // plugin name - // var pi_function = params[3]; - // var pi_params = params[4]; - var pi_hook = params[5]; - - connection.logdebug(this, "watch deny saw: " + pi_name + - ' deny from ' + pi_hook); - - this.get_connection_results(connection); - if (connection.transaction) { - this.get_transaction_results(connection.transaction); - } - - var req = { - uuid: connection.transaction ? connection.transaction.uuid - : connection.uuid, - local_port: { classy: 'bg_white', title: 'disconnected' }, - remote_host: get_remote_host(connection), - }; - - connection.logdebug(this, "watch sending dark red to "+pi_name); - var bg_class = pi_code === DENYSOFT ? 'bg_dyellow' : 'bg_dred'; - var report_as = plugin.get_plugin_name(pi_name); - if (req[report_as]) req[report_as].classy = bg_class; - if (!req[report_as]) req[report_as] = { classy: bg_class }; - - wss.broadcast(req); - return next(); -}; - -exports.disconnect = function (next, connection) { - - var incrDone = function () { - wss.broadcast( { - uuid: connection.uuid, - local_port: { classy: 'bg_white', title: 'disconnected' }, - }); - next(); - }; - - this.get_incremental_results(incrDone, connection); - // this.redis_unsubscribe(connection); -}; - -exports.get_connection_results = function (connection) { - var plugin = this; - - var au = connection.notes.auth_user; - - var req = { - uuid : connection.uuid, - local_port : get_local_port(connection), - remote_host: get_remote_host(connection), - // tls : get_tls(connection), - auth : au ? { classy: 'bg_green', title: au } : '', - relay : get_relay(connection), - // helo : get_helo(connection), - early : get_early, - queue : { newval: elapsed(connection.start_time, 1) }, - }; - - // see if changed since we last sent - [ - 'local_port', 'remote_host', 'tls', 'auth', 'relay', 'helo', 'early' - ] - .forEach(function (val) { - if (JSON.stringify(req[val]) === JSON.stringify(connection[val + '_watch'])) { - // same as last time, don't send - delete req[val]; - } - else { - // cache, so we can compare on the next run - connection[val + '_watch'] = JSON.stringify(req[val]); - } - }); - - var result_store = connection.results.get_all(); - for (var name in result_store) { - plugin.get_plugin_result(req, result_store, name); - } - - wss.broadcast(req); -}; - -exports.get_transaction_results = function (txn) { - var plugin = this; - if (!txn) return; - - var req = { - uuid: txn.uuid, - // mail_from: get_mailfrom(txn), - // rcpt_to: get_recipients(txn), - }; - - var result_store = txn.results.get_all(); - for (var name in result_store) { - plugin.get_plugin_result(req, result_store, name); - } - - wss.broadcast(req); -}; + }) +} exports.get_plugin_name = function (pi_name) { @@ -364,28 +274,13 @@ exports.get_plugin_name = function (pi_name) { return 'fcrdns'; case 'connect.p0f': return 'p0f'; + case 'dkim_verify': + case 'dkim_sign': + return 'dkim'; } return pi_name; -}; - -exports.get_plugin_result = function (req, res, name) { - var plugin = this; - if (name[0] === '_') return; // ignore anything with leading _ - - var formatted = plugin.format_results(name, res[name]); - if (res[name]._watch_saw === JSON.stringify(formatted)) { - // plugin.loginfo(name + ' skip, same as cached'); - return; // don't report - } - - // save to request that gets sent to client - // plugin.loginfo(name + ' saved to res'); - req[ plugin.get_plugin_name(name) ] = formatted; - - // cache formatted result to avoid sending dups to client - res[name]._watch_saw = JSON.stringify(formatted); -}; +} exports.format_any = function (pi_name, r) { var plugin = this; @@ -502,6 +397,7 @@ exports.format_any = function (pi_name, r) { if (/^from_match/.test(r.fail)) return { classy: 'bg_yellow' }; } break; + case 'dkim_sign': case 'dkim_verify': if (r.pass || r.fail) { return plugin.format_default(r); @@ -536,6 +432,8 @@ exports.format_any = function (pi_name, r) { case 'queue': if (r.pass) return { classy: 'bg_green', title: r.pass }; if (r.fail) return { classy: 'bg_red', title: r.fail }; + if (r.msg === '') return {}; + break; } plugin.loginfo(pi_name); @@ -545,7 +443,7 @@ exports.format_any = function (pi_name, r) { title: plugin.get_title(pi_name, r), classy: plugin.get_class(pi_name, r), }; -}; +} exports.format_recipient = function (r) { @@ -567,7 +465,7 @@ exports.format_default = function (r) { if (r.pass) return { classy: 'bg_green', title: r.pass }; if (r.fail) return { classy: 'bg_red', title: r.fail }; if (r.err) return { classy: 'bg_yellow', title: r.err }; -}; +} exports.format_fcrdns = function (r) { if (r.pass) return { classy: 'bg_green' }; @@ -580,7 +478,7 @@ exports.format_fcrdns = function (r) { } // this.loginfo(r); return {}; -}; +} exports.format_asn = function (r) { if (r.pass) return { classy: 'bg_green' }; @@ -588,7 +486,7 @@ exports.format_asn = function (r) { if (r.asn) return { newval: r.asn }; this.loginfo(r); return {}; -}; +} exports.format_p0f = function (r) { if (!r || !r.os_name) return {}; @@ -601,7 +499,7 @@ exports.format_p0f = function (r) { if (/windows/i.test(r.os_name)) r.classy = 'bg_red'; } return f; -}; +} exports.format_bounce = function (r) { if (!r) return {}; @@ -620,7 +518,7 @@ exports.format_results = function (pi_name, r) { if (pi_name === 'spf') { s.scope = r.scope; } return s; -}; +} exports.format_helo = function (uuid, r) { return { @@ -631,12 +529,13 @@ exports.format_helo = function (uuid, r) { classy: 'bg_white', }, } -}; +} exports.get_class = function (pi_name, r) { if (!r.pass) r.pass = []; if (!r.fail) r.fail = []; if (!r.err) r.err = []; + switch (pi_name) { case 'data.dmarc': if (!r.result) return 'got'; @@ -675,7 +574,7 @@ exports.get_class = function (pi_name, r) { r.err.length ? 'bg_yellow' : 'bg_lgrey'; } -}; +} exports.get_title = function (pi_name, r) { // title: the value shown in the HTML tooltip @@ -690,19 +589,6 @@ exports.get_title = function (pi_name, r) { default: return r.human_html; } -}; - -function get_local_port (connection) { - if (!connection) return { - classy: 'bg_white', newval: '25', title: 'disconnected' - }; - var p = connection.local.port || '25'; - if (!p || isNaN(p)) return { - classy: 'black', newval: '25', title: 'disconnected' - }; - return { - newval: p, classy: 'bg_dgreen', title: 'connected' - }; } exports.format_remote_host = function (uuid, r) { @@ -728,7 +614,7 @@ exports.format_remote_host = function (uuid, r) { newval: host ? (hostShort + ' / ' + ip) : ip, title: host ? (host + ' / ' + ip) : ip, } - }; + } } function get_remote_host (connection) { @@ -753,32 +639,3 @@ function get_remote_host (connection) { title: host ? (host + ' / ' + ip) : ip, }; } - -function get_early (connection) { - if (!connection) return; - var early = connection.early_talker; - return { - title: early ? 'yes' : 'no', - classy: early ? 'bg_red' : 'bg_green', - }; -} - -function get_relay (connection) { - if (!connection.relaying) return { title: 'no'}; - return { title: 'yes', classy: 'bg_green'}; -} - -function elapsed (start, decimal_places) { - var diff = (Date.now() - start) / 1000; // in seconds - - if (decimal_places === undefined) { - decimal_places = diff > 5 ? 0 : diff > 2 ? 1 : 2; - } - else { - decimal_places = parseInt(decimal_places, 10); - if (isNaN(decimal_places)) { - decimal_places = 2; - } - } - return diff.toFixed(decimal_places); -} diff --git a/package.json b/package.json index b9cb134..f92d868 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "haraka-plugin-watch", - "version": "1.0.6", + "version": "1.0.7", "description": "Watch live SMTP traffic in a web interface", "main": "index.js", "scripts": {