ProxyServer.prototype.listen = function(listen_port, listen_addr, opts) {
var that = this,
- serv_opts = {};
+ serv_opts = {},
+ connection_event = 'connection';
opts = opts || {};
this.server = tls.createServer(serv_opts);
+ connection_event = 'secureConnection';
+
}
// No SSL, start a simple clear text server
that.emit('listening');
});
- this.server.on('connection', function(socket) {
+ this.server.on(connection_event, function(socket) {
new ProxyPipe(socket, that);
});
};
* 4. If all ok, pipe data between the 2 sockets as a proxy
*/
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.buffer = '';
+ 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);
};
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);
};
this.proxy_addr = proxy_addr;
this.proxy_port = proxy_port;
this.proxy_opts = proxy_opts || {};
- this.meta = meta || {};
+
+ 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 = this.proxy_opts.ssl ?
- tls.connect(this.proxy_port, this.proxy_addr, this._onSocketConnect.bind(this)) :
- 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