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