Guest User

Untitled

a guest
Dec 6th, 2017
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Base authentication plugin.
  2. // This cannot be used on its own. You need to inherit from it.
  3. // See plugins/auth/flat_file.js for an example.
  4.  
  5. var crypto = require('crypto');
  6. var utils = require('haraka-utils');
  7. var AUTH_COMMAND = 'AUTH';
  8. var AUTH_METHOD_CRAM_MD5 = 'CRAM-MD5';
  9. var AUTH_METHOD_PLAIN = 'PLAIN';
  10. var AUTH_METHOD_LOGIN = 'LOGIN';
  11. var LOGIN_STRING1 = 'VXNlcm5hbWU6'; //UserLogin: base64 coded
  12. var LOGIN_STRING2 = 'UGFzc3dvcmQ6'; //Password: base64 coded
  13.  
  14. exports.hook_capabilities = function (next, connection) {
  15.     // Don't offer AUTH capabilities unless session is encrypted
  16. //    if (!connection.tls.enabled) { return next(); }
  17.  
  18.     var methods = [ 'PLAIN', 'LOGIN', 'CRAM-MD5' ];
  19.     connection.capabilities.push('AUTH ' + methods.join(' '));
  20.     connection.notes.allowed_auth_methods = methods;
  21.     next();
  22. };
  23.  
  24. // Override this at a minimum. Run cb(passwd) to provide a password.
  25. exports.get_plain_passwd = function (user, connection, cb) {
  26.     return cb();
  27. };
  28.  
  29. exports.hook_unrecognized_command = function (next, connection, params) {
  30.     var plugin = this;
  31.     if (params[0].toUpperCase() === AUTH_COMMAND && params[1]) {
  32.         return plugin.select_auth_method(next, connection,
  33.                 params.slice(1).join(' '));
  34.     }
  35.     if (!connection.notes.authenticating) { return next(); }
  36.  
  37.     var am = connection.notes.auth_method;
  38.     if (am === AUTH_METHOD_CRAM_MD5 && connection.notes.auth_ticket) {
  39.         return plugin.auth_cram_md5(next, connection, params);
  40.     }
  41.     if (am === AUTH_METHOD_LOGIN) {
  42.         return plugin.auth_login(next, connection, params);
  43.     }
  44.     if (am === AUTH_METHOD_PLAIN) {
  45.         return plugin.auth_plain(next, connection, params);
  46.     }
  47.     return next();
  48. };
  49.  
  50. exports.check_plain_passwd = function (connection, user, passwd, cb) {
  51.     var callback = function (plain_pw) {
  52.         if (plain_pw === null  ) { return cb(false); }
  53.         if (plain_pw !== passwd) { return cb(false); }
  54.         return cb(true);
  55.     }
  56.     if (this.get_plain_passwd.length == 2) {
  57.         this.get_plain_passwd(user, callback);
  58.     }
  59.     else if (this.get_plain_passwd.length == 3) {
  60.         this.get_plain_passwd(user, connection, callback);
  61.     }
  62.     else {
  63.         throw "Invalid number of arguments for get_plain_passwd";
  64.     }
  65. };
  66.  
  67. exports.check_cram_md5_passwd = function (connection, user, passwd, cb) {
  68.     var callback = function (plain_pw) {
  69.         if (plain_pw == null) {
  70.             return cb(false);
  71.         }
  72.  
  73.         var hmac = crypto.createHmac('md5', plain_pw.toString());
  74.         hmac.update(connection.notes.auth_ticket);
  75.  
  76.         if (hmac.digest('hex') === passwd) {
  77.             return cb(true);
  78.         }
  79.         return cb(false);
  80.     };
  81.     if (this.get_plain_passwd.length == 2) {
  82.         this.get_plain_passwd(user, callback);
  83.     }
  84.     else if (this.get_plain_passwd.length == 3) {
  85.         this.get_plain_passwd(user, connection, callback);
  86.     }
  87.     else {
  88.         throw "Invalid number of arguments for get_plain_passwd";
  89.     }
  90. };
  91.  
  92. exports.check_user = function (next, connection, credentials, method) {
  93.     var plugin = this;
  94.     connection.notes.authenticating = false;
  95.     if (!(credentials[0] && credentials[1])) {
  96.         connection.respond(504, "Invalid AUTH string", function () {
  97.             connection.reset_transaction(function () {
  98.                 return next(OK);
  99.             });
  100.         });
  101.         return;
  102.     }
  103.  
  104.     var passwd_ok = function (valid, message) {
  105.         if (valid) {
  106.             connection.relaying = true;
  107.             connection.results.add({name:'relay'}, {pass: plugin.name});
  108.             connection.results.add({name:'auth'}, {
  109.                 pass: plugin.name,
  110.                 method: method,
  111.                 user: credentials[0],
  112.             });
  113.             connection.respond(235, ((message) ? message : "Authentication successful"), function () {
  114.                 connection.authheader = "(authenticated bits=0)\n";
  115.                 connection.auth_results('auth=pass (' +
  116.                             method.toLowerCase() + ')' );
  117.                 connection.notes.auth_user = credentials[0];
  118.                 connection.notes.auth_passwd = credentials[1];
  119.                 return next(OK);
  120.             });
  121.             return;
  122.         }
  123.  
  124.         if (!connection.notes.auth_fails) {
  125.             connection.notes.auth_fails = 0;
  126.         }
  127.         connection.notes.auth_fails++;
  128.         connection.results.add({name: 'auth'}, {
  129.             fail: plugin.name + '/' + method,
  130.         });
  131.  
  132.         connection.notes.auth_login_userlogin = null;
  133.         connection.notes.auth_login_asked_login = false;
  134.  
  135.         var delay = Math.pow(2, connection.notes.auth_fails - 1);
  136.         if (plugin.timeout && delay >= plugin.timeout) {
  137.             delay = plugin.timeout - 1;
  138.         }
  139.         connection.lognotice(plugin, 'delaying for ' + delay + ' seconds');
  140.         // here we include the username, as shown in RFC 5451 example
  141.         connection.auth_results('auth=fail (' + method.toLowerCase() +
  142.                     ') smtp.auth='+ credentials[0]);
  143.         setTimeout(function () {
  144.             connection.respond(535, ((message) ? message : "Authentication failed"), function () {
  145.                 connection.reset_transaction(function () {
  146.                     return next(OK);
  147.                 });
  148.             });
  149.         }, delay * 1000);
  150.     };
  151.  
  152.     if (method === AUTH_METHOD_PLAIN || method === AUTH_METHOD_LOGIN) {
  153.         plugin.check_plain_passwd(connection, credentials[0], credentials[1],
  154.                 passwd_ok);
  155.     }
  156.     else if (method === AUTH_METHOD_CRAM_MD5) {
  157.         plugin.check_cram_md5_passwd(connection, credentials[0], credentials[1],
  158.                 passwd_ok);
  159.     }
  160. };
  161.  
  162. exports.select_auth_method = function(next, connection, method) {
  163.     var split = method.split(/\s+/);
  164.     method = split.shift().toUpperCase();
  165.     if (!connection.notes.allowed_auth_methods) return next();
  166.     if (connection.notes.allowed_auth_methods.indexOf(method) === -1) {
  167.         return next();
  168.     }
  169.  
  170.     connection.notes.authenticating = true;
  171.     connection.notes.auth_method = method;
  172.  
  173.     if (method === AUTH_METHOD_PLAIN) {
  174.         return this.auth_plain(next, connection, split);
  175.     }
  176.     if (method === AUTH_METHOD_LOGIN) {
  177.         return this.auth_login(next, connection, split);
  178.     }
  179.     if (method === AUTH_METHOD_CRAM_MD5) {
  180.         return this.auth_cram_md5(next, connection);
  181.     }
  182. };
  183.  
  184. exports.auth_plain = function(next, connection, params) {
  185.     var plugin = this;
  186.     // one parameter given on line, either:
  187.     //    AUTH PLAIN <param> or
  188.     //    AUTH PLAIN\n
  189.     //...
  190.     //    <param>
  191.     if (params[0]) {
  192.         var credentials = utils.unbase64(params[0]).split(/\0/);
  193.         credentials.shift();  // Discard authid
  194.         return plugin.check_user(next, connection, credentials, AUTH_METHOD_PLAIN);
  195.     } else {
  196.         if (connection.notes.auth_plain_asked_login) {
  197.             return next(DENYDISCONNECT, 'bad protocol');
  198.         } else {
  199.             connection.respond(334, ' ', function () {
  200.                 connection.notes.auth_plain_asked_login = true;
  201.                 return next(OK);
  202.             });
  203.             return;
  204.         }
  205.     }
  206. };
  207.  
  208. exports.auth_login = function(next, connection, params) {
  209.     var plugin = this;
  210.     if ((!connection.notes.auth_login_asked_login && params[0]) ||
  211.         ( connection.notes.auth_login_asked_login &&
  212.          !connection.notes.auth_login_userlogin))
  213.     {
  214.         if (!params[0]){
  215.             return next(DENYDISCONNECT, 'bad protocol');
  216.         }
  217.  
  218.         var login = utils.unbase64(params[0]);
  219.         connection.respond(334, LOGIN_STRING2, function () {
  220.             connection.notes.auth_login_userlogin = login;
  221.             connection.notes.auth_login_asked_login = true;
  222.             return next(OK);
  223.         });
  224.         return;
  225.     }
  226.  
  227.     if (connection.notes.auth_login_userlogin) {
  228.         var credentials = [
  229.             connection.notes.auth_login_userlogin,
  230.             utils.unbase64(params[0])
  231.         ];
  232.         return plugin.check_user(next, connection, credentials,
  233.                 AUTH_METHOD_LOGIN);
  234.     }
  235.  
  236.     connection.respond(334, LOGIN_STRING1, function () {
  237.         connection.notes.auth_login_asked_login = true;
  238.         return next(OK);
  239.     });
  240. };
  241.  
  242. exports.auth_cram_md5 = function(next, connection, params) {
  243.     var plugin = this;
  244.     if (params) {
  245.         var credentials = utils.unbase64(params[0]).split(' ');
  246.         return plugin.check_user(next, connection, credentials,
  247.                 AUTH_METHOD_CRAM_MD5);
  248.     }
  249.  
  250.     var ticket = '<' + plugin.hexi(Math.floor(Math.random() * 1000000)) + '.' +
  251.                 plugin.hexi(Date.now()) + '@' + plugin.config.get('me') + '>';
  252.  
  253.     connection.loginfo(plugin, "ticket: " + ticket);
  254.     connection.respond(334, utils.base64(ticket), function () {
  255.         connection.notes.auth_ticket = ticket;
  256.         return next(OK);
  257.     });
  258. };
  259.  
  260. exports.hexi = function (number) {
  261.     return String(Math.abs(parseInt(number)).toString(16));
  262. };
Add Comment
Please, Sign In to add comment