// Basic clamd plug-in
// Requires 'clamdscan' binary to function

var spawn = require('child_process').spawn;

var defaults = {
    clamdscan_bin: 'clamdscan',
    timeout: 30,
    only_with_attachments: 0,
};

exports.hook_data = function (next, connection) {
    var plugin = this;
    // Load config
    var config = this.config.get('clamdscan.ini', 'ini');
    for (var key in defaults) {
        config.main[key] = config.main[key] || defaults[key];
    }
    if (config.main['only_with_attachments']) {
        var transaction = connection.transaction;
        transaction.parse_body = 1;
        transaction.attachment_hooks(function (ctype, filename, body) {
            plugin.logdebug('Found ctype=' + ctype + ', filename=' + filename);
            transaction.notes.clamdscan_found_attachment = 1;
        });
    }
    return next();
}

exports.hook_data_post = function (next, connection) {
    var plugin = this;
    // Load config
    var config = this.config.get('clamdscan.ini', 'ini');
    for (var key in defaults) {
        config.main[key] = config.main[key] || defaults[key];
    }
    // Do we need to run?
    if (config.main['only_with_attachments'] &&
        !connection.transaction.notes.clamdscan_found_attachment) {
        plugin.logdebug('Skipping message as no attachments found');
        return next();
    }
    var virus_regexp = /^stream: (\S+) FOUND/;
    var virus_name;
    var clamdscan = spawn(config.main['clamdscan_bin'], ['-i', '--no-summary', '-']);
    plugin.logdebug('Spawned child pid: ' + clamdscan.pid);

    // Create a timeout
    var timeout = setTimeout(function () {
        plugin.logerror('timed out');
        clamdscan.kill();
    }, config.main['timeout'] * 1000);

    var data_marker = 0;
    var send_data = function() {
        if (data_marker < connection.transaction.data_lines.length) {
            var line = connection.transaction.data_lines[data_marker];
            plugin.logprotocol('wrote: ' + line);
            var wrote_all = clamdscan.stdin.write(line);
            data_marker++;
            if (wrote_all) 
                send_data();
        }
        else {
            clamdscan.stdin.end();
        }
    }

    clamdscan.stdin.on('error', function() {
        // Prevent EPIPE
        clamdscan.stdin.end();
    });

    clamdscan.stdin.on('drain', function() {
        plugin.logdebug('drain');
        send_data();
    });

    clamdscan.stdout.on('data', function(data) {
        plugin.logprotocol('received: ' + data);
        var m = virus_regexp.exec(data);
        if (m && m[1])
            virus_name = m[1];
    });

    clamdscan.stderr.on('data', function(data) {
        if (/^execvp\(\)/.test(data)) {
            plugin.logerror('Failed to start child: ' + data);
            clamdscan.stdin.end();
        } 
        else {
            plugin.logdebug('stderr: ' + data);
        }
    });

    clamdscan.on('exit', function(code, signal) {
        clearTimeout(timeout);
        if (code) {
            plugin.logdebug('Child exited with code: ' + code);
            switch (code) {
                case 1:         // Virus found
                    return next(DENY, 'Message is infected with ' + (virus_name || 'UNKNOWN'));
                    break;
                case 2:         // Error
                case 127:       // No such file or directory
                    return next(DENYSOFT, 'Error running virus scanner');
                    break;
                default:
                    return next();
            }
        }
        if (signal) {
            plugin.logdebug('Child terminated by signal: ' + signal);
            if (signal === 'SIGTERM')
                return next(DENYSOFT, 'Virus scanner timed out');
        }
        return next();
    });

    send_data();
}

@@ -913,32 +913,79 @@
  plugin = this;%0A
+    var transaction = connection.transaction;%0A%0A
     // Load conf
@@ -1141,24 +1141,25 @@
 key%5D;%0A    %7D%0A
+%0A
     // Do we
@@ -1229,27 +1229,16 @@
        !
-connection.
 transact
@@ -1275,16 +1275,21 @@
 chment) 
+%0A    
 %7B%0A      
@@ -1376,24 +1376,25 @@
 xt();%0A    %7D%0A
+%0A
     var viru
@@ -1504,16 +1504,43 @@
 _bin'%5D, 
+%0A                          
 %5B'-i', '
@@ -1877,27 +1877,16 @@
 arker %3C 
-connection.
 transact
@@ -1933,27 +1933,16 @@
  line = 
-connection.
 transact
@@ -2131,16 +2131,18 @@
 te_all) 
+%7B 
 %0A       
@@ -2159,24 +2159,38 @@
 end_data();%0A
+            %7D%0A
         %7D%0A  
@@ -2634,16 +2634,18 @@
 && m%5B1%5D)
+ %7B
 %0A       
@@ -2668,16 +2668,26 @@
 = m%5B1%5D;%0A
+        %7D%0A
     %7D);%0A
@@ -3183,22 +3183,16 @@
  case 1:
-      
    // Vi
@@ -3267,16 +3267,53 @@
  with ' 
+%0A                                    
 + (virus
@@ -3384,22 +3384,16 @@
  case 2:
-      
    // Er
@@ -3421,22 +3421,16 @@
 ase 127:
-      
  // No s
@@ -3765,16 +3765,18 @@
 IGTERM')
+ %7B
 %0A       
@@ -3826,32 +3826,46 @@
 er timed out');%0A
+            %7D%0A
         %7D%0A      

Update clamdscan plug-in