Merge branch 'i18n' into development
authorJack Allnutt <jack@allnutt.eu>
Sun, 21 Jul 2013 21:18:05 +0000 (22:18 +0100)
committerJack Allnutt <jack@allnutt.eu>
Sun, 21 Jul 2013 21:18:05 +0000 (22:18 +0100)
18 files changed:
client/assets/css/style.css
client/assets/img/more.png [deleted file]
client/assets/src/index.html.tmpl
client/assets/src/models/application.js
client/assets/src/views/serverselect.js
config.example.js
package.json
server/identd.js
server/irc/commands.js
server/irc/connection.js
server/irc/state.js
server/kiwi.js
server/modules.js
server/plugininterface.js
server/server.js
server/weblistener.js
server_modules/control.js
server_modules/dnsbl.js [new file with mode: 0644]

index d000f6a1b7a0b311f77951ae9b6ffde57238ab84..fb24c53c11926fb6c63eb9b06e21017b27df4e97 100644 (file)
@@ -762,7 +762,7 @@ html, body { height:100%; }
 #kiwi.theme_relaxed .server_select .basic tr.have_key { font-size:0.8em; }
 #kiwi.theme_relaxed .server_select .basic tr.channel td { padding-top:1em; }
 #kiwi.theme_relaxed .server_select .basic { border-bottom: 1px dashed gray; margin-bottom:1em; }
-#kiwi.theme_relaxed .server_select .basic .show_more { display: block; width:110px; margin:10px 0 0 0; font-size:0.8em; background: url(../img/more.png) no-repeat right 7px; }
+#kiwi.theme_relaxed .server_select .basic .show_more { display: block; width:110px; margin:10px 0 0 0; font-size:0.8em; }
 #kiwi.theme_relaxed .server_select.single_server .basic { border:none; }
 #kiwi.theme_relaxed .server_select .status { text-align: center; font-weight: bold; padding:1em; }
 #kiwi.theme_relaxed .server_select .status.ok { }
@@ -1275,7 +1275,7 @@ html, body { height:100%; }
 #kiwi.theme_cli .server_select .basic tr.have_pass { font-size:0.8em; }
 #kiwi.theme_cli .server_select .basic tr.channel td { padding-top:1em; }
 #kiwi.theme_cli .server_select .basic { border-bottom: 1px dashed gray; margin-bottom:1em; }
-#kiwi.theme_cli .server_select .basic .show_more { display: block; width:116px; margin:10px 0 0 0; font-size:0.8em; background: url(../img/more.png) no-repeat right 7px; }
+#kiwi.theme_cli .server_select .basic .show_more { display: block; width:116px; margin:10px 0 0 0; font-size:0.8em; }
 #kiwi.theme_cli .server_select.single_server .basic { border:none; }
 #kiwi.theme_cli .server_select .status { text-align: center; font-weight: bold; padding:1em; }
 #kiwi.theme_cli .server_select .status.ok { }
@@ -1592,7 +1592,7 @@ html, body { height:100%; }
 #kiwi.theme_basic .server_select .basic tr.have_pass { font-size:0.8em; }
 #kiwi.theme_basic .server_select .basic tr.channel td { padding-top:1em; }
 #kiwi.theme_basic .server_select .basic { border-bottom: 1px dashed gray; margin-bottom:1em; }
-#kiwi.theme_basic .server_select .basic .show_more { display: block; width:110px; margin:10px 0 0 0; font-size:0.8em; background: url(../img/more.png) no-repeat right 7px; }
+#kiwi.theme_basic .server_select .basic .show_more { display: block; width:110px; margin:10px 0 0 0; font-size:0.8em; }
 #kiwi.theme_basic .server_select.single_server .basic { border:none; }
 #kiwi.theme_basic .server_select .status { text-align: center; font-weight: bold; padding:1em; }
 #kiwi.theme_basic .server_select .status.ok { }
diff --git a/client/assets/img/more.png b/client/assets/img/more.png
deleted file mode 100644 (file)
index a0d4dd7..0000000
Binary files a/client/assets/img/more.png and /dev/null differ
index 7324b0fc84f669cbb4bb43afe72aa661b93e67c6..c33b2a4ab0fa65d521185a8cd6d5832e93fe6c84 100644 (file)
                             </tr>\r
                         </table>\r
 \r
-                        <a href="" onclick="return false;" class="show_more"><%= server_network %></a>\r
+                        <a href="" onclick="return false;" class="show_more"><%= server_network %> <i class="icon-caret-down"></i></a>\r
                     </div>\r
 \r
 \r
index 3fd84f0aaf0b168bd01901ab7778731ed117814b..2f4c2d9c735c01d73a670cfea6ea678d3a721713 100644 (file)
@@ -32,6 +32,11 @@ _kiwi.model.Application = function () {
 \r
             // Takes instances of model_network\r
             this.connections = new _kiwi.model.NetworkPanelList();\r
+\r
+            // Set any default settings before anything else is applied\r
+            if (this.server_settings && this.server_settings.client && this.server_settings.client.settings) {\r
+                this.applyDefaultClientSettings(this.server_settings.client.settings);\r
+            }\r
         };\r
 \r
 \r
@@ -49,7 +54,6 @@ _kiwi.model.Application = function () {
 \r
             this.initializeClient();\r
             this.initializeGlobals();\r
-            this.applyDefaultClientSettings(this.server_settings.client.settings);\r
 \r
             this.view.barsHide(true);\r
 \r
@@ -181,6 +185,9 @@ _kiwi.model.Application = function () {
 \r
                 if (this.server_settings.client.channel)\r
                     defaults.channel = this.server_settings.client.channel;\r
+\r
+                if (this.server_settings.client.channel_key)\r
+                    defaults.channel_key = this.server_settings.client.channel_key;\r
             }\r
 \r
 \r
@@ -285,6 +292,10 @@ _kiwi.model.Application = function () {
                     defaults.channel = this.server_settings.connection.channel;\r
                 }\r
 \r
+                if (this.server_settings.connection.channel_key) {\r
+                    defaults.channel_key = this.server_settings.connection.channel_key;\r
+                }\r
+\r
                 if (this.server_settings.connection.nick) {\r
                     defaults.nick = this.server_settings.connection.nick;\r
                 }\r
index a6dda5be8cfa5191e165f15f175494d72a66ce53..6745ccece9a1bca89d40eb4f953aeefd6183f2c2 100644 (file)
@@ -39,6 +39,8 @@ _kiwi.view.ServerSelect = function () {
                 }
             }
 
+            this.more_shown = false;
+
             _kiwi.gateway.bind('onconnect', this.networkConnected, this);
             _kiwi.gateway.bind('connecting', this.networkConnecting, this);
             _kiwi.gateway.bind('onirc_error', this.onIrcError, this);
@@ -115,8 +117,23 @@ _kiwi.view.ServerSelect = function () {
         },
 
         showMore: function (event) {
-            $('.more', this.$el).slideDown('fast');
-            $('input.server', this.$el).select();
+            if (!this.more_shown) {
+                $('.more', this.$el).slideDown('fast');
+                $('.show_more', this.$el)
+                    .children('.icon-caret-down')
+                    .removeClass('icon-caret-down')
+                    .addClass('icon-caret-up');
+                $('input.server', this.$el).select();
+                this.more_shown = true;
+            } else {
+                $('.more', this.$el).slideUp('fast');
+                $('.show_more', this.$el)
+                    .children('.icon-caret-up')
+                    .removeClass('icon-caret-up')
+                    .addClass('icon-caret-down');
+                $('input.nick', this.$el).select();
+                this.more_shown = false;
+            }
         },
 
         populateFields: function (defaults) {
index 2ca38eedda8d52412e84ffb74f13881ef5ed1260..bd1a3d535d92bf174f71a181aa4190b63b0efc71 100644 (file)
@@ -33,6 +33,11 @@ conf.servers.push({
 //    ssl_cert: "cert.pem"
 //});
 
+// Network interface for outgoing connections
+conf.outgoing_address = {
+    IPv4: '0.0.0.0'
+    //IPv6: '::'
+};
 
 
 // Do we want to enable the built in Identd server?
@@ -175,6 +180,7 @@ conf.client = {
     port:    6697,
     ssl:     true,
     channel: '#kiwiirc',
+    channel_key: '',
     nick:    'kiwi_?',
     settings: {
         theme: 'relaxed',
@@ -192,6 +198,7 @@ conf.client = {
 //conf.restrict_server_port = 6667;
 //conf.restrict_server_ssl = false;
 //conf.restrict_server_channel = "#kiwiirc";
+//conf.restrict_server_channel_key = "";
 //conf.restrict_server_password = "";
 //conf.restrict_server_nick = "kiwi_";
 
index e76397d598cf78b2c5e90b73899ac2edc59737f4..e7c34daed2d2e6adcdf7bf314c9c07018a63c5e9 100644 (file)
@@ -21,8 +21,9 @@
     "daemonize2": "0.4.0-rc.5",\r
     "eventemitter2": "0.4.11",\r
     "ipaddr.js": "0.1.1",\r
-    "socksjs": "0.3.3",\r
+    "socksjs": "0.4.1",\r
     "iconv-lite" : "0.2.10",\r
+    "spdy": "1.9.1",\r
     "po2json": "0.0.6"\r
   }\r
 }\r
index 2a8bfa556566b1385a47b23ea004c62646da5d9e..5c588e273d0b86390397e3db1681f295c20fcca0 100644 (file)
@@ -1,31 +1,46 @@
 var net = require('net');
 
-function IdentdServer(opts) {
+var IdentdServer = module.exports = function(opts) {
+
+    var that = this;
+
+    var default_user_id = 'kiwi',
+        default_system_id = 'UNIX-KiwiIRC';
 
     // Option defaults
     opts = opts || {};
     opts.bind_addr = opts.bind_addr || '0.0.0.0';
     opts.bind_port = opts.bind_port || 113;
-    opts.system_id = opts.system_id || 'UNIX-KiwiIRC';
-    opts.user_id = opts.user_id || 'kiwi';
+    opts.system_id = opts.system_id || default_system_id;
+    opts.user_id = opts.user_id || default_user_id;
 
 
     var server = net.createServer(function(socket) {
-        var user, system;
+        var buffer = '';
 
-        if (typeof opts.user_id === 'function') {
-            user = opts.user_id(socket).toString();
-        } else {
-            user = opts.user_id.toString();
-        }
+        socket.on('data', function(data){
+            var data_line, response;
 
-        if (typeof opts.system_id === 'function') {
-            system = opts.system_id(socket).toString();
-        } else {
-            system = opts.system_id.toString();
-        }
+            buffer += data.toString();
+
+            // If we exceeed 512 bytes, presume a flood and disconnect
+            if (buffer.length < 512) {
+
+                // Wait until we have a full line of data before processing it
+                if (buffer.indexOf('\n') === -1)
+                    return;
+
+                // Get the first line of data and process it for a rsponse
+                data_line = buffer.split('\n')[0];
+                response = that.processLine(data_line);
+
+            }
+
+            // Close down the socket while sending the response
+            socket.removeAllListeners();
+            socket.end(response);
+        });
 
-        socket.end('25,25 : USERID : ' + system + ' : ' + user);
     });
 
     server.on('listening', function() {
@@ -40,7 +55,42 @@ function IdentdServer(opts) {
     this.stop = function(callback) {
         server.close(callback);
     };
-}
 
 
-module.exports = IdentdServer;
\ No newline at end of file
+    /**
+     * Process a line of data for an Identd response
+     * 
+     * @param {String} The line of data to process
+     * @return {String} Data to send back to the Identd client
+     */
+    this.processLine = function(line) {
+        var ports = line.split(','),
+            port_here = 0,
+            port_there = 0;
+
+        // We need 2 port number to make this work
+        if (ports.length < 2)
+            return;
+
+        port_here = parseInt(ports[0], 10);
+        port_there = parseInt(ports[1], 10);
+
+        // Make sure we have both ports to work with
+        if (!port_here || !port_there)
+            return;
+
+        if (typeof opts.user_id === 'function') {
+            user = (opts.user_id(port_here, port_there) || '').toString() || default_user_id;
+        } else {
+            user = opts.user_id.toString();
+        }
+
+        if (typeof opts.system_id === 'function') {
+            system = (opts.system_id(port_here, port_there) || '').toString() || default_system_id;
+        } else {
+            system = opts.system_id.toString();
+        }
+
+        return port_here.toString() + ' , ' + port_there.toString() + ' : USERID : ' + system + ' : ' + user;
+    };
+};
index 0dc53c05d04ea0c63652011a30bcea820296638c..6979af870c18ec9a9935406e34f3be4d2838d022 100644 (file)
@@ -270,18 +270,17 @@ handlers = {
         var members = command.trailing.split(' ');
         var member_list = [];
         var that = this;
-        var i = 0;
         _.each(members, function (member) {
-            var j, k, modes = [];
+            var i = 0,
+                j = 0,
+                modes = [];
 
             // Make sure we have some prefixes already
             if (that.irc_connection.options.PREFIX) {
-                for (j = 0; j < member.length; j++) {
-                    for (k = 0; k < that.irc_connection.options.PREFIX.length; k++) {
-                        if (member.charAt(j) === that.irc_connection.options.PREFIX[k].symbol) {
-                            modes.push(that.irc_connection.options.PREFIX[k].mode);
-                            i++;
-                        }
+                for (j = 0; j < that.irc_connection.options.PREFIX.length; j++) {
+                    if (member.charAt(i) === that.irc_connection.options.PREFIX[j].symbol) {
+                        modes.push(that.irc_connection.options.PREFIX[j].mode);
+                        i++;
                     }
                 }
             }
index 2c9ad82949dcb08ffb236f1cb886434b61f913f7..eeb1350658d1279bc0c5d4e77080109b50574da0 100644 (file)
@@ -1,6 +1,7 @@
 var net             = require('net'),
     tls             = require('tls'),
     util            = require('util'),
+    dns             = require('dns'),
     _               = require('lodash'),
     EventBinder     = require('./eventbinder.js'),
     IrcServer       = require('./server.js'),
@@ -92,12 +93,6 @@ 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, EE);
 
@@ -128,63 +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.on('error', function (event) {
-        that.emit('error', event);
-    });
+        that.socket.on('data', function () {
+            parse.apply(that, arguments);
+        });
 
-    this.socket.on('data', function () {
-        parse.apply(that, arguments);
-    });
+        that.socket.on('close', function (had_error) {
+            that.connected = false;
 
-    this.socket.on('close', function (had_error) {
-        that.connected = false;
-        that.emit('close');
+            // 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()];
+            }
+
+            that.emit('close');
 
-        // Close the whole socket down
-        that.disposeSocket();
+            // Close the whole socket down
+            that.disposeSocket();
+        });
     });
 };
 
@@ -291,6 +334,30 @@ IrcConnection.prototype.setEncoding = function (encoding) {
     }
 };
 
+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) {
     var chan;
index 368684ee5b96d88d5fdbd36a097d2d74cce4c132..776ab5db2aa73104ec6e886df9a4dbb9e018b95d 100755 (executable)
@@ -80,6 +80,12 @@ State.prototype.connect = function (hostname, port, ssl, nick, user, pass, callb
         that.irc_connections[con_num] = null;
         global.servers.removeConnection(this);
     });
+
+    // Call any modules before making the connection
+    global.modules.emit('irc connecting', {connection: con})
+        .done(function () {
+            con.connect();
+        });
 };
 
 State.prototype.sendIrcCommand = function () {
index 35c0a499fb1f7667dae6ba9bedf09e679e99d24e..42ef9bc5a41c5d672efaf1e554eed874f97c25d7 100755 (executable)
@@ -67,7 +67,7 @@ modules.registerPublisher(global.modules);
 // Load any modules in the config
 if (global.config.module_dir) {
     (global.config.modules || []).forEach(function (module_name) {
-        if (modules.load(global.config.module_dir + module_name + '.js')) {
+        if (modules.load(module_name)) {
             console.log('Module ' + module_name + ' loaded successfuly');
         } else {
             console.log('Module ' + module_name + ' failed to load');
@@ -83,6 +83,10 @@ global.clients = {
     clients: Object.create(null),
     addresses: Object.create(null),
 
+    // Local and foriegn port pairs for identd lookups
+    // {'65483_6667': client_obj, '54356_6697': client_obj}
+    port_pairs: {},
+
     add: function (client) {
         this.clients[client.hash] = client;
         if (typeof this.addresses[client.real_address] === 'undefined') {
@@ -147,10 +151,23 @@ global.servers = {
  * Identd server
  */
 if (global.config.identd && global.config.identd.enabled) {
-    new Identd({
+    var identd_resolve_user = function(port_here, port_there) {
+        var key = port_here.toString() + '_' + port_there.toString();
+
+        if (typeof global.clients.port_pairs[key] == 'undefined') {
+            return;
+        }
+
+        return global.clients.port_pairs[key].username;
+    };
+
+    var identd_server = new Identd({
         bind_addr: global.config.identd.address,
-        bind_port: global.config.identd.port
-    }).start();
+        bind_port: global.config.identd.port,
+        user_id: identd_resolve_user
+    });
+
+    identd_server.start();
 }
 
 
@@ -231,6 +248,10 @@ process.on('SIGUSR1', function() {
 });
 
 
+process.on('SIGUSR2', function() {
+    console.log('Connected clients: ' + _.size(global.clients.clients).toString());
+    console.log('Num. remote hosts: ' + _.size(global.clients.addresses).toString());
+});
 
 
 /*
index 0553974931a3d2e7ec6a55595e39bc93cb0bbc48..08f17f5a5f76891376c157960fbca9fd5cbcddd4 100644 (file)
@@ -35,18 +35,18 @@ function registerPublisher (obj) {
  */
 
 // Hold the loaded modules
-var registered_modules = {};
+var registered_modules = [];
 
 function loadModule (module_file) {
-    var module;
+    var module,
+        full_module_filename = global.config.module_dir + module_file;
 
     // Get an instance of the module and remove it from the cache
     try {
-        module = require(module_file);
-        delete require.cache[require.resolve(module_file)];
+        module = require(full_module_filename);
+        delete require.cache[require.resolve(full_module_filename)];
     } catch (err) {
         // Module was not found
-        console.log(err);
         return false;
     }
 
@@ -56,12 +56,18 @@ function loadModule (module_file) {
 
 // Find a registered collection, .dispose() of it and remove it
 function unloadModule (module) {
+    var found_module = false;
+
     registered_modules = _.reject(registered_modules, function (registered_module) {
-        if (module === registered_module) {
-            module.dispose();
+        if (module.toLowerCase() === registered_module.module_name.toLowerCase()) {
+            found_module = true;
+
+            registered_module.dispose();
             return true;
         }
     });
+
+    return found_module;
 }
 
 
@@ -74,12 +80,13 @@ function unloadModule (module) {
  * To be created by modules to bind to server events
  */
 function Module (module_name) {
-    registered_modules[module_name] = this;
-}
+    registered_modules.push(this);
+    this.module_name = module_name;
 
+    // Holder for all the bound events by this module
+    this._events = {};
+}
 
-// Holder for all the bound events by this module
-Module.prototype._events = {};
 
 
 // Keep track of this modules events and bind
@@ -126,7 +133,7 @@ Module.prototype.off = function (event_name, fn) {
         }
     }
 
-    active_publisher.removeListener(event_name, fn);
+    active_publisher.off(event_name, fn);
 };
 
 
index a44fc7c8af84ef7dff529304518269fe5cb2f2df..3a02e39f5a1a5c320ff4e71113173cebdae9393c 100644 (file)
@@ -8,7 +8,11 @@
 function EmitCall (event_name, event_data) {
     var that = this,
         completed = false,
-        completed_fn = [];
+        completed_fn = [],
+
+        // Has event.preventDefault() been called
+        prevented = false,
+        prevented_fn = [];
 
 
     // Emit this event to an array of listeners
@@ -46,9 +50,14 @@ function EmitCall (event_name, event_data) {
                 // If wait is true, this callback must be called to continue running listeners
                 callback: function () {
                     // Invalidate this callback incase a listener decides to call it again
-                    callback = undefined;
+                    event_obj.callback = undefined;
 
                     nextListener.apply(that);
+                },
+
+                // Prevents the default 'done' functions from executing
+                preventDefault: function () {
+                    prevented = true;
                 }
             };
 
@@ -73,28 +82,47 @@ function EmitCall (event_name, event_data) {
     function emitComplete() {
         completed = true;
 
-        // Call the completed functions
-        (completed_fn || []).forEach(function (fn) {
+        var funcs = prevented ? prevented_fn : completed_fn;
+
+        // Call the completed/prevented functions
+        (funcs || []).forEach(function (fn) {
             if (typeof fn === 'function') fn();
         });
     }
 
 
 
-    function done(fn) {
+    function addCompletedFunc(fn) {
         // Only accept functions
         if (typeof fn !== 'function') return false;
 
         completed_fn.push(fn);
 
         // If we have already completed the emits, call this now
-        if (completed) fn();
+        if (completed && !prevented) fn();
+
+        return this;
+    }
+
+
+
+    function addPreventedFunc(fn) {
+        // Only accept functions
+        if (typeof fn !== 'function') return false;
+
+        prevented_fn.push(fn);
+
+        // If we have already completed the emits, call this now
+        if (completed && prevented) fn();
+
+        return this;
     }
 
 
     return {
         callListeners: callListeners,
-        done: done
+        done: addCompletedFunc,
+        prevented: addPreventedFunc
     };
 }
 
index 4da1da2372f2b5e133000357efb1c26819f2ab8c..c7e39e54202e060e8438816aac41b16622a9ee25 100644 (file)
@@ -39,6 +39,11 @@ switch (process.argv[2]) {
         daemon.sendSignal("SIGUSR1");\r
         break;\r
 \r
+    case 'stats':\r
+        console.log('Writing stats to log file..');\r
+        daemon.sendSignal("SIGUSR2");\r
+        break;\r
+\r
     case 'build':\r
         require('../client/assets/src/build.js');\r
         break;\r
index 651d1f69ff424483f3e12e09c99f098b317bc3c3..ce75b3e247465c495e4fdf06313699f02ae8c54b 100644 (file)
@@ -7,6 +7,7 @@ var ws          = require('socket.io'),
     dns         = require('dns'),
     url         = require('url'),
     _           = require('lodash'),
+    spdy        = require('spdy'),
     Client      = require('./client.js').Client,
     HttpHandler = require('./httphandler.js').HttpHandler,
     rehash      = require('./rehash.js'),
@@ -30,9 +31,9 @@ var WebListener = function (web_config, transports) {
 
 
     events.EventEmitter.call(this);
-    
+
     http_handler = new HttpHandler(web_config);
-    
+
     // Standard options for the socket.io connections
     ws_opts = {
         'log level': 0,
@@ -57,8 +58,8 @@ var WebListener = function (web_config, transports) {
             }
         }
 
-        hs = https.createServer(opts, handleHttpRequest);
-        
+        hs = spdy.createServer(opts, handleHttpRequest);
+
         // Start socket.io listening on this weblistener
         this.ws = ws.listen(hs, _.extend({ssl: true}, ws_opts));
         hs.listen(web_config.port, web_config.address, function () {
@@ -78,8 +79,8 @@ var WebListener = function (web_config, transports) {
 
     hs.on('error', function (err) {
         that.emit('error', err);
-    })
-    
+    });
+
     this.ws.enable('browser client minification');
     this.ws.enable('browser client etag');
     this.ws.set('transports', transports);
@@ -127,7 +128,7 @@ function authoriseConnection(handshakeData, callback) {
     }
 
     handshakeData.real_address = address;
-    
+
     // If enabled, don't go over the connection limit
     if (global.config.max_client_conns && global.config.max_client_conns > 0) {
         if (global.clients.numOnAddress(address) + 1 > global.config.max_client_conns) {
@@ -143,7 +144,7 @@ function authoriseConnection(handshakeData, callback) {
             } else {
                 handshakeData.revdns = _.first(domains) || address;
             }
-            
+
             // All is well, authorise the connection
             callback(null, true);
         });
index ac1a3e79b469aeb83eb0cb02d29b633bf911dd1f..bbe42544fab54d8f9df8766ebf5aaf3d11e3d13f 100644 (file)
@@ -10,11 +10,15 @@ var net         = require('net'),
     config      = require('../server/configuration.js'),\r
     _           = require('lodash');\r
 \r
-var module = new kiwiModules.Module('Control');\r
+var control_module = new kiwiModules.Module('Control');\r
 \r
 \r
+/**\r
+ * The socket client\r
+ */\r
 function SocketClient (socket) {\r
     this.socket = socket;\r
+    this.socket_closing = false;\r
 \r
     this.remoteAddress = this.socket.remoteAddress;\r
     console.log('Control connection from ' + this.socket.remoteAddress + ' opened');\r
@@ -39,7 +43,8 @@ SocketClient.prototype.unbindEvents = function() {
 \r
 SocketClient.prototype.write = function(data, append) {\r
     if (typeof append === 'undefined') append = '\n';\r
-    this.socket.write(data + append);\r
+    if (this.socket && !this.socket_closing)\r
+        this.socket.write(data + append);\r
 };\r
 SocketClient.prototype.displayPrompt = function(prompt) {\r
     prompt = prompt || 'Kiwi > ';\r
@@ -51,38 +56,17 @@ SocketClient.prototype.displayPrompt = function(prompt) {
 SocketClient.prototype.onData = function(data) {\r
     data = data.toString().trim();\r
 \r
-    try {\r
-        switch (data) {\r
-            case 'modules':\r
-                this.write('Loaded modules: ' + Object.keys(kiwiModules.getRegisteredModules()).join(', '));\r
-                break;\r
-\r
-            case 'stats':\r
-                this.write('Connected clients: ' + _.size(global.clients.clients).toString());\r
-                this.write('Num. remote hosts: ' + _.size(global.clients.addresses).toString());\r
-                break;\r
-\r
-            case 'rehash':\r
-                rehash.rehashAll();\r
-                this.write('Rehashed');\r
-                break;\r
 \r
-            case 'reconfig':\r
-                if (config.loadConfig()) {\r
-                    this.write('New config file loaded');\r
-                } else {\r
-                    this.write("No new config file was loaded");\r
-                }\r
-                break;\r
 \r
-            case 'exit':\r
-            case 'quit':\r
-                this.socket.destroy();\r
-                break;\r
+    try {\r
+        var data_split = data.split(' ');\r
 \r
-            default:\r
-                this.write('Unrecognised command: ' + data);\r
+        if (typeof socket_commands[data_split[0]] === 'function') {\r
+            socket_commands[data_split[0]].call(this, data_split.slice(1));\r
+        } else {\r
+            this.write('Unrecognised command: ' + data);\r
         }\r
+\r
     } catch (err) {\r
         console.log('[Control error] ' + err);\r
         this.write('An error occured. Check the Kiwi server log for more details');\r
@@ -94,13 +78,86 @@ SocketClient.prototype.onData = function(data) {
 \r
 SocketClient.prototype.onClose = function() {\r
     this.unbindEvents();\r
+    this.socket = null;\r
     console.log('Control connection from ' + this.remoteAddress + ' closed');\r
 };\r
 \r
 \r
 \r
+/**\r
+ * Available commands\r
+ * Each function is run in context of the SocketClient\r
+ */\r
+var socket_commands = {\r
+    module: function(data) {\r
+        switch(data[0]) {\r
+            case 'reload':\r
+                if (!data[1]) {\r
+                    this.write('A module name must be specified');\r
+                    return;\r
+                }\r
+\r
+                if (!kiwiModules.unload(data[1])) {\r
+                    this.write('Module ' + (data[1] || '') + ' is not loaded');\r
+                    return;\r
+                }\r
+\r
+                if (!kiwiModules.load(data[1])) {\r
+                    this.write('Error loading module ' + (data[1] || ''));\r
+                }\r
+                this.write('Module ' + data[1] + ' reloaded');\r
+\r
+                break;\r
+\r
+            case 'list':\r
+            case 'ls':\r
+            default:\r
+                var module_names = [];\r
+                kiwiModules.getRegisteredModules().forEach(function(module) {\r
+                    module_names.push(module.module_name);\r
+                });\r
+                this.write('Loaded modules: ' + module_names.join(', '));\r
+        }\r
+\r
+    },\r
+\r
+    stats: function(data) {\r
+        this.write('Connected clients: ' + _.size(global.clients.clients).toString());\r
+        this.write('Num. remote hosts: ' + _.size(global.clients.addresses).toString());\r
+    },\r
+\r
+    rehash: function(data) {\r
+        rehash.rehashAll();\r
+        this.write('Rehashed');\r
+    },\r
 \r
+    reconfig: function(data) {\r
+        if (config.loadConfig()) {\r
+            this.write('New config file loaded');\r
+        } else {\r
+            this.write("No new config file was loaded");\r
+        }\r
+    },\r
+\r
+    quit: function(data) {\r
+        this.socket.destroy();\r
+        this.socket_closing = true;\r
+    },\r
+    exit: function(data) {\r
+        this.socket.destroy();\r
+        this.socket_closing = true;\r
+    }\r
+};\r
+\r
+\r
+/**\r
+ * Start the control socket server to serve connections\r
+ */\r
 var server = net.createServer(function (socket) {\r
     new SocketClient(socket);\r
 });\r
-server.listen(8888);
\ No newline at end of file
+server.listen(8888);\r
+\r
+control_module.on('dispose', function() {\r
+    server.close();\r
+});\r
diff --git a/server_modules/dnsbl.js b/server_modules/dnsbl.js
new file mode 100644 (file)
index 0000000..984c0b4
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * DNS Blacklist support
+ *
+ * Check the client against a blacklist before connection to an IRC server
+ */
+
+var dns = require('dns'),
+    kiwiModules = require('../server/modules');
+
+
+// The available DNS zones to check against
+var bl_zones = {
+    dronebl: '.dnsbl.dronebl.org'
+};
+
+// The DNS zone we should use
+var current_bl = 'dronebl';
+
+
+var module = new kiwiModules.Module('DNSBL');
+
+module.on('irc connecting', function (event, event_data) {
+    event.wait = true;
+
+    var client_addr = event_data.connection.state.client.websocket.handshake.real_address;
+
+    isBlacklisted(client_addr, function(is_blocked) {
+        if (is_blocked) {
+            var err = new Error('DNSBL blocked');
+            err.code = 'Blacklisted';
+
+            event_data.connection.emit('error', err);
+            event.preventDefault();
+            event.callback();
+
+        } else {
+            event.callback();
+        }
+    });
+});
+
+
+
+// The actual checking against the DNS blacklist
+function isBlacklisted(ip, callback) {
+    var host_lookup = reverseIp(ip) + bl_zones[current_bl];
+
+    dns.resolve4(host_lookup, function(err, domain) {
+        if (err) {
+            // Not blacklisted
+            callback(false);
+        } else {
+            // It is blacklisted
+            callback(true);
+        }
+    });
+}
+
+
+function reverseIp(ip) {
+    return ip.split('.').reverse().join('.');
+}
\ No newline at end of file