From b5574d3b53af4f45401bea7236a7d75625f56595 Mon Sep 17 00:00:00 2001 From: Darren Date: Tue, 21 Jan 2014 23:01:18 +0000 Subject: [PATCH] Working proxy sockets --- proxytest.js | 4 + server/irc/connection.js | 7 + server/irc/state.js | 2 +- server/proxy.js | 330 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 proxytest.js create mode 100644 server/proxy.js diff --git a/proxytest.js b/proxytest.js new file mode 100644 index 0000000..09687ad --- /dev/null +++ b/proxytest.js @@ -0,0 +1,4 @@ +var Proxy = require('./server/proxy'); + +var serv = new Proxy.ProxyServer(); +serv.listen(7779, '127.0.0.1'); \ No newline at end of file diff --git a/server/irc/connection.js b/server/irc/connection.js index 06d4e2f..c035ecb 100644 --- a/server/irc/connection.js +++ b/server/irc/connection.js @@ -10,6 +10,7 @@ var net = require('net'), IrcUser = require('./user.js'), EE = require('../ee.js'), iconv = require('iconv-lite'), + Proxy = require('../proxy.js'), Socks; @@ -208,6 +209,12 @@ IrcConnection.prototype.connect = function () { localAddress: outgoing }); + } else if (true || that.proxy) { + that.socket = new Proxy.ProxySocket(7779, '127.0.0.1', { + username: that.username, + }); + that.socket.connect(that.irc_host.port, host); + } else { // No socks connection, connect directly to the IRCd diff --git a/server/irc/state.js b/server/irc/state.js index 7a0d385..7108d14 100755 --- a/server/irc/state.js +++ b/server/irc/state.js @@ -66,7 +66,7 @@ State.prototype.connect = function (hostname, port, ssl, nick, user, options, ca con.on('error', function IrcConnectionError(err) { console.log('irc_connection error (' + hostname + '):', err); - return callback(err.code); + return callback(err.message); }); con.on('close', function IrcConnectionClose() { diff --git a/server/proxy.js b/server/proxy.js new file mode 100644 index 0000000..a7e002f --- /dev/null +++ b/server/proxy.js @@ -0,0 +1,330 @@ +var stream = require('stream'), + util = require('util'), + net = require("net"), + tls = require("tls"), + Identd = require('./identd'); + + +module.exports = { + ProxyServer: ProxyServer, + ProxySocket: ProxySocket +}; + +function debug() { + console.log.apply(console, arguments); +} + +// Socket connection responses +var RESPONSE_ERROR = '0'; +var RESPONSE_OK = '1'; +var RESPONSE_ECONNRESET = '2'; +var RESPONSE_ECONNREFUSED = '3'; +var RESPONSE_ENOTFOUND = '4'; +var RESPONSE_ETIMEDOUT = '5'; + + + +/** + * ProxyServer + * Listens for connections from a kiwi server, dispatching ProxyPipe + * instances for each connection + */ +function ProxyServer() {} + + +ProxyServer.prototype.listen = function(listen_port, listen_addr) { + var that = this; + + // Username lookup function for the identd + var identdResolveUser = function(port_here, port_there, callback) { + var key = port_here.toString() + '_' + port_there.toString(); + + global.data.get(key, function(err, val) { + callback(val); + }); + }; + /* + this.identd_server = new Identd({ + bind_addr: global.config.identd.address, + bind_port: global.config.identd.port, + user_id: identdResolveUser + }); + + this.identd_server.start(); + */ + // Start listening for proxy connections connections + this.server = new net.Server(); + this.server.listen(listen_port, listen_addr); + + this.server.on('connection', function(socket) { + new ProxyPipe(socket); + }); +}; + + +ProxyServer.prototype.close = function(callback) { + if (this.server) { + return this.server.close(callback); + } + + if (typeof callback === 'function') + callback(); +}; + + + + +/** + * ProxyPipe + * Takes connections from a kiwi server, then: + * 1. Reads its meta data such as username for identd lookups + * 2. Make the connection to the IRC server + * 3. Reply to the kiwi server with connection status + * 4. If all ok, pipe data between the 2 sockets as a proxy + */ +function ProxyPipe(kiwi_socket) { + this.kiwi_socket = kiwi_socket; + this.irc_socket = null; + this.buffer = ''; + this.meta = null; + + kiwi_socket.on('data', this.kiwiSocketOnData.bind(this)); +} + + +ProxyPipe.prototype.destroy = function() { + this.buffer = null; + this.meta = null; + + if (this.irc_socket) { + this.irc_socket.destroy(); + this.irc_socket.removeAllListeners(); + this.irc_socket = null; + } + + if (this.kiwi_socket) { + this.kiwi_socket.destroy(); + this.kiwi_socket.removeAllListeners(); + this.kiwi_socket = null; + } +}; + + +ProxyPipe.prototype.kiwiSocketOnData = function(data) { + var meta; + + this.buffer += data.toString(); + + // Not got a complete line yet? Wait some more + if (this.buffer.substr(-1) !== '\n') + return; + + try { + meta = JSON.parse(this.buffer); + } catch (err) { + this.destroy(); + return; + } + + if (!meta.username) { + this.destroy(); + return; + } + + this.buffer = ''; + this.meta = meta; + this.kiwi_socket.removeAllListeners('data'); + + this.makeIrcConnection(); +}; + + +ProxyPipe.prototype.makeIrcConnection = function() { + debug('[KiwiProxy] Proxied connection to: ' + this.meta.host + ':' + this.meta.port.toString()); + this.irc_socket = this.meta.ssl ? + tls.connect(parseInt(this.meta.port, 10), this.meta.host, this._onSocketConnect.bind(this)) : + net.connect(parseInt(this.meta.port, 10), this.meta.host, this._onSocketConnect.bind(this)); + + this.irc_socket.setTimeout(10000); + this.irc_socket.on('error', this._onSocketError.bind(this)); + this.irc_socket.on('timeout', this._onSocketTimeout.bind(this)); +}; + + +ProxyPipe.prototype._onSocketConnect = function() { + // Socket has connected, return no error to the kiwi server + debug('[KiwiProxy] ProxyPipe::_onSocketConnect()'); + this.kiwi_socket.write(new Buffer(RESPONSE_OK.toString()), this.startPiping.bind(this)); +}; + + +ProxyPipe.prototype._onSocketError = function(err) { + var replies = { + ECONNRESET: RESPONSE_ECONNRESET, + ECONNREFUSED: RESPONSE_ECONNREFUSED, + ENOTFOUND: RESPONSE_ENOTFOUND, + ETIMEDOUT: RESPONSE_ETIMEDOUT + }; + debug('[KiwiProxy] IRC Error ' + err.code); + this.kiwi_socket.write(new Buffer((replies[err.code] || RESPONSE_ERROR).toString()), 'UTF8', this.destroy.bind(this)); +}; + + +ProxyPipe.prototype._onSocketTimeout = function() { + this.has_timed_out = true; + debug('[KiwiProxy] IRC Timeout'); + this.irc_socket.destroy(); + this.kiwi_socket.write(new Buffer(RESPONSE_ETIMEDOUT.toString()), 'UTF8', this.destroy.bind(this)); +}; + + +ProxyPipe.prototype.startPiping = function() { + debug('[KiwiProxy] ProxyPipe::startPiping()'); + this.kiwi_socket.pipe(this.irc_socket); + this.irc_socket.pipe(this.kiwi_socket); +}; + + + + + +/** + * ProxySocket + * Transparent socket interface to a kiwi proxy + */ +function ProxySocket(proxy_port, proxy_addr, meta) { + stream.Duplex.call(this); + + this.connected_fn = null; + this.proxy_addr = proxy_addr; + this.proxy_port = proxy_port; + this.meta = meta || {}; + + this.state = 'disconnected'; +} + +util.inherits(ProxySocket, stream.Duplex); + + +ProxySocket.prototype.setMeta = function(meta) { + this.meta = meta; +}; + + +ProxySocket.prototype.connect = function(dest_port, dest_addr, connected_fn) { + this.meta.host = dest_addr; + this.meta.port = dest_port; + this.connected_fn = connected_fn; + + if (!this.meta.host || !this.meta.port) { + debug('[KiwiProxy] Invalid destination addr/port', this.meta); + return false; + } + + debug('[KiwiProxy] Connecting to proxy ' + this.proxy_addr + ':' + this.proxy_port.toString()); + this.socket = net.connect(this.proxy_port, this.proxy_addr, this._onSocketConnect.bind(this)); + this.socket.setTimeout(10000); + + this.socket.on('data', this._onSocketData.bind(this)); + this.socket.on('close', this._onSocketClose.bind(this)); + this.socket.on('error', this._onSocketError.bind(this)); + + return this; +}; + + +ProxySocket.prototype.destroySocket = function() { + if (!this.socket) + return; + + this.socket.removeAllListeners(); + this.socket.destroy(); + delete this.socket; +}; + + +ProxySocket.prototype._read = function() { + var data; + + if (this.state === 'connected' && this.socket) { + while ((data = this.socket.read()) !== null) { + if ((this.push(data)) === false) { + break; + } + } + } else { + this.push(''); + } +}; + + +ProxySocket.prototype._write = function(chunk, encoding, callback) { + if (this.state === 'connected' && this.socket) { + return this.socket.write(chunk, encoding, callback); + } else { + callback("Not connected"); + } +}; + + +ProxySocket.prototype._onSocketConnect = function() { + var meta = this.meta || {}; + + this.state = 'handshaking'; + + debug('[KiwiProxy] Connected to proxy, sending meta'); + this.socket.write(JSON.stringify(meta) + '\n'); +}; + + +ProxySocket.prototype._onSocketData = function(data) { + if (this.state === 'connected') { + this.emit('data', data); + return; + } + + var buffer_str = data.toString(), + status = buffer_str[0], + error_code, + error_codes = {}; + + error_codes[RESPONSE_ERROR] = 'ERROR'; + error_codes[RESPONSE_ECONNRESET] = 'ECONNRESET'; + error_codes[RESPONSE_ECONNREFUSED] = 'ECONNREFUSED'; + error_codes[RESPONSE_ENOTFOUND] = 'ENOTFOUND'; + error_codes[RESPONSE_ETIMEDOUT] = 'ETIMEDOUT'; + + debug('[KiwiProxy] Recieved socket status: ' + data.toString()); + if (status === RESPONSE_OK) { + debug('[KiwiProxy] Remote socket connected OK'); + this.state = 'connected'; + + if (typeof this.connected_fn === 'function') + connected_fn(); + + this.emit('connect'); + + } else { + this.destroySocket(); + + error_code = error_codes[status] || error_codes[RESPONSE_ERROR]; + debug('[KiwiProxy] Error: ' + error_code); + this.emit('error', new Error(error_code)); + } +}; + + +ProxySocket.prototype._onSocketClose = function(had_error) { + if (this.state === 'connected') { + this.emit('close', had_error); + return; + } + + if (!this.ignore_close) + this.emit('error', new Error(RESPONSE_ERROR)); +}; + + +ProxySocket.prototype._onSocketError = function(err) { + this.emit('error', err); +}; \ No newline at end of file -- 2.25.1