<commit_before>// load all defined plugins

var logger      = require('./logger');
var config      = require('./config');
var constants   = require('./constants');
var path        = require('path');
var vm          = require('vm');
var fs          = require('fs');
var utils       = require('./utils');
var util = require('util');

var plugin_paths = [path.join(__dirname, './plugins')];
if (process.env.HARAKA) { plugin_paths.unshift(path.join(process.env.HARAKA, 'plugins')); }

// These are the hooks that qpsmtpd implements - I should get around
// to supporting them all some day... :-/
var regular_hooks = {
    'connect':1,
    'pre-connection': 1,
    'connect': 1,
    'ehlo_parse': 1,
    'ehlo': 1,
    'helo_parse': 1,
    'helo': 1,
    'auth_parse': 1,
    'auth': 1,
    'auth-plain': 1,
    'auth-login': 1,
    'auth-cram-md5': 1,
    'rcpt_parse': 1,
    'rcpt_pre': 1,
    'rcpt': 1,
    'mail_parse': 1,
    'mail': 1,
    'mail_pre': 1, 
    'data': 1,
    'data_headers_end': 1,
    'data_post': 1,
    'queue_pre': 1,
    'queue': 1,
    'queue_post': 1,
    'vrfy': 1,
    'noop': 1,
    'quit': 1,
    'reset_transaction': 1,
    'disconnect': 1,
    'unrecognized_command': 1,
    'help': 1
};

function Plugin(name) {
    this.name = name;
    this.timeout = config.get(name + '.timeout', 'nolog');
    if (this.timeout === null) {
        this.timeout = config.get('plugin_timeout', 'nolog') || 30;
    }
    else {
        logger.logdebug("plugin " + name + " set timeout to: " + this.timeout + "s");
    }
    var full_paths = []
    plugin_paths.forEach(function (pp) {
        full_paths.push(path.resolve(pp, name) + '.js');
    });
    this.full_paths = full_paths;
    this.config = config;
    this.hooks = {};
};

Plugin.prototype.register_hook = function(hook_name, method_name) {
    this.hooks[hook_name] = this.hooks[hook_name] || [];
    this.hooks[hook_name].push(method_name);
    
    logger.logdebug("registered hook " + hook_name + " to " + this.name + "." + method_name);
}

Plugin.prototype.register = function () {}; // noop

// copy logger methods into Plugin:

for (var key in logger) {
    if (key.match(/^log\w/)) {
        // console.log("adding Plugin." + key + " method");
        Plugin.prototype[key] = (function (key) {
            return function () {
                var args = ["[" + this.name + "] "];
                for (var i=0, l=arguments.length; i<l; i++) {
                    args.push(arguments[i]);
                }
                logger[key].apply(logger, args);
            }
        })(key);
    }
}

var plugins = exports;

plugins.Plugin = Plugin;

plugins.load_plugins = function () {
    logger.loginfo("Loading plugins");
    var plugin_list = config.get('plugins', 'nolog', 'list');
    
    plugins.plugin_list = plugin_list.map(plugins.load_plugin);
};

plugins.load_plugin = function(name) {
    logger.loginfo("Loading plugin: " + name);
    
    var plugin = new Plugin(name);
    var fp = plugin.full_paths,
        rf, last_err;
    for (var i=0, j=fp.length; i<j; i++) {
        try {
            rf = fs.readFileSync(fp[i]);
            break;
        }
        catch (err) {
            last_err = err;
            continue;
        }
    }
    if (!rf) {
        if (config.get('smtp.ini', 'nolog', 'ini').main.ignore_bad_plugins) {
            logger.logcrit("Loading plugin " + name + " failed: " + last_err);
            return;
        }
        throw "Loading plugin " + name + " failed: " + last_err;
    }
    var code = rf;
    var sandbox = { 
        require: require,
        exports: plugin,
        setTimeout: setTimeout,
        clearTimeout: clearTimeout,
        setInterval: setInterval,
        clearInterval: clearInterval,
        process: process,
        Buffer: Buffer
    };
    constants.import(sandbox);
    try {
        vm.runInNewContext(code, sandbox, name);
    }
    catch (err) {
        if (config.get('smtp.ini', 'nolog', 'ini').main.ignore_bad_plugins) {
            logger.logcrit("Loading plugin " + name + " failed: ", err.stack);
            return;
        }
        throw err; // default is to re-throw and stop Haraka
    }
    
    // register any hook_blah methods.
    for (var method in plugin) {
        var result;
        if (result = method.match(/^hook_(\w+)\b/)) {
            plugin.register_hook(result[1], method);
        }
    }
    
    plugin.register();
    
    return plugin;
}

plugins.run_hooks = function (hook, object, params) {
    if (hook != 'log')
        object.logdebug("running " + hook + " hooks");
    
    if (regular_hooks[hook] && object.hooks_to_run.length) {
        throw new Error("We are already running hooks! Fatal error!");
    }

    if (hook === 'deny') {
        // Save the hooks_to_run list so that we can run any remaining 
        // plugins on the previous hook once this hook is complete.
        object.saved_hooks_to_run = object.hooks_to_run;
    }
    object.hooks_to_run = [];
    
    for (i = 0; i < plugins.plugin_list.length; i++) {
        var plugin = plugins.plugin_list[i];
        <commit_msg>Made plugin inheritance work<commit_after>// load all defined plugins

var logger      = require('./logger');
var config      = require('./config');
var constants   = require('./constants');
var path        = require('path');
var vm          = require('vm');
var fs          = require('fs');
var utils       = require('./utils');
var util        = require('util');

var plugin_paths = [path.join(__dirname, './plugins')];
if (process.env.HARAKA) { plugin_paths.unshift(path.join(process.env.HARAKA, 'plugins')); }

// These are the hooks that qpsmtpd implements - I should get around
// to supporting them all some day... :-/
var regular_hooks = {
    'connect':1,
    'pre-connection': 1,
    'connect': 1,
    'ehlo_parse': 1,
    'ehlo': 1,
    'helo_parse': 1,
    'helo': 1,
    'auth_parse': 1,
    'auth': 1,
    'auth-plain': 1,
    'auth-login': 1,
    'auth-cram-md5': 1,
    'rcpt_parse': 1,
    'rcpt_pre': 1,
    'rcpt': 1,
    'mail_parse': 1,
    'mail': 1,
    'mail_pre': 1, 
    'data': 1,
    'data_headers_end': 1,
    'data_post': 1,
    'queue_pre': 1,
    'queue': 1,
    'queue_post': 1,
    'vrfy': 1,
    'noop': 1,
    'quit': 1,
    'reset_transaction': 1,
    'disconnect': 1,
    'unrecognized_command': 1,
    'help': 1
};

function Plugin(name) {
    this.name = name;
    this.timeout = config.get(name + '.timeout', 'nolog');
    if (this.timeout === null) {
        this.timeout = config.get('plugin_timeout', 'nolog') || 30;
    }
    else {
        logger.logdebug("plugin " + name + " set timeout to: " + this.timeout + "s");
    }
    var full_paths = []
    plugin_paths.forEach(function (pp) {
        full_paths.push(path.resolve(pp, name) + '.js');
    });
    this.full_paths = full_paths;
    this.config = config;
    this.hooks = {};
};

Plugin.prototype.register_hook = function(hook_name, method_name) {
    this.hooks[hook_name] = this.hooks[hook_name] || [];
    this.hooks[hook_name].push(method_name);
    
    logger.logdebug("registered hook " + hook_name + " to " + this.name + "." + method_name);
}

Plugin.prototype.register = function () {}; // noop

Plugin.prototype.inherits = function (parent_name) {
    var parent_plugin = plugins._load_and_compile_plugin(parent_name);
    for (var method in parent_plugin) {
        if (!this[method]) {
            this[method] = parent_plugin[method];
        }
    }
    if (parent_plugin.register) {
        parent_plugin.register.call(this);
    }
}

// copy logger methods into Plugin:

for (var key in logger) {
    if (key.match(/^log\w/)) {
        // console.log("adding Plugin." + key + " method");
        Plugin.prototype[key] = (function (key) {
            return function () {
                var args = ["[" + this.name + "] "];
                for (var i=0, l=arguments.length; i<l; i++) {
                    args.push(arguments[i]);
                }
                logger[key].apply(logger, args);
            }
        })(key);
    }
}

var plugins = exports;

plugins.Plugin = Plugin;

plugins.load_plugins = function () {
    logger.loginfo("Loading plugins");
    var plugin_list = config.get('plugins', 'nolog', 'list');
    
    plugins.plugin_list = plugin_list.map(plugins.load_plugin);
};

plugins.load_plugin = function(name) {
    logger.loginfo("Loading plugin: " + name);

    var plugin = plugins._load_and_compile_plugin(name);
    plugins._register_plugin(plugin);

    return plugin;
}

plugins._load_and_compile_plugin = function(name) {
    var plugin = new Plugin(name);
    var fp = plugin.full_paths,
        rf, last_err;
    for (var i=0, j=fp.length; i<j; i++) {
        try {
            rf = fs.readFileSync(fp[i]);
            break;
        }
        catch (err) {
            last_err = err;
            continue;
        }
    }
    if (!rf) {
        if (config.get('smtp.ini', 'nolog', 'ini').main.ignore_bad_plugins) {
            logger.logcrit("Loading plugin " + name + " failed: " + last_err);
            return;
        }
        throw "Loading plugin " + name + " failed: " + last_err;
    }
    var code = rf;
    var sandbox = { 
        require: require,
        exports: plugin,
        setTimeout: setTimeout,
        clearTimeout: clearTimeout,
        setInterval: setInterval,
        clearInterval: clearInterval,
        process: process,
        Buffer: Buffer,
    };
    constants.import(sandbox);
    try {
        vm.runInNewContext(code, sandbox, name);
    }
    catch (err) {
        if (config.get('smtp.ini', 'nolog', 'ini').main.ignore_bad_plugins) {
            logger.logcrit("Loading plugin " + name + " failed: ", err.stack);
            return;
        }
        throw err; // default is to re-throw and stop Haraka
    }
    
    return plugin;
}

plugins._register_plugin = function (plugin) {
    plugin.register();
    
    // register any hook_blah methods.
    for (var method in plugin) {
        var result;
        if (result = method.match(/^hook_(\w+)\b/)) {
            plugin.register_hook(result[1], method);
        }
    }
    
    return plugin;
}

plugins.run_hooks = function (hook, object, params) {
    if (hook != 'log')
        object.logdebug("running " + hook + " hooks");
    
    if (regular_hooks[hook] && object.hooks_to_run.length) {
        throw new Error("We are already running hooks! Fatal error!");
    }

    if (hook === 'deny') {
        // Save the hooks_to_run list so that we can run any remaining 
        // plugins on the previous hook once this hook is complete.
        object.saved_hooks_to_run = object.hooks_to_run;
    }
    object.hooks_to_run = [];
    
    for (i = 0; i < plugins.plugin_list.length; i++) {
        var plugin = plugins.plugin_list[i];
        
{'fraction_non_alphanumeric': 0.15264278441173748, 'fraction_numerical': 0.0070350828473572154, 'mean_word_length': 2.221228384019082, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4244619', 'n_tokens_mistral': 3584, 'n_tokens_neox': 3493, 'n_words': 812}