SECURITY FIX: Kiwi is vulnerable to XSS attack due to unsanitised topic text. Issue...
[KiwiIRC.git] / server / kiwi.js
index 24c26d9d28afc52d8604c2f5ba04587b9a3c0861..e8dbc73b219ec3f7d6deb21f5591d28f2657a234 100755 (executable)
-var fs          = require('fs'),
-    WebListener = require('./web.js').WebListener;
+/*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'),
+    node_static = require('node-static'),
+    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');
 
-//load config
 
+// 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);
+}
+
+
+/*
+ * Configuration and rehashing routines
+ */
 var config_filename = 'config.json',
-    config_dirs = ['/etc/kiwiirc/', __dirname + '/'];
-
-var config = Object.create(null);
-for (var i in config_dirs) {
-    try {
-        if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) {
-            config = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'utf-8'));
-            console.log('Loaded config file ' + config_dirs[i] + config_filename);
-            break;
+    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;
         }
-    } catch (e) {
-        switch (e.code) {
-        case 'ENOENT':      // No file/dir
-            break;
-        default:
-            console.log('An error occured parsing the config file ' + config_dirs[i] + config_filename + ': ' + e.message);
-            return false;
+    }
+    if (Object.keys(this.config).length === 0) {
+        if (!found_config) {
+            this.log('Couldn\'t find a config file!');
         }
-        continue;
+        return false;
     }
+    return [nconf, cconf];
+};
+
+
+// Reloads the config during runtime
+this.rehash = function () {
+    return app.rehash();
 }
 
-if (Object.keys(config).length === 0) {
-    console.log('Couldn\'t find a valid config file!');
-    process.exit(1);
+// 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'];
+    }
+
+    app = null;
+    app = require(__dirname + '/app.js');
+
+    var objs = {tls:tls, net:net, http:http, https:https, node_static:node_static, 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;
 }
 
-if ((!config.servers) || (config.servers.length < 1)) {
-    console.log('No servers defined in config file');
-    process.exit(2);
+
+
+
+
+
+/*
+ * Before we continue we need the config loaded
+ */
+if (!this.loadConfig()) {
+    process.exit(0);
 }
 
-//Create web listeners
 
-var clients = [];
-_.each(config.servers, function (server) {
-    var wl = new WebListener(server, config.transports);
-    wl.on('connection', function (client) {
-        clients.push(client);
-    });
-    wl.on('destroy', function (client) {
-        clients = _.reject(clients, function (c) {
-            return client === c;
-        });
-    });
-});
 
 
-//Set process title
-process.title = 'Kiwi IRC';
 
-//Change UID/GID
-if ((config.user) && (config.user !== '')) {
-    process.setuid(config.user);
+
+
+/*
+ * HTTP file serving
+ */
+if (this.config.handle_http) {
+    this.fileServer = new (require('node-static').Server)(__dirname + this.config.public_http);
+    this.cache = {alljs: '', html: []};
 }
-if ((config.group) && (config.group !== '')) {
-    process.setgid(config.group);
+this.httpServers = [];
+this.httpHandler = function (request, response) {
+    return app.httpHandler(request, response);
 }
 
-//Listen to STDIN
-process.stdin.resume();
-process.stdin.on('data', function (data) {
-    console.log(data.toString());
+
+
+
+
+
+/*
+ * 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.websocketKiwiMessage = function (msg, callback) {
+    return app.websocketKiwiMessage(this, msg, callback);
+}
+this.websocketIRCConnect = function (nick, host, port, ssl, callback) {
+    return app.websocketIRCConnect(this, nick, host, port, ssl, callback);
+}
+
+
+
+
+/*
+ * IRC handling
+ */
+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);
+}
+
+
+
+
+
+
+/*
+ * Load up main application source
+ */
+if (!this.recode()) {
+    process.exit(0);
+}
+
+
+
+// Set the process title
+app.setTitle();
+
+
+
+/*
+ * Load the modules as set in the config and print them out
+ */
+this.kiwi_mod = require('./lib/kiwi_mod.js');
+this.kiwi_mod.loadModules(this.kiwi_root, this.config);
+this.kiwi_mod.printMods();
+
+
+// Make sure Kiwi doesn't simply quit on an exception
+process.on('uncaughtException', function (e) {
+    console.log('[Uncaught exception] ' + e);
 });
+
+// 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
+process.stdin.resume();
+process.stdin.on('data', function (data) { app.manageControll(data); });
+
+
+
+