GitHub PullRequestReviewCommentEvent at 2014-01-20 17:20:51
Repository: Haraka
Actor: smfreegard
Payload: {"comment":{"url":"https://api.github.com/repos/baudehlo/Haraka/pulls/comments/9011562","id":9011562,"diff_hunk":"@@ -0,0 +1,392 @@\n+// karma - reward good and penalize bad mail senders\n+\n+var ipaddr = require('ipaddr.js');\n+var redis = require('redis');\n+var db;\n+\n+exports.register = function () {\n+    var plugin = this;\n+\n+    this.register_hook('init_master',  'karma_onInit');\n+    this.register_hook('init_child',   'karma_onInit');\n+    this.register_hook('deny',         'karma_onDeny');\n+    this.register_hook('lookup_rdns',  'karma_onConnect');\n+    this.register_hook('mail',         'karma_onMailFrom');\n+    this.register_hook('rcpt',         'karma_onRcptTo');\n+    this.register_hook('data',         'karma_onData');\n+    this.register_hook('data_post',    'karma_onDataPost');\n+    this.register_hook('disconnect',   'karma_onDisconnect');\n+};\n+\n+exports.karma_onInit = function (next,server) {\n+    var config     = this.config.get('karma.ini');\n+    var redis_ip  = '127.0.0.1';\n+    var redis_port = '6379';\n+    if ( config.redis ) {\n+        redis_ip = config.redis.server_ip || '127.0.0.1';\n+        redis_port = config.redis.server_port || '6379';\n+    };\n+    db = redis.createClient(redis_port, redis_ip);\n+    return next();\n+};\n+\n+exports.karma_onConnect = function (next, connection) {\n+    var plugin = this;\n+    var config = this.config.get('karma.ini');\n+\n+    initConnectionNote(connection, config);\n+\n+    var r_ip = connection.remote_ip;\n+    var dbkey = 'karma|'+r_ip;\n+    var expire = (config.main.expire_days || 60) * 86400; // convert to days\n+\n+    function initRemoteIP () {\n+        db.multi()\n+            .hmset(dbkey, {'penalty_start_ts': 0, 'bad': 0, 'good': 0, 'connections': 1})\n+            .expire(dbkey, expire)\n+            .exec();\n+        connection.logdebug(plugin,\"first connect\");\n+    };\n+\n+    db.multi()\n+        .get('concurrent|'+r_ip)\n+        .hgetall(dbkey)\n+        .exec( function redisResults (err,replies) {\n+            if (err) {\n+                connection.logdebug(plugin,\"err: \"+err);\n+                return next();\n+            };\n+\n+            if (replies[1] === null) { initRemoteIP(); return next(); };\n+\n+            db.hincrby(dbkey, 'connections', 1); // increment total connections\n+            db.expire(dbkey, expire);            // extend expiration date\n+\n+            var dbr = replies[1]; // 2nd element of DB reply is our karma object\n+            var history = (dbr.good || 0) - (dbr.bad || 0);\n+            connection.notes.karma.history = history;\n+\n+            var summary = dbr.bad+\" bad, \"+dbr.good+\" good, \"\n+                         +dbr.connections+\" connects, \"+history+\" history\";\n+\n+            var too_many = checkConcurrency(plugin, 'concurrent|'+r_ip, replies[0], history);\n+            if ( too_many ) {\n+                connection.loginfo(plugin, too_many + \", (\"+summary+\")\");\n+                return next(DENYSOFT, too_many);\n+            };\n+\n+            if (dbr.penalty_start_ts === '0') {\n+                connection.loginfo(plugin, \"no penalty, \"+karmaSummary(connection));\n+                return next();\n+            }\n+\n+            var days_old = (Date.now() - Date.parse(dbr.penalty_start_ts)) / 86.4;\n+            var penalty_days = config.main.penalty_days;\n+            if (days_old >= penalty_days) {\n+                connection.loginfo(plugin, \"penalty expired, \"+karmaSummary(connection));\n+                return next();\n+            }\n+\n+            var left = +( penalty_days - days_old ).toFixed(2);\n+            var mess = \"Bad karma, you can try again in \"+left+\" more days.\";\n+\n+            return next(DENY, mess);\n+        });\n+\n+    checkAwards(config, connection, plugin);\n+};\n+\n+exports.karma_onDeny = function (next, connection, params) {\n+    /* params\n+    ** [0] = plugin return value (constants.deny or constants.denysoft)\n+    ** [1] = plugin return message\n+    */\n+\n+    var pi_name     = params[2];\n+    var pi_function = params[3];\n+    var pi_params   = params[4];\n+    var pi_hook     = params[5];\n+\n+    var plugin = this;\n+    var transaction = connection.transaction;\n+\n+    var config = this.config.get('karma.ini');\n+    initConnectionNote(connection, config);\n+\n+// TO CONSIDER: decrement karma two points for a 5XX deny?\n+    connection.notes.karma.connection--;\n+    connection.notes.karma.penalties.push(pi_name);\n+\n+    connection.loginfo(plugin, 'deny, '+karmaSummary(connection));\n+\n+    checkAwards(config, connection, plugin);\n+    return next();\n+};\n+\n+exports.karma_onMailFrom = function (next, connection, params) {\n+    var plugin = this;\n+    var config = this.config.get('karma.ini');\n+\n+    checkSpammyTLD(params[0], config, connection, plugin);\n+    checkSyntaxMailFrom(connection, plugin);\n+\n+    checkAwards(config, connection, plugin);\n+    connection.loginfo(plugin, karmaSummary(connection));\n+    return next();\n+};\n+\n+exports.karma_onRcptTo = function (next, connection, params) {\n+    var plugin = this;\n+    var rcpt = params[0];\n+    var config = this.config.get('karma.ini');\n+\n+    checkSyntaxRcptTo(connection, plugin);\n+    var too_many = checkMaxRecipients(connection, plugin, config);\n+    if (too_many) return next(DENY, too_many);\n+\n+    checkAwards(config, connection, plugin);\n+    connection.loginfo(plugin, karmaSummary(connection));\n+    return next();\n+};\n+\n+exports.karma_onData = function (next, connection) {\n+// cut off bad senders at DATA to prevent transferring the message\n+    var config = this.config.get('karma.ini');\n+    var negative_limit = config.threshhold.negative || -5;\n+    var karma = connection.notes.karma * 1;\n+\n+    if (karma.connection <= negative_limit) {\n+        return next(DENY, \"very bad karma score: \"+karma);\n+    }\n+\n+    checkAwards(config, connection, this);\n+    return next();\n+}\n+\n+exports.karma_onDataPost = function (next, connection) {\n+    connection.transaction.add_header('X-Haraka-Karma',\n+        karmaSummary(connection)\n+    );\n+    var config = this.config.get('karma.ini');\n+    checkAwards(config, connection, this);\n+    return next();\n+}\n+\n+exports.karma_onDisconnect = function (next, connection) {\n+    var plugin = this;\n+    var config = this.config.get('karma.ini');\n+\n+    if ( config.concurrency ) db.incrby('concurrent|'+connection.remote_ip, -1);\n+\n+    var k = connection.notes.karma;\n+    if (!k) { connection.logerror(plugin, \"karma note missing!\"); return next(); };\n+\n+    if (!k.connection) {\n+        connection.loginfo(plugin, \"neutral: \"+karmaSummary(connection));\n+        return next();\n+    };\n+\n+    var key = 'karma|'+connection.remote_ip;\n+    var history = k.history;\n+\n+    if (config.threshhold) {\n+        var pos_lim = config.threshhold.positive || 2;\n+\n+        if (k.connection > pos_lim) {\n+            db.hincrby(key, 'good', 1);\n+            connection.loginfo(plugin, \"positive: \"+karmaSummary(connection));\n+            return next();\n+        };\n+\n+        var bad_limit = config.threshhold.negative || -3;\n+        if (k.connection < bad_limit) {\n+            db.hincrby(key, 'bad', 1);\n+            history--;\n+\n+            if (history <= config.threshhold.history_negative) {\n+                if (history < -5) {\n+                    db.hset(key, 'penalty_start_ts', addDays(Date(), history * -1 ) );\n+                    connection.loginfo(plugin, \"penalty box bonus!: \"+karmaSummary(connection));\n+                }\n+                else {\n+                    db.hset(key, 'penalty_start_ts', Date());\n+                    connection.loginfo(plugin, \"penalty box: \"+karmaSummary(connection));\n+                }\n+                return next();\n+            }\n+        }\n+    };\n+    checkAwards(config, connection, plugin);\n+    connection.loginfo(plugin, \"no action, \"+karmaSummary(connection));\n+    return next();\n+};\n+\n+function karmaSummary(c) {\n+    var k = c.notes.karma;\n+    return '('\n+        +'conn:'+k.connection\n+        +', hist: '+k.history\n+        +(k.penalties.length ? ', penalties: '+k.penalties : '')\n+        +(k.awards.length    ? ', awards: '   +k.awards    : '')\n+        +')';\n+}\n+\n+function addDays(date, days) {\n+    var result = new Date(date);\n+    result.setDate(date.getDate() + days);\n+    return result;\n+}\n+\n+function checkAwards (config, connection, plugin) {\n+    if (!connection.notes.karma) return;\n+    if (!connection.notes.karma.todo) return;\n+    if (!plugin) plugin = connection;\n+    var awards = config.awards;\n+\n+    Object.keys(connection.notes.karma.todo).forEach(function(key) {\n+        var e = key.split('@').slice(0,2);\n+        var suffix = e[0];\n+        var wants = e[1];\n+\n+        // assemble the object path using the note name\n+        var note = assembleNoteObj(connection, suffix);","path":"plugins/karma.js","position":252,"original_position":252,"commit_id":"322e66676192efd921f3b08ca628e311d2a417ed","original_commit_id":"322e66676192efd921f3b08ca628e311d2a417ed","user":{"login":"smfreegard","id":550490,"avatar_url":"https://gravatar.com/avatar/8e7e8a204729c1d9acb3dd4fa9855bdd?d=https%3A%2F%2Fidenticons.github.com%2F6c44deca6b4f4c8f517cd61dd05457a9.png&r=x","gravatar_id":"8e7e8a204729c1d9acb3dd4fa9855bdd","url":"https://api.github.com/users/smfreegard","html_url":"https://github.com/smfreegard","followers_url":"https://api.github.com/users/smfreegard/followers","following_url":"https://api.github.com/users/smfreegard/following{/other_user}","gists_url":"https://api.github.com/users/smfreegard/gists{/gist_id}","starred_url":"https://api.github.com/users/smfreegard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/smfreegard/subscriptions","organizations_url":"https://api.github.com/users/smfreegard/orgs","repos_url":"https://api.github.com/users/smfreegard/repos","events_url":"https://api.github.com/users/smfreegard/events{/privacy}","received_events_url":"https://api.github.com/users/smfreegard/received_events","type":"User","site_admin":false},"body":"I can see a problem with this section.   What happens if you legitimately want to have:\r\n\r\nconnection.notes.foo = 'bar';\r\n*and*\r\nconnection.transaction.notes.foo = 'baz';\r\n\r\nThen this function will only ever check connection.notes.foo and get 'bar'.","created_at":"2014-01-20T17:20:51Z","updated_at":"2014-01-20T17:20:51Z","html_url":"https://github.com/baudehlo/Haraka/pull/418#discussion_r9011562","pull_request_url":"https://api.github.com/repos/baudehlo/Haraka/pulls/418","_links":{"self":{"href":"https://api.github.com/repos/baudehlo/Haraka/pulls/comments/9011562"},"html":{"href":"https://github.com/baudehlo/Haraka/pull/418#discussion_r9011562"},"pull_request":{"href":"https://api.github.com/repos/baudehlo/Haraka/pulls/418"}}}}