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