var stream = require('stream'),
util = require('util'),
- net = require("net"),
- tls = require("tls"),
- Identd = require('./identd');
+ events = require('events'),
+ net = require('net'),
+ tls = require('tls'),
+ fs = require('fs');
module.exports = {
};
function debug() {
- console.log.apply(console, arguments);
+ //console.log.apply(console, arguments);
}
// Socket connection responses
* Listens for connections from a kiwi server, dispatching ProxyPipe
* instances for each connection
*/
-function ProxyServer() {}
+function ProxyServer() {
+ events.EventEmitter.call(this);
+}
+util.inherits(ProxyServer, events.EventEmitter);
-ProxyServer.prototype.listen = function(listen_port, listen_addr) {
- var that = this;
+ProxyServer.prototype.listen = function(listen_port, listen_addr, opts) {
+ var that = this,
+ serv_opts = {},
+ connection_event = 'connection';
- // Username lookup function for the identd
- var identdResolveUser = function(port_here, port_there, callback) {
- var key = port_here.toString() + '_' + port_there.toString();
+ opts = opts || {};
- 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
- });
+ // Listen using SSL?
+ if (opts.ssl) {
+ serv_opts = {
+ key: fs.readFileSync(opts.ssl_key),
+ cert: fs.readFileSync(opts.ssl_cert)
+ };
+
+ // Do we have an intermediate certificate?
+ if (typeof opts.ssl_ca !== 'undefined') {
+ // An array of them?
+ if (typeof opts.ssl_ca.map !== 'undefined') {
+ serv_opts.ca = opts.ssl_ca.map(function (f) { return fs.readFileSync(f); });
+
+ } else {
+ serv_opts.ca = fs.readFileSync(opts.ssl_ca);
+ }
+ }
+
+ this.server = tls.createServer(serv_opts);
- this.identd_server.start();
- */
- // Start listening for proxy connections connections
- this.server = new net.Server();
- this.server.listen(listen_port, listen_addr);
+ connection_event = 'secureConnection';
+
+ }
- this.server.on('connection', function(socket) {
- new ProxyPipe(socket);
+ // No SSL, start a simple clear text server
+ else {
+ this.server = new net.Server();
+ }
+
+ this.server.listen(listen_port, listen_addr, function() {
+ that.emit('listening');
+ });
+
+ this.server.on(connection_event, function(socket) {
+ new ProxyPipe(socket, that);
});
};
* 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;
+function ProxyPipe(kiwi_socket, proxy_server) {
+ debug('[KiwiProxy] New Kiwi connection');
+
+ this.kiwi_socket = kiwi_socket;
+ this.proxy_server = proxy_server;
+ this.irc_socket = null;
+ this.buffers = [];
+ this.meta = null;
- kiwi_socket.setEncoding('utf8');
kiwi_socket.on('readable', this.kiwiSocketOnReadable.bind(this));
}
ProxyPipe.prototype.destroy = function() {
- this.buffer = null;
+ this.buffers = null;
this.meta = null;
if (this.irc_socket) {
ProxyPipe.prototype.kiwiSocketOnReadable = function() {
- var chunk, meta;
+ var chunk, buffer, meta;
while ((chunk = this.kiwi_socket.read()) !== null) {
- this.buffer += chunk;
+ this.buffers.push(chunk);
}
// Not got a complete line yet? Wait some more
- if (this.buffer.indexOf('\n') === -1)
+ chunk = this.buffers[this.buffers.length-1];
+ if (!chunk || chunk[chunk.length-1] !== 0x0A)
return;
+ buffer = new Buffer.concat(this.buffers);
+ this.buffers = null;
+
try {
- meta = JSON.parse(this.buffer.substr(0, this.buffer.indexOf('\n')));
+ debug('[KiwiProxy] Found a complete line in the buffer');
+ meta = JSON.parse(buffer.toString('utf8'));
} catch (err) {
+ debug('[KiwiProxy] Error parsing meta');
this.destroy();
return;
}
if (!meta.username) {
+ debug('[KiwiProxy] Meta does not contain a username');
this.destroy();
return;
}
- this.buffer = '';
this.meta = meta;
this.kiwi_socket.removeAllListeners('readable');
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));
+ debug('[KiwiProxy] Opening proxied connection to: ' + this.meta.host + ':' + this.meta.port.toString());
+
+ var local_address = this.meta.interface ?
+ this.meta.interface :
+ '0.0.0.0';
+
+ if (this.meta.ssl) {
+ this.irc_socket = tls.connect({
+ port: parseInt(this.meta.port, 10),
+ host: this.meta.host,
+ rejectUnauthorized: global.config.reject_unauthorised_certificates,
+ localAddress: local_address
+ }, this._onSocketConnect.bind(this));
+
+ } else {
+ this.irc_socket = net.connect({
+ port: parseInt(this.meta.port, 10),
+ host: this.meta.host,
+ localAddress: local_address
+ }, 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));
+
+ // We need the raw socket connect event, not after any SSL handshakes or anything
+ if (this.irc_socket.socket) {
+ this.irc_socket.socket.on('connect', this._onRawSocketConnect.bind(this));
+ } else {
+ this.irc_socket.on('connect', this._onRawSocketConnect.bind(this));
+ }
+};
+
+
+ProxyPipe.prototype._onRawSocketConnect = function() {
+ this.proxy_server.emit('socket_connected', this);
};
ProxyPipe.prototype._onSocketConnect = function() {
debug('[KiwiProxy] ProxyPipe::_onSocketConnect()');
+ this.proxy_server.emit('connection_open', this);
+
// Now that we're connected to the detination, return no
// error back to the kiwi server and start piping
this.kiwi_socket.write(new Buffer(RESPONSE_OK.toString()), this.startPiping.bind(this));
ProxyPipe.prototype._onSocketClose = function() {
debug('[KiwiProxy] IRC Socket closed');
+ this.proxy_server.emit('connection_close', this);
this.destroy();
};
this.irc_socket.on('close', this._onSocketClose.bind(this));
- this.kiwi_socket.setEncoding('binary');
-
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) {
+function ProxySocket(proxy_port, proxy_addr, meta, proxy_opts) {
stream.Duplex.call(this);
this.connected_fn = null;
this.proxy_addr = proxy_addr;
this.proxy_port = proxy_port;
- this.meta = meta || {};
+ this.proxy_opts = proxy_opts || {};
+
+ this.setMeta(meta || {});
this.state = 'disconnected';
}
};
+ProxySocket.prototype.connectTls = function() {
+ this.meta.ssl = true;
+ return this.connect.apply(this, arguments);
+};
+
+
ProxySocket.prototype.connect = function(dest_port, dest_addr, connected_fn) {
this.meta.host = dest_addr;
this.meta.port = dest_port;
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);
+ debug('[KiwiProxy] Connecting to proxy ' + this.proxy_addr + ':' + this.proxy_port.toString() + ' SSL: ' + (!!this.proxy_opts.ssl).toString());
+ if (this.proxy_opts.ssl) {
+ this.socket = tls.connect({
+ port: this.proxy_port,
+ host: this.proxy_addr,
+ rejectUnauthorized: !!global.config.reject_unauthorised_certificates,
+ }, this._onSocketConnect.bind(this));
+ } else {
+ 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));
if (this.state === 'connected' && this.socket) {
return this.socket.write(chunk, encoding, callback);
} else {
+ debug('[KiwiProxy] Trying to write to an unfinished socket. State=' + this.state);
callback('Not connected');
}
};
ProxySocket.prototype._onSocketClose = function(had_error) {
+ debug('[KiwiProxy] _onSocketClose() had_error=' + had_error.toString());
if (this.state === 'connected') {
this.emit('close', had_error);
return;
ProxySocket.prototype._onSocketError = function(err) {
+ debug('[KiwiProxy] _onSocketError() err=' + err.toString());
this.ignore_close = true;
this.emit('error', err);
};
\ No newline at end of file