From a8bf3ea47a49cb2038112d1d892febe9dabb257b Mon Sep 17 00:00:00 2001 From: Jack Allnutt Date: Fri, 20 Jul 2012 15:11:27 +0100 Subject: [PATCH] Grafting the new server to the new backbone client --- .gitignore | 1 + client/js/backbone-0.5.3-min.js | 0 client/js/iscroll.js | 0 client/js/jquery-ui.1.8.16.min.js | 0 client/js/jquery.1.6.4.min.js | 0 client/js/jquery.json-2.2.min.js | 0 client/js/mobile.js | 0 client/js/touchscreen_tweaks.js | 0 client/js/underscore.min.js | 0 client_backbone/{ => css}/style.css | 0 client_backbone/index.html | 58 - client_backbone/index.jade | 31 + client_backbone/{ => js}/backbone-git.js | 0 client_backbone/{ => js}/jquery-1.7.1.min.js | 0 client_backbone/{ => js}/model.js | 0 client_backbone/{ => js}/model_application.js | 2 +- client_backbone/{ => js}/model_gateway.js | 72 +- client_backbone/{ => js}/underscore-min.js | 0 client_backbone/{ => js}/utils.js | 0 client_backbone/{ => js}/view.js | 14 +- client_backbone/manifest.json | 13 + server/app.js | 1252 ----------------- server/client.js | 135 ++ server/http-handler.js | 169 +++ server/irc-commands.js | 480 +++++++ server/irc-connection.js | 124 ++ server/kiwi.js | 261 +--- server/kiwi_modules/forcessl.js | 44 - server/kiwi_modules/spamfilter.js | 24 - server/kiwi_modules/statistics.js | 34 - server/lib/kiwi_mod.js | 102 -- server/lib/starttls.js | 68 - server/lib/underscore.min.js | 31 - server/web.js | 72 + 34 files changed, 1126 insertions(+), 1861 deletions(-) mode change 100644 => 100755 client/js/backbone-0.5.3-min.js mode change 100644 => 100755 client/js/iscroll.js mode change 100644 => 100755 client/js/jquery-ui.1.8.16.min.js mode change 100644 => 100755 client/js/jquery.1.6.4.min.js mode change 100644 => 100755 client/js/jquery.json-2.2.min.js mode change 100644 => 100755 client/js/mobile.js mode change 100644 => 100755 client/js/touchscreen_tweaks.js mode change 100644 => 100755 client/js/underscore.min.js rename client_backbone/{ => css}/style.css (100%) delete mode 100644 client_backbone/index.html create mode 100644 client_backbone/index.jade rename client_backbone/{ => js}/backbone-git.js (100%) mode change 100644 => 100755 rename client_backbone/{ => js}/jquery-1.7.1.min.js (100%) mode change 100644 => 100755 rename client_backbone/{ => js}/model.js (100%) mode change 100644 => 100755 rename client_backbone/{ => js}/model_application.js (95%) mode change 100644 => 100755 rename client_backbone/{ => js}/model_gateway.js (83%) mode change 100644 => 100755 rename client_backbone/{ => js}/underscore-min.js (100%) mode change 100644 => 100755 rename client_backbone/{ => js}/utils.js (100%) mode change 100644 => 100755 rename client_backbone/{ => js}/view.js (93%) mode change 100644 => 100755 create mode 100644 client_backbone/manifest.json delete mode 100755 server/app.js create mode 100755 server/client.js create mode 100755 server/http-handler.js create mode 100755 server/irc-commands.js create mode 100755 server/irc-connection.js delete mode 100644 server/kiwi_modules/forcessl.js delete mode 100644 server/kiwi_modules/spamfilter.js delete mode 100644 server/kiwi_modules/statistics.js delete mode 100644 server/lib/kiwi_mod.js delete mode 100644 server/lib/starttls.js delete mode 100644 server/lib/underscore.min.js create mode 100755 server/web.js diff --git a/.gitignore b/.gitignore index 6d95f87..0880872 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.DS_* node/node_modules/ node_modules/ +doc/ \ No newline at end of file diff --git a/client/js/backbone-0.5.3-min.js b/client/js/backbone-0.5.3-min.js old mode 100644 new mode 100755 diff --git a/client/js/iscroll.js b/client/js/iscroll.js old mode 100644 new mode 100755 diff --git a/client/js/jquery-ui.1.8.16.min.js b/client/js/jquery-ui.1.8.16.min.js old mode 100644 new mode 100755 diff --git a/client/js/jquery.1.6.4.min.js b/client/js/jquery.1.6.4.min.js old mode 100644 new mode 100755 diff --git a/client/js/jquery.json-2.2.min.js b/client/js/jquery.json-2.2.min.js old mode 100644 new mode 100755 diff --git a/client/js/mobile.js b/client/js/mobile.js old mode 100644 new mode 100755 diff --git a/client/js/touchscreen_tweaks.js b/client/js/touchscreen_tweaks.js old mode 100644 new mode 100755 diff --git a/client/js/underscore.min.js b/client/js/underscore.min.js old mode 100644 new mode 100755 diff --git a/client_backbone/style.css b/client_backbone/css/style.css similarity index 100% rename from client_backbone/style.css rename to client_backbone/css/style.css diff --git a/client_backbone/index.html b/client_backbone/index.html deleted file mode 100644 index ea3a52a..0000000 --- a/client_backbone/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - KiwiIRC - - - - - -
-
-
    - -
    - -
    -
    - -
    -
    -
    - -
    - -
    -
    - -
    -
    -
    -
    - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/client_backbone/index.jade b/client_backbone/index.jade new file mode 100644 index 0000000..4f66130 --- /dev/null +++ b/client_backbone/index.jade @@ -0,0 +1,31 @@ +html + head + meta(http-equiv='Content-Type', content='text/html; charset=UTF-8') + title KiwiIRC + link(rel='stylesheet', type='text/css', href='/css/style.css') + script + document.write(unescape('%3Cscript type="text/javascript" src="' + document.location.protocol + '//' + document.location.host + '/socket.io/socket.io.js"%3E%3C/script%3E')); + body + #kiwi + #toolbar + ul.panellist.channels + #topic + input(type='text') + #panels + .panel_container.container1 + #memberlists + #controlbox + .input + span.nick + .input_wrap + input.inp(type='text') + script#tmpl_userbox(type='text/x-jquery-tmpl') +
    + Message + Info +
    + script(src='all.js') + script + $(function () { + kiwi.app = new kiwi.model.Application({container: $('body')[0]}); + }); \ No newline at end of file diff --git a/client_backbone/backbone-git.js b/client_backbone/js/backbone-git.js old mode 100644 new mode 100755 similarity index 100% rename from client_backbone/backbone-git.js rename to client_backbone/js/backbone-git.js diff --git a/client_backbone/jquery-1.7.1.min.js b/client_backbone/js/jquery-1.7.1.min.js old mode 100644 new mode 100755 similarity index 100% rename from client_backbone/jquery-1.7.1.min.js rename to client_backbone/js/jquery-1.7.1.min.js diff --git a/client_backbone/model.js b/client_backbone/js/model.js old mode 100644 new mode 100755 similarity index 100% rename from client_backbone/model.js rename to client_backbone/js/model.js diff --git a/client_backbone/model_application.js b/client_backbone/js/model_application.js old mode 100644 new mode 100755 similarity index 95% rename from client_backbone/model_application.js rename to client_backbone/js/model_application.js index d5cc4bc..fde35bb --- a/client_backbone/model_application.js +++ b/client_backbone/js/model_application.js @@ -13,7 +13,7 @@ kiwi.model.Application = Backbone.Model.extend(new (function () { this.initializeClient(); kiwi.gateway.set('nick', 'kiwi_' + Math.ceil(Math.random() * 10000).toString()); - kiwi.gateway.connect('ate.anonnet.org', 6667, false, false, function () { + kiwi.gateway.connect('localhost', 6667, false, false, function () { console.log('gateway connected'); }); diff --git a/client_backbone/model_gateway.js b/client_backbone/js/model_gateway.js old mode 100644 new mode 100755 similarity index 83% rename from client_backbone/model_gateway.js rename to client_backbone/js/model_gateway.js index a901fc7..8460449 --- a/client_backbone/model_gateway.js +++ b/client_backbone/js/model_gateway.js @@ -37,7 +37,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { * @type String */ //kiwi_server: '//kiwi' - kiwi_server: 'http://localhost:7778/kiwi' + kiwi_server: document.location.protocol + '//' + document.location.host + '/kiwi' }; @@ -48,9 +48,9 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { // For ease of access. The socket.io object this.socket = this.get('socket'); - // Redundant perhaps? Legacy - this.session_id = ''; + this.server_num = null; + // Global variable? ~Jack network = this; }; @@ -90,15 +90,25 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { }); this.socket.on('connect', function () { - this.emit('irc connect', that.get('nick'), host, port, ssl, password, callback); - console.log("kiwi.gateway.socket.on('connect')"); + //{command: 'connect', nick: kiwi.gateway.nick, hostname: host, port: port, ssl: ssl, password: password} + this.emit('kiwi', {command: 'connect', nick: that.get('nick'), hostname: host, port: port, ssl: ssl, password:password}, function (err, server_num) { + console.log('err, server_num', err, server_num); + if (!err) { + that.server_num = server_num; + console.log("kiwi.gateway.socket.on('connect')"); + } else { + console.log("kiwi.gateway.socket.on('error')", {reason: err}); + } + }); }); this.socket.on('too_many_connections', function () { this.emit("connect_fail", {reason: 'too_many_connections'}); }); - this.socket.on('message', this.parse); + this.socket.on('irc', function (data, callback) { + that.parse(data.command, data.data); + }); this.socket.on('disconnect', function () { this.emit("disconnect", {}); @@ -142,18 +152,18 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { /** * Parses the response from the server */ - this.parse = function (item) { - console.log('gateway event', item); - if (item.event !== undefined) { - that.trigger('on' + item.event, item); + this.parse = function (command, data) { + console.log('gateway event', command, data); + if (command !== undefined) { + that.trigger('on' + command, data); - switch (item.event) { + switch (command) { case 'options': - $.each(item.options, function (name, value) { + $.each(data.options, function (name, value) { switch (name) { case 'CHANTYPES': // TODO: Check this. Why is it only getting the first char? - that.set('channel_prefix', value.charAt(0)); + that.set('channel_prefix', value.join('').charAt(0)); break; case 'NETWORK': that.set('name', value); @@ -166,11 +176,11 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { break; case 'connect': - that.set('nick', item.nick); + that.set('nick', data.nick); break; case 'nick': - that.set('nick', item.newnick); + that.set('nick', data.newnick); break; /* case 'sync': @@ -182,7 +192,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { */ case 'kiwi': - this.emit('kiwi.' + item.namespace, item.data); + this.emit('kiwi.' + data.namespace, data); break; } } @@ -195,7 +205,9 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { * @param {Function} callback A callback function */ this.sendData = function (data, callback) { - this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback); + //this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback); + //kiwi.gateway.socket.emit('irc', {server: this.server_num, data: $.toJSON(data)}, callback); + this.socket.emit('irc', {server: this.server_num, data: JSON.stringify(data)}, callback); }; /** @@ -208,8 +220,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'privmsg', args: { - target: target, - msg: msg + params: [target], + trailing: msg } }; @@ -226,8 +238,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'notice', args: { - target: target, - msg: msg + params: [target], + trailing: msg } }; @@ -275,8 +287,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'join', args: { - channel: channel, - key: key + params: [channel, key] } }; @@ -292,7 +303,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'part', args: { - channel: channel + params: channel } }; @@ -309,8 +320,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'topic', args: { - channel: channel, - topic: new_topic + params: [channel], + trailing: new_topic } }; @@ -328,9 +339,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'kick', args: { - channel: channel, - nick: nick, - reason: reason + params: [channel, nick], + trailing: reason } }; @@ -347,7 +357,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'quit', args: { - message: msg + trailing: msg } }; @@ -379,7 +389,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () { var data = { method: 'nick', args: { - nick: new_nick + params: [new_nick] } }; diff --git a/client_backbone/underscore-min.js b/client_backbone/js/underscore-min.js old mode 100644 new mode 100755 similarity index 100% rename from client_backbone/underscore-min.js rename to client_backbone/js/underscore-min.js diff --git a/client_backbone/utils.js b/client_backbone/js/utils.js old mode 100644 new mode 100755 similarity index 100% rename from client_backbone/utils.js rename to client_backbone/js/utils.js diff --git a/client_backbone/view.js b/client_backbone/js/view.js old mode 100644 new mode 100755 similarity index 93% rename from client_backbone/view.js rename to client_backbone/js/view.js index 65158fe..b19d5bc --- a/client_backbone/view.js +++ b/client_backbone/js/view.js @@ -157,7 +157,9 @@ kiwi.view.Panel = Backbone.View.extend({ // Scroll to the bottom of the panel scrollToBottom: function () { // TODO: Don't scroll down if we're scrolled up the panel a little - this.$container[0].scrollTop = this.$container[0].scrollHeight; + if (this.$container[0]) { + this.$container[0].scrollTop = this.$container[0].scrollHeight; + } } }); @@ -325,9 +327,9 @@ kiwi.view.ControlBox = Backbone.View.extend({ // If we didn't have any listeners for this event, fire a special case // TODO: This feels dirty. Should this really be done..? - if (!this._callbacks['command' + command]) { + /*if (!this._callbacks['command' + command]) { this.trigger('unknown_command', {command: command, params: params}); - } + }*/ } }); @@ -356,8 +358,10 @@ kiwi.view.Application = Backbone.View.extend({ } // If we're typing into an input box somewhere, ignore - if (ev.srcElement.tagName.toLowerCase() === 'input') { - return; + if (ev.secElement) { + if (ev.srcElement.tagName.toLowerCase() === 'input') { + return; + } } $('#controlbox .inp').focus(); diff --git a/client_backbone/manifest.json b/client_backbone/manifest.json new file mode 100644 index 0000000..48eec3d --- /dev/null +++ b/client_backbone/manifest.json @@ -0,0 +1,13 @@ +{ + "js": [ + "jquery-1.7.1.min.js", + "underscore-min.js", + "backbone-git.js", + "utils.js", + "model.js", + "model_application.js", + "model_gateway.js", + "view.js" + ], + "css": ["style.css"] +} \ No newline at end of file diff --git a/server/app.js b/server/app.js deleted file mode 100755 index fa08a42..0000000 --- a/server/app.js +++ /dev/null @@ -1,1252 +0,0 @@ -/*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ -/*globals kiwi_root */ -/* Fuck you, git. */ -var tls = null, - net = null, - http = 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; - -this.init = function (objs) { - tls = objs.tls; - net = objs.net; - http = objs.http; - https = objs.https; - 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); -}; - - - - - - -/* - * 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(); - } - } -}; - - - - - - - - - -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)}); - } - - } - } - 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 (msg) { - 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); - }); - - 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, serverconf) { - var uri, uri_parts, subs, useragent, agent, server_set, serverconf, nick, debug, touchscreen, hash, - min = {}, public_http_path, port, ssl, obj, args, ircuri, target, modifiers, query, - secure = serverconf.secure || false; - - try { - if (kiwi.config.handle_http) { - // Run through any plugins.. - args = {request: request, response: response, ssl: secure}; - obj = kiwi.kiwi_mod.run('http', args); - if (obj === null) { - return; - } - response = args.response; - - uri = url.parse(request.url, true); - uri_parts = uri.pathname.split('/'); - - subs = uri.pathname.substr(0, 4); - public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http; - - if (typeof uri.query.ircuri !== 'undefined') { - ircuri = url.parse(uri.query.ircuri, true); - if (ircuri.protocol === 'irc:') { - uri_parts = /^\/([^,\?]*)((,[^,\?]*)*)?$/.exec(ircuri.pathname); - target = uri_parts[1]; - modifiers = (typeof uri_parts[2] !== 'undefined') ? uri_parts[2].split(',') : []; - query = ircuri.query; - - nick = _.detect(modifiers, function (mod) { - return mod === ',isnick'; - }); - kiwi.log(request.headers); - response.statusCode = 303; - response.setHeader('Location', 'http' + ((secure) ? 's' : '') + '://' + request.headers.host + '/client/' + ircuri.host + '/' + ((!nick) ? target : '')); - response.end(); - } - } else if (uri.pathname === '/js/all.js') { - if (kiwi.cache.alljs === '') { - - min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js'); - min.util = fs.readFileSync(public_http_path + 'js/util.js'); - min.backbone = fs.readFileSync(public_http_path + 'js/backbone-git.js'); - min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js'); - min.model = fs.readFileSync(public_http_path + 'js/model.js'); - min.view = fs.readFileSync(public_http_path + 'js/view.js'); - min.front = fs.readFileSync(public_http_path + 'js/front.js'); - min.front_events = fs.readFileSync(public_http_path + 'js/front.events.js'); - min.front_ui = fs.readFileSync(public_http_path + 'js/front.ui.js'); - min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js'); - min.ast = jsp.parse(min.underscore + min.util + min.backbone + min.gateway + min.model + min.view + min.front + min.front_events + min.front_ui + min.iscroll); - min.ast = pro.ast_mangle(min.ast); - min.ast = pro.ast_squeeze(min.ast); - min.final_code = pro.gen_code(min.ast); - kiwi.cache.alljs = min.final_code; - hash = crypto.createHash('md5').update(kiwi.cache.alljs); - kiwi.cache.alljs_hash = hash.digest('base64'); - } - if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) { - response.statusCode = 304; - } else { - response.setHeader('Content-type', 'application/javascript'); - response.setHeader('ETag', kiwi.cache.alljs_hash); - if ((secure) && (serverconf.hsts)) { - response.setHeader("Strict-Transport-Security", "max-age=604 800"); - } - response.write(kiwi.cache.alljs); - } - response.end(); - } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) { - request.addListener('end', function () { - kiwi.fileServer.serve(request, response); - }); - } else if (uri.pathname === '/' || uri_parts[1] === 'client') { - useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : ''; - if (useragent.match(/android/i) !== -1) { - agent = 'android'; - touchscreen = true; - } else if (useragent.match(/iphone/) !== -1) { - agent = 'iphone'; - touchscreen = true; - } else if (useragent.match(/ipad/) !== -1) { - agent = 'ipad'; - touchscreen = true; - } else if (useragent.match(/ipod/) !== -1) { - agent = 'ipod'; - touchscreen = true; - } else { - agent = 'normal'; - touchscreen = false; - } - agent = 'normal'; - touchscreen = false; - - debug = (typeof uri.query.debug !== 'undefined'); - - ssl = secure; // ssl is passed to the client - port = ssl ? kiwi.config.client_defaults.port_ssl : kiwi.config.client_defaults.port; - if (uri_parts[1] !== 'client') { - if (uri.query) { - server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== '')); - server = uri.query.server || kiwi.config.client_defaults.server; - nick = uri.query.nick || ''; - } else { - server_set = false; - server = kiwi.config.client_defaults.server; - nick = ''; - } - } else { - server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== '')); - server = server_set ? uri_parts[2] : kiwi.config.client_defaults.server; - if (server.search(/:/) > 0) { - port = server.substring(server.search(/:/) + 1); - server = server.substring(0, server.search(/:/)); - if (port[0] === '+') { - port = port.substring(1); - ssl = true; - } else { - ssl = false; - } - } - nick = uri.query.nick || ''; - } - - // Set the default nick if one isn't provided - if (nick === '') { - nick = 'kiwi_?'; - } - - // Set any random numbers if needed - nick = nick.replace('?', Math.floor(Math.random() * 100000).toString()); - - response.setHeader('X-Generated-By', 'KiwiIRC'); - hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f') - .update(debug ? 't' : 'f') - .update(server_set ? 't' : 'f') - .update(secure ? 't' : 'f') - .update(server) - .update(port.toString()) - .update(ssl ? 't' : 'f') - .update(nick) - .update(agent) - .update(JSON.stringify(kiwi.config)) - .digest('base64'); - if (kiwi.cache.html[hash]) { - if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) { - response.statusCode = 304; - } else { - response.setHeader('Etag', kiwi.cache.html[hash].hash); - response.setHeader('Content-type', 'text/html'); - if ((secure) && (serverconf.hsts)) { - response.setHeader("Strict-Transport-Security", "max-age=604 800"); - } - response.write(kiwi.cache.html[hash].html); - } - response.end(); - } else { - fs.readFile(public_http_path + 'index.html.jade', 'utf8', function (err, str) { - var html, hash2; - if (!err) { - try { - html = kiwi.jade.compile(str)({ "touchscreen": touchscreen, "debug": debug, "secure": secure, "server_set": server_set, "server": server, "port": port, "ssl": ssl, "nick": nick, "agent": agent, "config": kiwi.config }); - hash2 = crypto.createHash('md5').update(html).digest('base64'); - kiwi.cache.html[hash] = {"html": html, "hash": hash2}; - if (request.headers['if-none-match'] === hash2) { - response.statusCode = 304; - } else { - response.setHeader('Etag', hash2); - response.setHeader('Content-type', 'text/html'); - if ((secure) && (serverconf.hsts)) { - response.setHeader("Strict-Transport-Security", "max-age=604 800"); - } - response.write(html); - } - } catch (e) { - response.statusCode = 500; - kiwi.log(e); - } - } else { - kiwi.log(err); - response.statusCode = 500; - } - response.end(); - }); - } - } else if (uri.pathname.substr(0, 10) === '/socket.io') { - return; - } else { - response.statusCode = 404; - response.end(); - } - } - - } catch (e) { - kiwi.log('ERROR app.httpHandler()'); - kiwi.log(e); - } -}; - - - - -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, function (request, response) { - handler(request, response, server); - }); - 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(function (request, response) { - handler(request, response, server); - }); - 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('message', 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('irc connect', function (nick, host, port, ssl, password, callback) { - websocket.ircConnection = new kiwi.IRCConnection(this, nick, host, port, ssl, password, callback); - }); - websocket.on('message', 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.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/client.js b/server/client.js new file mode 100755 index 0000000..0f30cc5 --- /dev/null +++ b/server/client.js @@ -0,0 +1,135 @@ +var util = require('util'), + events = require('events'), + IRCConnection = require('./irc-connection.js').IRCConnection; + IRCCommands = require('./irc-commands.js'); + +var Client = function (websocket) { + var c = this; + + events.EventEmitter.call(this); + this.websocket = websocket; + + this.IRC_connections = []; + this.next_connection = 0; + + this.buffer = { + list: [], + motd: '' + }; + + websocket.on('irc', function () { + IRC_command.apply(c, arguments); + }); + websocket.on('kiwi', function () { + kiwi_command.apply(c, arguments); + }); + websocket.on('disconnect', function () { + disconnect.apply(c, arguments); + }); + websocket.on('error', function () { + error.apply(c, arguments); + }); +}; +util.inherits(Client, events.EventEmitter); + +module.exports.Client = Client; + +// Callback API: +// Callbacks SHALL accept 2 arguments, error and response, in that order. +// error MUST be null where the command is successul. +// error MUST otherwise be a truthy value and SHOULD be a string where the cause of the error is known. +// response MAY be given even if error is truthy + +Client.prototype.sendIRCCommand = function (command, data, callback) { + var c = {command: command, data: data}; + console.log('C<--', c); + this.websocket.emit('irc', c, callback); +}; + +Client.prototype.sendKiwiCommand = function (command, callback) { + this.websocket.emit('kiwi', command, callback); +}; + +var IRC_command = function (command, callback) { + console.log('C-->', command); + var method, str = ''; + if (typeof callback !== 'function') { + callback = function () {}; + } + if ((command.server === null) || (typeof command.server !== 'number')) { + return callback('server not specified'); + } else if (!this.IRC_connections[command.server]) { + return callback('not connected to server'); + } + + command.data = JSON.parse(command.data); + + if (command.data.method === 'ctcp') { + if (command.data.args.request) { + str += 'PRIVMSG '; + } else { + str += 'NOTICE '; + } + str += command.data.args.target + ' :' + str += String.fromCharCode(1) + command.data.args.type + ' '; + str += command.data.args.params + String.fromCharCode(1); + this.IRC_connections[command.server].send(str); + } else if (command.data.method === 'raw') { + this.IRC_connections[command.server].send(command.data.args.data); + } else if (command.data.method === 'kiwi') { + // do special Kiwi stuff here + } else { + method = command.data.method; + command.data = command.data.args; + this.IRC_connections[command.server].send(method + ((command.data.params) ? ' ' + command.data.params.join(' ') : '') + ((command.data.trailing) ? ' :' + command.data.trailing : ''), callback); + } +}; + +var kiwi_command = function (command, callback) { + console.log(typeof callback); + if (typeof callback !== 'function') { + callback = function () {}; + } + switch (command.command) { + case 'connect': + if ((command.hostname) && (command.port) && (command.nick)) { + var con = new IRCConnection(command.hostname, command.port, command.ssl, + command.nick, {hostname: this.websocket.handshake.revdns, address: this.websocket.handshake.address.address}, + command.password, null); + + var con_num = this.next_connection++; + this.IRC_connections[con_num] = con; + + var binder = new IRCCommands.Binder(con, con_num, this); + binder.bind_irc_commands(); + + con.on('connected', function () { + console.log("con.on('connected')"); + return callback(null, con_num); + }); + + con.on('error', function (err) { + this.websocket.sendKiwiCommand('error', {server: con_num, error: err}); + }); + } else { + return callback('Hostname, port and nickname must be specified'); + } + break; + default: + callback(); + } +}; + +var extension_command = function (command, callback) { + if (typeof callback === 'function') { + callback('not implemented'); + } +}; + +var disconnect = function () { + this.emit('destroy'); +}; + +var error = function () { + this.emit('destroy'); +}; diff --git a/server/http-handler.js b/server/http-handler.js new file mode 100755 index 0000000..aa3d2c0 --- /dev/null +++ b/server/http-handler.js @@ -0,0 +1,169 @@ +var fs = require('fs'), + crypto = require('crypto'); + url = require('url'), + _ = require('underscore'), + uglify = require('uglify-js'), + jade = require('jade'), + node_static = require ('node-static'); + +var HTTPHandler = function (config) { + var site = config.site; + this.config = config; + var files; + files = fs.readdirSync('client'); + if ((typeof site !== 'undefined') && (typeof site === 'string') && (_.include(files, site))) { + this.site = site; + this.static_file_server = new StaticFileServer(site); + } + else { + this.site = 'default'; + this.static_file_server = null; + } + +}; + +module.exports.HTTPHandler = HTTPHandler; + +var default_static_file_server = new node_static.Server('client_backbone/'); + +var StaticFileServer = function (site) { + this.fileServer = new node_static.Server('client_backbone/'); +}; + +StaticFileServer.prototype.serve = function (request, response) { + this.fileServer.serve(request, response, function (err) { + if (err) { + default_static_file_server.serve(request, response); + } + }); +}; + +var serve_static_file = function (request, response) { + if (this.static_file_server !== null) { + this.static_file_server.serve(request, response); + } else { + default_static_file_server.serve(request, response); + } +}; + +HTTPHandler.prototype.handler = function (request, response) { + var file_list, default_file_list, hash, uri, site, subs, self = this; + + site = 'default'; + uri = url.parse(request.url, true); + subs = uri.pathname.substr(0, 4); + + if (uri.pathname === '/all.js') { + hash = is_cached(site,'all.js'); + if (!hash) { + file_list = []; + default_file_list = []; + console.log('a'); + fs.readFile('client_backbone/manifest.json', 'utf-8', function (err, manifest) { + console.log('b'); + var js = ''; + manifest = JSON.parse(manifest); + _.each(manifest.js, function (file) { + console.log(file) + js += fs.readFileSync('client_backbone/js/' + file, 'utf-8') + '\r\n'; + }); + + // TODO: Replace false with check for debug flag + if (/* debug === */ false) { + js = uglify.uglify.gen_code(uglify.uglify.ast_squeeze(uglify.uglify.ast_mangle(uglify.parser.parse(js)))); + } + + hash = set_cache(site, 'all.js', js); + if (request.headers['if-none-match'] === hash) { + response.statusCode = 304; + } else { + response.setHeader('Content-type', 'application/javascript'); + response.setHeader('ETag', hash); + response.write(js); + } + response.end(); + }); + } else { + if (request.headers['if-none-match'] === hash) { + response.statusCode = 304; + } else { + response.setHeader('Content-type', 'application/javascript'); + response.setHeader('ETag', hash); + response.write(get_cache(site, 'all.js')); + } + response.end(); + } + } else if (uri.pathname === '/') { + var jadefile = ''; + + hash = is_cached(site, '/'); + + if (!hash) { + try { + fs.readFile('client_backbone/index.jade', 'utf-8', function (err, str) { + if (err) { + console.log(err + ''); + response.end(); + } else { + jadefile = str; + } + hash = set_cache('default', '/', jade.compile(jadefile, {pretty: true})()); + if (response.statusCode !== 500) { + if (request.headers['if-none-match'] === hash) { + response.statusCode = 304; + } else { + response.setHeader('Content-type', 'text/html; charset=utf-8'); + response.setHeader('ETag', hash); + response.write(get_cache(site, '/')); + } + } + response.end(); + }); + + } catch (e) { + console.log(e); + response.statusCode = 500; + response.end(); + } + } else { + if (request.headers['if-none-match'] === hash) { + response.statusCode = 304; + } else { + response.setHeader('Content-type', 'text/html; charset=utf-8'); + response.setHeader('ETag', hash); + response.write(get_cache(site, '/')); + } + response.end(); + } + } else if ((subs === '/img') || (subs === '/css')) { + serve_static_file.call(this, request, response); + } else if (uri.pathname.substr(0, 10) === '/socket.io') { + return; + } else { + response.statusCode = 404; + response.end(); + } +}; + +var cache = Object.create(null); + +var set_cache = function (site, file, data) { + if (!cache[site]) { + cache[site] = Object.create(null); + } + var hash = crypto.createHash('md5').update(data).digest('base64'); + cache[site][file] = {'data': data, 'hash': hash}; + return hash; +}; + +var is_cached = function (site, file) { + if ((cache[site]) && (cache[site][file])) { + return cache[site][file].hash; + } else { + return false; + } +}; + +var get_cache = function (site, file) { + return cache[site][file].data; +}; diff --git a/server/irc-commands.js b/server/irc-commands.js new file mode 100755 index 0000000..23667c0 --- /dev/null +++ b/server/irc-commands.js @@ -0,0 +1,480 @@ +var _ = require('underscore'); + +var irc_numerics = { + 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' +}; + + +var Binder = function (irc_connection, con_num, client) { + this.irc_connection = irc_connection; + this.con_num = con_num; + this.client = client; +}; +module.exports.Binder = Binder; + +Binder.prototype.bind_irc_commands = function () { + var that = this; + _.each(listeners, function (listener, command) { + var s = command.substr(0, 4); + if ((s === 'RPL_') || (s === 'ERR_')) { + command = irc_numerics[command]; + } + that.irc_connection.on('irc_' + command, function () { + listener.apply(that, arguments); + }); + }); +}; + +var listeners = { + 'RPL_WELCOME': function (command) { + var nick = command.params[0]; + this.irc_connection.registered = true; + this.client.sendKiwiCommand({server: this.con_num, command: 'connect', nick: nick}); + }, + 'RPL_ISUPPORT': function (command) { + var options, i, option, matches, j; + options = command.params; + for (i = 1; i < options.length; i++) { + option = options[i].split("=", 2); + option[0] = option[0].toUpperCase(); + this.irc_connection.options[option[0]] = (typeof option[1] !== 'undefined') ? option[1] : true; + if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option[0])) { + if (option[0] === 'PREFIX') { + matches = /\(([^)]*)\)(.*)/.exec(option[1]); + if ((matches) && (matches.length === 3)) { + this.irc_connection.options.PREFIX = []; + for (j = 0; j < matches[2].length; j++) { + this.irc_connection.options.PREFIX.push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)}); + } + } + } else if (option[0] === 'CHANTYPES') { + this.irc_connection.options.CHANTYPES = this.irc_connection.options.CHANTYPES.split(''); + } else if (option[0] === 'CHANMODES') { + this.irc_connection.options.CHANMODES = option[1].split(','); + } else if (option[0] === 'NAMESX') { + this.irc_connection.send('PROTOCTL NAMESX'); + } + } + } + //this.client.sendIRCCommand({server: this.con_num, command: 'RPL_ISUPPORT', options: this.irc_connection.options}); + //websocket.sendClientEvent('options', {server: '', "options": irc_connection.IRC.options}); + this.client.sendIRCCommand('options', {server: this.con_num, options: this.irc_connection.options}); + }, + 'RPL_ENDOFWHOIS': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_ENDOFWHOIS'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: true}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: true}); + }, + 'RPL_WHOISUSER': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_WHOISUSER'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false}); + }, + 'RPL_WHOISSERVER': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_WHOISSERVER'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false}); + }, + 'RPL_WHOISOPERATOR': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_WHOISOPERATOR'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false}); + }, + 'RPL_WHOISCHANNELS': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_WHOISCHANNELS'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false}); + }, + 'RPL_WHOISMODES': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_WHOISMODES'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false}); + }, + 'RPL_WHOISIDLE': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_WHOISIDLE'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false}); + this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false}); + }, + 'RPL_LISTSTART': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_LISTSTART'; + this.client.sendIRCCommand(command);*/ + this.client.sendIRCCommand('list_start', {server: this.con_num}); + this.client.buffer.list = []; + }, + 'RPL_LISTEND': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_LISTEND'; + this.client.sendIRCCommand(command);*/ + if (this.client.buffer.list.length > 0) { + this.client.buffer.list = _.sortBy(this.client.buffer.list, function (channel) { + return channel.num_users; + }); + this.client.sendIRCCommand('list_channel', {server: this.con_num, chans: this.client.buffer.list}); + this.client.buffer.list = []; + } + this.client.sendIRCCommand('list_end', {server: this.con_num}); + }, + 'RPL_LIST': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_LIST'; + this.client.sendIRCCommand(command);*/ + this.client.buffer.list.push({server: this.con_num, channel: command.params[1], num_users: parseInt(command.params[2]), topic: command.trailing}); + if (this.client.buffer.list.length > 200){ + this.client.buffer.list = _.sortBy(this.client.buffer.list, function (channel) { + return channel.num_users; + }); + this.client.sendIRCCommand('list_channel', {server: this.con_num, chans: this.client.buffer.list}); + this.client.buffer.list = []; + } + }, + 'RPL_MOTD': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_MOTD'; + this.client.sendIRCCommand(command);*/ + this.client.buffer.motd += command.trailing + '\n'; + }, + 'RPL_MOTDSTART': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_MOTDSTART'; + this.client.sendIRCCommand(command);*/ + this.client.buffer.motd = ''; + }, + 'RPL_ENDOFMOTD': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_ENDOFMOTD'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('motd', {server: '', 'msg': websocket.kiwi.buffer.motd}); + this.client.sendIRCCommand('motd', {server: this.con_num, msg: this.client.buffer.motd}); + }, + 'RPL_NAMEREPLY': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_NAMEREPLY'; + this.client.sendIRCCommand(command);*/ + var members = command.trailing.split(' '); + var member_list = []; + var that = this; + var i = 0; + _.each(members, function (member) { + var j, k, modes = []; + for (j = 0; j < member.length; j++) { + for (k = 0; k < that.irc_connection.options.PREFIX.length; k++) { + if (member.charAt(j) === that.irc_connection.options.PREFIX[k].symbol) { + modes.push(that.irc_connection.options.PREFIX[k].mode); + i++; + } + } + } + member_list.push({nick: member, modes: modes}); + if (i++ >= 50) { + that.client.sendIRCCommand('userlist', {server: that.con_num, users: member_list, channel: command.params[2]}); + member_list = []; + i = 0; + } + }); + if (i > 0) { + this.client.sendIRCCommand('userlist', {server: this.con_num, users: member_list, channel: command.params[2]}); + } + }, + 'RPL_ENDOFNAMES': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_ENDOFNAMES'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]}); + this.client.sendIRCCommand('userlist_end', {server: this.con_num, channel: command.params[1]}); + }, + 'RPL_BANLIST': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_BANLIST'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('banlist', {server: '', channel: params[1], banned: params[2], banned_by: params[3], banned_at: params[4]}); + this.client.sendIRCCommand('banlist', {server: this.con_num, channel: command.params[1], banned: command.params[2], banned_by: command.params[3], banned_at: command.params[4]}); + }, + 'RPL_ENDOFBANLIST': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_ENDOFBANLIST'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('banlist_end', {server: '', channel: msg.params.split(" ")[1]}); + this.client.sendIRCCommand('banlist_end', {server: this.con_num, channel: command.params[1]}); + }, + 'RPL_TOPIC': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_TOPIC'; + this.client.sendIRCCommand(command);*/ + //{nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing}; + this.client.sendIRCCommand('topic', {server: this.con_num, nick: '', channel: command.params[1], topic: command.trailing}); + }, + 'RPL_NOTOPIC': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_NOTOPIC'; + this.client.sendIRCCommand(command);*/ + this.client.sendIRCCommand('topic', {server: this.con_num, nick: '', channel: command.params[1], topic: ''}); + }, + 'RPL_TOPICWHOTIME': function (command) { + /*command.server = this.con_num; + command.command = 'RPL_TOPICWHOTIME'; + this.client.sendIRCCommand(command);*/ + //{nick: nick, channel: channel, when: when}; + this.client.sendIRCCommand('topicsetby', {server: this.con_num, nick: command.params[2], channel: command.params[1], when: command.params[3]}); + }, + 'PING': function (command) { + this.irc_connection.send('PONG ' + command.trailing); + }, + 'JOIN': function (command) { + var channel; + if (typeof command.trailing === 'string' && command.trailing !== '') { + channel = command.trailing; + } else if (typeof command.params[0] === 'string' && command.params[0] !== '') { + channel = command.params[0]; + } + /*command.server = this.con_num; + command.command = 'JOIN'; + command.params = [channel]; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel}); + this.client.sendIRCCommand('join', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: channel}); + + if (command.nick === this.nick) { + this.irc_connection.send('NAMES ' + channel); + } + }, + 'PART': function (command) { + /*command.server = this.con_num; + command.command = 'PART'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing}); + this.client.sendIRCCommand('part', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], message: command.trailing}); + }, + 'KICK': function (command) { + /*command.server = this.con_num; + command.command = 'KICK'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing}); + this.client.sendIRCCommand('kick', {server: this.con_num, kicked: command.params[1], nick: command.nick, ident: command.ident, hostname: command.hostname, channel: params[0], message: command.trailing}); + }, + 'QUIT': function (command) { + /*command.server = this.con_num; + command.command = 'QUIT'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing}); + this.client.sendIRCCommand('quit', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, message: command.trailing}); + }, + 'NOTICE': function (command) { + /*command.server = this.con_num; + command.command = 'NOTICE'; + this.client.sendIRCCommand(command);*/ + if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.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)}); + this.client.sendIRCCommand('ctcp_response', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing.substr(1, command.trailing.length - 2)}); + } else { + //websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing}); + this.client.sendIRCCommand('notice', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, target: command.params[0], msg: command.trailing}); + } + }, + 'NICK': function (command) { + /*command.server = this.con_num; + command.command = 'NICK'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing}); + this.client.sendIRCCommand('nick', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, newnick: command.trailing}); + }, + 'TOPIC': function (command) { + /*command.server = this.con_num; + command.command = 'TOPIC'; + this.client.sendIRCCommand(command);*/ + //{nick: msg.nick, channel: msg.params, topic: msg.trailing}; + this.client.sendIRCCommand('topic', {server: this.con_num, nick: command.nick, channel: msg.params[0], topic: command.trailing}); + }, + 'MODE': function (command) { + /*command.server = this.con_num; + command.command = 'MODE'; + this.client.sendIRCCommand(command);*/ + var ret = { server: this.con_num, nick: command.nick } + switch (command.params.length) { + case 1: + ret.affected_nick = command.params[0]; + ret.mode = command.trailing; + break; + case 2: + ret.channel = command.params[0]; + ret.mode = command.params[1]; + break; + default: + ret.channel = command.params[0]; + ret.mode = command.params[1]; + ret.affected_nick = command.params[2]; + break; + } + this.client.sendIRCCommand('mode', ret); + }, + 'PRIVMSG': function (command) { + /*command.server = this.con_num; + command.command = 'PRIVMSG'; + this.client.sendIRCCommand(command);*/ + var tmp, namespace; + if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) { + //CTCP request + if (command.trailing.substr(1, 6) === 'ACTION') { + this.client.sendIRCCommand('action', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing.substr(7, command.trailing.length - 2)}); + } else if (command.trailing.substr(1, 4) === 'KIWI') { + tmp = msg.trailing.substr(6, msg.trailing.length - 2); + namespace = tmp.split(' ', 1)[0]; + this.client.sendIRCCommand('kiwi', {server: this.con_num, namespace: namespace, data: tmp.substr(namespace.length + 1)}); + } else if (msg.trailing.substr(1, 7) === 'VERSION') { + this.irc_connection.send('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1)); + } else { + this.client.sendIRCCommand('ctcp_request', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing.substr(1, command.trailing.length - 2)}); + } + } else { + //{nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing} + this.client.sendIRCCommand('msg', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing}); + } + }, + 'ERROR': function (command) { + /*command.server = this.con_num; + command.command = 'ERROR'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'error', reason: command.trailing}); + }, + ERR_LINKCHANNEL: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_LINKCHANNEL'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]}); + this.client.sendIRCCommand('channel_redirect', {server: this.con_num, from: command.params[1], to: command.params[2]}); + }, + ERR_NOSUCHNICK: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_NOSUCHNICK'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'no_such_nick', nick: command.params[1], reason: command.trailing}); + }, + ERR_CANNOTSENDTOCHAN: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_CANNOTSENDTOCHAN'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'cannot_send_to_chan', channel: command.params[1], reason: command.trailing}); + }, + ERR_TOOMANYCHANNELS: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_TOOMANYCHANNELS'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'too_many_channels', channel: command.params[1], reason: command.trailing}); + }, + ERR_USERNOTINCHANNEL: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_USERNOTINCHANNEL'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'user_not_in_channel', nick: command.params[0], channel: command.params[1], reason: command.trailing}); + }, + ERR_NOTONCHANNEL: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_NOTONCHANNEL'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'not_on_channel', channel: command.params[1], reason: command.trailing}); + }, + ERR_CHANNELISFULL: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_CHANNELISFULL'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'channel_is_full', channel: command.params[1], reason: command.trailing}); + }, + ERR_INVITEONLYCHAN: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_INVITEONLYCHAN'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'invite_only_channel', channel: command.params[1], reason: command.trailing}); + }, + ERR_BANNEDFROMCHAN: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_BANNEDFROMCHAN'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'banned_from_channel', channel: command.params[1], reason: command.trailing}); + }, + ERR_BADCHANNELKEY: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_BADCHANNELKEY'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'bad_channel_key', channel: command.params[1], reason: command.trailing}); + }, + ERR_CHANOPRIVSNEEDED: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_CHANOPRIVSNEEDED'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'chanop_privs_needed', channel: command.params[1], reason: command.trailing}); + }, + ERR_NICKNAMEINUSE: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_NICKNAMEINUSE'; + this.client.sendIRCCommand(command);*/ + //websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing}); + this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'nickname_in_use', nick: command.params[1], reason: command.trailing}); + }, + ERR_NOTREGISTERED: function (command) { + /*command.server = this.con_num; + command.command = 'ERR_NOTREGISTERED'; + this.client.sendIRCCommand(command);*/ + } +}; diff --git a/server/irc-connection.js b/server/irc-connection.js new file mode 100755 index 0000000..163d69e --- /dev/null +++ b/server/irc-connection.js @@ -0,0 +1,124 @@ +var net = require('net'), + tls = require('tls'), + events = require('events'), + util = require('util'); + +var IRCConnection = function (hostname, port, ssl, nick, user, pass, webirc) { + var that = this; + events.EventEmitter.call(this); + + if (ssl) { + this.socket = tls.connect(port, hostname, {}, connect_handler); + } else { + this.socket = net.createConnection(port, hostname); + this.socket.on('connect', function () { + connect_handler.apply(that, arguments); + }); + } + + this.socket.on('error', function () { + var a = Array.prototype.slice.call(arguments); + a.unshift('error'); + that.emit.apply(this, a); + }); + + this.socket.setEncoding('utf-8'); + + this.socket.on('data', function () { + parse.apply(that, arguments); + }); + + this.connected = false; + this.registered = false; + this.nick = nick; + this.user = user; + this.ssl = !(!ssl); + this.options = Object.create(null); + + this.webirc = webirc; + this.password = pass; + this.hold_last = false; + this.held_data = ''; +}; +util.inherits(IRCConnection, events.EventEmitter); + +IRCConnection.prototype.send = function (data, callback) { + console.log('S<--', data); + write.call(this, data + '\r\n', 'utf-8', callback); +}; + +var write = function (data, encoding, callback) { + this.socket.write(data, encoding, callback); +}; + +module.exports.IRCConnection = IRCConnection; + +var connect_handler = function () { + if (this.webirc) { + this.send('WEBIRC ' + webirc.pass + ' KiwiIRC ' + this.user.hostname + ' ' + this.user.address); + } + if (this.password) { + this.send('PASS ' + password); + } + //this.send('CAP LS'); + this.send('NICK ' + this.nick); + this.send('USER kiwi_' + this.nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + this.nick); + + this.connected = true; + console.log("IRCConnection.emit('connected')"); + this.emit('connected'); +}; + +//parse_regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; +alt_regex = /(?::(([0-9a-z][\x2d0-9a-z]*[0-9a-z]*(?:\x2e[0-9a-z][\x2d0-9a-z]*[0-9a-z]*)*|[\x5b-\x7d][\x2d0-9\x5b-\x7d]{0,8})(?:(?:!([\x01-\t\v\f\x0e-\x1f!-\x3f\x5b-\xff]+))?@([0-9a-z][\x2d0-9a-z]*[0-9a-z]*(?:\x2e[0-9a-z][\x2d0-9a-z]*[0-9a-z]*)*|\d{1,3}\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3}|[0-9a-f]+(?::[0-9a-f]+){7}|0:0:0:0:0:(?:0|ffff):\d{1,3}\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3}))?)\x20)?([a-z]+|\d{3})((?:\x20[\x01-\t\v\f\x0e-\x1f!-9;-@\x5b-\xff][\x01-\t\v\f\x0e-\x1f!-@\x5b-\xff]*){0,14}(?:\x20:[\x01-\t\v\f\x0e-@\x5b-\xff]*)?|(?:\x20[\x01-\t\v\f\x0e-\x1f!-9;-@\x5b-\xff][\x01-\t\v\f\x0e-\x1f!-@\x5b-\xff]*){14}(?:\x20:?[\x01-\t\v\f\x0e-@\x5b-\xff]*)?)?/i; + +var parse = function (data) { + var i, + msg, + msg2, + trm; + + if ((this.hold_last) && (this.held_data !== '')) { + data = this.held_data + data; + this.hold_last = false; + this.held_data = ''; + } + if (data.substr(-1) !== '\n') { + this.hold_last = true; + } + data = data.split("\n"); + for (i = 0; i < data.length; i++) { + if (data[i]) { + if ((this.hold_last) && (i === data.length - 1)) { + this.held_data = data[i]; + break; + } + + // We have a complete line of data, parse it! + //msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, '')); + msg2 = alt_regex.exec(data[i].replace(/^\r+|\r+$/, '')); + //console.log(msg2); + if (msg2) { + msg = { + prefix: msg2[1], + nick: msg2[2], + ident: msg2[3], + hostname: msg2[4], + command: msg2[5] + }; + trm = msg2[6].indexOf(':'); + if (trm !== -1){ + msg.params = msg2[6].substr(0, trm - 1).trim().split(" "); + msg.trailing = msg2[6].substr(trm + 1).trim(); + } else { + msg.params = msg2[6].trim().split(" "); + } + console.log('S-->', data[i]); + //console.log(msg); + this.emit('irc_' + msg.command.toUpperCase(), msg); + } else { + console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, '')); + } + } + } +}; diff --git a/server/kiwi.js b/server/kiwi.js index a4afaa2..24c26d9 100755 --- a/server/kiwi.js +++ b/server/kiwi.js @@ -1,231 +1,70 @@ -/*jslint continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */ -"use strict"; -var tls = require('tls'), - net = require('net'), - http = require('http'), - https = require('https'), - fs = require('fs'), - url = require('url'), - dns = require('dns'), - crypto = require('crypto'), - events = require("events"), - util = require('util'), - ws = require('socket.io'), - jsp = require("uglify-js").parser, - pro = require("uglify-js").uglify, - _ = require('./lib/underscore.min.js'), - starttls = require('./lib/starttls.js'), - app = require(__dirname + '/app.js'); +var fs = require('fs'), + WebListener = require('./web.js').WebListener; +//load config -// Libraries may need to know kiwi.js path as __dirname -// only gives that librarys path. Set it here for usage later. -this.kiwi_root = __dirname; - - - -// How to handle log output -this.log = function(str, level) { - level = level || 0; - console.log(str); -} - - -/* - * Configuration and rehashing routines - */ var config_filename = 'config.json', - config_dirs = ['/etc/kiwiirc/', 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; + config_dirs = ['/etc/kiwiirc/', __dirname + '/']; + +var config = Object.create(null); +for (var i in config_dirs) { + try { + if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) { + config = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'utf-8')); + console.log('Loaded config file ' + config_dirs[i] + config_filename); + break; } - } - if (Object.keys(this.config).length === 0) { - if (!found_config) { - this.log('Couldn\'t find a config file!'); + } catch (e) { + switch (e.code) { + case 'ENOENT': // No file/dir + break; + default: + console.log('An error occured parsing the config file ' + config_dirs[i] + config_filename + ': ' + e.message); + return false; } - 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']; + continue; } - - app = null; - app = require(__dirname + '/app.js'); - - var objs = {tls:tls, net:net, http:http, https:https, fs:fs, url:url, dns:dns, crypto:crypto, events:events, util:util, ws:ws, jsp:jsp, pro:pro, _:_, starttls:starttls}; - app.init(objs); - app.rebindIRCCommands(); - - return true; } - - - - - -/* - * Before we continue we need the config loaded - */ -if (!this.loadConfig()) { - process.exit(0); +if (Object.keys(config).length === 0) { + console.log('Couldn\'t find a valid config file!'); + process.exit(1); } - - - - - - -/* - * 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, server) { - return app.httpHandler(request, response, server); -} - - - - - - -/* - * Websocket handling - */ -this.connections = {}; -this.io = []; -this.websocketListen = function (servers, handler) { - return app.websocketListen(servers, handler); -} -this.websocketConnection = function (websocket) { - return app.websocketConnection(websocket); -} -this.websocketDisconnect = function () { - return app.websocketDisconnect(this); -} -this.websocketMessage = function (msg, callback) { - return app.websocketMessage(this, msg, callback); -} -this.websocketIRCConnect = function (nick, host, port, ssl, callback) { - return app.websocketIRCConnect(this, nick, host, port, ssl, callback); +if ((!config.servers) || (config.servers.length < 1)) { + console.log('No servers defined in config file'); + process.exit(2); } +//Create web listeners +var clients = []; +_.each(config.servers, function (server) { + var wl = new WebListener(server, config.transports); + wl.on('connection', function (client) { + clients.push(client); + }); + wl.on('destroy', function (client) { + clients = _.reject(clients, function (c) { + return client === c; + }); + }); +}); -/* - * 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); +//Set process title +process.title = 'Kiwi IRC'; -this.bindIRCCommands = function (irc_connection, websocket) { - return app.bindIRCCommands.call(this, irc_connection, websocket); +//Change UID/GID +if ((config.user) && (config.user !== '')) { + process.setuid(config.user); } -this.rebindIRCCommands = function () { - return app.rebindIRCCommands.call(this); -} - - - - - - -/* - * Load up main application source - */ -if (!this.recode()) { - process.exit(0); +if ((config.group) && (config.group !== '')) { + process.setgid(config.group); } - - -// 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 +//Listen to STDIN process.stdin.resume(); -process.stdin.on('data', function (data) { app.manageControll(data); }); - - - - +process.stdin.on('data', function (data) { + console.log(data.toString()); +}); diff --git a/server/kiwi_modules/forcessl.js b/server/kiwi_modules/forcessl.js deleted file mode 100644 index d510299..0000000 --- a/server/kiwi_modules/forcessl.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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/kiwi_modules/spamfilter.js b/server/kiwi_modules/spamfilter.js deleted file mode 100644 index 5f3ff76..0000000 --- a/server/kiwi_modules/spamfilter.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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/kiwi_modules/statistics.js b/server/kiwi_modules/statistics.js deleted file mode 100644 index 09b4546..0000000 --- a/server/kiwi_modules/statistics.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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/lib/kiwi_mod.js b/server/lib/kiwi_mod.js deleted file mode 100644 index bdac9c3..0000000 --- a/server/lib/kiwi_mod.js +++ /dev/null @@ -1,102 +0,0 @@ -/*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/lib/starttls.js b/server/lib/starttls.js deleted file mode 100644 index e7f2240..0000000 --- a/server/lib/starttls.js +++ /dev/null @@ -1,68 +0,0 @@ -// 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/lib/underscore.min.js b/server/lib/underscore.min.js deleted file mode 100644 index ad19500..0000000 --- a/server/lib/underscore.min.js +++ /dev/null @@ -1,31 +0,0 @@ -// 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/web.js b/server/web.js new file mode 100755 index 0000000..6751e00 --- /dev/null +++ b/server/web.js @@ -0,0 +1,72 @@ +var ws = require('socket.io'), + events = require('events'), + http = require('http'), + https = require('https'), + util = require('util'), + fs = require('fs'), + dns = require('dns'), + _ = require('underscore'), + Client = require('./client.js').Client; + HTTPHandler = require('./http-handler.js').HTTPHandler; + +var WebListener = function (config, transports) { + var handler, + hs, + opts, + that = this; + + events.EventEmitter.call(this); + + http_handler = new HTTPHandler(config); + + if (config.secure) { + opts = { + key: fs.readFileSync(__dirname + '/' + config.ssl_key), + cert: fs.readFileSync(__dirname + '/' + config.ssl_cert) + }; + // Do we have an intermediate certificate? + if (typeof config.ssl_ca !== 'undefined') { + opts.ca = fs.readFileSync(__dirname + '/' + config.ssl_ca); + } + hs = https.createServer(opts, function (request, response) { + http_handler.handler(request, response); + }); + + this.ws = ws.listen(hs, {secure: true}); + hs.listen(config.port, config.address); + console.log('Listening on ' + config.address + ':' + config.port.toString() + ' with SSL'); + } else { + // Start some plain-text server up + hs = http.createServer(function (request, response) { + http_handler.handler(request, response); + }); + this.ws = ws.listen(hs, {secure: false}); + hs.listen(config.port, config.address); + console.log('Listening on ' + config.address + ':' + config.port.toString() + ' without SSL'); + } + + this.ws.set('log level', 1); + this.ws.enable('browser client minification'); + this.ws.enable('browser client etag'); + this.ws.set('transports', transports); + + this.ws.of('/kiwi').authorization(authorisation).on('connection', function () { + connection.apply(that, arguments); + }); + this.ws.of('/kiwi').on('error', console.log); +}; +util.inherits(WebListener, events.EventEmitter); + +module.exports.WebListener = WebListener; + +var authorisation = function (handshakeData, callback) { + dns.reverse(handshakeData.address.address, function (err, domains) { + handshakeData.revdns = (err) ? handshakeData.address.address : _.first(domains); + callback(null, true); + }); +}; + +var connection = function (websocket) { + //console.log(websocket); + this.emit('connection', new Client(websocket)); +}; -- 2.25.1