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