Repository: haraka/Haraka (JavaScript)

Issue:
Plugin dkim_sign failed: No results
Another [CRIT] log I've found at my system

### system info

Haraka | Haraka.js — Version: 2.8.18
 --- | :--- 
Node | v8.11.2
OS | Linux ... 4.4.0-98-generic #121-Ubuntu SMP Tue Oct 10 14:24:03 UTC 2017 x86_64 GNU/Linux
openssl | OpenSSL 1.1.0f  25 May 2017

### Observed behavior

It is typicaly seen with cron messages
- MAIL FROM:<system@localhost>
- email header "From: root (Cron Daemon)"

2018-05-19T04:29:39.698Z [DEBUG] [core] running queue_outbound hook in dkim_sign plugin
2018-05-19T04:29:39.702Z [CRIT] [core] Plugin dkim_sign failed: No results

Log message is kind of cryptic, I was unable to find where it is from. Obviously dkim keys for this domain doesn't exist and plugin should end without crit



Discussion:

Patch:
diff --git a/Changes.md b/Changes.md
index a1e2a923e..1a6d2175e 100644
--- a/Changes.md
+++ b/Changes.md
@@ -1,3 +1,18 @@
+## 2.8.20 - Mmm DD, 2018
+
+* New Features
+    * n/a
+* Fixes
+    * data_headers: check defined-ness of hdr_address *after* try/catch #2458
+    * tls: remove tls.ini loading from plugins/tls #2459
+    * tls: remove invalid opt from load_tls_ini #2456
+    * outbound: escape values in HTML bounce correctly #2446
+    * dkim_sign: catch exceptions when address-rfc2822 fails to parse From #2457
+* Changes
+    * logger: Add "obj" log param to log hook that contains log data by type #2425
+    * logger: include outbound client ID in logging #2425
+    * logger: allow specifying uuid in params when logging #2425
+
 ## 2.8.19 - Jun 26, 2018
 
 * New features
diff --git a/plugins/dkim_sign.js b/plugins/dkim_sign.js
index c8ee8649c..2e6b96069 100644
--- a/plugins/dkim_sign.js
+++ b/plugins/dkim_sign.js
@@ -165,7 +165,7 @@ exports.load_key = function (file) {
 
 exports.hook_queue_outbound = exports.hook_pre_send_trans_email = function (next, connection) {
     const plugin = this;
-    if (plugin.cfg.main.disabled) { return next(); }
+    if (plugin.cfg.main.disabled) return next();
     if (connection.transaction.notes.dkim_signed) {
         connection.logdebug(plugin, 'email already signed');
         return next();
@@ -186,18 +186,17 @@ exports.hook_queue_outbound = exports.hook_pre_send_trans_email = function (next
         }
         else {
             domain = path.basename(keydir);
-            connection.logdebug(plugin, 'dkim_domain: ' + domain);
             private_key = plugin.load_key(path.join('dkim', domain, 'private'));
             selector    = plugin.load_key(path.join('dkim', domain, 'selector')).trim();
         }
 
-        if (!plugin.has_key_data(connection,domain,selector,private_key)) {
-            return next();
-        }
+        if (!plugin.has_key_data(connection, domain, selector, private_key)) return next();
+        connection.logdebug(plugin, 'dkim_domain: ' + domain);
 
         const headers_to_sign = plugin.get_headers_to_sign();
         const txn = connection.transaction;
-        const dkimCallback = function (err2, dkim_header) {
+
+        function dkimCallback (err2, dkim_header) {
             if (err2) {
                 txn.results.add(plugin, {err: err2.message});
             }
@@ -208,17 +207,18 @@ exports.hook_queue_outbound = exports.hook_pre_send_trans_email = function (next
             }
             connection.transaction.notes.dkim_signed = true;
             return next();
-        };
-        txn.message_stream.pipe(new DKIMSignStream(
-            selector, domain, private_key, headers_to_sign,
-            txn.header, dkimCallback));
+        }
+
+        txn.message_stream.pipe(
+            new DKIMSignStream(selector, domain, private_key, headers_to_sign, txn.header, dkimCallback)
+        );
     });
 }
 
 exports.get_key_dir = function (connection, done) {
     const plugin = this;
     const domain = plugin.get_sender_domain(connection.transaction);
-    if (!domain) { return done(); }
+    if (!domain) return done();
 
     // split the domain name into labels
     const labels     = domain.split('.');
@@ -236,10 +236,8 @@ exports.get_key_dir = function (connection, done) {
             if (err) return iterDone(null, false);
             iterDone(null, stats.isDirectory());
         });
-    }, function (err, results) {
-        if (err) {
-            connection.logerror(err);
-        }
+    },
+    function (err, results) {
         connection.logdebug(plugin, results);
         done(err, results);
     });
@@ -262,7 +260,7 @@ exports.has_key_data = function (conn, domain, selector, private_key) {
         return false;
     }
 
-    conn.logprotocol(plugin, 'selector: '+selector);
+    conn.logprotocol(plugin, `selector: ${selector}`);
     return true;
 }
 
@@ -287,7 +285,7 @@ exports.get_headers_to_sign = function () {
 
 exports.get_sender_domain = function (txn) {
     const plugin = this;
-    if (!txn) { return; }
+    if (!txn) return;
 
     // a fallback, when header parsing fails
     let domain;
@@ -296,17 +294,24 @@ exports.get_sender_domain = function (txn) {
         plugin.logerror(e);
     }
 
-    if (!txn.header) { return domain; }
+    if (!txn.header) return domain;
 
     // the DKIM signing key should be aligned with the domain in the From
     // header (see DMARC). Try to parse the domain from there.
     const from_hdr = txn.header.get('From');
-    if (!from_hdr) { return domain; }
+    if (!from_hdr) return domain;
 
     // The From header can contain multiple addresses and should be
     // parsed as described in RFC 2822 3.6.2.
-    const addrs = addrparser.parse(from_hdr);
-    if (!addrs || ! addrs.length) { return domain; }
+    let addrs;
+    try {
+        addrs = addrparser.parse(from_hdr);
+    }
+    catch (e) {
+        plugin.logerror(`address-rfc2822 failed to parse From header: ${from_hdr}`)
+        return domain;
+    }
+    if (!addrs || ! addrs.length) return domain;
 
     // If From has a single address, we're done
     if (addrs.length === 1 && addrs[0].host) {


Test patch:
diff --git a/tests/plugins/dkim_sign.js b/tests/plugins/dkim_sign.js
index e3d829422..56ed52dfc 100644
--- a/tests/plugins/dkim_sign.js
+++ b/tests/plugins/dkim_sign.js
@@ -15,7 +15,7 @@ const _set_up = function (done) {
     this.plugin.cfg = { main: { } };
 
     this.connection = Connection.createConnection();
-    this.connection.transaction = fixtures.transaction.createTransaction();
+    this.connection.init_transaction();
 
     done();
 }
@@ -62,6 +62,14 @@ exports.get_sender_domain = {
         test.equal('example.com', r);
         test.done();
     },
+    'From header not a fqdn': function (test) {
+        test.expect(1);
+        this.connection.transaction.header.add('From', 'root (Cron Daemon)');
+        this.plugin.get_key_dir(this.connection, function (err, dir) {
+            test.equal(dir, undefined);
+            test.done();
+        });
+    },
     'from header, simple': function (test) {
         test.expect(1);
         this.connection.transaction.header.add('From', 'John Doe <jdoe@example.com>');
@@ -116,7 +124,7 @@ exports.get_key_dir = {
         this.plugin.cfg = { main: { } };
 
         this.connection = Connection.createConnection();
-        this.connection.transaction = fixtures.transaction.createTransaction();
+        this.connection.init_transaction();
 
         fs.mkdir(path.resolve('tests','config','dkim'), function (err) {
             // if (err) console.error(err);
@@ -136,8 +144,7 @@ exports.get_key_dir = {
     },
     'no key dir': function (test) {
         test.expect(1);
-        this.connection.transaction.mail_from =
-            new Address.Address('<matt@non-exist.com>');
+        this.connection.transaction.mail_from = new Address.Address('<matt@non-exist.com>');
         this.plugin.get_key_dir(this.connection, function (err, dir) {
             test.equal(dir, undefined);
             test.done();
@@ -146,8 +153,7 @@ exports.get_key_dir = {
     'test example.com key dir': function (test) {
         test.expect(1);
         process.env.HARAKA = path.resolve('tests');
-        this.connection.transaction.mail_from =
-            new Address.Address('<matt@example.com>');
+        this.connection.transaction.mail_from = new Address.Address('<matt@example.com>');
         this.plugin.get_key_dir(this.connection, function (err, dir) {
             // console.log(arguments);
             const expected = path.resolve('tests','config','dkim','example.com');