Check the correct user for op status on a nick click
[KiwiIRC.git] / server / irc / connection.js
index cf155709436a9f5f55110c7128ee81183f890a73..eeb1350658d1279bc0c5d4e77080109b50574da0 100644 (file)
@@ -1,12 +1,14 @@
 var net             = require('net'),
     tls             = require('tls'),
     util            = require('util'),
+    dns             = require('dns'),
     _               = require('lodash'),
-    EventEmitter2   = require('eventemitter2').EventEmitter2,
     EventBinder     = require('./eventbinder.js'),
     IrcServer       = require('./server.js'),
     IrcChannel      = require('./channel.js'),
     IrcUser         = require('./user.js'),
+    EE              = require('../ee.js'),
+    iconv           = require('iconv-lite'),
     Socks;
 
 
@@ -23,12 +25,15 @@ if (version_values[1] >= 10) {
 var IrcConnection = function (hostname, port, ssl, nick, user, pass, state) {
     var that = this;
 
-    EventEmitter2.call(this,{
+    EE.call(this,{
         wildcard: true,
         delimiter: ' '
     });
     this.setMaxListeners(0);
 
+    // Set the first configured encoding as the default encoding
+    this.encoding = global.config.default_encoding;
+    
     // Socket state
     this.connected = false;
 
@@ -88,14 +93,8 @@ var IrcConnection = function (hostname, port, ssl, nick, user, pass, state) {
     this.held_data = '';
 
     this.applyIrcEvents();
-
-    // Call any modules before making the connection
-    global.modules.emit('irc connecting', {connection: this})
-        .done(function () {
-            that.connect();
-        });
 };
-util.inherits(IrcConnection, EventEmitter2);
+util.inherits(IrcConnection, EE);
 
 module.exports.IrcConnection = IrcConnection;
 
@@ -124,65 +123,111 @@ IrcConnection.prototype.applyIrcEvents = function () {
  */
 IrcConnection.prototype.connect = function () {
     var that = this;
-    var socks;
 
     // The socket connect event to listener for
     var socket_connect_event_name = 'connect';
 
+    // The destination address
+    var dest_addr = this.socks ?
+        this.socks.host :
+        this.irc_host.hostname;
 
     // Make sure we don't already have an open connection
     this.disposeSocket();
 
-    // Are we connecting through a SOCKS proxy?
-    if (this.socks) {
-        this.socket = Socks.connect({
-            host: this.irc_host.hostname,
-            port: this.irc_host.port,
-            ssl: this.ssl,
-            rejectUnauthorized: global.config.reject_unauthorised_certificates
-        }, {host: this.socks.host,
-            port: this.socks.port,
-            user: this.socks.user,
-            pass: this.socks.pass
-        });
+    // Get the IP family for the dest_addr (either socks or IRCd destination)
+    getConnectionFamily(dest_addr, function (err, family, host) {
+        var outgoing;
+
+        // Decide which net. interface to make the connection through
+        if (global.config.outgoing_address) {
+            if ((family === 'IPv6') && (global.config.outgoing_address.IPv6)) {
+                outgoing = global.config.outgoing_address.IPv6;
+            } else {
+                outgoing = global.config.outgoing_address.IPv4 || '0.0.0.0';
+
+                // We don't have an IPv6 interface but dest_addr may still resolve to
+                // an IPv4 address. Reset `host` and try connecting anyway, letting it
+                // fail if an IPv4 resolved address is not found
+                host = dest_addr;
+            }
 
-    } else if (this.ssl) {
-        this.socket = tls.connect({
-            host: this.irc_host.hostname,
-            port: this.irc_host.port,
-            rejectUnauthorized: global.config.reject_unauthorised_certificates
-        });
+        } else {
+            // No config was found so use the default
+            outgoing = '0.0.0.0';
+        }
 
-        socket_connect_event_name = 'secureConnect';
+        // Are we connecting through a SOCKS proxy?
+        if (this.socks) {
+            that.socket = Socks.connect({
+                host: host,
+                port: that.irc_host.port,
+                ssl: that.ssl,
+                rejectUnauthorized: global.config.reject_unauthorised_certificates
+            }, {host: that.socks.host,
+                port: that.socks.port,
+                user: that.socks.user,
+                pass: that.socks.pass,
+                localAddress: outgoing
+            });
 
-    } else {
-        this.socket = net.connect({
-            host: this.irc_host.hostname,
-            port: this.irc_host.port
+        } else {
+            // No socks connection, connect directly to the IRCd
+
+            if (that.ssl) {
+                that.socket = tls.connect({
+                    host: host,
+                    port: that.irc_host.port,
+                    rejectUnauthorized: global.config.reject_unauthorised_certificates,
+                    localAddress: outgoing
+                });
+
+                socket_connect_event_name = 'secureConnect';
+
+            } else {
+                that.socket = net.connect({
+                    host: host,
+                    port: that.irc_host.port,
+                    localAddress: outgoing
+                });
+            }
+        }
+
+        // Apply the socket listeners
+        that.socket.on(socket_connect_event_name, function () {
+            that.connected = true;
+
+            // Make note of the port numbers for any identd lookups
+            // Nodejs < 0.9.6 has no socket.localPort so check this first
+            if (this.localPort) {
+                global.clients.port_pairs[this.localPort.toString() + '_' + this.remotePort.toString()] = that;
+            }
+
+            socketConnectHandler.call(that);
         });
-    }
 
-    this.socket.on(socket_connect_event_name, function () {
-        that.connected = true;
-        socketConnectHandler.call(that);
-    });
+        that.socket.on('error', function (event) {
+            that.emit('error', event);
+        });
 
-    this.socket.setEncoding('utf-8');
+        that.socket.on('data', function () {
+            parse.apply(that, arguments);
+        });
 
-    this.socket.on('error', function (event) {
-        that.emit('error', event);
-    });
+        that.socket.on('close', function (had_error) {
+            that.connected = false;
 
-    this.socket.on('data', function () {
-        parse.apply(that, arguments);
-    });
+            // Remove this socket form the identd lookup
+            // Nodejs < 0.9.6 has no socket.localPort so check this first
+            if (this.localPort) {
+                delete global.clients.port_pairs[this.localPort.toString() + '_' + this.remotePort.toString()];
+            }
 
-    this.socket.on('close', function (had_error) {
-        that.connected = false;
-        that.emit('close');
+            that.emit('close');
 
-        // Close the whole socket down
-        that.disposeSocket();
+            // Close the whole socket down
+            that.disposeSocket();
+        });
     });
 };
 
@@ -198,7 +243,9 @@ IrcConnection.prototype.clientEvent = function (event_name, data, callback) {
  * Write a line of data to the IRCd
  */
 IrcConnection.prototype.write = function (data, callback) {
-    this.socket.write(data + '\r\n', 'utf-8', callback);
+    //ENCODE string to encoding of the server
+    encoded_buffer = iconv.encode(data + '\r\n', this.encoding);
+    this.socket.write(encoded_buffer);
 };
 
 
@@ -219,6 +266,8 @@ IrcConnection.prototype.end = function (data, callback) {
  * Clean up this IrcConnection instance and any sockets
  */
 IrcConnection.prototype.dispose = function () {
+    var that = this;
+
     _.each(this.irc_users, function (user) {
         user.dispose();
     });
@@ -233,8 +282,20 @@ IrcConnection.prototype.dispose = function () {
 
     EventBinder.unbindIrcEvents('', this.irc_events, this);
 
-    this.disposeSocket();
-    this.removeAllListeners();
+    // If we're still connected, wait until the socket is closed before disposing
+    // so that all the events are still correctly triggered
+    if (this.socket && this.connected) {
+        this.socket.once('close', function() {
+            that.disposeSocket();
+            that.removeAllListeners();
+        });
+
+        this.socket.end();
+
+    } else {
+        this.disposeSocket();
+        this.removeAllListeners();
+    }
 };
 
 
@@ -250,6 +311,52 @@ IrcConnection.prototype.disposeSocket = function () {
     }
 };
 
+/**
+ * Set a new encoding for this connection
+ * Return true in case of success
+ */
+
+IrcConnection.prototype.setEncoding = function (encoding) {
+    var encoded_test;
+
+    try {
+        encoded_test = iconv.encode("TEST", encoding);
+        //This test is done to check if this encoding also supports
+        //the ASCII charset required by the IRC protocols
+        //(Avoid the use of base64 or incompatible encodings)
+        if (encoded_test == "TEST") {
+            this.encoding = encoding;
+            return true;
+        }
+        return false;
+    } catch (err) {
+        return false;
+    }
+};
+
+function getConnectionFamily(host, callback) {
+    if (net.isIP(host)) {
+        if (net.isIPv4(host)) {
+            setImmediate(callback, null, 'IPv4', host);
+        } else {
+            setImmediate(callback, null, 'IPv6', host);
+        }
+    } else {
+        dns.resolve6(host, function (err, addresses) {
+            if (!err) {
+                callback(null, 'IPv6', addresses[0]);
+            } else {
+                dns.resolve4(host, function (err, addresses) {
+                    if (!err) {
+                        callback(null, 'IPv4',addresses[0]);
+                    } else {
+                        callback(err);
+                    }
+                });
+            }
+        });
+    }
+}
 
 
 function onChannelJoin(event) {
@@ -392,7 +499,13 @@ function findWebIrc(connect_data) {
     if (ip_as_username && ip_as_username.indexOf(this.irc_host.hostname) > -1) {
         // Get a hex value of the clients IP
         this.username = this.user.address.split('.').map(function(i, idx){
-            return parseInt(i, 10).toString(16);
+            var hex = parseInt(i, 10).toString(16);
+
+            // Pad out the hex value if it's a single char
+            if (hex.length === 1)
+                hex = '0' + hex;
+
+            return hex;
         }).join('');
 
     }
@@ -418,6 +531,9 @@ var parse = function (data) {
         tags = [],
         tag;
 
+    //DECODE server encoding 
+    data = iconv.decode(data, this.encoding);
+
     if (this.hold_last && this.held_data !== '') {
         data = this.held_data + data;
         this.hold_last = false;
@@ -439,7 +555,7 @@ var parse = function (data) {
             this.held_data = data[i];
             break;
         }
-
+        
         // Parse the complete line, removing any carriage returns
         msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, ''));