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