Refactor to use the new streams API
authorJack Allnutt <m2ys4u@gmail.com>
Mon, 1 Apr 2013 05:24:12 +0000 (05:24 +0000)
committerJack Allnutt <m2ys4u@gmail.com>
Mon, 1 Apr 2013 05:35:37 +0000 (05:35 +0000)
SocksConnection will no longer emit the socket along with the 'connect' event.

SocksConnection is now a DuplexStream and can be read/written from/to.

SSL handling no longer manually creates a SecurePair; Uses native starttls support in tls.connect.

As it now depends on the new streams API, add the node engine restriction in package.json

config.example.js
package.json
server/irc/connection.js
server/socks.js

index 06d077d899d0165e397d0d3c657862f8a69986e8..5eb1fee548a72f4ddd0dbef0cd1d73665c7d8b53 100644 (file)
@@ -105,7 +105,7 @@ conf.http_base_path = "/kiwi";
 
 
 /*
- * SOCKS proxy settings
+ * SOCKS (version 5) proxy settings
  */
 conf.socks_proxy = {};
 
index 4e643305d0bc2cabcf40a5b3e39229c15f8a3f1d..8bcf245db9c25396ec6725bcf13a8058aa8bd61f 100644 (file)
@@ -22,4 +22,7 @@
     "eventemitter2": "0.4.11",\r
     "ipaddr.js": "0.1.1"\r
   }\r
+  "engines": {\r
+    "node": ">=0.10.0"\r
+  }\r
 }\r
index c48346b7b4e4ca658a3713e0040c0890c8377ba6..45605535d7651b7a1d72d8fc24e1bfeef2000fd0 100644 (file)
@@ -7,7 +7,7 @@ var net             = require('net'),
     IrcServer       = require('./server.js'),
     IrcChannel      = require('./channel.js'),
     IrcUser         = require('./user.js'),
-    SocksConnection = require('../socks.js');
+    Socks           = require('../socks.js');
 
 
 var IrcConnection = function (hostname, port, ssl, nick, user, pass, state) {
@@ -122,54 +122,37 @@ IrcConnection.prototype.connect = function () {
 
     // Are we connecting through a SOCKS proxy?
     if (this.socks) {
-        socks = new SocksConnection({
+        this.socket = Socks.connect({
             host: this.irc_host.hostname, 
             port: this.irc_host.port,
-            ssl: this.ssl
+            ssl: this.ssl,
+            rejectUnauthorized: global.config.reject_unauthorised_certificates
         }, {host: this.socks.host,
             port: this.socks.port,
             user: this.socks.user,
             pass: this.socks.pass
         });
         
-        socks.on('connect', function (socket) {
-            that.socket = socket;
-            setupSocket.call(that);
-            that.connected = true;
-            socketConnectHandler.call(that);
+    } else if (this.ssl) {
+        this.socket = tls.connect({
+            host: this.irc_host.hostname,
+            port: this.irc_host.port,
+            rejectUnauthorized: global.config.reject_unauthorised_certificates
         });
-    } else {
-        // Open either a secure or plain text socket
-        if (this.ssl) {
-            this.socket = tls.connect({
-                host: this.irc_host.hostname,
-                port: this.irc_host.port,
-                rejectUnauthorized: global.config.reject_unauthorised_certificates
-            });
-
-            socket_connect_event_name = 'secureConnect';
 
-        } else {
-            this.socket = net.connect({
-                host: this.irc_host.hostname,
-                port: this.irc_host.port
-            });
-        }
+        socket_connect_event_name = 'secureConnect';
 
-        this.socket.on(socket_connect_event_name, function () {
-            that.connected = true;
-            socketConnectHandler.call(that);
+    } else {
+        this.socket = net.connect({
+            host: this.irc_host.hostname,
+            port: this.irc_host.port
         });
-        
-        setupSocket.call(this);
     }
-};
-
-/**
- * Set the socket's encoding and add event handlers
- */
-var setupSocket = function () {
-    var that = this;
+    
+    this.socket.on(socket_connect_event_name, function () {
+        that.connected = true;
+        socketConnectHandler.call(that);
+    });
     
     this.socket.setEncoding('utf-8');
     
index c1b0047b15ca640a0eba5d8372496e3116723925..5cdce47dc0a4334b8e170777e4bc2f5c6f34cae1 100755 (executable)
@@ -1,46 +1,65 @@
-var net             = require('net'),
-    tls             = require('tls'),
-    util            = require('util'),
-    EventEmitter    = require('events').EventEmitter,
-    crypto          = require('crypto'),
-    ipaddr          = require('ipaddr.js');
-    
-/*
- * API:
- * var s = new SocksConnection({host: irc.example.com, port: 6667, ssl: false}, {host: socks.example.net, port: 1080, user: null, pass: null});
- * s.on('connect', function (socket) {
- *     // send data to socket
- * });
- */
+var stream  = require('stream'),
+    util    = require('util'),
+    net     = require('net'),
+    tls     = require('tls'),
+    _       = require('lodash'),
+    ipaddr  = require('ipaddr.js');
 
-var SocksConnection = function (destination, socks) {
+var SocksConnection = function (remote_options, socks_options) {
     var that = this;
-    EventEmitter.call(this);
+    stream.Duplex.call(this);
     
-    this.remoteAddress = destination.host;
-    this.remotePort = destination.port;
-    this.ssl = destination.ssl;
+    this.remote_options = _.defaults(remote_options, {
+        host: 'localhost',
+        ssl: false,
+        rejectUnauthorized: false
+    });
+    socks_options = _.defaults(socks_options, {
+        host: 'localhost',
+        port: 1080,
+        user: null,
+        pass: null
+    });
     
     this.socksAddress = null;
     this.socksPort = null;
     
-    this.socksSocket = net.connect({host: socks.host, port: socks.port}, socksConnected.bind(this, !(!socks.user)));
-    this.socksSocket.once('data', socksAuth.bind(this, {user: socks.user || null, pass: socks.pass || null}));
-    this.socksSocket.on('error', this._error);
+    this.socksSocket = net.connect({host: socks_options.host, port: socks_options.port}, socksConnected.bind(this, !(!socks_options.user)));
+    this.socksSocket.once('data', socksAuth.bind(this, {user: socks_options.user, pass: socks_options.pass}));
+    this.socksSocket.on('error', function (err) {
+        that.emit('error', err);
+    });
+    
+    this.outSocket = this.socksSocket;
 };
 
-util.inherits(SocksConnection, EventEmitter);
+util.inherits(SocksConnection, stream.Duplex);
 
-module.exports = SocksConnection;
+SocksConnection.connect = function (remote_options, socks_options, connection_listener) {
+    var socks_connection = new SocksConnection(remote_options, socks_options);
+    if (typeof connection_listener === 'Function') {
+        socks_connection.on('connect', connection_listener);
+    }
+    return socks_connection;
+};
+
+SocksConnection.prototype._read = function () {
+    this.outSocket.resume();
+};
+
+SocksConnection.prototype._write = function (chunk, encoding, callback) {
+    this.outSocket.write(chunk, 'utf8', callback);
+};
 
 SocksConnection.prototype.dispose = function () {
-    this.socksSocket.destroy();
+    this.outSocket.destroy();
+    this.outSocket.removeAllListeners();
+    if (this.outSocket !== this.socksSocket) {
+        this.socksSocket.destroy();
+        this.socksSocket.removeAllListeners();
+    }
     this.removeAllListeners();
-}
-
-SocksConnection.prototype._error = function (err) {
-    this.emit('error', err);
-}
+};
 
 var socksConnected = function (auth) {
     if (auth) {
@@ -68,13 +87,13 @@ var socksAuth = function (auth, data) {
         this.socksSocket.once('data', socksAuthStatus.bind(this));
         break;
     default:
-        socksRequest.call(this, this.remoteAddress, this.remotePort);
+        socksRequest.call(this, this.remote_options.host, this.remote_options.port);
     }
 };
 
 var socksAuthStatus = function (data) {
     if (data.readUInt8(1) === 1) {
-        socksRequest.call(this, this.remoteHost, this.remotePort);
+        socksRequest.call(this, this.remote_options.host, this.remote_options.port);
     } else {
         this.emit('error', 'SOCKS: Authentication failed');
         this.socksSocket.destroy();
@@ -133,7 +152,12 @@ var socksReply = function (data) {
         this.socksAddress = addr;
         this.socksPort = port;
         
-        emitSocket.call(this);
+        if (this.remote_options.ssl) {
+            startTLS.call(this);
+        } else {
+            proxyData.call(this);
+            this.emit('connect');
+        }
         
     } else {
         switch (status) {
@@ -168,28 +192,37 @@ var socksReply = function (data) {
     }
 };
 
-var starttls = function () {
+var startTLS = function () {
     var that = this;
+    var plaintext = tls.connect({
+        socket: this.socksSocket,
+        rejectUnauthorized: this.rejectUnauthorized
+    });
     
-    var pair = tls.createSecurePair(crypto.createCredentials(), false);
-    pair.encrypted.pipe(this.socksSocket);
-    this.socksSocket.pipe(pair.encrypted);
-    
-    pair.cleartext.socket = this.socksSocket;
-    pair.cleartext.encrypted = pair.encrypted;
-    pair.cleartext.authorised = false;
+    plaintext.on('error', function (err) {
+        that.emit('error', err);
+    });
     
-    pair.on('secure', function () { 
-        that.emit('connect', pair.cleartext, this.socksSocket);
-        that.socksSocket.removeListener('error', that._error);
+    plaintext.on('secureConnect', function () {
+        that.emit('connect');
     });
-}
+    this.outSocket = plaintext;
+    proxyData.call(this);
+};
 
-var emitSocket = function () {
-    if (this.ssl) {
-        starttls.call(this);
-    } else {
-        this.emit('connect', this.socksSocket);
-        this.socksSocket.removeListener('error', this._error);
-    }
+var proxyData = function () {
+    var that = this;
+    
+    this.outSocket.on('data', function (data) {
+        var buffer_not_full = that.push(data);
+        if (!buffer_not_full) {
+            this.pause();
+        }
+    });
+    
+    this.outSocket.on('end', function () {
+        that.push(null);
+    });
 };
+
+module.exports = SocksConnection;