channel:join + channel:leave plugin events
[KiwiIRC.git] / server / proxy.js
index 5950dcf6a16921d6334141475b4366f25c0c9550..0782fdd97d974a1b6f6f55c5c76cb2575f6a5887 100644 (file)
@@ -38,7 +38,8 @@ util.inherits(ProxyServer, events.EventEmitter);
 
 ProxyServer.prototype.listen = function(listen_port, listen_addr, opts) {
     var that = this,
-        serv_opts = {};
+        serv_opts = {},
+        connection_event = 'connection';
 
     opts = opts || {};
 
@@ -62,6 +63,8 @@ ProxyServer.prototype.listen = function(listen_port, listen_addr, opts) {
 
         this.server = tls.createServer(serv_opts);
 
+        connection_event = 'secureConnection';
+
     }
 
     // No SSL, start a simple clear text server
@@ -73,7 +76,7 @@ ProxyServer.prototype.listen = function(listen_port, listen_addr, opts) {
         that.emit('listening');
     });
 
-    this.server.on('connection', function(socket) {
+    this.server.on(connection_event, function(socket) {
         new ProxyPipe(socket, that);
     });
 };
@@ -100,19 +103,20 @@ ProxyServer.prototype.close = function(callback) {
  * 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) {
@@ -130,29 +134,35 @@ ProxyPipe.prototype.destroy = function() {
 
 
 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');
 
@@ -161,14 +171,43 @@ ProxyPipe.prototype.kiwiSocketOnReadable = function() {
 
 
 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);
 };
 
 
@@ -219,8 +258,6 @@ ProxyPipe.prototype.startPiping = function() {
 
     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);
 };
@@ -240,7 +277,8 @@ function ProxySocket(proxy_port, proxy_addr, meta, proxy_opts) {
     this.proxy_addr   = proxy_addr;
     this.proxy_port   = proxy_port;
     this.proxy_opts   = proxy_opts || {};
-    this.meta         = meta || {};
+
+    this.setMeta(meta || {});
 
     this.state = 'disconnected';
 }
@@ -253,6 +291,12 @@ ProxySocket.prototype.setMeta = function(meta) {
 };
 
 
+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;
@@ -263,12 +307,19 @@ ProxySocket.prototype.connect = function(dest_port, dest_addr, connected_fn) {
         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));
@@ -308,6 +359,7 @@ ProxySocket.prototype._write = function(chunk, encoding, callback) {
     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');
     }
 };
@@ -361,6 +413,7 @@ ProxySocket.prototype._onSocketData = function(data) {
 
 
 ProxySocket.prototype._onSocketClose = function(had_error) {
+    debug('[KiwiProxy] _onSocketClose() had_error=' + had_error.toString());
     if (this.state === 'connected') {
         this.emit('close', had_error);
         return;
@@ -372,6 +425,7 @@ ProxySocket.prototype._onSocketClose = function(had_error) {
 
 
 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