Merged old+new server
authorDarren <darren@darrenwhitlen.com>
Mon, 8 Oct 2012 21:15:01 +0000 (22:15 +0100)
committerDarren <darren@darrenwhitlen.com>
Mon, 8 Oct 2012 21:15:01 +0000 (22:15 +0100)
server_merging/app.js [new file with mode: 0644]
server_merging/cert.pem [new file with mode: 0644]
server_merging/config.json [new file with mode: 0644]
server_merging/kiwi.js [new file with mode: 0644]
server_merging/kiwi_modules/forcessl.js [new file with mode: 0644]
server_merging/kiwi_modules/spamfilter.js [new file with mode: 0644]
server_merging/kiwi_modules/statistics.js [new file with mode: 0644]
server_merging/lib/kiwi_mod.js [new file with mode: 0644]
server_merging/lib/starttls.js [new file with mode: 0644]
server_merging/lib/underscore.min.js [new file with mode: 0644]
server_merging/server.key [new file with mode: 0644]

diff --git a/server_merging/app.js b/server_merging/app.js
new file mode 100644 (file)
index 0000000..c197401
--- /dev/null
@@ -0,0 +1,1149 @@
+/*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
+/*globals kiwi_root */
+var tls = null,
+    net = null,
+    http = null,
+    node_static = null,
+    https = null,
+    fs = null,
+    url = null,
+    dns = null,
+    crypto = null,
+    events = null,
+    util = null,
+    ws = null,
+    jsp = null,
+    pro = null,
+    _ = null,
+    starttls = null,
+    kiwi = null;
+
+var file_server;
+
+this.init = function (objs) {
+    tls = objs.tls;
+    net = objs.net;
+    http = objs.http;
+    https = objs.https;
+    node_static = objs.node_static;
+    fs = objs.fs;
+    url = objs.url;
+    dns = objs.dns;
+    crypto = objs.crypto;
+    events = objs.events;
+    util = objs.util;
+    ws = objs.ws;
+    jsp = objs.jsp;
+    pro = objs.pro;
+    _ = objs._;
+    starttls = objs.starttls;
+    kiwi = require('./kiwi.js');
+
+    util.inherits(this.IRCConnection, events.EventEmitter);
+
+    file_server = new StaticFileServer();
+};
+
+
+
+
+
+
+/*
+ * Some process changes
+ */
+this.setTitle = function () {
+    process.title = 'kiwiirc';
+};
+
+this.changeUser = function () {
+    if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
+        try {
+            process.setgid(kiwi.config.group);
+        } catch (err) {
+            kiwi.log('Failed to set gid: ' + err);
+            process.exit();
+        }
+    }
+
+    if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
+        try {
+            process.setuid(kiwi.config.user);
+        } catch (e) {
+            kiwi.log('Failed to set uid: ' + e);
+            process.exit();
+        }
+    }
+};
+
+
+
+function StaticFileServer(public_html) {
+    public_html = public_html || 'client/';
+    this.file_server = new node_static.Server(public_html);
+}
+
+StaticFileServer.prototype.serve = function (request, response) {
+    // Any requests for /client to load the index file
+    if (request.url.match(/^\/client/)) {
+        request.url = '/';
+    }
+
+    this.file_server.serve(request, response, function (err) {
+        if (err) {
+            response.writeHead(err.status, err.headers);
+            response.end();
+        }
+    });
+};
+
+
+
+
+
+
+
+
+var ircNumerics = {
+    RPL_WELCOME:            '001',
+    RPL_MYINFO:             '004',
+    RPL_ISUPPORT:           '005',
+    RPL_WHOISUSER:          '311',
+    RPL_WHOISSERVER:        '312',
+    RPL_WHOISOPERATOR:      '313',
+    RPL_WHOISIDLE:          '317',
+    RPL_ENDOFWHOIS:         '318',
+    RPL_WHOISCHANNELS:      '319',
+    RPL_LISTSTART:          '321',
+    RPL_LIST:               '322',
+    RPL_LISTEND:            '323',
+    RPL_NOTOPIC:            '331',
+    RPL_TOPIC:              '332',
+    RPL_TOPICWHOTIME:       '333',
+    RPL_NAMEREPLY:          '353',
+    RPL_ENDOFNAMES:         '366',
+    RPL_BANLIST:            '367',
+    RPL_ENDOFBANLIST:       '368',
+    RPL_MOTD:               '372',
+    RPL_MOTDSTART:          '375',
+    RPL_ENDOFMOTD:          '376',
+    RPL_WHOISMODES:         '379',
+    ERR_NOSUCHNICK:         '401',
+    ERR_CANNOTSENDTOCHAN:   '404',
+    ERR_TOOMANYCHANNELS:    '405',
+    ERR_NICKNAMEINUSE:      '433',
+    ERR_USERNOTINCHANNEL:   '441',
+    ERR_NOTONCHANNEL:       '442',
+    ERR_NOTREGISTERED:      '451',
+    ERR_LINKCHANNEL:        '470',
+    ERR_CHANNELISFULL:      '471',
+    ERR_INVITEONLYCHAN:     '473',
+    ERR_BANNEDFROMCHAN:     '474',
+    ERR_BADCHANNELKEY:      '475',
+    ERR_CHANOPRIVSNEEDED:   '482',
+    RPL_STARTTLS:           '670'
+};
+
+this.bindIRCCommands = function (irc_connection, websocket) {
+    var bound_events = [],
+        bindCommand = function (command, listener) {
+            command = 'irc_' + command;
+            irc_connection.on(command, listener);
+            bound_events.push({"command": command, "listener": listener});
+        };
+
+    bindCommand('PING', function (msg) {
+        websocket.sendServerLine('PONG ' + msg.trailing);
+    });
+
+    bindCommand(ircNumerics.RPL_WELCOME, function (msg) {
+        if (irc_connection.IRC.CAP.negotiating) {
+            irc_connection.IRC.CAP.negotiating = false;
+            irc_connection.IRC.CAP.enabled = [];
+            irc_connection.IRC.CAP.requested = [];
+            irc_connection.IRC.registered = true;
+        }
+        var nick =  msg.params.split(' ')[0];
+        websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
+    });
+
+    bindCommand(ircNumerics.RPL_ISUPPORT, function (msg) {
+        var opts = msg.params.split(" "),
+            opt,
+            i,
+            j,
+            regex,
+            matches;
+        for (i = 0; i < opts.length; i++) {
+            opt = opts[i].split("=", 2);
+            opt[0] = opt[0].toUpperCase();
+            irc_connection.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
+            if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt[0])) {
+                if (opt[0] === 'PREFIX') {
+                    regex = /\(([^)]*)\)(.*)/;
+                    matches = regex.exec(opt[1]);
+                    if ((matches) && (matches.length === 3)) {
+                        irc_connection.IRC.options[opt[0]] = [];
+                        for (j = 0; j < matches[2].length; j++) {
+                            irc_connection.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
+                        }
+
+                    }
+                } else if (opt[0] === 'CHANTYPES') {
+                    irc_connection.IRC.options.CHANTYPES = irc_connection.IRC.options.CHANTYPES.split('');
+                } else if (opt[0] === 'CHANMODES') {
+                    irc_connection.IRC.options.CHANMODES = option[1].split(',');
+                } else if (opt[0] === 'NAMESX') {
+                    websocket.sendServerLine('PROTOCTL NAMESX');
+                }
+            }
+        }
+
+        websocket.sendClientEvent('options', {server: '', "options": irc_connection.IRC.options});
+    });
+
+    bindCommand(ircNumerics.RPL_ENDOFWHOIS, function (msg) {
+        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: true});
+    });
+
+    bindCommand(ircNumerics.RPL_WHOISUSER, function (msg) {
+        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+    });
+
+    bindCommand(ircNumerics.RPL_WHOISSERVER, function (msg) {
+        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+    });
+
+    bindCommand(ircNumerics.RPL_WHOISOPERATOR, function (msg) {
+        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+    });
+
+    bindCommand(ircNumerics.RPL_WHOISCHANNELS, function (msg) {
+        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+    });
+
+    bindCommand(ircNumerics.RPL_WHOISMODES, function (msg) {
+        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+    });
+
+    bindCommand(ircNumerics.RPL_LISTSTART, function (msg) {
+        websocket.sendClientEvent('list_start', {server: ''});
+        websocket.kiwi.buffer.list = [];
+    });
+
+    bindCommand(ircNumerics.RPL_LISTEND, function (msg) {
+        if (websocket.kiwi.buffer.list.length > 0) {
+            websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
+                return channel.num_users;
+            });
+            websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
+            websocket.kiwi.buffer.list = [];
+        }
+        websocket.sendClientEvent('list_end', {server: ''});
+    });
+
+    bindCommand(ircNumerics.RPL_LIST, function (msg) {
+        var parts, channel, num_users, topic;
+
+        parts = msg.params.split(' ');
+        channel = parts[1];
+        num_users = parts[2];
+        topic = msg.trailing;
+
+        //websocket.sendClientEvent('list_channel', {
+        websocket.kiwi.buffer.list.push({
+            server: '',
+            channel: channel,
+            topic: topic,
+            //modes: modes,
+            num_users: parseInt(num_users, 10)
+        });
+
+        if (websocket.kiwi.buffer.list.length > 200) {
+            websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
+                return channel.num_users;
+            });
+            websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
+            websocket.kiwi.buffer.list = [];
+        }
+    });
+
+    bindCommand(ircNumerics.RPL_WHOISIDLE, function (msg) {
+        var params = msg.params.split(" ", 4),
+            rtn = {server: '', nick: params[1], idle: params[2]};
+        if (params[3]) {
+            rtn.logon = params[3];
+        }
+        websocket.sendClientEvent('whois', rtn);
+    });
+
+    bindCommand(ircNumerics.RPL_MOTD, function (msg) {
+        websocket.kiwi.buffer.motd += msg.trailing + '\n';
+    });
+
+    bindCommand(ircNumerics.RPL_MOTDSTART, function (msg) {
+        websocket.kiwi.buffer.motd = '';
+    });
+
+    bindCommand(ircNumerics.RPL_ENDOFMOTD, function (msg) {
+        websocket.sendClientEvent('motd', {server: '', 'msg': websocket.kiwi.buffer.motd});
+    });
+
+    bindCommand(ircNumerics.RPL_NAMEREPLY, function (msg) {
+        var params = msg.params.split(" "),
+            chan = params[2],
+            users = msg.trailing.split(" "),
+            nicklist = [],
+            i = 0;
+
+        _.each(users, function (user) {
+            var j, k, modes = [];
+            for (j = 0; j < user.length; j++) {
+                for (k = 0; k < irc_connection.IRC.options.PREFIX.length; k++) {
+                    if (user.charAt(j) === irc_connection.IRC.options.PREFIX[k].symbol) {
+                        modes.push(irc_connection.IRC.options.PREFIX[k].mode);
+                    }
+                }
+            }
+            nicklist.push({nick: user, modes: modes});
+            if (i++ >= 50) {
+                websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
+                nicklist = [];
+                i = 0;
+            }
+        });
+        if (i > 0) {
+            websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
+        } else {
+            kiwi.log("oops");
+        }
+    });
+
+    bindCommand(ircNumerics.RPL_ENDOFNAMES, function (msg) {
+        websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
+    });
+
+    bindCommand(ircNumerics.ERR_LINKCHANNEL, function (msg) {
+        var params = msg.params.split(" ");
+        websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
+    });
+
+    bindCommand(ircNumerics.ERR_NOSUCHNICK, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.RPL_BANLIST, function (msg) {
+        var params = msg.params.split(" ");
+        kiwi.log(params);
+        websocket.sendClientEvent('banlist', {server: '', channel: params[1], banned: params[2], banned_by: params[3], banned_at: params[4]});
+    });
+
+    bindCommand(ircNumerics.RPL_ENDOFBANLIST, function (msg) {
+        websocket.sendClientEvent('banlist_end', {server: '', channel: msg.params.split(" ")[1]});
+    });
+
+    bindCommand('JOIN', function (msg) {
+        var channel;
+
+        // Some BNC's send malformed JOIN causing the channel to be as a
+        // parameter instead of trailing.
+        if (typeof msg.trailing === 'string' && msg.trailing !== '') {
+            channel = msg.trailing;
+        } else if (typeof msg.params === 'string' && msg.params !== '') {
+            channel = msg.params;
+        }
+
+        websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
+        if (msg.nick === irc_connection.IRC.nick) {
+            websocket.sendServerLine('NAMES ' + msg.trailing);
+        }
+    });
+
+    bindCommand('PART', function (msg) {
+        websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
+    });
+
+    bindCommand('KICK', function (msg) {
+        var params = msg.params.split(" ");
+        websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
+    });
+
+    bindCommand('QUIT', function (msg) {
+        websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
+    });
+
+    bindCommand('NOTICE', function (msg) {
+        if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
+            // It's a CTCP response
+            websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
+        } else {
+            websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
+        }
+    });
+
+    bindCommand('NICK', function (msg) {
+        websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
+    });
+
+    bindCommand('TOPIC', function (msg) {
+        var obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
+        websocket.sendClientEvent('topic', obj);
+    });
+
+    bindCommand(ircNumerics.RPL_TOPIC, function (msg) {
+        var obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
+        websocket.sendClientEvent('topic', obj);
+    });
+
+    bindCommand(ircNumerics.RPL_NOTOPIC, function (msg) {
+        var obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
+        websocket.sendClientEvent('topic', obj);
+    });
+
+    bindCommand(ircNumerics.RPL_TOPICWHOTIME, function (msg) {
+        var parts = msg.params.split(' '),
+            nick = parts[2],
+            channel = parts[1],
+            when = parts[3],
+            obj = {nick: nick, channel: channel, when: when};
+        websocket.sendClientEvent('topicsetby', obj);
+    });
+
+    bindCommand('MODE', function (command) {
+        /*
+        var opts = msg.params.split(" "),
+            params = {nick: msg.nick};
+
+        switch (opts.length) {
+        case 1:
+            params.effected_nick = opts[0];
+            params.mode = msg.trailing;
+            break;
+        case 2:
+            params.channel = opts[0];
+            params.mode = opts[1];
+            break;
+        default:
+            params.channel = opts[0];
+            params.mode = opts[1];
+            params.effected_nick = opts[2];
+            break;
+        }
+        websocket.sendClientEvent('mode', params);
+        */
+        command.params = command.params.split(" ");
+        var chanmodes = irc_connection.IRC.options.CHANMODES,
+            prefixes = irc_connection.IRC.options.PREFIX,
+            always_param = chanmodes[0].concat(chanmodes[1]),
+            modes = [],
+            has_param, i, j, add;
+        
+        prefixes = _.reduce(prefixes, function (list, prefix) {
+            list.push(prefix.mode);
+            return list;
+        }, []);
+        always_param = always_param.split('').concat(prefixes);
+        
+        has_param = function (mode, add) {
+            if (_.find(always_param, function (m) {
+                return m === mode;
+            })) {
+                return true;
+            } else if (add && _.find(chanmodes[2].split(''), function (m) {
+                return m === mode;
+            })) {
+                return true;
+            } else {
+                return false;
+            }
+        };
+        
+        if (!command.params[1]) {
+            command.params[1] = command.trailing;
+        }
+        j = 0;
+        for (i = 0; i < command.params[1].length; i++) {
+            switch (command.params[1][i]) {
+                case '+':
+                    add = true;
+                    break;
+                case '-':
+                    add = false;
+                    break;
+                default:
+                    if (has_param(command.params[1][i], add)) {
+                        modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: command.params[2 + j]});
+                        j++;
+                    } else {
+                        modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: null});
+                    }
+            }
+        }
+        
+        websocket.sendClientEvent('mode', {
+            target: command.params[0],
+            nick: command.nick || command.prefix || '',
+            modes: modes
+        });
+    });
+
+    bindCommand('PRIVMSG', function (msg) {
+        var tmp, namespace, obj;
+        if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
+            // It's a CTCP request
+            if (msg.trailing.substr(1, 6) === 'ACTION') {
+                websocket.sendClientEvent('action', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(7, msg.trailing.length - 2)});
+            } else if (msg.trailing.substr(1, 4) === 'KIWI') {
+                tmp = msg.trailing.substr(6, msg.trailing.length - 2);
+                namespace = tmp.split(' ', 1)[0];
+                websocket.sendClientEvent('kiwi', {namespace: namespace, data: tmp.substr(namespace.length + 1)});
+
+            } else if (msg.trailing.substr(1, 7) === 'VERSION') {
+                irc_connection.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
+            } else {
+                websocket.sendClientEvent('ctcp_request', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
+            }
+        } else {
+            obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
+            websocket.sendClientEvent('msg', obj);
+        }
+    });
+
+    bindCommand('CAP', function (msg) {
+        var caps = kiwi.config.cap_options,
+            options = msg.trailing.split(" "),
+            opts;
+
+        switch (_.last(msg.params.split(" "))) {
+        case 'LS':
+            opts = '';
+            _.each(_.intersect(caps, options), function (cap) {
+                if (opts !== '') {
+                    opts += " ";
+                }
+                opts += cap;
+                irc_connection.IRC.CAP.requested.push(cap);
+            });
+            if (opts.length > 0) {
+                websocket.sendServerLine('CAP REQ :' + opts);
+            } else {
+                websocket.sendServerLine('CAP END');
+            }
+            // TLS is special
+            /*if (_.include(options, 'tls')) {
+                websocket.sendServerLine('STARTTLS');
+                ircSocket.IRC.CAP.requested.push('tls');
+            }*/
+            break;
+        case 'ACK':
+            _.each(options, function (cap) {
+                irc_connection.IRC.CAP.enabled.push(cap);
+            });
+            if (_.last(msg.params.split(" ")) !== '*') {
+                irc_connection.IRC.CAP.requested = [];
+                irc_connection.IRC.CAP.negotiating = false;
+                websocket.sendServerLine('CAP END');
+            }
+            break;
+        case 'NAK':
+            irc_connection.IRC.CAP.requested = [];
+            irc_connection.IRC.CAP.negotiating = false;
+            websocket.sendServerLine('CAP END');
+            break;
+        }
+    });
+        /*case ircNumerics.RPL_STARTTLS:
+            try {
+                IRC = ircSocket.IRC;
+                listeners = ircSocket.listeners('data');
+                ircSocket.removeAllListeners('data');
+                ssl_socket = starttls(ircSocket, {}, function () {
+                    ssl_socket.on("data", function (data) {
+                        ircSocketDataHandler(data, websocket, ssl_socket);
+                    });
+                    ircSocket = ssl_socket;
+                    ircSocket.IRC = IRC;
+                    _.each(listeners, function (listener) {
+                        ircSocket.addListener('data', listener);
+                    });
+                });
+                //log(ircSocket);
+            } catch (e) {
+                kiwi.log(e);
+            }
+            break;*/
+    bindCommand(ircNumerics.ERR_CANNOTSENDTOCHAN, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_TOOMANYCHANNELS, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_USERNOTINCHANNEL, function (msg) {
+        var params = msg.params.split(" ");
+        websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
+    });
+
+    bindCommand(ircNumerics.ERR_NOTONCHANNEL, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_CHANNELISFULL, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_INVITEONLYCHAN, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_BANNEDFROMCHAN, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_BADCHANNELKEY, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_CHANOPRIVSNEEDED, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
+    });
+
+    bindCommand(ircNumerics.ERR_NICKNAMEINUSE, function (msg) {
+        websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
+    });
+
+    bindCommand('ERROR', function (msg) {
+        irc_connection.end();
+        websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
+        websocket.disconnect();
+    });
+
+    bindCommand(ircNumerics.ERR_NOTREGISTERED, function (msg) {
+        if (irc_connection.IRC.registered) {
+            kiwi.log('Kiwi thinks user is registered, but the IRC server thinks differently');
+        }
+    });
+
+    return bound_events;
+};
+
+this.rebindIRCCommands = function () {
+    _.each(kiwi.connections, function (con) {
+        _.each(con.sockets, function (sock) {
+            sock.ircConnection.rebindIRCCommands();
+        });
+    });
+};
+
+
+this.httpHandler = function (request, response) {
+    var uri, subs;
+    
+    uri = url.parse(request.url, true);
+    subs = uri.pathname.substr(0, 4);
+    
+    if (uri.pathname.substr(0, 10) === '/socket.io') {
+        return;
+    } else {
+        file_server.serve(request, response);
+    }
+};
+
+
+
+
+this.websocketListen = function (servers, handler) {
+    if (kiwi.httpServers.length > 0) {
+        _.each(kiwi.httpServers, function (hs) {
+            hs.close();
+        });
+        kiwi.httpsServers = [];
+    }
+
+    _.each(servers, function (server) {
+        var hs, opts;
+        if (server.secure === true) {
+            // Start some SSL server up
+            opts = {
+                key: fs.readFileSync(__dirname + '/' + server.ssl_key),
+                cert: fs.readFileSync(__dirname + '/' + server.ssl_cert)
+            };
+
+            // Do we have an intermediate certificate?
+            if (typeof server.ssl_ca !== 'undefined') {
+                opts.ca = fs.readFileSync(__dirname + '/' + server.ssl_ca);
+            }
+
+            hs = https.createServer(opts, handler);
+            kiwi.io.push(ws.listen(hs, {secure: true}));
+            hs.listen(server.port, server.address);
+            kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' with SSL');
+        } else {
+            // Start some plain-text server up
+            hs = http.createServer(handler);
+            kiwi.io.push(ws.listen(hs, {secure: false}));
+            hs.listen(server.port, server.address);
+            kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' without SSL');
+        }
+
+        kiwi.httpServers.push(hs);
+    });
+
+    _.each(kiwi.io, function (io) {
+        io.set('log level', 1);
+        io.enable('browser client minification');
+        io.enable('browser client etag');
+        io.set('transports', kiwi.config.transports);
+
+        io.of('/kiwi').authorization(function (handshakeData, callback) {
+            var address = handshakeData.address.address;
+            if (typeof kiwi.connections[address] === 'undefined') {
+                kiwi.connections[address] = {count: 0, sockets: []};
+            }
+            callback(null, true);
+        }).on('connection', kiwi.websocketConnection);
+        io.of('/kiwi').on('error', console.log);
+    });
+};
+
+
+
+
+
+
+this.websocketConnection = function (websocket) {
+    var con;
+    kiwi.log("New connection!");
+    websocket.kiwi = {address: websocket.handshake.address.address, buffer: {list: []}};
+    con = kiwi.connections[websocket.kiwi.address];
+
+    if (con.count >= kiwi.config.max_client_conns) {
+        websocket.emit('too_many_connections');
+        websocket.disconnect();
+    } else {
+        con.count += 1;
+        con.sockets.push(websocket);
+
+        websocket.sendClientEvent = function (event_name, data) {
+            var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
+            if (ev === null) {
+                return;
+            }
+
+            //data.event = event_name;
+            websocket.emit('irc', {command:event_name, data:data});
+        };
+
+        websocket.sendServerLine = function (data, eol, callback) {
+            if ((arguments.length < 3) && (typeof eol === 'function')) {
+                callback = eol;
+            }
+            eol = (typeof eol !== 'string') ? '\r\n' : eol;
+
+            try {
+                websocket.ircConnection.write(data + eol, 'utf-8', callback);
+            } catch (e) { }
+        };
+
+        websocket.on('kiwi', kiwi.websocketKiwiMessage);
+        websocket.on('irc', kiwi.websocketMessage);
+        websocket.on('disconnect', kiwi.websocketDisconnect);
+        websocket.on('error', console.log);
+    }
+};
+
+
+this.IRCConnection = function (websocket, nick, host, port, ssl, password, callback) {
+    var ircSocket,
+        that = this,
+        regex,
+        onConnectHandler,
+        bound_events;
+
+    events.EventEmitter.call(this);
+
+    onConnectHandler = function () {
+        that.IRC.nick = nick;
+        // Send the login data
+        dns.reverse(websocket.kiwi.address, function (err, domains) {
+            websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
+            if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
+                websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
+            }
+            if (password) {
+                websocket.sendServerLine('PASS ' + password);
+            }
+            websocket.sendServerLine('CAP LS');
+            websocket.sendServerLine('NICK ' + nick);
+            websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);
+
+            that.connected = true;
+            that.emit('connect');
+        });
+    };
+
+    if (!ssl) {
+        ircSocket = net.createConnection(port, host);
+        ircSocket.on('connect', onConnectHandler);
+    } else {
+        ircSocket = tls.connect(port, host, {}, onConnectHandler);
+    }
+
+    ircSocket.setEncoding('utf-8');
+    this.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
+
+    this.on('error', function (e) {
+        if (that.IRC.registered) {
+            websocket.emit('disconnect');
+        } else {
+            websocket.emit('error', e.message);
+        }
+    });
+
+    ircSocket.on('error', function (e) {
+        that.connected = false;
+        that.emit('error', e);
+        that.destroySoon();
+    });
+
+    if (typeof callback === 'function') {
+        this.on('connect', callback);
+    }
+
+    regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
+    ircSocket.holdLast = false;
+    ircSocket.held = '';
+    ircSocket.on('data', function (data) {
+        var i, msg;
+        if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
+            data = ircSocket.held + data;
+            ircSocket.holdLast = false;
+            ircSocket.held = '';
+        }
+        if (data.substr(-1) !== '\n') {
+            ircSocket.holdLast = true;
+        }
+        data = data.split("\n");
+        for (i = 0; i < data.length; i++) {
+            if (data[i]) {
+                if ((ircSocket.holdLast) && (i === data.length - 1)) {
+                    ircSocket.held = data[i];
+                    break;
+                }
+
+                // We have a complete line of data, parse it!
+                msg = regex.exec(data[i].replace(/^\r+|\r+$/, ''));
+                if (msg) {
+                    msg = {
+                        prefix:     msg[1],
+                        nick:       msg[2],
+                        ident:      msg[3],
+                        hostname:   msg[4] || '',
+                        command:    msg[5],
+                        params:     msg[6] || '',
+                        trailing:   (msg[7]) ? msg[7].trim() : ''
+                    };
+                    that.emit('irc_' + msg.command.toUpperCase(), msg);
+                    if (that.listeners('irc_' + msg.command.toUpperCase()).length < 1) {
+                        kiwi.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
+                    }
+                } else {
+                    kiwi.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
+                }
+            }
+        }
+    });
+
+    if (callback) {
+        ircSocket.on('connect', callback);
+    }
+
+    ircSocket.on('end', function () {
+        that.connected = false;
+        that.emit('disconnect', false);
+    });
+
+    ircSocket.on('close', function (had_error) {
+        that.connected = false;
+        that.emit('disconnect', had_error);
+    });
+
+    ircSocket.on('timeout', function () {
+        ircSocket.destroy();
+        that.connected = false;
+        that.emit('error', {message: 'Connection timed out'});
+    });
+
+    ircSocket.on('drain', function () {
+        that.emit('drain');
+    });
+
+    this.write = function (data, encoding, callback) {
+        ircSocket.write(data, encoding, callback);
+    };
+
+    this.end = function (data, encoding, callback) {
+        that.connected = false;
+        ircSocket.end(data, encoding, callback);
+    };
+
+    this.destroySoon = function () {
+        ircSocket.destroySoon();
+    };
+
+    bound_events = kiwi.bindIRCCommands(this, websocket);
+
+    this.rebindIRCCommands = function () {
+        _.each(bound_events, function (event) {
+            that.removeListener(event.command, event.listener);
+        });
+        bound_events = kiwi.bindIRCCommands(that, websocket);
+    };
+    
+    that.on('error', console.log);
+    
+};
+
+
+
+this.websocketKiwiMessage = function (websocket, msg, callback) {
+    websocket.ircConnection = new kiwi.IRCConnection(websocket, msg.nick, msg.hostname, msg.port, msg.ssl, msg.password, callback);
+};
+
+
+this.websocketMessage = function (websocket, msg, callback) {
+    var args, obj, channels, keys;
+    //try {
+        if ((callback) && (typeof (callback) !== 'function')) {
+            callback = null;
+        }
+        try {
+            msg.data = JSON.parse(msg.data);
+        } catch (e) {
+            kiwi.log('[app.websocketMessage] JSON parsing error ' + msg.data);
+            return;
+        }
+        args = msg.data.args;
+        switch (msg.data.method) {
+        case 'privmsg':
+            if ((args.target) && (args.msg)) {
+                obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
+                if (obj !== null) {
+                    websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg, callback);
+                }
+            }
+            break;
+        case 'ctcp':
+            if ((args.target) && (args.type)) {
+                if (args.request) {
+                    websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1), callback);
+                } else {
+                    websocket.sendServerLine('NOTICE ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1), callback);
+                }
+            }
+            break;
+
+        case 'raw':
+            websocket.sendServerLine(args.data, callback);
+            break;
+
+        case 'join':
+            if (args.channel) {
+                channels = args.channel.split(",");
+                keys = (args.key) ? args.key.split(",") : [];
+                _.each(channels, function (chan, index) {
+                    websocket.sendServerLine('JOIN ' + chan + ' ' + (keys[index] || ''), callback);
+                });
+            }
+            break;
+
+        case 'part':
+            if (args.channel) {
+                _.each(args.channel.split(","), function (chan) {
+                    websocket.sendServerLine('PART ' + chan, callback);
+                });
+            }
+            break;
+
+        case 'topic':
+            if (args.channel) {
+                if (args.topic) {
+                    websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic, callback);
+                } else {
+                    websocket.sendServerLine('TOPIC ' + args.channel, callback);
+                }
+            }
+            break;
+
+        case 'kick':
+            if ((args.channel) && (args.nick)) {
+                websocket.sendServerLine('KICK ' + args.channel + ' ' + args.nick + ':' + args.reason, callback);
+            }
+            break;
+
+        case 'quit':
+            websocket.ircConnection.end('QUIT :' + args.message + '\r\n');
+            websocket.sentQUIT = true;
+            websocket.ircConnection.destroySoon();
+            websocket.disconnect();
+            break;
+
+        case 'notice':
+            if ((args.target) && (args.msg)) {
+                websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg, callback);
+            }
+            break;
+
+        case 'mode':
+            if ((args.target) && (args.mode)) {
+                websocket.sendServerLine('MODE ' + args.target + ' ' + args.mode + ' ' + args.params, callback);
+            }
+            break;
+
+        case 'nick':
+            if (args.nick) {
+                websocket.sendServerLine('NICK ' + args.nick, callback);
+            }
+            break;
+
+        case 'kiwi':
+            if ((args.target) && (args.data)) {
+                websocket.sendServerLine('PRIVMSG ' + args.target + ': ' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1), callback);
+            }
+            break;
+        default:
+        }
+    //} catch (e) {
+    //    kiwi.log("Caught error (app.websocketMessage): " + e);
+    //}
+};
+
+
+
+this.websocketDisconnect = function (websocket) {
+    var con;
+
+    if ((!websocket.sentQUIT) && (websocket.ircConnection.connected)) {
+        try {
+            websocket.ircConnection.end('QUIT :' + kiwi.config.quit_message + '\r\n');
+            websocket.sentQUIT = true;
+            websocket.ircConnection.destroySoon();
+        } catch (e) {
+        }
+    }
+    con = kiwi.connections[websocket.kiwi.address];
+    con.count -= 1;
+    con.sockets = _.reject(con.sockets, function (sock) {
+        return sock === websocket;
+    });
+};
+
+
+
+
+
+
+this.rehash = function () {
+    var changes, i,
+        reload_config = kiwi.loadConfig();
+
+    // If loading the new config errored out, dont attempt any changes
+    if (reload_config === false) {
+        return false;
+    }
+
+    // We just want the settings that have been changed
+    changes = reload_config[1];
+
+    if (Object.keys(changes).length !== 0) {
+        kiwi.log('%s config changes: \n', Object.keys(changes).length, changes);
+        for (i in changes) {
+            switch (i) {
+            case 'servers':
+                kiwi.websocketListen(kiwi.config.servers, kiwi.httpHandler);
+                delete changes.ports;
+                delete changes.bind_address;
+                delete changes.ssl_key;
+                delete changes.ssl_cert;
+                break;
+            case 'user':
+            case 'group':
+                kiwi.changeUser();
+                delete changes.user;
+                delete changes.group;
+                break;
+            case 'module_dir':
+            case 'modules':
+                kiwi.kiwi_mod.loadModules(kiwi_root, kiwi.config);
+                kiwi.kiwi_mod.printMods();
+                delete changes.module_dir;
+                delete changes.modules;
+                break;
+            }
+        }
+    }
+
+    // Also clear the kiwi.cached javascript and HTML
+    if (kiwi.config.handle_http) {
+        kiwi.cache = {alljs: '', html: []};
+    }
+
+    return true;
+};
+
+
+
+
+
+/*
+ * KiwiIRC controlling via STDIN
+ */
+this.manageControll = function (data) {
+    var parts = data.toString().trim().split(' '),
+        connections_cnt = 0,
+        i;
+    switch (parts[0]) {
+    case 'rehash':
+        kiwi.log('Rehashing...');
+        kiwi.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
+        break;
+
+    case 'recode':
+        kiwi.log('Recoding...');
+        kiwi.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
+        break;
+
+    case 'mod':
+        if (parts[1] === 'reload') {
+            if (!parts[2]) {
+                kiwi.log('Usage: mod reload module_name');
+                return;
+            }
+
+            kiwi.log('Reloading module (' + parts[2] + ')..');
+            kiwi.kiwi_mod.reloadModule(parts[2]);
+        } else if (parts[1] === 'list') {
+            kiwi.kiwi_mod.printMods();
+        }
+        break;
+
+    case 'cache':
+        if (parts[1] === 'clear') {
+            kiwi.cache.html = {};
+            kiwi.cache.alljs = '';
+            kiwi.log('HTML cache cleared');
+        }
+        break;
+
+    case 'status':
+        for (i in kiwi.connections) {
+            connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
+        }
+        kiwi.log(connections_cnt.toString() + ' connected clients');
+        break;
+
+    default:
+        kiwi.log('Unknown command \'' + parts[0] + '\'');
+    }
+};
diff --git a/server_merging/cert.pem b/server_merging/cert.pem
new file mode 100644 (file)
index 0000000..1b1aaed
--- /dev/null
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICKzCCAZQCCQCHW0Kmpb9HBTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJH
+QjERMA8GA1UEChMIS2l3aSBJUkMxFzAVBgNVBAMTDktpd2kgV3JhbmdsZXJzMR8w
+HQYJKoZIhvcNAQkBFhBraXdpQGtpd2lpcmMuY29tMB4XDTExMDcxNjE1NDQxM1oX
+DTExMDgxNTE1NDQxM1owWjELMAkGA1UEBhMCR0IxETAPBgNVBAoTCEtpd2kgSVJD
+MRcwFQYDVQQDEw5LaXdpIFdyYW5nbGVyczEfMB0GCSqGSIb3DQEJARYQa2l3aUBr
+aXdpaXJjLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0h+kOA69tiJD
+u0COP0Wh0I9wVqAENQlRA5GowcH7r2Y3D9CbBIguw4Ss48kfXhDQa6sP9qsGvEAR
+kSkHcxIt+BRVtGjmzrZbGzObyOOm8rStcLiYCXas7m5U/mxy4vppL2eAX26az5wy
+f1vYGukhH1XRUdObxNNnLoNDWPJG8+sCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBU
+fARp2ZADZ89EiqUTRsCe8b8gXTO0Z3ov9LFTYhlpwH0RZ3fn9sE9A4mwrRlJC44W
+Z21sflIuXNFDerraedmx+fQwmwNBR6MMJgVN5nYw/x24QJf0C7ujIZrs4lxJdqwf
+yBRE6B7pJYPkk+aPHx/LSsbi9avMBfW+LQtkVOCuZg==
+-----END CERTIFICATE-----
diff --git a/server_merging/config.json b/server_merging/config.json
new file mode 100644 (file)
index 0000000..75a38be
--- /dev/null
@@ -0,0 +1,53 @@
+{
+    "servers":            [
+                            {
+                                "secure":   true,
+                                "port":   7777,
+                                "address": "0.0.0.0",
+
+                                "ssl_key": "server.key",
+                                "ssl_cert": "cert.pem"
+                            },
+                            {
+                                "secure":   false,
+                                "port":   7778,
+                                "address": "0.0.0.0"
+                            }
+                            
+                        ],
+
+    "user":             "",
+    "group":            "",
+
+    "quit_message":     "KiwiIRC",
+    "cap_options":      [],
+
+    "handle_http":      true,
+    "public_http":      "./../client/",
+
+    "max_client_conns": 2,
+
+    "module_dir":       "./kiwi_modules/",
+    "modules":          ["spamfilter", "statistics"],
+    
+    "webirc":           true,
+    "webirc_pass":      {
+                            "irc.example.com":  "examplepassword",
+                            "127.0.0.1":        "foobar"
+                        },
+
+    "transports":       [
+                            "websocket",
+                            "flashsocket",
+                            "htmlfile",
+                            "xhr-polling",
+                            "jsonp-polling"
+                        ],
+
+    "client_defaults":  {
+                            "server": "irc.anonnet.org",
+                            "port": 6667,
+                            "port_ssl": 6697,
+                            "ssl": false
+                        }
+}
diff --git a/server_merging/kiwi.js b/server_merging/kiwi.js
new file mode 100644 (file)
index 0000000..b200f0d
--- /dev/null
@@ -0,0 +1,235 @@
+/*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');
+
+
+// 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/', 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];
+};
+
+
+// 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'];
+    }
+
+    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;
+}
+
+
+
+
+
+
+/*
+ * Before we continue we need the config loaded
+ */
+if (!this.loadConfig()) {
+    process.exit(0);
+}
+
+
+
+
+
+
+
+/*
+ * 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);
+}
+
+
+
+
+
+
+/*
+ * 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); });
+
+
+
+
diff --git a/server_merging/kiwi_modules/forcessl.js b/server_merging/kiwi_modules/forcessl.js
new file mode 100644 (file)
index 0000000..d510299
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * forcessl Kiwi module
+ * Force clients to use an SSL port by redirecting them
+ */
+
+var kiwi = require('../kiwi.js');
+
+
+exports.onhttp = function (ev, opts) {
+       var host, port = null, i;
+
+       if (!ev.ssl) {
+           host = ev.request.headers.host;
+
+           // Remove the port if one is set
+           if (host.search(/:/) > -1) {
+               host = host.substring(0, host.search(/:/));
+           }
+
+       for (i in kiwi.config.servers) {
+               if (kiwi.config.servers[i].secure) {
+                       port = kiwi.config.servers[i].port;
+                       break;
+               }
+       }
+
+           // If we didn't find an SSL listener, don't redirect
+           if (port == null) {
+               return ev;
+           }
+
+           // No need to specify port 443 since it's the standard
+           if (port !== 443) {
+               host += ':' + port.toString();
+           }
+           
+           ev.response.writeHead(302, {'Location': 'https://' + host + ev.request.url});
+           ev.response.end();
+
+           return null;
+       }
+
+       return ev;
+}
\ No newline at end of file
diff --git a/server_merging/kiwi_modules/spamfilter.js b/server_merging/kiwi_modules/spamfilter.js
new file mode 100644 (file)
index 0000000..5f3ff76
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Example Kiwi module.
+ * This is by no means is a production ready module.
+ */
+
+var filters;
+var compiled_regex;
+
+exports.onload = function(){
+       filters = [];
+
+       if (filter.length > 0) {
+               compiled_regex = new RegExp(filters.join('|'), 'im');
+       }
+}
+
+
+exports.onmsg = function(msg){
+       if (typeof compiled_regex !== 'undefined' && msg.msg.search(compiled_regex) > -1) {
+               return null;
+       }
+
+       return msg;
+}
\ No newline at end of file
diff --git a/server_merging/kiwi_modules/statistics.js b/server_merging/kiwi_modules/statistics.js
new file mode 100644 (file)
index 0000000..09b4546
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Example Kiwi module.
+ * This is by no means is a production ready module.
+ */
+
+var kiwi = require('../kiwi.js');
+var stats = {msgs: 0, topic_changes: 0};
+
+exports.onmsgsend = function (msg, opts) {
+    stats.msgs++;
+
+    var connections_cnt = 0;
+    for (var i in kiwi.connections) {
+        connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
+    }
+
+    if (msg.msg === '!kiwistats') {
+        msg.msg = '';
+        msg.msg += 'Connections: ' + connections_cnt.toString() + '. ';
+        msg.msg += 'Messages sent: ' + stats.msgs.toString() + '. ';
+        msg.msg += 'Topics set: ' + stats.topic_changes.toString() + '. ';
+
+        opts.websocket.sendClientEvent('msg', {nick: msg.target, ident: '', hostname: '', channel: msg.target, msg: msg.msg});
+        return null;
+    }
+
+    return msg;
+}
+
+exports.ontopic = function (topic, opts) {
+    stats.topic_changes++;
+
+    return topic;
+}
diff --git a/server_merging/lib/kiwi_mod.js b/server_merging/lib/kiwi_mod.js
new file mode 100644 (file)
index 0000000..bdac9c3
--- /dev/null
@@ -0,0 +1,102 @@
+/*jslint node: true, sloppy: true, forin: true, maxerr: 50, indent: 4 */
+/*
+ * Kiwi module handler
+ *
+ * To run module events:
+ *     kiwi_mod.run(event_name, obj);
+ *
+ * - Each module call must return obj, with or without changes.
+ * - If a module call returns null, the event is considered cancelled
+ *   and null is passed back to the caller to take action.
+ *   For example, if null is returned for onmsg, kiwi stops sending
+ *   the message to any clients.
+ */
+
+var kiwi = require('../kiwi.js');
+var fs = require('fs');
+this.loaded_modules = {};
+
+
+/*
+ * Load any unloaded modules as set in config
+ */
+exports.loadModules = function (kiwi_root, config) {
+    var i, mod_name;
+    // Warn each module it is about to be unloaded
+    //this.run('unload');
+    //this.loaded_modules = {};
+
+    // Load each module and run the onload event
+    for (i in kiwi.config.modules) {
+        mod_name = kiwi.config.modules[i];
+        if (typeof this.loaded_modules[mod_name] !== 'undefined') continue;
+
+        this.loaded_modules[mod_name] = require(kiwi.kiwi_root + '/' + kiwi.config.module_dir + mod_name);
+    }
+    this.run('load');
+};
+
+
+/*
+ * Unload and reload a specific module
+ */
+exports.reloadModule = function (mod_name) {
+    fs.realpath(kiwi.kiwi_root + '/' + kiwi.config.module_dir + mod_name + '.js', function(err, resolvedPath){
+        try {
+            var mod_path = resolvedPath;
+
+            if (typeof kiwi.kiwi_mod.loaded_modules[mod_name] !== 'undefined') {
+                delete kiwi.kiwi_mod.loaded_modules[mod_name];
+            }
+            if (typeof require.cache[mod_path] !== 'undefined') {
+                delete require.cache[mod_path];
+            }
+
+            kiwi.kiwi_mod.loaded_modules[mod_name] = null;
+            kiwi.kiwi_mod.loaded_modules[mod_name] = require(mod_path);
+
+            kiwi.log('Module ' + mod_name + ' reloaded.');
+        } catch (e) {
+            kiwi.log('reloadModule error!');
+            kiwi.log(e);
+            return false;
+        }
+    });
+
+    //return this.loaded_modules[mod_name] ? true : false;
+};
+
+
+/*
+ * Run an event against all loaded modules
+ */
+exports.run = function (event_name, event_data, opts) {
+    var ret = event_data,
+        ret_tmp, mod_name;
+    
+    event_data = (typeof event_data === 'undefined') ? {} : event_data;
+    opts = (typeof opts === 'undefined') ? {} : opts;
+    
+    for (mod_name in this.loaded_modules) {
+        if (typeof this.loaded_modules[mod_name]['on' + event_name] === 'function') {
+            try {
+                ret_tmp = this.loaded_modules[mod_name]['on' + event_name](ret, opts);
+                if (ret_tmp === null) {
+                    return null;
+                }
+                ret = ret_tmp;
+            } catch (e) {
+            }
+        }
+    }
+
+    return ret;
+};
+
+exports.printMods = function () {
+    var mod_name;
+    kiwi.log('Loaded Kiwi modules:');
+    for (mod_name in this.loaded_modules) {
+        kiwi.log(' - ' + mod_name);
+    }
+};
diff --git a/server_merging/lib/starttls.js b/server_merging/lib/starttls.js
new file mode 100644 (file)
index 0000000..e7f2240
--- /dev/null
@@ -0,0 +1,68 @@
+// Target API:
+//
+//  var s = require('net').createStream(25, 'smtp.example.com');
+//  s.on('connect', function() {
+//   require('starttls')(s, options, function() {
+//      if (!s.authorized) {
+//        s.destroy();
+//        return;
+//      }
+//
+//      s.end("hello world\n");
+//    });
+//  });
+//
+//
+module.exports = function starttls(socket, options, cb) {
+
+  var sslcontext = require('crypto').createCredentials(options);
+
+  var pair = require('tls').createSecurePair(sslcontext, false);
+
+  var cleartext = pipe(pair, socket);
+
+  pair.on('secure', function() {
+    var verifyError = pair.ssl.verifyError();
+
+    if (verifyError) {
+      cleartext.authorized = false;
+      cleartext.authorizationError = verifyError;
+    } else {
+      cleartext.authorized = true;
+    }
+
+    if (cb) cb();
+  });
+
+  cleartext._controlReleased = true;
+  return cleartext;
+};
+
+
+function pipe(pair, socket) {
+  pair.encrypted.pipe(socket);
+  socket.pipe(pair.encrypted);
+
+  pair.fd = socket.fd;
+  var cleartext = pair.cleartext;
+  cleartext.socket = socket;
+  cleartext.encrypted = pair.encrypted;
+  cleartext.authorized = false;
+
+  function onerror(e) {
+    if (cleartext._controlReleased) {
+      cleartext.emit('error', e);
+    }
+  }
+
+  function onclose() {
+    socket.removeListener('error', onerror);
+    socket.removeListener('close', onclose);
+  }
+
+  socket.on('error', onerror);
+  socket.on('close', onclose);
+
+  return cleartext;
+}
+
diff --git a/server_merging/lib/underscore.min.js b/server_merging/lib/underscore.min.js
new file mode 100644 (file)
index 0000000..ad19500
--- /dev/null
@@ -0,0 +1,31 @@
+// Underscore.js 1.2.2
+// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the MIT license.
+// Portions of Underscore are inspired or borrowed from Prototype,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+(function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return String(a)==String(c);case "[object Number]":return a=+a,c=+c,a!=a?c!=c:a==0?1/a==1/c:a==c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
+c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c,
+h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd?
+define("underscore",function(){return b}):s._=b;b.VERSION="1.2.2";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=
+d!==void 0;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;
+D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});
+return e};var D=b.some=b.any=function(a,c,d){var c=c||b.identity,e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};
+b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var c=[],b;
+j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,c){var b=e(a,c);(d[b]||(d[b]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<
+f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||
+d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,
+true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);
+for(d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;
+e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=
+function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=
+null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=i.call(arguments);return function(){for(var b=i.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!==
+Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?
+a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}:
+function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};
+b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,
+interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,
+"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e(a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return u(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,
+arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this);
+
diff --git a/server_merging/server.key b/server_merging/server.key
new file mode 100644 (file)
index 0000000..6d6a3a4
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDSH6Q4Dr22IkO7QI4/RaHQj3BWoAQ1CVEDkajBwfuvZjcP0JsE
+iC7DhKzjyR9eENBrqw/2qwa8QBGRKQdzEi34FFW0aObOtlsbM5vI46bytK1wuJgJ
+dqzublT+bHLi+mkvZ4BfbprPnDJ/W9ga6SEfVdFR05vE02cug0NY8kbz6wIDAQAB
+AoGBANBlPVOzmwfWd+JxJiMuhkv41uuzDDklokmt3vc70sik0ZtHw1b9UZPsNCQ+
+RnPerTb7k3uLJ8TwrfuP+6lusFL8bwzXBaPZOZmSf2aQz8o6MAyY8B8gxjDi/NoW
+b5jtpXGFNayjc5O7tDjBsd/g88vk3EjnpJZ0P4H3gC+hhCCRAkEA7lGFVou8/ht7
+vTpbHEP13mjYG7OUmJqTXavkrit9UDDcZukt6I7TAB42LPyI7DnjB8i358bdmQQj
+x4R1mNZadQJBAOG2msKY+PFQGUpP18HlFze7JPbc0L5CLeiVIrXV9+xY7FgyGzwU
+UvI9ZyHhqzsgU2/9yW2+beaS8S8LCkGAiN8CQQCcmfMNiOua6wJnuQYPz9Sr3qdL
+pLjbgo+duQufK7K/1CuwcD+bluauKCwfaZ6r4+n8vneilXoeR6sfOzpvQUPVAkBJ
+ZJUB/bfEz6TJkxi3BYT9LC8izj5Z/y7qV8QHmGGbSnbfXruYV4t5FRo53CVPfn1j
+BwS+WJNnzBP8lfxpvB/FAkAf6mPKzWlMnWYXg6gII9lQZQ1EIn258Hi7vrFw9+Ic
+lDbndynaxh97wqKoloRckXF3D9FZM0w7YIS541Cih42u
+-----END RSA PRIVATE KEY-----