      1 // SOURCE: https://gist.github.com/848444
      2
      3 // Target API:
      4 //
      5 //  var s = require('net').createStream(25, 'smtp.example.com');
      6 //  s.on('connect', function() {
      7 //   require('starttls')(s, options, function() {
      8 //      if (!s.authorized) {
      9 //        s.destroy();
     10 //        return;
     11 //      }
     12 //
     13 //      s.end("hello world\n");
     14 //    });
     15 //  });
     16 //
     17 //
     18
     19 /**
     20  * @namespace STARTTLS module
     21  * @name starttls
     22  */
     23 module.exports.starttls = starttls;
     24
     25 /**
     26  * <p>Upgrades a socket to a secure TLS connection</p>
     27  *
     28  * @memberOf starttls
     29  * @param {Object} socket Plaintext socket to be upgraded
     30  * @param {Object} options Certificate data for the server
     31  * @param {Function} callback Callback function to be run after upgrade
     32  */
     33 function starttls(socket, options, callback) {
     34     var sslcontext, pair, cleartext;
     35
     36     socket.removeAllListeners("data");
     37     sslcontext = require('crypto').createCredentials(options);
     38     pair = require('tls').createSecurePair(sslcontext, true);
     39     cleartext = pipe(pair, socket);
     40
     41     pair.on('secure', function() {
     42         var verifyError = (pair._ssl || pair.ssl).verifyError();
     43
     44         if (verifyError) {
     45             cleartext.authorized = false;
     46             cleartext.authorizationError = verifyError;
     47         } else {
     48             cleartext.authorized = true;
     49         }
     50
     51         callback(cleartext);
     52     });
     53
     54     cleartext._controlReleased = true;
     55     return pair;
     56 }
     57
     58 function forwardEvents(events, emitterSource, emitterDestination) {
     59     var map = [], name, handler;
     60
     61     for(var i = 0, len = events.length; i < len; i++) {
     62         name = events[i];
     63
     64         handler = forwardEvent.bind(emitterDestination, name);
     65
     66         map.push(name);
     67         emitterSource.on(name, handler);
     68     }
     69
     70     return map;
     71 }
     72
     73 function forwardEvent() {
     74     this.emit.apply(this, arguments);
     75 }
     76
     77 function removeEvents(map, emitterSource) {
     78     for(var i = 0, len = map.length; i < len; i++){
     79         emitterSource.removeAllListeners(map[i]);
     80     }
     81 }
     82
     83 function pipe(pair, socket) {
     84     pair.encrypted.pipe(socket);
     85     socket.pipe(pair.encrypted);
     86
     87     pair.fd = socket.fd;
     88
     89     var cleartext = pair.cleartext;
     90
     91     cleartext.socket = socket;
     92     cleartext.encrypted = pair.encrypted;
     93     cleartext.authorized = false;
     94
     95     function onerror(e) {
     96         if (cleartext._controlReleased) {
     97             cleartext.emit('error', e);
     98         }
     99     }
    100
    101     var map = forwardEvents(["timeout", "end", "close", "drain", "error"], socket, cleartext);
    102
    103     function onclose() {
    104         socket.removeListener('error', onerror);
    105         socket.removeListener('close', onclose);
    106         removeEvents(map,socket);
    107     }
    108
    109     socket.on('error', onerror);
    110     socket.on('close', onclose);
    111
    112     return cleartext;
    113 }