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