CTCP TIME fix
[KiwiIRC.git] / server / kiwi.js
old mode 100644 (file)
new mode 100755 (executable)
index 68c0bb6..1361071
-/*jslint continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
-"use strict";
-var tls = require('tls'),
-    net = require('net'),
-    http = require('http'),
-    https = require('https'),
-    fs = require('fs'),
-    url = require('url'),
-    dns = require('dns'),
-    crypto = require('crypto'),
-    events = require("events"),
-    util = require('util'),
-    ws = require('socket.io'),
-    jsp = require("uglify-js").parser,
-    pro = require("uglify-js").uglify,
-    _ = require('./lib/underscore.min.js'),
-    starttls = require('./lib/starttls.js'),
-    app = require(__dirname + '/app.js');
-
-
-// Libraries may need to know kiwi.js path as __dirname
-// only gives that librarys path. Set it here for usage later.
-this.kiwi_root = __dirname;
-
-
-
-// How to handle log output
-this.log = function(str, level) {
-    level = level || 0;
-    console.log(str);
-}
+var fs          = require('fs'),
+    _           = require('lodash'),
+    WebListener = require('./weblistener.js'),
+    config      = require('./configuration.js'),
+    rehash      = require('./rehash.js'),
+    modules     = require('./modules.js');
 
 
-/*
- * Configuration and rehashing routines
- */
-var config_filename = 'config.json',
-    config_dirs = ['/etc/kiwiirc/', this.kiwi_root + '/'];
-
-this.config = {};
-this.loadConfig = function () {
-    var i, j,
-        nconf = {},
-        cconf = {},
-        found_config = false;
-    
-    for (i in config_dirs) {
-        try {
-            if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) {
-                found_config = true;
-                nconf = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'ascii'));
-                for (j in nconf) {
-                    // If this has changed from the previous config, mark it as changed
-                    if (!_.isEqual(this.config[j], nconf[j])) {
-                        cconf[j] = nconf[j];
-                    }
-
-                    this.config[j] = nconf[j];
-                }
-
-                this.log('Loaded config file ' + config_dirs[i] + config_filename);
-                break;
-            }
-        } catch (e) {
-            switch (e.code) {
-            case 'ENOENT':      // No file/dir
-                break;
-            default:
-                this.log('An error occured parsing the config file ' + config_dirs[i] + config_filename + ': ' + e.message);
-                return false;
-            }
-            continue;
-        }
-    }
-    if (Object.keys(this.config).length === 0) {
-        if (!found_config) {
-            this.log('Couldn\'t find a config file!');
-        }
-        return false;
-    }
-    return [nconf, cconf];
-};
 
+process.chdir(__dirname + '/../');
+config.loadConfig();
 
-// Reloads the config during runtime
-this.rehash = function () {
-    return app.rehash();
-}
 
-// Reloads app.js during runtime for any recoding
-this.recode = function () {
-    if (typeof require.cache[this.kiwi_root + '/app.js'] !== 'undefined'){
-        delete require.cache[this.kiwi_root + '/app.js'];
-    }
+// If we're not running in the forground and we have a log file.. switch
+// console.log to output to a file
+if (process.argv.indexOf('-f') === -1 && global.config.log) {
+    (function () {
+        var log_file_name = global.config.log;
 
-    app = null;
-    app = require(__dirname + '/app.js');
+        if (log_file_name[0] !== '/') {
+            log_file_name = __dirname + '/../' + log_file_name;
+        }
 
-    var objs = {tls:tls, net:net, http:http, https:https, fs:fs, url:url, dns:dns, crypto:crypto, events:events, util:util, ws:ws, jsp:jsp, pro:pro, _:_, starttls:starttls};
-    app.init(objs);
-    app.rebindIRCCommands();
 
-    return true;
-}
 
+        console.log = function() {
+            var logfile = fs.openSync(log_file_name, 'a'),
+                out;
 
+            out = Array.prototype.join.apply(arguments, [' ']);
 
+            // Make sure we out somthing to log and we have an open file
+            if (!out || !logfile) return;
 
+            out += '\n';
+            fs.writeSync(logfile, out, null);
 
+            fs.closeSync(logfile);
+        };
+    })();
+}
 
-/*
- * Before we continue we need the config loaded
- */
-if (!this.loadConfig()) {
-    process.exit(0);
+
+
+// Make sure we have a valid config file and at least 1 server
+if (!global.config || Object.keys(global.config).length === 0) {
+    console.log('Couldn\'t find a valid config.js file (Did you copy the config.example.js file yet?)');
+    process.exit(1);
 }
 
+if ((!global.config.servers) || (global.config.servers.length < 1)) {
+    console.log('No servers defined in config file');
+    process.exit(2);
+}
 
 
 
 
+// Create a plugin interface
+global.modules = new modules.Publisher();
 
+// Register as the active interface
+modules.registerPublisher(global.modules);
 
-/*
- * HTTP file serving
- */
-if (this.config.handle_http) {
-    this.fileServer = new (require('node-static').Server)(__dirname + this.config.public_http);
-    this.jade = require('jade');
-    this.cache = {alljs: '', html: []};
-}
-this.httpServers = [];
-this.httpHandler = function (request, response) {
-    return app.httpHandler(request, response);
+// 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')) {
+            console.log('Module ' + module_name + ' loaded successfuly');
+        } else {
+            console.log('Module ' + module_name + ' failed to load');
+        }
+    });
 }
 
 
 
 
+// Holder for all the connected clients
+global.clients = {
+    clients: Object.create(null),
+    addresses: Object.create(null),
 
+    add: function (client) {
+        this.clients[client.hash] = client;
+        if (typeof this.addresses[client.real_address] === 'undefined') {
+            this.addresses[client.real_address] = Object.create(null);
+        }
+        this.addresses[client.real_address][client.hash] = client;
+    },
+
+    remove: function (client) {
+        if (typeof this.clients[client.hash] !== 'undefined') {
+            delete this.clients[client.hash];
+            delete this.addresses[client.real_address][client.hash];
+            if (Object.keys(this.addresses[client.real_address]).length < 1) {
+                delete this.addresses[client.real_address];
+            }
+        }
+    },
 
-/*
- * Websocket handling
- */
-this.connections = {};
-this.io = [];
-this.websocketListen = function (servers, handler) {
-    return app.websocketListen(servers, handler);
-}
-this.websocketConnection = function (websocket) {
-    return app.websocketConnection(websocket);
-}
-this.websocketDisconnect = function () {
-    return app.websocketDisconnect(this);
-}
-this.websocketMessage = function (msg, callback) {
-    return app.websocketMessage(this, msg, callback);
-}
-this.websocketIRCConnect = function (nick, host, port, ssl, callback) {
-    return app.websocketIRCConnect(this, nick, host, port, ssl, callback);
-}
+    numOnAddress: function (addr) {
+        if (typeof this.addresses[addr] !== 'undefined') {
+            return Object.keys(this.addresses[addr]).length;
+        } else {
+            return 0;
+        }
+    }
+};
 
 
 
 
 /*
- * IRC handling
+ * Web listeners
  */
-this.parseIRCMessage = function (websocket, ircSocket, data) {
-    return app.parseIRCMessage(websocket, ircSocket, data);
-}
-this.ircSocketDataHandler = function (data, websocket, ircSocket) {
-    return app.ircSocketDataHandler(data, websocket, ircSocket);
-}
-this.IRCConnection = function (websocket, nick, host, port, ssl, password, callback) {
-    return app.IRCConnection.call(this, websocket, nick, host, port, ssl, password, callback);
-}
-util.inherits(this.IRCConnection, events.EventEmitter);
 
-this.bindIRCCommands = function (irc_connection, websocket) {
-    return app.bindIRCCommands.call(this, irc_connection, websocket);
-}
-this.rebindIRCCommands = function () {
-    return app.rebindIRCCommands.call(this);
-}
 
+// Start up a weblistener for each found in the config
+_.each(global.config.servers, function (server) {
+    var wl = new WebListener(server, global.config.transports);
 
+    wl.on('connection', function (client) {
+        clients.add(client);
+    });
 
+    wl.on('destroy', function (client) {
+        clients.remove(client);
+    });
 
+    wl.on('listening', webListenerRunning);
+});
 
-
-/*
- * Load up main application source
- */
-if (!this.recode()) {
-    process.exit(0);
+// Once all the listeners are listening, set the processes UID/GID
+var num_listening = 0;
+function webListenerRunning() {
+    num_listening++;
+    if (num_listening === global.config.servers.length) {
+        setProcessUid();
+    }
 }
 
 
 
-// Set the process title
-app.setTitle();
-
-
 
 /*
- * Load the modules as set in the config and print them out
+ * Process settings
  */
-this.kiwi_mod = require('./lib/kiwi_mod.js');
-this.kiwi_mod.loadModules(this.kiwi_root, this.config);
-this.kiwi_mod.printMods();
+
+// Set process title
+process.title = 'kiwiirc';
+
+// Change UID/GID
+function setProcessUid() {
+    if ((global.config.group) && (global.config.group !== '')) {
+        process.setgid(global.config.group);
+    }
+    if ((global.config.user) && (global.config.user !== '')) {
+        process.setuid(global.config.user);
+    }
+}
 
 
 // Make sure Kiwi doesn't simply quit on an exception
-/*process.on('uncaughtException', function (e) {
+process.on('uncaughtException', function (e) {
     console.log('[Uncaught exception] ' + e);
-});*/
+    console.log(e.stack);
+});
+
+
+process.on('SIGUSR1', function() {
+    if (config.loadConfig()) {
+        console.log('New config file loaded');
+    } else {
+        console.log("No new config file was loaded");
+    }
+});
 
-// Start the server up
-this.websocketListen(this.config.servers, this.httpHandler);
 
-// Now we're listening on the network, set our UID/GIDs if required
-app.changeUser();
 
-// Listen for controll messages
+
+/*
+ * Listen for runtime commands
+ */
+
 process.stdin.resume();
-process.stdin.on('data', function (data) { app.manageControll(data); });
+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);
+    }
+});