Mostly working SOCKS client
authorJack Allnutt <m2ys4u@gmail.com>
Wed, 6 Mar 2013 23:53:21 +0000 (23:53 +0000)
committerJack Allnutt <m2ys4u@gmail.com>
Wed, 6 Mar 2013 23:53:21 +0000 (23:53 +0000)
Not plumbed in to anything yet, though

package.json
server/socks.js [new file with mode: 0755]

index 09de3f199ae84435ca5d76f224f1be3703f4fa52..4e643305d0bc2cabcf40a5b3e39229c15f8a3f1d 100644 (file)
@@ -19,6 +19,7 @@
     "range_check": "0.0.1",\r
     "lodash": "0.9.1",\r
     "daemonize2": "0.4.0-rc.5",\r
-    "eventemitter2": "0.4.11"\r
+    "eventemitter2": "0.4.11",\r
+    "ipaddr.js": "0.1.1"\r
   }\r
 }\r
diff --git a/server/socks.js b/server/socks.js
new file mode 100755 (executable)
index 0000000..25ad4bb
--- /dev/null
@@ -0,0 +1,150 @@
+var net             = require('net'),
+    tls             = require('tls'),
+    util            = require('util'),
+    EventEmitter    = require('events').EventEmitter,
+    ipaddr          = require('ipaddr.js');
+    
+var SocksConnection = function (destination, socks) {
+    var that = this;
+    EventEmitter.call(this);
+    
+    this.socks = socks;
+    this.destination = destination;
+    
+    this.socksSocket = net.connect({host: socks.host, port: socks.port}, socksConnected.bind(this));
+    this.socksSocket.once('data', socksAuth.bind(this));
+};
+
+util.inherits(SocksConnection, EventEmitter);
+
+var socksConnected = function () {
+    if (this.socks.auth) {
+        this.socksSocket.write('\x05\x02\x02\x00'); // SOCKS version 5, supporting two auth methods
+                                                    // username/password and 'no authentication'
+    } else {
+        this.socksSocket.write('\x05\x01\x00');     // SOCKS version 5, only supporting 'no auth' scheme
+    }
+};
+
+var socksAuth = function (data) {
+    var bufs = [];
+    switch (data.readUInt8(1)) {
+    case 255:
+        this.emit('error', 'SOCKS: No acceptable authentication methods');
+        this.socksSocket.destroy();
+        break;
+    case 2:
+        bufs[0] = new Buffer([1]);
+        bufs[1] = new Buffer([Buffer.byteLength(this.socks.auth.user)]);
+        bufs[2] = new Buffer(this.socks.auth.user);
+        bufs[3] = new Buffer([Buffer.byteLength(this.socks.auth.pass)]);
+        bufs[4] = new Buffer(this.socks.auth.pass);
+        this.socksSocket.write(Buffer.concat(bufs));
+        this.socksSocket.once('data', socksAuthStatus.bind(this));
+        break;
+    default:
+        socksRequest.call(this, this.destination.host, this.destination.port);
+    }
+};
+
+var socksAuthStatus = function (data) {
+    if (data.readUInt8(1) === 1) {
+        socksRequest.call(this, this.destination.host, this.destination.port);
+    } else {
+        this.emit('error', 'SOCKS: Authentication failed');
+        this.socksSocket.destroy();
+    }
+};
+
+var socksRequest = function (host, port) {
+    var header, type, hostBuf, portBuf;
+    if (net.isIP(host)) {
+        if (net.isIPv4(host)) {
+            type = new Buffer([1]);
+        } else if (net.isIPv6(host)) {
+            type = new Buffer([4]);
+        }
+        host = new Buffer(ipaddr.parse(host).toByteArray());
+    } else {
+        type = new Buffer([3]);
+        hostBuf = new Buffer(host);
+        hostBuf = Buffer.concat([new Buffer([Buffer.byteLength(host)]), hostBuf]);
+    }
+    header = new Buffer([5, 1, 0]);
+    portBuf = new Buffer(2);
+    portBuf.writeUInt16BE(port, 0);
+    this.socksSocket.write(Buffer.concat([header, type, hostBuf, portBuf]));
+    this.socksSocket.once('data', socksReply.bind(this));
+};
+
+var socksReply = function (data) {
+    var err, port, i, addr_len, addr = '';
+    var status = data.readUInt8(1);
+    if (status === 0) {
+        switch (data.readUInt8(3)) {
+        case 1:
+            for (i = 0; i < 4; i++) {
+                if (i !== 0) {
+                    addr += '.';
+                }
+                addr += data.readUInt8(4 + i);
+            }
+            port = data.readUInt16BE(8);
+            break;
+        case 4:
+            for (i = 0; i < 16; i++) {
+                if (i !== 0) {
+                    addr += ':';
+                }
+                addr += data.readUInt8(4 + i);
+            }
+            port = data.readUInt16BE(20);
+            break;
+        case 3:
+            addr_len = data.readUInt8(4);
+            addr = (data.slice(5, 5 + addr_len)).toString();
+            port = data.readUInt16BE(5 + addr_len);
+        }
+        this.socksAddress = addr;
+        this.socksPort = port;
+        
+        emitSocket.call(this);
+        
+    } else {
+        switch (status) {
+        case 1:
+            err = 'SOCKS: general SOCKS server failure';
+            break;
+        case 2:
+            err = 'SOCKS: Connection not allowed by ruleset';
+            break;
+        case 3:
+            err = 'SOCKS: Network unreachable';
+            break;
+        case 4:
+            err = 'SOCKS: Host unreachable';
+            break;
+        case 5:
+            err = 'SOCKS: Connection refused';
+            break;
+        case 6:
+            err = 'SOCKS: TTL expired';
+            break;
+        case 7:
+            err = 'SOCKS: Command not supported';
+            break;
+        case 8:
+            err = 'SOCKS: Address type not supported';
+            break;
+        default:
+            err = 'SOCKS: Unknown error';
+        }
+        this.emit('error', err);
+    }
+};
+
+var emitSocket = function () {
+    var that = this;
+    this.socksSocket.setEncoding('utf8');
+    this.emit('socksConnect', this.socksSocket);
+};
\ No newline at end of file