Herp derp, correct check for null
[KiwiIRC.git] / server / app.js
1 /*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
2 /*globals kiwi_root */
3 /* Fuck you, git. */
4 var tls = null,
5 net = null,
6 http = null,
7 https = null,
8 fs = null,
9 url = null,
10 dns = null,
11 crypto = null,
12 events = null,
13 util = null,
14 ws = null,
15 jsp = null,
16 pro = null,
17 _ = null,
18 starttls = null,
19 kiwi = null;
20
21 this.init = function (objs) {
22 tls = objs.tls;
23 net = objs.net;
24 http = objs.http;
25 https = objs.https;
26 fs = objs.fs;
27 url = objs.url;
28 dns = objs.dns;
29 crypto = objs.crypto;
30 events = objs.events;
31 util = objs.util;
32 ws = objs.ws;
33 jsp = objs.jsp;
34 pro = objs.pro;
35 _ = objs._;
36 starttls = objs.starttls;
37 kiwi = require('./kiwi.js');
38
39 util.inherits(this.IRCConnection, events.EventEmitter);
40 };
41
42
43
44
45
46
47 /*
48 * Some process changes
49 */
50 this.setTitle = function () {
51 process.title = 'kiwiirc';
52 };
53
54 this.changeUser = function () {
55 if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
56 try {
57 process.setgid(kiwi.config.group);
58 } catch (err) {
59 kiwi.log('Failed to set gid: ' + err);
60 process.exit();
61 }
62 }
63
64 if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
65 try {
66 process.setuid(kiwi.config.user);
67 } catch (e) {
68 kiwi.log('Failed to set uid: ' + e);
69 process.exit();
70 }
71 }
72 };
73
74
75
76
77
78
79
80
81
82 var ircNumerics = {
83 RPL_WELCOME: '001',
84 RPL_MYINFO: '004',
85 RPL_ISUPPORT: '005',
86 RPL_WHOISUSER: '311',
87 RPL_WHOISSERVER: '312',
88 RPL_WHOISOPERATOR: '313',
89 RPL_WHOISIDLE: '317',
90 RPL_ENDOFWHOIS: '318',
91 RPL_WHOISCHANNELS: '319',
92 RPL_LISTSTART: '321',
93 RPL_LIST: '322',
94 RPL_LISTEND: '323',
95 RPL_NOTOPIC: '331',
96 RPL_TOPIC: '332',
97 RPL_TOPICWHOTIME: '333',
98 RPL_NAMEREPLY: '353',
99 RPL_ENDOFNAMES: '366',
100 RPL_BANLIST: '367',
101 RPL_ENDOFBANLIST: '368',
102 RPL_MOTD: '372',
103 RPL_MOTDSTART: '375',
104 RPL_ENDOFMOTD: '376',
105 RPL_WHOISMODES: '379',
106 ERR_NOSUCHNICK: '401',
107 ERR_CANNOTSENDTOCHAN: '404',
108 ERR_TOOMANYCHANNELS: '405',
109 ERR_NICKNAMEINUSE: '433',
110 ERR_USERNOTINCHANNEL: '441',
111 ERR_NOTONCHANNEL: '442',
112 ERR_NOTREGISTERED: '451',
113 ERR_LINKCHANNEL: '470',
114 ERR_CHANNELISFULL: '471',
115 ERR_INVITEONLYCHAN: '473',
116 ERR_BANNEDFROMCHAN: '474',
117 ERR_BADCHANNELKEY: '475',
118 ERR_CHANOPRIVSNEEDED: '482',
119 RPL_STARTTLS: '670'
120 };
121
122
123
124 this.bindIRCCommands = function (irc_connection, websocket) {
125 var bound_events = [];
126
127 irc_connection.on('irc_PING', function (msg) {
128 websocket.sendServerLine('PONG ' + msg.trailing);
129 });
130 bound_events.push('irc_PING');
131
132 irc_connection.on('irc_' + ircNumerics.RPL_WELCOME, function (msg) {
133 if (irc_connection.IRC.CAP.negotiating) {
134 irc_connection.IRC.CAP.negotiating = false;
135 irc_connection.IRC.CAP.enabled = [];
136 irc_connection.IRC.CAP.requested = [];
137 irc_connection.IRC.registered = true;
138 }
139 var nick = msg.params.split(' ')[0];
140 websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
141 });
142 bound_events.push('irc_' + ircNumerics.RPL_WELCOME);
143
144 irc_connection.on('irc_' + ircNumerics.RPL_ISUPPORT, function (msg) {
145 var opts = msg.params.split(" "),
146 opt,
147 i,
148 j,
149 regex,
150 matches;
151 for (i = 0; i < opts.length; i++) {
152 opt = opts[i].split("=", 2);
153 opt[0] = opt[0].toUpperCase();
154 irc_connection.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
155 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt[0])) {
156 if (opt[0] === 'PREFIX') {
157 regex = /\(([^)]*)\)(.*)/;
158 matches = regex.exec(opt[1]);
159 if ((matches) && (matches.length === 3)) {
160 irc_connection.IRC.options[opt[0]] = [];
161 for (j = 0; j < matches[2].length; j++) {
162 irc_connection.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
163 }
164
165 }
166 }
167 if (opt[0] === 'NAMESX') {
168 websocket.sendServerLine('PROTOCTL NAMESX');
169 }
170 }
171 }
172
173 websocket.sendClientEvent('options', {server: '', "options": irc_connection.IRC.options});
174 });
175 bound_events.push('irc_' + ircNumerics.RPL_ISUPPORT);
176
177 irc_connection.on('irc_' + ircNumerics.RPL_ENDOFWHOIS, function (msg) {
178 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: true});
179 });
180 bound_events.push('irc_' + ircNumerics.RPL_ENDOFWHOIS);
181
182 irc_connection.on('irc_' + ircNumerics.RPL_WHOISUSER, function (msg) {
183 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
184 });
185 bound_events.push('irc_' + ircNumerics.RPL_WHOISUSER);
186
187 irc_connection.on('irc_' + ircNumerics.RPL_WHOISSERVER, function (msg) {
188 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
189 });
190 bound_events.push('irc_' + ircNumerics.RPL_WHOISSERVER);
191
192 irc_connection.on('irc_' + ircNumerics.RPL_WHOISOPERATOR, function (msg) {
193 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
194 });
195 bound_events.push('irc_' + ircNumerics.RPL_WHOISOPERATOR);
196
197 irc_connection.on('irc_' + ircNumerics.RPL_WHOISCHANNELS, function (msg) {
198 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
199 });
200 bound_events.push('irc_' + ircNumerics.RPL_WHOISCHANNELS);
201
202 irc_connection.on('irc_' + ircNumerics.RPL_WHOISMODES, function (msg) {
203 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
204 });
205 bound_events.push('irc_' + ircNumerics.RPL_WHOISMODES);
206
207 irc_connection.on('irc_' + ircNumerics.RPL_LISTSTART, function (msg) {
208 websocket.sendClientEvent('list_start', {server: ''});
209 websocket.kiwi.buffer.list = [];
210 });
211 bound_events.push('irc_' + ircNumerics.RPL_LISTSTART);
212
213 irc_connection.on('irc_' + ircNumerics.RPL_LISTEND, function (msg) {
214 if (websocket.kiwi.buffer.list.length > 0) {
215 websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
216 return channel.num_users;
217 });
218 websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
219 websocket.kiwi.buffer.list = [];
220 }
221 websocket.sendClientEvent('list_end', {server: ''});
222 });
223 bound_events.push('irc_' + ircNumerics.RPL_LISTEND);
224
225 irc_connection.on('irc_' + ircNumerics.RPL_LIST, function (msg) {
226 var parts, channel, num_users, topic;
227
228 parts = msg.params.split(' ');
229 channel = parts[1];
230 num_users = parts[2];
231 topic = msg.trailing;
232
233 //websocket.sendClientEvent('list_channel', {
234 websocket.kiwi.buffer.list.push({
235 server: '',
236 channel: channel,
237 topic: topic,
238 //modes: modes,
239 num_users: parseInt(num_users, 10)
240 });
241
242 if (websocket.kiwi.buffer.list.length > 200) {
243 websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
244 return channel.num_users;
245 });
246 websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
247 websocket.kiwi.buffer.list = [];
248 }
249 });
250 bound_events.push('irc_' + ircNumerics.RPL_LIST);
251
252 irc_connection.on('irc_' + ircNumerics.RPL_WHOISIDLE, function (msg) {
253 var params = msg.params.split(" ", 4),
254 rtn = {server: '', nick: params[1], idle: params[2]};
255 if (params[3]) {
256 rtn.logon = params[3];
257 }
258 websocket.sendClientEvent('whois', rtn);
259 });
260 bound_events.push('irc_' + ircNumerics.RPL_WHOISIDLE);
261
262 irc_connection.on('irc_' + ircNumerics.RPL_MOTD, function (msg) {
263 websocket.kiwi.buffer.motd += msg.trailing + '\n';
264 });
265 bound_events.push('irc_' + ircNumerics.RPL_MOTD);
266
267 irc_connection.on('irc_' + ircNumerics.RPL_MOTDSTART, function (msg) {
268 websocket.kiwi.buffer.motd = '';
269 });
270 bound_events.push('irc_' + ircNumerics.RPL_MOTDSTART);
271
272 irc_connection.on('irc_' + ircNumerics.RPL_ENDOFMOTD, function (msg) {
273 websocket.sendClientEvent('motd', {server: '', 'msg': websocket.kiwi.buffer.motd});
274 });
275 bound_events.push('irc_' + ircNumerics.RPL_ENDOFMOTD);
276
277 irc_connection.on('irc_' + ircNumerics.RPL_NAMEREPLY, function (msg) {
278 var params = msg.params.split(" "),
279 chan = params[2],
280 users = msg.trailing.split(" "),
281 nicklist = [],
282 i = 0;
283
284 _.each(users, function (user) {
285 var j, k, modes = [];
286 for (j = 0; j < user.length; j++) {
287 for (k = 0; k < irc_connection.IRC.options.PREFIX.length; k++) {
288 if (user.charAt(j) === irc_connection.IRC.options.PREFIX[k].symbol) {
289 modes.push(irc_connection.IRC.options.PREFIX[k].mode);
290 }
291 }
292 }
293 nicklist.push({nick: user, modes: modes});
294 if (i++ >= 50) {
295 websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
296 nicklist = [];
297 i = 0;
298 }
299 });
300 if (i > 0) {
301 websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
302 } else {
303 kiwi.log("oops");
304 }
305 });
306 bound_events.push('irc_' + ircNumerics.RPL_NAMEREPLY);
307
308 irc_connection.on('irc_' + ircNumerics.RPL_ENDOFNAMES, function (msg) {
309 websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
310 });
311 bound_events.push('irc_' + ircNumerics.RPL_ENDOFNAMES);
312
313 irc_connection.on('irc_' + ircNumerics.ERR_LINKCHANNEL, function (msg) {
314 var params = msg.params.split(" ");
315 websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
316 });
317 bound_events.push('irc_' + ircNumerics.ERR_LINKCHANNEL);
318
319 irc_connection.on('irc_' + ircNumerics.ERR_NOSUCHNICK, function (msg) {
320 websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
321 });
322 bound_events.push('irc_' + ircNumerics.ERR_NOSUCHNICK);
323
324 irc_connection.on('irc_' + ircNumerics.RPL_BANLIST, function (msg) {
325 var params = msg.params.split(" ");
326 kiwi.log(params);
327 websocket.sendClientEvent('banlist', {server: '', channel: params[1], banned: params[2], banned_by: params[3], banned_at: params[4]});
328 });
329 bound_events.push('irc_' + ircNumerics.RPL_BANLIST);
330
331 irc_connection.on('irc_' + ircNumerics.RPL_ENDOFBANLIST, function (msg) {
332 websocket.sendClientEvent('banlist_end', {server: '', channel: msg.params.split(" ")[1]});
333 });
334 bound_events.push('irc_' + ircNumerics.RPL_ENDOFBANLIST);
335
336 irc_connection.on('irc_JOIN', function (msg) {
337 var channel;
338
339 // Some BNC's send malformed JOIN causing the channel to be as a
340 // parameter instead of trailing.
341 if (typeof msg.trailing === 'string' && msg.trailing !== '') {
342 channel = msg.trailing;
343 } else if (typeof msg.params === 'string' && msg.params !== '') {
344 channel = msg.params;
345 }
346
347 websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
348 if (msg.nick === irc_connection.IRC.nick) {
349 websocket.sendServerLine('NAMES ' + msg.trailing);
350 }
351 });
352 bound_events.push('irc_JOIN');
353
354 irc_connection.on('irc_PART', function (msg) {
355 websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
356 });
357 bound_events.push('irc_PART');
358
359 irc_connection.on('irc_KICK', function (msg) {
360 var params = msg.params.split(" ");
361 websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
362 });
363 bound_events.push('irc_KICK');
364
365 irc_connection.on('irc_QUIT', function (msg) {
366 websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
367 });
368 bound_events.push('irc_QUIT');
369
370 irc_connection.on('irc_NOTICE', function (msg) {
371 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
372 // It's a CTCP response
373 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)});
374 } else {
375 websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
376 }
377 });
378 bound_events.push('irc_NOTICE');
379
380 irc_connection.on('irc_NICK', function (msg) {
381 websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
382 });
383 bound_events.push('irc_NICK');
384
385 irc_connection.on('irc_TOPIC', function (msg) {
386 var obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
387 websocket.sendClientEvent('topic', obj);
388 });
389 bound_events.push('irc_TOPIC');
390
391 irc_connection.on('irc_' + ircNumerics.RPL_TOPIC, function (msg) {
392 var obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
393 websocket.sendClientEvent('topic', obj);
394 });
395 bound_events.push('irc_' + ircNumerics.RPL_TOPIC);
396
397 irc_connection.on('irc_' + ircNumerics.RPL_NOTOPIC, function (msg) {
398 var obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
399 websocket.sendClientEvent('topic', obj);
400 });
401 bound_events.push('irc_' + ircNumerics.RPL_NOTOPIC);
402
403 irc_connection.on('irc_' + ircNumerics.RPL_TOPICWHOTIME, function (msg) {
404 var parts = msg.params.split(' '),
405 nick = parts[2],
406 channel = parts[1],
407 when = parts[3],
408 obj = {nick: nick, channel: channel, when: when};
409 websocket.sendClientEvent('topicsetby', obj);
410 });
411 bound_events.push('irc_' + ircNumerics.RPL_TOPICWHOTIME);
412
413 irc_connection.on('irc_MODE', function (msg) {
414 var opts = msg.params.split(" "),
415 params = {nick: msg.nick};
416
417 switch (opts.length) {
418 case 1:
419 params.effected_nick = opts[0];
420 params.mode = msg.trailing;
421 break;
422 case 2:
423 params.channel = opts[0];
424 params.mode = opts[1];
425 break;
426 default:
427 params.channel = opts[0];
428 params.mode = opts[1];
429 params.effected_nick = opts[2];
430 break;
431 }
432 websocket.sendClientEvent('mode', params);
433 });
434 bound_events.push('irc_MODE');
435
436 irc_connection.on('irc_PRIVMSG', function (msg) {
437 var tmp, namespace, obj;
438 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
439 // It's a CTCP request
440 if (msg.trailing.substr(1, 6) === 'ACTION') {
441 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)});
442 } else if (msg.trailing.substr(1, 4) === 'KIWI') {
443 tmp = msg.trailing.substr(6, msg.trailing.length - 2);
444 namespace = tmp.split(' ', 1)[0];
445 websocket.sendClientEvent('kiwi', {namespace: namespace, data: tmp.substr(namespace.length + 1)});
446
447 } else if (msg.trailing.substr(1, 7) === 'VERSION') {
448 irc_connection.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
449 } else {
450 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)});
451 }
452 } else {
453 obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
454 websocket.sendClientEvent('msg', obj);
455 }
456 });
457 bound_events.push('irc_PRIVMSG');
458
459 irc_connection.on('irc_CAP', function (msg) {
460 var caps = kiwi.config.cap_options,
461 options = msg.trailing.split(" "),
462 opts;
463
464 switch (_.last(msg.params.split(" "))) {
465 case 'LS':
466 opts = '';
467 _.each(_.intersect(caps, options), function (cap) {
468 if (opts !== '') {
469 opts += " ";
470 }
471 opts += cap;
472 irc_connection.IRC.CAP.requested.push(cap);
473 });
474 if (opts.length > 0) {
475 websocket.sendServerLine('CAP REQ :' + opts);
476 } else {
477 websocket.sendServerLine('CAP END');
478 }
479 // TLS is special
480 /*if (_.include(options, 'tls')) {
481 websocket.sendServerLine('STARTTLS');
482 ircSocket.IRC.CAP.requested.push('tls');
483 }*/
484 break;
485 case 'ACK':
486 _.each(options, function (cap) {
487 irc_connection.IRC.CAP.enabled.push(cap);
488 });
489 if (_.last(msg.params.split(" ")) !== '*') {
490 irc_connection.IRC.CAP.requested = [];
491 irc_connection.IRC.CAP.negotiating = false;
492 websocket.sendServerLine('CAP END');
493 }
494 break;
495 case 'NAK':
496 irc_connection.IRC.CAP.requested = [];
497 irc_connection.IRC.CAP.negotiating = false;
498 websocket.sendServerLine('CAP END');
499 break;
500 }
501 });
502 bound_events.push('irc_CAP');
503 /*case ircNumerics.RPL_STARTTLS:
504 try {
505 IRC = ircSocket.IRC;
506 listeners = ircSocket.listeners('data');
507 ircSocket.removeAllListeners('data');
508 ssl_socket = starttls(ircSocket, {}, function () {
509 ssl_socket.on("data", function (data) {
510 ircSocketDataHandler(data, websocket, ssl_socket);
511 });
512 ircSocket = ssl_socket;
513 ircSocket.IRC = IRC;
514 _.each(listeners, function (listener) {
515 ircSocket.addListener('data', listener);
516 });
517 });
518 //log(ircSocket);
519 } catch (e) {
520 kiwi.log(e);
521 }
522 break;*/
523 irc_connection.on('irc_' + ircNumerics.ERR_CANNOTSENDTOCHAN, function (msg) {
524 websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
525 });
526 bound_events.push('irc_' + ircNumerics.ERR_CANNOTSENDTOCHAN);
527
528 irc_connection.on('irc_' + ircNumerics.ERR_TOOMANYCHANNELS, function (msg) {
529 websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
530 });
531 bound_events.push('irc_' + ircNumerics.ERR_TOOMANYCHANNELS);
532
533 irc_connection.on('irc_' + ircNumerics.ERR_USERNOTINCHANNEL, function (msg) {
534 var params = msg.params.split(" ");
535 websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
536 });
537 bound_events.push('irc_' + ircNumerics.ERR_USERNOTINCHANNEL);
538
539 irc_connection.on('irc_' + ircNumerics.ERR_NOTONCHANNEL, function (msg) {
540 websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
541 });
542 bound_events.push('irc_' + ircNumerics.ERR_NOTONCHANNEL);
543
544 irc_connection.on('irc_' + ircNumerics.ERR_CHANNELISFULL, function (msg) {
545 websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
546 });
547 bound_events.push('irc_' + ircNumerics.ERR_CHANNELISFULL);
548
549 irc_connection.on('irc_' + ircNumerics.ERR_INVITEONLYCHAN, function (msg) {
550 websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
551 });
552 bound_events.push('irc_' + ircNumerics.ERR_INVITEONLYCHAN);
553
554 irc_connection.on('irc_' + ircNumerics.ERR_BANNEDFROMCHAN, function (msg) {
555 websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
556 });
557 bound_events.push('irc_' + ircNumerics.ERR_BANNEDFROMCHAN);
558
559 irc_connection.on('irc_' + ircNumerics.ERR_BADCHANNELKEY, function (msg) {
560 websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
561 });
562 bound_events.push('irc_' + ircNumerics.ERR_BADCHANNELKEY);
563
564 irc_connection.on('irc_' + ircNumerics.ERR_CHANOPRIVSNEEDED, function (msg) {
565 websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
566 });
567 bound_events.push('irc_' + ircNumerics.ERR_CHANOPRIVSNEEDED);
568
569 irc_connection.on('irc_' + ircNumerics.ERR_NICKNAMEINUSE, function (msg) {
570 websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
571 });
572 bound_events.push('irc_' + ircNumerics.ERR_NICKNAMEINUSE);
573
574 irc_connection.on('irc_ERROR', function (msg) {
575 irc_connection.end();
576 websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
577 websocket.disconnect();
578 });
579 bound_events.push('irc_ERROR');
580
581 irc_connection.on('irc_' + ircNumerics.ERR_NOTREGISTERED, function (msg) {
582 if (irc_connection.IRC.registered) {
583 kiwi.log('Kiwi thinks user is registered, but the IRC server thinks differently');
584 }
585 });
586 bound_events.push('irc_' + ircNumerics.ERR_NOTREGISTERED);
587
588 return bound_events;
589 };
590
591 this.rebindIRCCommands = function () {
592 _.each(kiwi.connections, function (con) {
593 _.each(con.sockets, function (sock) {
594 sock.ircConnection.rebindIRCCommands();
595 });
596 });
597 };
598
599
600 this.httpHandler = function (request, response) {
601 var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
602 min = {}, public_http_path, port, ssl, obj, args, ircuri, target, modifiers, query,
603 secure = (typeof request.client.encrypted === 'object');
604
605 try {
606 if (kiwi.config.handle_http) {
607 // Run through any plugins..
608 args = {request: request, response: response, ssl: secure};
609 obj = kiwi.kiwi_mod.run('http', args);
610 if (obj === null) {
611 return;
612 }
613 response = args.response;
614
615 uri = url.parse(request.url, true);
616 uri_parts = uri.pathname.split('/');
617
618 subs = uri.pathname.substr(0, 4);
619 public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;
620
621 if (typeof uri.query.ircuri !== 'undefined') {
622 ircuri = url.parse(uri.query.ircuri, true);
623 if (ircuri.protocol === 'irc:') {
624 uri_parts = /^\/([^,\?]*)((,[^,\?]*)*)?$/.exec(ircuri.pathname);
625 target = uri_parts[1];
626 modifiers = (typeof uri_parts[2] !== 'undefined') ? uri_parts[2].split(',') : [];
627 query = ircuri.query;
628
629 nick = _.detect(modifiers, function (mod) {
630 return mod === ',isnick';
631 });
632 kiwi.log(request.headers);
633 response.statusCode = 303;
634 response.setHeader('Location', 'http' + ((secure) ? 's' : '') + '://' + request.headers.host + '/client/' + ircuri.host + '/' + ((!nick) ? target : ''));
635 response.end();
636 }
637 } else if (uri.pathname === '/js/all.js') {
638 if (kiwi.cache.alljs === '') {
639
640 min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js');
641 min.util = fs.readFileSync(public_http_path + 'js/util.js');
642 min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
643 min.front = fs.readFileSync(public_http_path + 'js/front.js');
644 min.front_events = fs.readFileSync(public_http_path + 'js/front.events.js');
645 min.front_ui = fs.readFileSync(public_http_path + 'js/front.ui.js');
646 min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
647 min.ast = jsp.parse(min.underscore + min.util + min.gateway + min.front + min.front_events + min.front_ui + min.iscroll);
648 min.ast = pro.ast_mangle(min.ast);
649 min.ast = pro.ast_squeeze(min.ast);
650 min.final_code = pro.gen_code(min.ast);
651 kiwi.cache.alljs = min.final_code;
652 hash = crypto.createHash('md5').update(kiwi.cache.alljs);
653 kiwi.cache.alljs_hash = hash.digest('base64');
654 }
655 if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) {
656 response.statusCode = 304;
657 } else {
658 response.setHeader('Content-type', 'application/javascript');
659 response.setHeader('ETag', kiwi.cache.alljs_hash);
660 response.write(kiwi.cache.alljs);
661 }
662 response.end();
663 } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
664 request.addListener('end', function () {
665 kiwi.fileServer.serve(request, response);
666 });
667 } else if (uri.pathname === '/' || uri_parts[1] === 'client') {
668 useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : '';
669 if (useragent.match(/android/i) !== -1) {
670 agent = 'android';
671 touchscreen = true;
672 } else if (useragent.match(/iphone/) !== -1) {
673 agent = 'iphone';
674 touchscreen = true;
675 } else if (useragent.match(/ipad/) !== -1) {
676 agent = 'ipad';
677 touchscreen = true;
678 } else if (useragent.match(/ipod/) !== -1) {
679 agent = 'ipod';
680 touchscreen = true;
681 } else {
682 agent = 'normal';
683 touchscreen = false;
684 }
685 agent = 'normal';
686 touchscreen = false;
687
688 debug = (typeof uri.query.debug !== 'undefined');
689
690 ssl = secure; // ssl is passed to the client
691 port = ssl ? kiwi.config.client_defaults.port_ssl : kiwi.config.client_defaults.port;
692 if (uri_parts[1] !== 'client') {
693 if (uri.query) {
694 server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
695 server = uri.query.server || kiwi.config.client_defaults.server;
696 nick = uri.query.nick || '';
697 } else {
698 server_set = false;
699 server = kiwi.config.client_defaults.server;
700 nick = '';
701 }
702 } else {
703 server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
704 server = server_set ? uri_parts[2] : kiwi.config.client_defaults.server;
705 if (server.search(/:/) > 0) {
706 port = server.substring(server.search(/:/) + 1);
707 server = server.substring(0, server.search(/:/));
708 if (port[0] === '+') {
709 port = port.substring(1);
710 ssl = true;
711 } else {
712 ssl = false;
713 }
714 }
715 nick = uri.query.nick || '';
716 }
717
718 // Set the default nick if one isn't provided
719 if (nick === '') {
720 nick = 'kiwi_?';
721 }
722
723 // Set any random numbers if needed
724 nick = nick.replace('?', Math.floor(Math.random() * 100000).toString());
725
726 response.setHeader('X-Generated-By', 'KiwiIRC');
727 hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f')
728 .update(debug ? 't' : 'f')
729 .update(server_set ? 't' : 'f')
730 .update(secure ? 't' : 'f')
731 .update(server)
732 .update(port.toString())
733 .update(ssl ? 't' : 'f')
734 .update(nick)
735 .update(agent)
736 .update(JSON.stringify(kiwi.config))
737 .digest('base64');
738 if (kiwi.cache.html[hash]) {
739 if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) {
740 response.statusCode = 304;
741 } else {
742 response.setHeader('Etag', kiwi.cache.html[hash].hash);
743 response.setHeader('Content-type', 'text/html');
744 response.write(kiwi.cache.html[hash].html);
745 }
746 response.end();
747 } else {
748 fs.readFile(public_http_path + 'index.html.jade', 'utf8', function (err, str) {
749 var html, hash2;
750 if (!err) {
751 try {
752 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 });
753 hash2 = crypto.createHash('md5').update(html).digest('base64');
754 kiwi.cache.html[hash] = {"html": html, "hash": hash2};
755 if (request.headers['if-none-match'] === hash2) {
756 response.statusCode = 304;
757 } else {
758 response.setHeader('Etag', hash2);
759 response.setHeader('Content-type', 'text/html');
760 response.write(html);
761 }
762 } catch (e) {
763 response.statusCode = 500;
764 kiwi.log(e);
765 }
766 } else {
767 kiwi.log(err);
768 response.statusCode = 500;
769 }
770 response.end();
771 });
772 }
773 } else if (uri.pathname.substr(0, 10) === '/socket.io') {
774 return;
775 } else {
776 response.statusCode = 404;
777 response.end();
778 }
779 }
780
781 } catch (e) {
782 kiwi.log('ERROR app.httpHandler()');
783 kiwi.log(e);
784 }
785 };
786
787
788
789
790 this.websocketListen = function (servers, handler) {
791 if (kiwi.httpServers.length > 0) {
792 _.each(kiwi.httpServers, function (hs) {
793 hs.close();
794 });
795 kiwi.httpsServers = [];
796 }
797
798 _.each(servers, function (server) {
799 var hs, opts;
800 if (server.secure === true) {
801 // Start some SSL server up
802 opts = {
803 key: fs.readFileSync(__dirname + '/' + server.ssl_key),
804 cert: fs.readFileSync(__dirname + '/' + server.ssl_cert)
805 };
806
807 // Do we have an intermediate certificate?
808 if (typeof server.ssl_ca !== 'undefined') {
809 opts.ca = fs.readFileSync(__dirname + '/' + server.ssl_ca);
810 }
811
812 hs = https.createServer(opts, handler);
813 kiwi.io.push(ws.listen(hs, {secure: true}));
814 hs.listen(server.port, server.address);
815 kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' with SSL');
816 } else {
817 // Start some plain-text server up
818 hs = http.createServer(handler);
819 kiwi.io.push(ws.listen(hs, {secure: false}));
820 hs.listen(server.port, server.address);
821 kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' without SSL');
822 }
823
824 kiwi.httpServers.push(hs);
825 });
826
827 _.each(kiwi.io, function (io) {
828 io.set('log level', 1);
829 io.enable('browser client minification');
830 io.enable('browser client etag');
831 io.set('transports', kiwi.config.transports);
832
833 io.of('/kiwi').authorization(function (handshakeData, callback) {
834 var address = handshakeData.address.address;
835 if (typeof kiwi.connections[address] === 'undefined') {
836 kiwi.connections[address] = {count: 0, sockets: []};
837 }
838 callback(null, true);
839 }).on('connection', kiwi.websocketConnection);
840 });
841 };
842
843
844
845
846
847
848 this.websocketConnection = function (websocket) {
849 var con;
850 kiwi.log("New connection!");
851 websocket.kiwi = {address: websocket.handshake.address.address, buffer: {list: []}};
852 con = kiwi.connections[websocket.kiwi.address];
853
854 if (con.count >= kiwi.config.max_client_conns) {
855 websocket.emit('too_many_connections');
856 websocket.disconnect();
857 } else {
858 con.count += 1;
859 con.sockets.push(websocket);
860
861 websocket.sendClientEvent = function (event_name, data) {
862 var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
863 if (ev === null) {
864 return;
865 }
866
867 data.event = event_name;
868 websocket.emit('message', data);
869 };
870
871 websocket.sendServerLine = function (data, eol, callback) {
872 if ((arguments.length < 3) && (typeof eol === 'function')) {
873 callback = eol;
874 eol = '\r\n';
875 }
876 eol = ((typeof eol === 'undefined') || (eol === null)) ? '\r\n' : eol;
877
878 try {
879 websocket.ircConnection.write(data + eol, 'utf-8', callback);
880 } catch (e) { }
881 };
882
883 websocket.on('irc connect', function (nick, host, port, ssl, password, callback) {
884 websocket.ircConnection = new kiwi.IRCConnection(this, nick, host, port, ssl, password, callback);
885 });
886 websocket.on('message', kiwi.websocketMessage);
887 websocket.on('disconnect', kiwi.websocketDisconnect);
888 }
889 };
890
891
892 this.IRCConnection = function (websocket, nick, host, port, ssl, password, callback) {
893 var ircSocket,
894 that = this,
895 regex,
896 onConnectHandler,
897 bound_events;
898
899 events.EventEmitter.call(this);
900
901 onConnectHandler = function () {
902 that.IRC.nick = nick;
903 // Send the login data
904 dns.reverse(websocket.kiwi.address, function (err, domains) {
905 websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
906 if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
907 websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
908 }
909 if (password) {
910 websocket.sendServerLine('PASS ' + password);
911 }
912 websocket.sendServerLine('CAP LS');
913 websocket.sendServerLine('NICK ' + nick);
914 websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);
915
916 that.connected = true;
917 that.emit('connect');
918 });
919 };
920
921 if (!ssl) {
922 ircSocket = net.createConnection(port, host);
923 ircSocket.on('connect', onConnectHandler);
924 } else {
925 ircSocket = tls.connect(port, host, {}, onConnectHandler);
926 }
927
928 ircSocket.setEncoding('utf-8');
929 this.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
930
931 this.on('error', function (e) {
932 if (that.IRC.registered) {
933 websocket.emit('disconnect');
934 } else {
935 websocket.emit('error', e.message);
936 }
937 });
938
939 ircSocket.on('error', function (e) {
940 that.connected = false;
941 that.emit('error', e);
942 that.destroySoon();
943 });
944
945 if (typeof callback === 'function') {
946 this.on('connect', callback);
947 }
948
949 regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
950 ircSocket.holdLast = false;
951 ircSocket.held = '';
952 ircSocket.on('data', function (data) {
953 var i, msg;
954 if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
955 data = ircSocket.held + data;
956 ircSocket.holdLast = false;
957 ircSocket.held = '';
958 }
959 if (data.substr(-1) !== '\n') {
960 ircSocket.holdLast = true;
961 }
962 data = data.split("\n");
963 for (i = 0; i < data.length; i++) {
964 if (data[i]) {
965 if ((ircSocket.holdLast) && (i === data.length - 1)) {
966 ircSocket.held = data[i];
967 break;
968 }
969
970 // We have a complete line of data, parse it!
971 msg = regex.exec(data[i].replace(/^\r+|\r+$/, ''));
972 if (msg) {
973 msg = {
974 prefix: msg[1],
975 nick: msg[2],
976 ident: msg[3],
977 hostname: msg[4] || '',
978 command: msg[5],
979 params: msg[6] || '',
980 trailing: (msg[7]) ? msg[7].trim() : ''
981 };
982 that.emit('irc_' + msg.command.toUpperCase(), msg);
983 if (that.listeners('irc_' + msg.command.toUpperCase()).length < 1) {
984 kiwi.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
985 }
986 } else {
987 kiwi.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
988 }
989 }
990 }
991 });
992
993 if (callback) {
994 ircSocket.on('connect', callback);
995 }
996
997 ircSocket.on('end', function () {
998 that.connected = false;
999 that.emit('disconnect', false);
1000 });
1001
1002 ircSocket.on('close', function (had_error) {
1003 that.connected = false;
1004 that.emit('disconnect', had_error);
1005 });
1006
1007 ircSocket.on('timeout', function () {
1008 ircSocket.destroy();
1009 that.connected = false;
1010 that.emit('error', {message: 'Connection timed out'});
1011 });
1012
1013 ircSocket.on('drain', function () {
1014 that.emit('drain');
1015 });
1016
1017 this.write = function (data, encoding, callback) {
1018 ircSocket.write(data, encoding, callback);
1019 };
1020
1021 this.end = function (data, encoding, callback) {
1022 that.connected = false;
1023 ircSocket.end(data, encoding, callback);
1024 };
1025
1026 this.destroySoon = function () {
1027 ircSocket.destroySoon();
1028 };
1029
1030 bound_events = kiwi.bindIRCCommands(this, websocket);
1031
1032 this.rebindIRCCommands = function () {
1033 _.each(bound_events, function (event) {
1034 that.removeAllListeners(event);
1035 });
1036 bound_events = kiwi.bindIRCCommands(that, websocket);
1037 };
1038 };
1039
1040
1041
1042 this.websocketMessage = function (websocket, msg, callback) {
1043 var args, obj, channels, keys;
1044 try {
1045 if ((callback) && (typeof (callback) !== 'function')) {
1046 callback = null;
1047 }
1048 msg.data = JSON.parse(msg.data);
1049 args = msg.data.args;
1050 switch (msg.data.method) {
1051 case 'privmsg':
1052 if ((args.target) && (args.msg)) {
1053 obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
1054 if (obj !== null) {
1055 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg, callback);
1056 }
1057 }
1058 break;
1059 case 'ctcp':
1060 if ((args.target) && (args.type)) {
1061 if (args.request) {
1062 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1), callback);
1063 } else {
1064 websocket.sendServerLine('NOTICE ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1), callback);
1065 }
1066 }
1067 break;
1068
1069 case 'raw':
1070 websocket.sendServerLine(args.data, callback);
1071 break;
1072
1073 case 'join':
1074 if (args.channel) {
1075 channels = args.channel.split(",");
1076 keys = (args.key) ? args.key.split(",") : [];
1077 _.each(channels, function (chan, index) {
1078 websocket.sendServerLine('JOIN ' + chan + ' ' + (keys[index] || ''), callback);
1079 });
1080 }
1081 break;
1082
1083 case 'part':
1084 if (args.channel) {
1085 _.each(args.channel.split(","), function (chan) {
1086 websocket.sendServerLine('PART ' + chan, callback);
1087 });
1088 }
1089 break;
1090
1091 case 'topic':
1092 if (args.channel) {
1093 if (args.topic) {
1094 websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic, callback);
1095 } else {
1096 websocket.sendServerLine('TOPIC ' + args.channel, callback);
1097 }
1098 }
1099 break;
1100
1101 case 'kick':
1102 if ((args.channel) && (args.nick)) {
1103 websocket.sendServerLine('KICK ' + args.channel + ' ' + args.nick + ':' + args.reason, callback);
1104 }
1105 break;
1106
1107 case 'quit':
1108 websocket.ircConnection.end('QUIT :' + args.message + '\r\n');
1109 websocket.sentQUIT = true;
1110 websocket.ircConnection.destroySoon();
1111 websocket.disconnect();
1112 break;
1113
1114 case 'notice':
1115 if ((args.target) && (args.msg)) {
1116 websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg, callback);
1117 }
1118 break;
1119
1120 case 'mode':
1121 if ((args.target) && (args.mode)) {
1122 websocket.sendServerLine('MODE ' + args.target + ' ' + args.mode + ' ' + args.params, callback);
1123 }
1124 break;
1125
1126 case 'nick':
1127 if (args.nick) {
1128 websocket.sendServerLine('NICK ' + args.nick, callback);
1129 }
1130 break;
1131
1132 case 'kiwi':
1133 if ((args.target) && (args.data)) {
1134 websocket.sendServerLine('PRIVMSG ' + args.target + ': ' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1), callback);
1135 }
1136 break;
1137 default:
1138 }
1139 } catch (e) {
1140 kiwi.log("Caught error: " + e);
1141 }
1142 };
1143
1144
1145
1146 this.websocketDisconnect = function (websocket) {
1147 var con;
1148
1149 if ((!websocket.sentQUIT) && (websocket.ircConnection.connected)) {
1150 try {
1151 websocket.ircConnection.end('QUIT :' + kiwi.config.quit_message + '\r\n');
1152 websocket.sentQUIT = true;
1153 websocket.ircConnection.destroySoon();
1154 } catch (e) {
1155 }
1156 }
1157 con = kiwi.connections[websocket.kiwi.address];
1158 con.count -= 1;
1159 con.sockets = _.reject(con.sockets, function (sock) {
1160 return sock === websocket;
1161 });
1162 };
1163
1164
1165
1166
1167
1168
1169 this.rehash = function () {
1170 var changes, i,
1171 reload_config = kiwi.loadConfig();
1172
1173 // If loading the new config errored out, dont attempt any changes
1174 if (reload_config === false) {
1175 return false;
1176 }
1177
1178 // We just want the settings that have been changed
1179 changes = reload_config[1];
1180
1181 if (Object.keys(changes).length !== 0) {
1182 kiwi.log('%s config changes: \n', Object.keys(changes).length, changes);
1183 for (i in changes) {
1184 switch (i) {
1185 case 'ports':
1186 case 'bind_address':
1187 case 'ssl_key':
1188 case 'ssl_cert':
1189 kiwi.websocketListen(kiwi.config.servers, kiwi.httpHandler);
1190 delete changes.ports;
1191 delete changes.bind_address;
1192 delete changes.ssl_key;
1193 delete changes.ssl_cert;
1194 break;
1195 case 'user':
1196 case 'group':
1197 kiwi.changeUser();
1198 delete changes.user;
1199 delete changes.group;
1200 break;
1201 case 'module_dir':
1202 case 'modules':
1203 kiwi.kiwi_mod.loadModules(kiwi_root, kiwi.config);
1204 kiwi.kiwi_mod.printMods();
1205 delete changes.module_dir;
1206 delete changes.modules;
1207 break;
1208 }
1209 }
1210 }
1211
1212 // Also clear the kiwi.cached javascript and HTML
1213 if (kiwi.config.handle_http) {
1214 kiwi.cache = {alljs: '', html: []};
1215 }
1216
1217 return true;
1218 };
1219
1220
1221
1222
1223
1224 /*
1225 * KiwiIRC controlling via STDIN
1226 */
1227 this.manageControll = function (data) {
1228 var parts = data.toString().trim().split(' '),
1229 connections_cnt = 0,
1230 i;
1231 switch (parts[0]) {
1232 case 'rehash':
1233 kiwi.log('Rehashing...');
1234 kiwi.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
1235 break;
1236
1237 case 'recode':
1238 kiwi.log('Recoding...');
1239 kiwi.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
1240 break;
1241
1242 case 'mod':
1243 if (parts[1] === 'reload') {
1244 if (!parts[2]) {
1245 kiwi.log('Usage: mod reload module_name');
1246 return;
1247 }
1248
1249 kiwi.log('Reloading module (' + parts[2] + ')..');
1250 kiwi.kiwi_mod.reloadModule(parts[2]);
1251 } else if (parts[1] === 'list') {
1252 kiwi.kiwi_mod.printMods();
1253 }
1254 break;
1255
1256 case 'cache':
1257 if (parts[1] === 'clear') {
1258 kiwi.cache.html = {};
1259 kiwi.cache.alljs = '';
1260 kiwi.log('HTML cache cleared');
1261 }
1262 break;
1263
1264 case 'status':
1265 for (i in kiwi.connections) {
1266 connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
1267 }
1268 kiwi.log(connections_cnt.toString() + ' connected clients');
1269 break;
1270
1271 default:
1272 kiwi.log('Unknown command \'' + parts[0] + '\'');
1273 }
1274 };