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