Merge branch 'master' of github.com:prawnsalad/KiwiIRC
[KiwiIRC.git] / node / app.js
1 /*jslint 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 var net = null;
5 var http = null;
6 var https = null;
7 var fs = null;
8 var url = null;
9 var dns = null;
10 var crypto = null;
11 var ws = null;
12 var jsp = null;
13 var pro = null;
14 var _ = null;
15 var starttls = null;
16 var kiwi = null;
17
18 this.init = function (objs) {
19 tls = objs.tls;
20 net = objs.net;
21 http = objs.http;
22 https = objs.https;
23 fs = objs.fs;
24 url = objs.url;
25 dns = objs.dns;
26 crypto = objs.crypto;
27 ws = objs.ws;
28 jsp = objs.jsp;
29 pro = objs.pro;
30 _ = objs._;
31 starttls = objs.starttls;
32 kiwi = require('./kiwi.js');
33 };
34
35
36
37
38
39
40 /*
41 * Some process changes
42 */
43 this.setTitle = function () {
44 process.title = 'kiwiirc';
45 };
46
47 this.changeUser = function () {
48 if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
49 try {
50 process.setgid(kiwi.config.group);
51 } catch (err) {
52 console.log('Failed to set gid: ' + err);
53 process.exit();
54 }
55 }
56
57 if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
58 try {
59 process.setuid(kiwi.config.user);
60 } catch (e) {
61 console.log('Failed to set uid: ' + e);
62 process.exit();
63 }
64 }
65 };
66
67
68
69
70
71
72
73
74
75 var ircNumerics = {
76 RPL_WELCOME: '001',
77 RPL_MYINFO: '004',
78 RPL_ISUPPORT: '005',
79 RPL_WHOISUSER: '311',
80 RPL_WHOISSERVER: '312',
81 RPL_WHOISOPERATOR: '313',
82 RPL_WHOISIDLE: '317',
83 RPL_ENDOFWHOIS: '318',
84 RPL_WHOISCHANNELS: '319',
85 RPL_LISTSTART: '321',
86 RPL_LIST: '322',
87 RPL_LISTEND: '323',
88 RPL_NOTOPIC: '331',
89 RPL_TOPIC: '332',
90 RPL_NAMEREPLY: '353',
91 RPL_ENDOFNAMES: '366',
92 RPL_MOTD: '372',
93 RPL_WHOISMODES: '379',
94 ERR_NOSUCHNICK: '401',
95 ERR_CANNOTSENDTOCHAN: '404',
96 ERR_TOOMANYCHANNELS: '405',
97 ERR_NICKNAMEINUSE: '433',
98 ERR_USERNOTINCHANNEL: '441',
99 ERR_NOTONCHANNEL: '442',
100 ERR_NOTREGISTERED: '451',
101 ERR_LINKCHANNEL: '470',
102 ERR_CHANNELISFULL: '471',
103 ERR_INVITEONLYCHAN: '473',
104 ERR_BANNEDFROMCHAN: '474',
105 ERR_BADCHANNELKEY: '475',
106 ERR_CHANOPRIVSNEEDED: '482',
107 RPL_STARTTLS: '670'
108 };
109
110
111
112 this.parseIRCMessage = function (websocket, ircSocket, data) {
113 /*global ircSocketDataHandler */
114 var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, prefix, prefixes, nicklist, caps, rtn, obj, tmp, namespace;
115 //regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i;
116 //regex = /^(?::(\S+) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
117 regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
118
119 msg = regex.exec(data);
120 if (msg) {
121 msg = {
122 prefix: msg[1],
123 nick: msg[2],
124 ident: msg[3],
125 hostname: msg[4] || '',
126 command: msg[5],
127 params: msg[6] || '',
128 trailing: (msg[7]) ? msg[7].trim() : ''
129 };
130
131 switch (msg.command.toUpperCase()) {
132 case 'PING':
133 websocket.sendServerLine('PONG ' + msg.trailing);
134 break;
135 case ircNumerics.RPL_WELCOME:
136 if (ircSocket.IRC.CAP.negotiating) {
137 ircSocket.IRC.CAP.negotiating = false;
138 ircSocket.IRC.CAP.enabled = [];
139 ircSocket.IRC.CAP.requested = [];
140 ircSocket.IRC.registered = true;
141 }
142 //regex = /([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)/i;
143 //matches = regex.exec(msg.trailing);
144 nick = msg.params.split(' ')[0];
145 websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
146 break;
147 case ircNumerics.RPL_ISUPPORT:
148 opts = msg.params.split(" ");
149 options = [];
150 for (i = 0; i < opts.length; i++) {
151 opt = opts[i].split("=", 2);
152 opt[0] = opt[0].toUpperCase();
153 ircSocket.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
154 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) {
155 if (opt[0] === 'PREFIX') {
156 regex = /\(([^)]*)\)(.*)/;
157 matches = regex.exec(opt[1]);
158 if ((matches) && (matches.length === 3)) {
159 ircSocket.IRC.options[opt[0]] = [];
160 for (j = 0; j < matches[2].length; j++) {
161 //ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
162 ircSocket.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
163 }
164
165 }
166 }
167 }
168 }
169
170 websocket.sendClientEvent('options', {server: '', "options": ircSocket.IRC.options});
171 break;
172 case ircNumerics.RPL_WHOISUSER:
173 case ircNumerics.RPL_WHOISSERVER:
174 case ircNumerics.RPL_WHOISOPERATOR:
175 case ircNumerics.RPL_ENDOFWHOIS:
176 case ircNumerics.RPL_WHOISCHANNELS:
177 case ircNumerics.RPL_WHOISMODES:
178 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
179 break;
180
181 case ircNumerics.RPL_LISTSTART:
182 (function () {
183 websocket.sendClientEvent('list_start', {server: ''});
184 }());
185 break;
186 case ircNumerics.RPL_LISTEND:
187 (function () {
188 websocket.sendClientEvent('list_end', {server: ''});
189 }());
190 break;
191
192 case ircNumerics.RPL_LIST:
193 (function () {
194 var parts, channel, num_users, modes, topic;
195
196 parts = msg.params.split(' ');
197 channel = parts[1];
198 num_users = parts[2];
199 modes = msg.trailing.split(' ', 1);
200 topic = msg.trailing.substring(msg.trailing.indexOf(' ')+1);
201
202 websocket.sendClientEvent('list_channel', {
203 server: '',
204 channel: channel,
205 topic: topic,
206 modes: modes,
207 num_users: num_users
208 });
209 }());
210 break;
211
212 case ircNumerics.RPL_WHOISIDLE:
213 params = msg.params.split(" ", 4);
214 rtn = {server: '', nick: params[1], idle: params[2]};
215 if (params[3]) {
216 rtn.logon = params[3];
217 }
218 websocket.sendClientEvent('whois', rtn);
219 break;
220 case ircNumerics.RPL_MOTD:
221 websocket.sendClientEvent('motd', {server: '', "msg": msg.trailing});
222 break;
223 case ircNumerics.RPL_NAMEREPLY:
224 params = msg.params.split(" ");
225 nick = params[0];
226 chan = params[2];
227 users = msg.trailing.split(" ");
228 prefixes = _.values(ircSocket.IRC.options.PREFIX);
229 nicklist = {};
230 i = 0;
231 _.each(users, function (user) {
232 if (_.include(prefix, user.charAt(0))) {
233 prefix = user.charAt(0);
234 user = user.substring(1);
235 nicklist[user] = prefix;
236 } else {
237 nicklist[user] = '';
238 }
239 if (i++ >= 50) {
240 websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
241 nicklist = {};
242 i = 0;
243 }
244 });
245 if (i > 0) {
246 websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
247 } else {
248 console.log("oops");
249 }
250 break;
251 case ircNumerics.RPL_ENDOFNAMES:
252 websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
253 break;
254 case ircNumerics.ERR_LINKCHANNEL:
255 params = msg.params.split(" ");
256 websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
257 break;
258 case ircNumerics.ERR_NOSUCHNICK:
259 websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
260 break;
261 case 'JOIN':
262 // Some BNC's send malformed JOIN causing the channel to be as a
263 // parameter instead of trailing.
264 if (typeof msg.trailing === 'string' && msg.trailing !== '') {
265 channel = msg.trailing;
266 } else if (typeof msg.params === 'string' && msg.params !== '') {
267 channel = msg.params;
268 }
269
270 websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
271 if (msg.nick === ircSocket.IRC.nick) {
272 websocket.sendServerLine('NAMES ' + msg.trailing);
273 }
274 break;
275 case 'PART':
276 websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
277 break;
278 case 'KICK':
279 params = msg.params.split(" ");
280 websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
281 break;
282 case 'QUIT':
283 websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
284 break;
285 case 'NOTICE':
286 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
287 // It's a CTCP response
288 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)});
289 } else {
290 websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
291 }
292 break;
293 case 'NICK':
294 websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
295 break;
296 case 'TOPIC':
297 obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
298 websocket.sendClientEvent('topic', obj);
299 break;
300 case ircNumerics.RPL_TOPIC:
301 obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
302 websocket.sendClientEvent('topic', obj);
303 break;
304 case ircNumerics.RPL_NOTOPIC:
305 obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
306 websocket.sendClientEvent('topic', obj);
307 break;
308 case 'MODE':
309 opts = msg.params.split(" ");
310 params = {nick: msg.nick};
311 switch (opts.length) {
312 case 1:
313 params.effected_nick = opts[0];
314 params.mode = msg.trailing;
315 break;
316 case 2:
317 params.channel = opts[0];
318 params.mode = opts[1];
319 break;
320 default:
321 params.channel = opts[0];
322 params.mode = opts[1];
323 params.effected_nick = opts[2];
324 break;
325 }
326 websocket.sendClientEvent('mode', params);
327 break;
328 case 'PRIVMSG':
329 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
330 // It's a CTCP request
331 if (msg.trailing.substr(1, 6) === 'ACTION') {
332 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)});
333 } else if (msg.trailing.substr(1, 4) === 'KIWI') {
334 tmp = msg.trailing.substr(6, msg.trailing.length - 2);
335 namespace = tmp.split(' ', 1)[0];
336 websocket.sendClientEvent('kiwi', {namespace: namespace, data: tmp.substr(namespace.length + 1)});
337
338 } else if (msg.trailing.substr(1, 7) === 'VERSION') {
339 ircSocket.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
340 } else {
341 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)});
342 }
343 } else {
344 obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
345 websocket.sendClientEvent('msg', obj);
346 }
347 break;
348 case 'CAP':
349 caps = kiwi.config.cap_options;
350 options = msg.trailing.split(" ");
351 switch (_.last(msg.params.split(" "))) {
352 case 'LS':
353 opts = '';
354 _.each(_.intersect(caps, options), function (cap) {
355 if (opts !== '') {
356 opts += " ";
357 }
358 opts += cap;
359 ircSocket.IRC.CAP.requested.push(cap);
360 });
361 if (opts.length > 0) {
362 websocket.sendServerLine('CAP REQ :' + opts);
363 } else {
364 websocket.sendServerLine('CAP END');
365 }
366 // TLS is special
367 /*if (_.include(options, 'tls')) {
368 websocket.sendServerLine('STARTTLS');
369 ircSocket.IRC.CAP.requested.push('tls');
370 }*/
371 break;
372 case 'ACK':
373 _.each(options, function (cap) {
374 ircSocket.IRC.CAP.enabled.push(cap);
375 });
376 if (_.last(msg.params.split(" ")) !== '*') {
377 ircSocket.IRC.CAP.requested = [];
378 ircSocket.IRC.CAP.negotiating = false;
379 websocket.sendServerLine('CAP END');
380 }
381 break;
382 case 'NAK':
383 ircSocket.IRC.CAP.requested = [];
384 ircSocket.IRC.CAP.negotiating = false;
385 websocket.sendServerLine('CAP END');
386 break;
387 }
388 break;
389 /*case ircNumerics.RPL_STARTTLS:
390 try {
391 IRC = ircSocket.IRC;
392 listeners = ircSocket.listeners('data');
393 ircSocket.removeAllListeners('data');
394 ssl_socket = starttls(ircSocket, {}, function () {
395 ssl_socket.on("data", function (data) {
396 ircSocketDataHandler(data, websocket, ssl_socket);
397 });
398 ircSocket = ssl_socket;
399 ircSocket.IRC = IRC;
400 _.each(listeners, function (listener) {
401 ircSocket.addListener('data', listener);
402 });
403 });
404 //console.log(ircSocket);
405 } catch (e) {
406 console.log(e);
407 }
408 break;*/
409 case ircNumerics.ERR_CANNOTSENDTOCHAN:
410 websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
411 break;
412 case ircNumerics.ERR_TOOMANYCHANNELS:
413 websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
414 break;
415 case ircNumerics.ERR_USERNOTINCHANNEL:
416 params = msg.params.split(" ");
417 websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
418 break;
419 case ircNumerics.ERR_NOTONCHANNEL:
420 websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
421 break;
422 case ircNumerics.ERR_CHANNELISFULL:
423 websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
424 break;
425 case ircNumerics.ERR_INVITEONLYCHAN:
426 websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
427 break;
428 case ircNumerics.ERR_BANNEDFROMCHAN:
429 websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
430 break;
431 case ircNumerics.ERR_BADCHANNELKEY:
432 websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
433 break;
434 case ircNumerics.ERR_CHANOPRIVSNEEDED:
435 websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
436 break;
437 case ircNumerics.ERR_NICKNAMEINUSE:
438 websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
439 break;
440 case 'ERROR':
441 ircSocket.end();
442 websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
443 websocket.disconnect();
444 break;
445 case ircNumerics.ERR_NOTREGISTERED:
446 if (ircSocket.IRC.registered) {
447 console.log('Kiwi thinks user is registered, but the IRC server thinks differently');
448 }
449 break;
450 default:
451 console.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
452 }
453 } else {
454 console.log("Malformed IRC line: " + data);
455 }
456 };
457
458
459
460
461
462
463 /*
464 * NOTE: Some IRC servers or BNC's out there incorrectly use
465 * only \n as a line splitter.
466 */
467 this.ircSocketDataHandler = function (data, websocket, ircSocket) {
468 var i;
469 if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
470 data = ircSocket.held + data;
471 ircSocket.holdLast = false;
472 ircSocket.held = '';
473 }
474 if (data.substr(-1) !== '\n') {
475 ircSocket.holdLast = true;
476 }
477 data = data.split("\n");
478 for (i = 0; i < data.length; i++) {
479 if (data[i]) {
480 if ((ircSocket.holdLast) && (i === data.length - 1)) {
481 ircSocket.held = data[i];
482 break;
483 }
484
485 // We have a complete line of data, parse it!
486 kiwi.parseIRCMessage(websocket, ircSocket, data[i].replace(/^\r+|\r+$/, ''));
487 }
488 }
489 };
490
491
492
493
494
495 this.httpHandler = function (request, response) {
496 var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
497 min = {}, public_http_path;
498 if (kiwi.config.handle_http) {
499 uri = url.parse(request.url, true);
500 uri_parts = uri.pathname.split('/');
501
502 subs = uri.pathname.substr(0, 4);
503 if (uri.pathname === '/js/all.js') {
504 if (kiwi.cache.alljs === '') {
505 public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;
506
507 min.util = fs.readFileSync(public_http_path + 'js/util.js');
508 min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
509 min.front = fs.readFileSync(public_http_path + 'js/front.js');
510 min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
511 min.ast = jsp.parse(min.util + min.gateway + min.front + min.iscroll);
512 min.ast = pro.ast_mangle(min.ast);
513 min.ast = pro.ast_squeeze(min.ast);
514 min.final_code = pro.gen_code(min.ast);
515 kiwi.cache.alljs = min.final_code;
516 hash = crypto.createHash('md5').update(kiwi.cache.alljs);
517 kiwi.cache.alljs_hash = hash.digest('base64');
518 }
519 if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) {
520 response.statusCode = 304;
521 } else {
522 response.setHeader('ETag', kiwi.cache.alljs_hash);
523 response.write(kiwi.cache.alljs);
524 }
525 response.end();
526 } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
527 request.addListener('end', function () {
528 kiwi.fileServer.serve(request, response);
529 });
530 } else if (uri.pathname === '/' || uri_parts[1] === 'client') {
531 useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : '';
532 if (useragent.match(/android/i) !== -1) {
533 agent = 'android';
534 touchscreen = true;
535 } else if (useragent.match(/iphone/) !== -1) {
536 agent = 'iphone';
537 touchscreen = true;
538 } else if (useragent.match(/ipad/) !== -1) {
539 agent = 'ipad';
540 touchscreen = true;
541 } else if (useragent.match(/ipod/) !== -1) {
542 agent = 'ipod';
543 touchscreen = true;
544 } else {
545 agent = 'normal';
546 touchscreen = false;
547 }
548 agent = 'normal';
549 touchscreen = false;
550
551 if (uri_parts[1] !== 'client') {
552 if (uri.query) {
553 server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
554 server = uri.query.server || 'irc.anonnet.org';
555 nick = uri.query.nick || '';
556 debug = (uri.query.debug !== '');
557 } else {
558 server_set = false;
559 server = 'irc.anonnet.org';
560 nick = '';
561 }
562 } else {
563 server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
564 server = server_set ? uri_parts[2] : 'irc.anonnet.org';
565 nick = uri.query.nick || '';
566 }
567
568 response.setHeader('X-Generated-By', 'KiwiIRC');
569 hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f').update(debug ? 't' : 'f').update(server_set ? 't' : 'f').update(server).update(nick).update(agent).update(JSON.stringify(kiwi.config)).digest('base64');
570 if (kiwi.cache.html[hash]) {
571 if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) {
572 response.statusCode = 304;
573 } else {
574 response.setHeader('Etag', kiwi.cache.html[hash].hash);
575 response.write(kiwi.cache.html[hash].html);
576 }
577 response.end();
578 } else {
579 kiwi.jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": kiwi.config }}, function (err, html) {
580 if (!err) {
581 var hash2 = crypto.createHash('md5').update(html).digest('base64');
582 kiwi.cache.html[hash] = {"html": html, "hash": hash2};
583 if (request.headers['if-none-match'] === hash2) {
584 response.statusCode = 304;
585 } else {
586 response.setHeader('Etag', hash2);
587 response.write(html);
588 }
589 } else {
590 response.statusCode = 500;
591 }
592 response.end();
593 });
594 }
595 } else if (uri.pathname.substr(0, 10) === '/socket.io') {
596 return;
597 } else {
598 response.statusCode = 404;
599 response.end();
600 }
601 }
602 };
603
604
605
606
607 this.websocketListen = function (port, host, handler, secure, key, cert) {
608 if (kiwi.httpServer) {
609 kiwi.httpServer.close();
610 }
611 if (secure) {
612 kiwi.httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + key), cert: fs.readFileSync(__dirname + '/' + cert)}, handler);
613 kiwi.io = ws.listen(kiwi.httpServer, {secure: true});
614 kiwi.httpServer.listen(port, host);
615 } else {
616 kiwi.httpServer = http.createServer(handler);
617 kiwi.io = ws.listen(kiwi.httpServer, {secure: false});
618 kiwi.httpServer.listen(port, host);
619 }
620
621 kiwi.io.set('log level', 1);
622 kiwi.io.enable('browser client minification');
623 kiwi.io.enable('browser client etag');
624 kiwi.io.set('transports', kiwi.config.transports);
625
626 kiwi.io.of('/kiwi').authorization(function (handshakeData, callback) {
627 var address = handshakeData.address.address;
628
629 if (typeof kiwi.connections[address] === 'undefined') {
630 kiwi.connections[address] = {count: 0, sockets: []};
631 }
632 callback(null, true);
633 }).on('connection', kiwi.websocketConnection);
634 };
635
636
637
638
639
640
641 this.websocketConnection = function (websocket) {
642 var con;
643 websocket.kiwi = {address: websocket.handshake.address.address};
644 con = kiwi.connections[websocket.kiwi.address];
645
646 if (con.count >= kiwi.config.max_client_conns) {
647 websocket.emit('too_many_connections');
648 websocket.disconnect();
649 } else {
650 con.count += 1;
651 con.sockets.push(websocket);
652
653 websocket.sendClientEvent = function (event_name, data) {
654 var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
655 if (ev === null) {
656 return;
657 }
658
659 data.event = event_name;
660 websocket.emit('message', data);
661 };
662
663 websocket.sendServerLine = function (data, eol) {
664 eol = (typeof eol === 'undefined') ? '\r\n' : eol;
665
666 try {
667 websocket.ircSocket.write(data + eol);
668 } catch (e) { }
669 };
670
671 websocket.on('irc connect', kiwi.websocketIRCConnect);
672 websocket.on('message', kiwi.websocketMessage);
673 websocket.on('disconnect', kiwi.websocketDisconnect);
674 }
675 };
676
677
678
679
680
681 this.websocketIRCConnect = function (websocket, nick, host, port, ssl, callback) {
682 var ircSocket;
683 //setup IRC connection
684 if (!ssl) {
685 ircSocket = net.createConnection(port, host);
686 } else {
687 ircSocket = tls.connect(port, host);
688 }
689 ircSocket.setEncoding('ascii');
690 ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
691 ircSocket.on('error', function (e) {
692 if (ircSocket.IRC.registered) {
693 websocket.emit('disconnect');
694 } else {
695 websocket.emit('error', e.message);
696 }
697 });
698 websocket.ircSocket = ircSocket;
699 ircSocket.holdLast = false;
700 ircSocket.held = '';
701
702 ircSocket.on('data', function (data) {
703 kiwi.ircSocketDataHandler(data, websocket, ircSocket);
704 });
705
706 ircSocket.IRC.nick = nick;
707 // Send the login data
708 dns.reverse(websocket.kiwi.address, function (err, domains) {
709 //console.log(domains);
710 websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
711 if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
712 websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
713 }
714 websocket.sendServerLine('CAP LS');
715 websocket.sendServerLine('NICK ' + nick);
716 websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);
717
718 if ((callback) && (typeof (callback) === 'function')) {
719 callback();
720 }
721 });
722 };
723
724
725
726 this.websocketMessage = function (websocket, msg, callback) {
727 var args, obj;
728 try {
729 msg.data = JSON.parse(msg.data);
730 args = msg.data.args;
731 switch (msg.data.method) {
732 case 'msg':
733 if ((args.target) && (args.msg)) {
734 obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
735 if (obj !== null) {
736 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg);
737 }
738 }
739 break;
740 case 'action':
741 if ((args.target) && (args.msg)) {
742 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'ACTION ' + args.msg + String.fromCharCode(1));
743 }
744 break;
745
746 case 'kiwi':
747 if ((args.target) && (args.data)) {
748 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1));
749 }
750 break;
751
752 case 'raw':
753 websocket.sendServerLine(args.data);
754 break;
755 case 'join':
756 if (args.channel) {
757 _.each(args.channel.split(","), function (chan) {
758 websocket.sendServerLine('JOIN ' + chan);
759 });
760 }
761 break;
762 case 'topic':
763 if (args.channel) {
764 websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic);
765 }
766 break;
767 case 'quit':
768 websocket.ircSocket.end('QUIT :' + args.message + '\r\n');
769 websocket.sentQUIT = true;
770 websocket.ircSocket.destroySoon();
771 websocket.disconnect();
772 break;
773 case 'notice':
774 if ((args.target) && (args.msg)) {
775 websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg);
776 }
777 break;
778 default:
779 }
780 if ((callback) && (typeof (callback) === 'function')) {
781 callback();
782 }
783 } catch (e) {
784 console.log("Caught error: " + e);
785 }
786 };
787
788
789
790 this.websocketDisconnect = function (websocket) {
791 var con;
792
793 if ((!websocket.sentQUIT) && (websocket.ircSocket)) {
794 try {
795 websocket.ircSocket.end('QUIT :' + kiwi.config.quit_message + '\r\n');
796 websocket.sentQUIT = true;
797 websocket.ircSocket.destroySoon();
798 } catch (e) {
799 }
800 }
801 con = kiwi.connections[websocket.kiwi.address];
802 con.count -= 1;
803 con.sockets = _.reject(con.sockets, function (sock) {
804 return sock === websocket;
805 });
806 };
807
808
809
810
811
812
813 this.rehash = function () {
814 var changes, i,
815 reload_config = kiwi.loadConfig();
816
817 // If loading the new config errored out, dont attempt any changes
818 if (reload_config === false) {
819 return false;
820 }
821
822 // We just want the settings that have been changed
823 changes = reload_config[1];
824
825 if (Object.keys(changes).length !== 0) {
826 console.log('%s config changes: \n', Object.keys(changes).length, changes);
827 for (i in changes) {
828 switch (i) {
829 case 'port':
830 case 'bind_address':
831 case 'listen_ssl':
832 case 'ssl_key':
833 case 'ssl_cert':
834 kiwi.websocketListen(kiwi.config.port, kiwi.config.bind_address, kiwi.httpHandler, kiwi.config.listen_ssl, kiwi.config.ssl_key, kiwi.config.ssl_cert);
835 delete changes.port;
836 delete changes.bind_address;
837 delete changes.listen_ssl;
838 delete changes.ssl_key;
839 delete changes.ssl_cert;
840 break;
841 case 'user':
842 case 'group':
843 kiwi.changeUser();
844 delete changes.user;
845 delete changes.group;
846 break;
847 case 'module_dir':
848 case 'modules':
849 kiwi.kiwi_mod.loadModules(kiwi_root, kiwi.config);
850 kiwi.kiwi_mod.printMods();
851 delete changes.module_dir;
852 delete changes.modules;
853 break;
854 }
855 }
856 }
857
858 // Also clear the kiwi.cached javascript and HTML
859 if (kiwi.config.handle_http) {
860 kiwi.cache = {alljs: '', html: []};
861 }
862
863 return true;
864 };
865
866
867
868
869
870 /*
871 * KiwiIRC controlling via STDIN
872 */
873 this.manageControll = function (data) {
874 var parts = data.toString().trim().split(' '),
875 connections_cnt = 0,
876 i;
877 switch (parts[0]) {
878 case 'rehash':
879 console.log('Rehashing...');
880 console.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
881 break;
882
883 case 'recode':
884 console.log('Recoding...');
885 console.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
886 break;
887
888 case 'mod':
889 if (parts[1] === 'reload') {
890 console.log('Reloading module (' + parts[2] + ')..');
891 kiwi.kiwi_mod.reloadModule(parts[2]);
892 }
893 break;
894
895 case 'cache':
896 if (parts[1] === 'clear') {
897 kiwi.cache.html = {};
898 kiwi.cache.alljs = '';
899 console.log('HTML cache cleared');
900 }
901 break;
902
903 case 'status':
904 for (i in kiwi.connections) {
905 connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
906 }
907 console.log(connections_cnt.toString() + ' connected clients');
908 break;
909
910 default:
911 console.log('Unknown command \'' + parts[0] + '\'');
912 }
913 };