From 94423dfc25eaae4ea175fb17c9c926c27bdff984 Mon Sep 17 00:00:00 2001 From: Darren Date: Mon, 8 Oct 2012 22:15:01 +0100 Subject: [PATCH] Merged old+new server --- server_merging/app.js | 1149 +++++++++++++++++++++ server_merging/cert.pem | 14 + server_merging/config.json | 53 + server_merging/kiwi.js | 235 +++++ server_merging/kiwi_modules/forcessl.js | 44 + server_merging/kiwi_modules/spamfilter.js | 24 + server_merging/kiwi_modules/statistics.js | 34 + server_merging/lib/kiwi_mod.js | 102 ++ server_merging/lib/starttls.js | 68 ++ server_merging/lib/underscore.min.js | 31 + server_merging/server.key | 15 + 11 files changed, 1769 insertions(+) create mode 100644 server_merging/app.js create mode 100644 server_merging/cert.pem create mode 100644 server_merging/config.json create mode 100644 server_merging/kiwi.js create mode 100644 server_merging/kiwi_modules/forcessl.js create mode 100644 server_merging/kiwi_modules/spamfilter.js create mode 100644 server_merging/kiwi_modules/statistics.js create mode 100644 server_merging/lib/kiwi_mod.js create mode 100644 server_merging/lib/starttls.js create mode 100644 server_merging/lib/underscore.min.js create mode 100644 server_merging/server.key diff --git a/server_merging/app.js b/server_merging/app.js new file mode 100644 index 0000000..c197401 --- /dev/null +++ b/server_merging/app.js @@ -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 index 0000000..1b1aaed --- /dev/null +++ b/server_merging/cert.pem @@ -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 index 0000000..75a38be --- /dev/null +++ b/server_merging/config.json @@ -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 index 0000000..b200f0d --- /dev/null +++ b/server_merging/kiwi.js @@ -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 index 0000000..d510299 --- /dev/null +++ b/server_merging/kiwi_modules/forcessl.js @@ -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 index 0000000..5f3ff76 --- /dev/null +++ b/server_merging/kiwi_modules/spamfilter.js @@ -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 index 0000000..09b4546 --- /dev/null +++ b/server_merging/kiwi_modules/statistics.js @@ -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 index 0000000..bdac9c3 --- /dev/null +++ b/server_merging/lib/kiwi_mod.js @@ -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 index 0000000..e7f2240 --- /dev/null +++ b/server_merging/lib/starttls.js @@ -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 index 0000000..ad19500 --- /dev/null +++ b/server_merging/lib/underscore.min.js @@ -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=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;bd?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])=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=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/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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 index 0000000..6d6a3a4 --- /dev/null +++ b/server_merging/server.key @@ -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----- -- 2.25.1