Add .jshintrc file for server code
[KiwiIRC.git] / server / kiwi.js
index 0dc1d491bea0f6152d64f501562e6e4154c2b529..44c267eae1cf5b7b661be6e3496048d93df5665d 100755 (executable)
@@ -1,15 +1,28 @@
 var fs          = require('fs'),
     _           = require('lodash'),
+    util        = require('util'),
     WebListener = require('./weblistener.js'),
     config      = require('./configuration.js'),
-    rehash      = require('./rehash.js'),
     modules     = require('./modules.js'),
-    Identd      = require('./identd.js');
+    Identd      = require('./identd.js'),
+    Proxy       = require('./proxy.js'),
+    ControlInterface = require('./controlinterface.js');
 
 
 
 process.chdir(__dirname + '/../');
-config.loadConfig();
+
+(function (argv) {
+    var conf_switch = argv.indexOf('-c');
+    if (conf_switch !== -1) {
+        if (argv[conf_switch + 1]) {
+            return config.loadConfig(argv[conf_switch + 1]);
+        }
+    }
+
+    config.loadConfig();
+
+})(process.argv);
 
 
 // If we're not running in the forground and we have a log file.. switch
@@ -28,7 +41,7 @@ if (process.argv.indexOf('-f') === -1 && global.config && global.config.log) {
             var logfile = fs.openSync(log_file_name, 'a'),
                 out;
 
-            out = Array.prototype.join.apply(arguments, [' ']);
+            out = util.format.apply(util, arguments);
 
             // Make sure we out somthing to log and we have an open file
             if (!out || !logfile) return;
@@ -66,7 +79,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');
@@ -82,6 +95,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') {
@@ -106,12 +123,52 @@ global.clients = {
         } else {
             return 0;
         }
+    },
+
+    broadcastKiwiCommand: function (command, data, callback) {
+        var clients = [];
+
+        // Get an array of clients for us to work with
+        for (var client in global.clients.clients) {
+            clients.push(global.clients.clients[client]);
+        }
+
+
+        // Sending of the command in batches
+        var sendCommandBatch = function (list) {
+            var batch_size = 100,
+                cutoff;
+
+            if (list.length >= batch_size) {
+                // If we have more clients than our batch size, call ourself with the next batch
+                setTimeout(function () {
+                    sendCommandBatch(list.slice(batch_size));
+                }, 200);
+
+                cutoff = batch_size;
+
+            } else {
+                cutoff = list.length;
+            }
+
+            list.slice(0, cutoff).forEach(function (client) {
+                if (!client.disposed) {
+                    client.sendKiwiCommand(command, data);
+                }
+            });
+
+            if (cutoff === list.length && typeof callback === 'function') {
+                callback();
+            }
+        };
+
+        sendCommandBatch(clients);
     }
 };
 
 global.servers = {
     servers: Object.create(null),
-    
+
     addConnection: function (connection) {
         var host = connection.irc_host.hostname;
         if (!this.servers[host]) {
@@ -119,7 +176,7 @@ global.servers = {
         }
         this.servers[host].push(connection);
     },
-    
+
     removeConnection: function (connection) {
         var host = connection.irc_host.hostname
         if (this.servers[host]) {
@@ -129,7 +186,7 @@ global.servers = {
             }
         }
     },
-    
+
     numOnHost: function (host) {
         if (this.servers[host]) {
             return this.servers[host].length;
@@ -141,15 +198,37 @@ global.servers = {
 
 
 
+/**
+ * When a new config is loaded, send out an alert to the clients so
+ * so they can reload it
+ */
+config.on('loaded', function () {
+    global.clients.broadcastKiwiCommand('reconfig');
+});
+
+
 
 /*
  * 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();
 }
 
 
@@ -162,17 +241,52 @@ if (global.config.identd && global.config.identd.enabled) {
 
 // Start up a weblistener for each found in the config
 _.each(global.config.servers, function (server) {
-    var wl = new WebListener(server, global.config.transports);
+    if (server.type == 'proxy') {
+        // Start up a kiwi proxy server
+        var serv = new Proxy.ProxyServer();
+        serv.listen(server.port, server.address, server);
 
-    wl.on('connection', function (client) {
-        clients.add(client);
-    });
+        serv.on('listening', function() {
+            console.log('Kiwi proxy listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
+        });
 
-    wl.on('client_dispose', function (client) {
-        clients.remove(client);
-    });
+        serv.on('socket_connected', function(pipe) {
+            // SSL connections have the raw socket as a property
+            var socket = pipe.irc_socket.socket ?
+                    pipe.irc_socket.socket :
+                    pipe.irc_socket;
+
+            pipe.identd_pair = socket.localPort.toString() + '_' + socket.remotePort.toString();
+            global.clients.port_pairs[pipe.identd_pair] = pipe.meta;
+        });
+
+        serv.on('connection_close', function(pipe) {
+            delete global.clients.port_pairs[pipe.identd_pair];
+        });
 
-    wl.on('listening', webListenerRunning);
+    } else {
+        // Start up a kiwi web server
+        var wl = new WebListener(server, global.config.transports);
+
+        wl.on('connection', function (client) {
+            clients.add(client);
+        });
+
+        wl.on('client_dispose', function (client) {
+            clients.remove(client);
+        });
+
+        wl.on('listening', function () {
+            console.log('Listening on %s:%s %s SSL', server.address, server.port, (server.ssl ? 'with' : 'without'));
+            webListenerRunning();
+        });
+
+        wl.on('error', function (err) {
+            console.log('Error listening on %s:%s: %s', server.address, server.port, err.code);
+            // TODO: This should probably be refactored. ^JA
+            webListenerRunning();
+        });
+    }
 });
 
 // Once all the listeners are listening, set the processes UID/GID
@@ -221,41 +335,14 @@ 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());
+});
 
 
 /*
  * Listen for runtime commands
  */
-
 process.stdin.resume();
-process.stdin.on('data', function (buffered) {
-    var data = buffered.toString().trim();
-
-    switch (data) {
-        case 'stats':
-            console.log('Connected clients: ' + _.size(global.clients.clients).toString());
-            console.log('Num. remote hosts: ' + _.size(global.clients.addresses).toString());
-            break;
-
-        case 'reconfig':
-            if (config.loadConfig()) {
-                console.log('New config file loaded');
-            } else {
-                console.log("No new config file was loaded");
-            }
-
-            break;
-
-
-        case 'rehash':
-            (function () {
-                rehash.rehashAll();
-                console.log('Rehashed');
-            })();
-
-            break;
-
-        default:
-            console.log('Unrecognised command: ' + data);
-    }
-});
+new ControlInterface(process.stdin, process.stdout, {prompt: ''});