1 /*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
23 this.init = function (objs
) {
28 node_static
= objs
.node_static
;
39 starttls
= objs
.starttls
;
40 kiwi
= require('./kiwi.js');
42 util
.inherits(this.IRCConnection
, events
.EventEmitter
);
44 file_server
= new StaticFileServer();
53 * Some process changes
55 this.setTitle = function () {
56 process
.title
= 'kiwiirc';
59 this.changeUser = function () {
60 if (typeof kiwi
.config
.group
!== 'undefined' && kiwi
.config
.group
!== '') {
62 process
.setgid(kiwi
.config
.group
);
64 kiwi
.log('Failed to set gid: ' + err
);
69 if (typeof kiwi
.config
.user
!== 'undefined' && kiwi
.config
.user
!== '') {
71 process
.setuid(kiwi
.config
.user
);
73 kiwi
.log('Failed to set uid: ' + e
);
81 function StaticFileServer(public_html
) {
82 public_html
= public_html
|| 'client/';
83 this.file_server
= new node_static
.Server(public_html
);
86 StaticFileServer
.prototype.serve = function (request
, response
) {
87 // The incoming requests root directory (ie. /kiwiclient/)
88 var root_path
= kiwi
.config
.http_base_path
|| '/client',
89 root_path_regex
= root_path
.replace(/[-/\\^$*+?.()|[\]{}]/g
, '\\$&');
91 // Any asset request to head into the asset dir
92 request
.url
= request
.url
.replace(root_path
+ '/assets/', '/assets/');
94 // Any requests for /client to load the index file
95 if (request
.url
.match(new RegExp('^' + root_path_regex
, 'i'))) {
99 // If a forwarded-for header is found, switch the source address
100 if (request
.headers
['x-forwarded-for']) {
101 // Check we're connecting from a whitelisted proxy
102 if (!kiwi
.config
.http_proxies
103 || kiwi
.config
.http_proxies
.indexOf(request
.connection
.remoteAddress
) < 0)
105 console
.log('Unlisted proxy:', request
.connection
.remoteAddress
);
106 response
.writeHead(503);
111 // We're sent from a whitelisted proxy, replace the hosts
112 request
.connection
.remoteAddress
= request
.headers
['x-forwarded-for'];
115 this.file_server
.serve(request
, response
, function (err
) {
117 response
.writeHead(err
.status
, err
.headers
);
134 RPL_WHOISUSER
: '311',
135 RPL_WHOISSERVER
: '312',
136 RPL_WHOISOPERATOR
: '313',
137 RPL_WHOISIDLE
: '317',
138 RPL_ENDOFWHOIS
: '318',
139 RPL_WHOISCHANNELS
: '319',
140 RPL_LISTSTART
: '321',
145 RPL_TOPICWHOTIME
: '333',
146 RPL_NAMEREPLY
: '353',
147 RPL_ENDOFNAMES
: '366',
149 RPL_ENDOFBANLIST
: '368',
151 RPL_MOTDSTART
: '375',
152 RPL_ENDOFMOTD
: '376',
153 RPL_WHOISMODES
: '379',
154 ERR_NOSUCHNICK
: '401',
155 ERR_CANNOTSENDTOCHAN
: '404',
156 ERR_TOOMANYCHANNELS
: '405',
157 ERR_NICKNAMEINUSE
: '433',
158 ERR_USERNOTINCHANNEL
: '441',
159 ERR_NOTONCHANNEL
: '442',
160 ERR_NOTREGISTERED
: '451',
161 ERR_LINKCHANNEL
: '470',
162 ERR_CHANNELISFULL
: '471',
163 ERR_INVITEONLYCHAN
: '473',
164 ERR_BANNEDFROMCHAN
: '474',
165 ERR_BADCHANNELKEY
: '475',
166 ERR_CHANOPRIVSNEEDED
: '482',
170 this.bindIRCCommands = function (irc_connection
, websocket
) {
171 var bound_events
= [],
172 bindCommand = function (command
, listener
) {
173 command
= 'irc_' + command
;
174 irc_connection
.on(command
, listener
);
175 bound_events
.push({"command": command
, "listener": listener
});
178 bindCommand('PING', function (msg
) {
179 websocket
.sendServerLine('PONG ' + msg
.trailing
);
182 bindCommand(ircNumerics
.RPL_WELCOME
, function (msg
) {
183 if (irc_connection
.IRC
.CAP
.negotiating
) {
184 irc_connection
.IRC
.CAP
.negotiating
= false;
185 irc_connection
.IRC
.CAP
.enabled
= [];
186 irc_connection
.IRC
.CAP
.requested
= [];
187 irc_connection
.IRC
.registered
= true;
189 var nick
= msg
.params
.split(' ')[0];
190 websocket
.sendClientEvent('connect', {connected
: true, host
: null, nick
: nick
});
193 bindCommand(ircNumerics
.RPL_ISUPPORT
, function (msg
) {
194 var opts
= msg
.params
.split(" "),
200 for (i
= 0; i
< opts
.length
; i
++) {
201 opt
= opts
[i
].split("=", 2);
202 opt
[0] = opt
[0].toUpperCase();
203 irc_connection
.IRC
.options
[opt
[0]] = (typeof opt
[1] !== 'undefined') ? opt
[1] : true;
204 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt
[0])) {
205 if (opt
[0] === 'PREFIX') {
206 regex
= /\(([^)]*)\)(.*)/;
207 matches
= regex
.exec(opt
[1]);
208 if ((matches
) && (matches
.length
=== 3)) {
209 irc_connection
.IRC
.options
[opt
[0]] = [];
210 for (j
= 0; j
< matches
[2].length
; j
++) {
211 irc_connection
.IRC
.options
[opt
[0]].push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
215 } else if (opt
[0] === 'CHANTYPES') {
216 irc_connection
.IRC
.options
.CHANTYPES
= irc_connection
.IRC
.options
.CHANTYPES
.split('');
217 } else if (opt
[0] === 'CHANMODES') {
218 irc_connection
.IRC
.options
.CHANMODES
= option
[1].split(',');
219 } else if (opt
[0] === 'NAMESX') {
220 websocket
.sendServerLine('PROTOCTL NAMESX');
225 websocket
.sendClientEvent('options', {server
: '', "options": irc_connection
.IRC
.options
});
228 bindCommand(ircNumerics
.RPL_ENDOFWHOIS
, function (msg
) {
229 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: true});
232 bindCommand(ircNumerics
.RPL_WHOISUSER
, function (msg
) {
233 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
236 bindCommand(ircNumerics
.RPL_WHOISSERVER
, function (msg
) {
237 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
240 bindCommand(ircNumerics
.RPL_WHOISOPERATOR
, function (msg
) {
241 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
244 bindCommand(ircNumerics
.RPL_WHOISCHANNELS
, function (msg
) {
245 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
248 bindCommand(ircNumerics
.RPL_WHOISMODES
, function (msg
) {
249 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
252 bindCommand(ircNumerics
.RPL_LISTSTART
, function (msg
) {
253 websocket
.sendClientEvent('list_start', {server
: ''});
254 websocket
.kiwi
.buffer
.list
= [];
257 bindCommand(ircNumerics
.RPL_LISTEND
, function (msg
) {
258 if (websocket
.kiwi
.buffer
.list
.length
> 0) {
259 websocket
.kiwi
.buffer
.list
= _
.sortBy(websocket
.kiwi
.buffer
.list
, function (channel
) {
260 return channel
.num_users
;
262 websocket
.sendClientEvent('list_channel', {chans
: websocket
.kiwi
.buffer
.list
});
263 websocket
.kiwi
.buffer
.list
= [];
265 websocket
.sendClientEvent('list_end', {server
: ''});
268 bindCommand(ircNumerics
.RPL_LIST
, function (msg
) {
269 var parts
, channel
, num_users
, topic
;
271 parts
= msg
.params
.split(' ');
273 num_users
= parts
[2];
274 topic
= msg
.trailing
;
276 //websocket.sendClientEvent('list_channel', {
277 websocket
.kiwi
.buffer
.list
.push({
282 num_users
: parseInt(num_users
, 10)
285 if (websocket
.kiwi
.buffer
.list
.length
> 200) {
286 websocket
.kiwi
.buffer
.list
= _
.sortBy(websocket
.kiwi
.buffer
.list
, function (channel
) {
287 return channel
.num_users
;
289 websocket
.sendClientEvent('list_channel', {chans
: websocket
.kiwi
.buffer
.list
});
290 websocket
.kiwi
.buffer
.list
= [];
294 bindCommand(ircNumerics
.RPL_WHOISIDLE
, function (msg
) {
295 var params
= msg
.params
.split(" ", 4),
296 rtn
= {server
: '', nick
: params
[1], idle
: params
[2]};
298 rtn
.logon
= params
[3];
300 websocket
.sendClientEvent('whois', rtn
);
303 bindCommand(ircNumerics
.RPL_MOTD
, function (msg
) {
304 websocket
.kiwi
.buffer
.motd
+= msg
.trailing
+ '\n';
307 bindCommand(ircNumerics
.RPL_MOTDSTART
, function (msg
) {
308 websocket
.kiwi
.buffer
.motd
= '';
311 bindCommand(ircNumerics
.RPL_ENDOFMOTD
, function (msg
) {
312 websocket
.sendClientEvent('motd', {server
: '', 'msg': websocket
.kiwi
.buffer
.motd
});
315 bindCommand(ircNumerics
.RPL_NAMEREPLY
, function (msg
) {
316 var params
= msg
.params
.split(" "),
318 users
= msg
.trailing
.split(" "),
322 _
.each(users
, function (user
) {
323 var j
, k
, modes
= [];
324 for (j
= 0; j
< user
.length
; j
++) {
325 for (k
= 0; k
< irc_connection
.IRC
.options
.PREFIX
.length
; k
++) {
326 if (user
.charAt(j
) === irc_connection
.IRC
.options
.PREFIX
[k
].symbol
) {
327 modes
.push(irc_connection
.IRC
.options
.PREFIX
[k
].mode
);
331 nicklist
.push({nick
: user
, modes
: modes
});
333 websocket
.sendClientEvent('userlist', {server
: '', 'users': nicklist
, channel
: chan
});
339 websocket
.sendClientEvent('userlist', {server
: '', "users": nicklist
, channel
: chan
});
345 bindCommand(ircNumerics
.RPL_ENDOFNAMES
, function (msg
) {
346 websocket
.sendClientEvent('userlist_end', {server
: '', channel
: msg
.params
.split(" ")[1]});
349 bindCommand(ircNumerics
.ERR_LINKCHANNEL
, function (msg
) {
350 var params
= msg
.params
.split(" ");
351 websocket
.sendClientEvent('channel_redirect', {from: params
[1], to
: params
[2]});
354 bindCommand(ircNumerics
.ERR_NOSUCHNICK
, function (msg
) {
355 websocket
.sendClientEvent('irc_error', {error
: 'no_such_nick', nick
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
358 bindCommand(ircNumerics
.RPL_BANLIST
, function (msg
) {
359 var params
= msg
.params
.split(" ");
361 websocket
.sendClientEvent('banlist', {server
: '', channel
: params
[1], banned
: params
[2], banned_by
: params
[3], banned_at
: params
[4]});
364 bindCommand(ircNumerics
.RPL_ENDOFBANLIST
, function (msg
) {
365 websocket
.sendClientEvent('banlist_end', {server
: '', channel
: msg
.params
.split(" ")[1]});
368 bindCommand('JOIN', function (msg
) {
371 // Some BNC's send malformed JOIN causing the channel to be as a
372 // parameter instead of trailing.
373 if (typeof msg
.trailing
=== 'string' && msg
.trailing
!== '') {
374 channel
= msg
.trailing
;
375 } else if (typeof msg
.params
=== 'string' && msg
.params
!== '') {
376 channel
= msg
.params
;
379 websocket
.sendClientEvent('join', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: channel
});
380 if (msg
.nick
=== irc_connection
.IRC
.nick
) {
381 websocket
.sendServerLine('NAMES ' + msg
.trailing
);
385 bindCommand('PART', function (msg
) {
386 websocket
.sendClientEvent('part', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), message
: msg
.trailing
});
389 bindCommand('KICK', function (msg
) {
390 var params
= msg
.params
.split(" ");
391 websocket
.sendClientEvent('kick', {kicked
: params
[1], nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: params
[0].trim(), message
: msg
.trailing
});
394 bindCommand('QUIT', function (msg
) {
395 websocket
.sendClientEvent('quit', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, message
: msg
.trailing
});
398 bindCommand('NOTICE', function (msg
) {
399 if ((msg
.trailing
.charAt(0) === String
.fromCharCode(1)) && (msg
.trailing
.charAt(msg
.trailing
.length
- 1) === String
.fromCharCode(1))) {
400 // It's a CTCP response
401 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)});
403 websocket
.sendClientEvent('notice', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, target
: msg
.params
.trim(), msg
: msg
.trailing
});
407 bindCommand('NICK', function (msg
) {
408 websocket
.sendClientEvent('nick', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, newnick
: msg
.trailing
});
411 bindCommand('TOPIC', function (msg
) {
412 var obj
= {nick
: msg
.nick
, channel
: msg
.params
, topic
: msg
.trailing
};
413 websocket
.sendClientEvent('topic', obj
);
416 bindCommand(ircNumerics
.RPL_TOPIC
, function (msg
) {
417 var obj
= {nick
: '', channel
: msg
.params
.split(" ")[1], topic
: msg
.trailing
};
418 websocket
.sendClientEvent('topic', obj
);
421 bindCommand(ircNumerics
.RPL_NOTOPIC
, function (msg
) {
422 var obj
= {nick
: '', channel
: msg
.params
.split(" ")[1], topic
: ''};
423 websocket
.sendClientEvent('topic', obj
);
426 bindCommand(ircNumerics
.RPL_TOPICWHOTIME
, function (msg
) {
427 var parts
= msg
.params
.split(' '),
431 obj
= {nick
: nick
, channel
: channel
, when
: when
};
432 websocket
.sendClientEvent('topicsetby', obj
);
435 bindCommand('MODE', function (command
) {
437 var opts = msg.params.split(" "),
438 params = {nick: msg.nick};
440 switch (opts.length) {
442 params.effected_nick = opts[0];
443 params.mode = msg.trailing;
446 params.channel = opts[0];
447 params.mode = opts[1];
450 params.channel = opts[0];
451 params.mode = opts[1];
452 params.effected_nick = opts[2];
455 websocket.sendClientEvent('mode', params);
457 command
.params
= command
.params
.split(" ");
458 var chanmodes
= irc_connection
.IRC
.options
.CHANMODES
,
459 prefixes
= irc_connection
.IRC
.options
.PREFIX
,
460 always_param
= chanmodes
[0].concat(chanmodes
[1]),
462 has_param
, i
, j
, add
;
464 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
465 list
.push(prefix
.mode
);
468 always_param
= always_param
.split('').concat(prefixes
);
470 has_param = function (mode
, add
) {
471 if (_
.find(always_param
, function (m
) {
475 } else if (add
&& _
.find(chanmodes
[2].split(''), function (m
) {
484 if (!command
.params
[1]) {
485 command
.params
[1] = command
.trailing
;
488 for (i
= 0; i
< command
.params
[1].length
; i
++) {
489 switch (command
.params
[1][i
]) {
497 if (has_param(command
.params
[1][i
], add
)) {
498 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: command
.params
[2 + j
]});
501 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: null});
506 websocket
.sendClientEvent('mode', {
507 target
: command
.params
[0],
508 nick
: command
.nick
|| command
.prefix
|| '',
513 bindCommand('PRIVMSG', function (msg
) {
514 var tmp
, namespace, obj
;
515 if ((msg
.trailing
.charAt(0) === String
.fromCharCode(1)) && (msg
.trailing
.charAt(msg
.trailing
.length
- 1) === String
.fromCharCode(1))) {
516 // It's a CTCP request
517 if (msg
.trailing
.substr(1, 6) === 'ACTION') {
518 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)});
519 } else if (msg
.trailing
.substr(1, 4) === 'KIWI') {
520 tmp
= msg
.trailing
.substr(6, msg
.trailing
.length
- 2);
521 namespace = tmp
.split(' ', 1)[0];
522 websocket
.sendClientEvent('kiwi', {namespace: namespace, data
: tmp
.substr(namespace.length
+ 1)});
524 } else if (msg
.trailing
.substr(1, 7) === 'VERSION') {
525 irc_connection
.write('NOTICE ' + msg
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1) + '\r\n');
527 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)});
530 obj
= {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), msg
: msg
.trailing
};
531 websocket
.sendClientEvent('msg', obj
);
535 bindCommand('CAP', function (msg
) {
536 var caps
= kiwi
.config
.cap_options
,
537 options
= msg
.trailing
.split(" "),
540 switch (_
.last(msg
.params
.split(" "))) {
543 _
.each(_
.intersect(caps
, options
), function (cap
) {
548 irc_connection
.IRC
.CAP
.requested
.push(cap
);
550 if (opts
.length
> 0) {
551 websocket
.sendServerLine('CAP REQ :' + opts
);
553 websocket
.sendServerLine('CAP END');
556 /*if (_.include(options, 'tls')) {
557 websocket.sendServerLine('STARTTLS');
558 ircSocket.IRC.CAP.requested.push('tls');
562 _
.each(options
, function (cap
) {
563 irc_connection
.IRC
.CAP
.enabled
.push(cap
);
565 if (_
.last(msg
.params
.split(" ")) !== '*') {
566 irc_connection
.IRC
.CAP
.requested
= [];
567 irc_connection
.IRC
.CAP
.negotiating
= false;
568 websocket
.sendServerLine('CAP END');
572 irc_connection
.IRC
.CAP
.requested
= [];
573 irc_connection
.IRC
.CAP
.negotiating
= false;
574 websocket
.sendServerLine('CAP END');
578 /*case ircNumerics.RPL_STARTTLS:
581 listeners = ircSocket.listeners('data');
582 ircSocket.removeAllListeners('data');
583 ssl_socket = starttls(ircSocket, {}, function () {
584 ssl_socket.on("data", function (data) {
585 ircSocketDataHandler(data, websocket, ssl_socket);
587 ircSocket = ssl_socket;
589 _.each(listeners, function (listener) {
590 ircSocket.addListener('data', listener);
598 bindCommand(ircNumerics
.ERR_CANNOTSENDTOCHAN
, function (msg
) {
599 websocket
.sendClientEvent('irc_error', {error
: 'cannot_send_to_chan', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
602 bindCommand(ircNumerics
.ERR_TOOMANYCHANNELS
, function (msg
) {
603 websocket
.sendClientEvent('irc_error', {error
: 'too_many_channels', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
606 bindCommand(ircNumerics
.ERR_USERNOTINCHANNEL
, function (msg
) {
607 var params
= msg
.params
.split(" ");
608 websocket
.sendClientEvent('irc_error', {error
: 'user_not_in_channel', nick
: params
[0], channel
: params
[1], reason
: msg
.trainling
});
611 bindCommand(ircNumerics
.ERR_NOTONCHANNEL
, function (msg
) {
612 websocket
.sendClientEvent('irc_error', {error
: 'not_on_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
615 bindCommand(ircNumerics
.ERR_CHANNELISFULL
, function (msg
) {
616 websocket
.sendClientEvent('irc_error', {error
: 'channel_is_full', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
619 bindCommand(ircNumerics
.ERR_INVITEONLYCHAN
, function (msg
) {
620 websocket
.sendClientEvent('irc_error', {error
: 'invite_only_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
623 bindCommand(ircNumerics
.ERR_BANNEDFROMCHAN
, function (msg
) {
624 websocket
.sendClientEvent('irc_error', {error
: 'banned_from_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
627 bindCommand(ircNumerics
.ERR_BADCHANNELKEY
, function (msg
) {
628 websocket
.sendClientEvent('irc_error', {error
: 'bad_channel_key', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
631 bindCommand(ircNumerics
.ERR_CHANOPRIVSNEEDED
, function (msg
) {
632 websocket
.sendClientEvent('irc_error', {error
: 'chanop_privs_needed', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
635 bindCommand(ircNumerics
.ERR_NICKNAMEINUSE
, function (msg
) {
636 websocket
.sendClientEvent('irc_error', {error
: 'nickname_in_use', nick
: _
.last(msg
.params
.split(" ")), reason
: msg
.trailing
});
639 bindCommand('ERROR', function (msg
) {
640 irc_connection
.end();
641 websocket
.sendClientEvent('irc_error', {error
: 'error', reason
: msg
.trailing
});
642 websocket
.disconnect();
645 bindCommand(ircNumerics
.ERR_NOTREGISTERED
, function (msg
) {
646 if (irc_connection
.IRC
.registered
) {
647 kiwi
.log('Kiwi thinks user is registered, but the IRC server thinks differently');
654 this.rebindIRCCommands = function () {
655 _
.each(kiwi
.connections
, function (con
) {
656 _
.each(con
.sockets
, function (sock
) {
657 sock
.ircConnection
.rebindIRCCommands();
663 this.httpHandler = function (request
, response
) {
666 uri
= url
.parse(request
.url
, true);
667 subs
= uri
.pathname
.substr(0, 4);
669 if (uri
.pathname
.substr(0, 10) === '/socket.io') {
672 file_server
.serve(request
, response
);
679 this.websocketListen = function (servers
, handler
) {
680 if (kiwi
.httpServers
.length
> 0) {
681 _
.each(kiwi
.httpServers
, function (hs
) {
684 kiwi
.httpsServers
= [];
687 _
.each(servers
, function (server
) {
689 if (server
.secure
=== true) {
690 // Start some SSL server up
692 key
: fs
.readFileSync(__dirname
+ '/' + server
.ssl_key
),
693 cert
: fs
.readFileSync(__dirname
+ '/' + server
.ssl_cert
)
696 // Do we have an intermediate certificate?
697 if (typeof server
.ssl_ca
!== 'undefined') {
698 opts
.ca
= fs
.readFileSync(__dirname
+ '/' + server
.ssl_ca
);
701 hs
= https
.createServer(opts
, handler
);
702 kiwi
.io
.push(ws
.listen(hs
, {secure
: true}));
703 hs
.listen(server
.port
, server
.address
);
704 kiwi
.log('Listening on ' + server
.address
+ ':' + server
.port
.toString() + ' with SSL');
706 // Start some plain-text server up
707 hs
= http
.createServer(handler
);
708 kiwi
.io
.push(ws
.listen(hs
, {secure
: false}));
709 hs
.listen(server
.port
, server
.address
);
710 kiwi
.log('Listening on ' + server
.address
+ ':' + server
.port
.toString() + ' without SSL');
713 kiwi
.httpServers
.push(hs
);
716 _
.each(kiwi
.io
, function (io
) {
717 io
.set('log level', 1);
718 io
.enable('browser client minification');
719 io
.enable('browser client etag');
720 io
.set('transports', kiwi
.config
.transports
);
722 io
.of('/kiwi').authorization(function (handshakeData
, callback
) {
723 var address
= handshakeData
.address
.address
;
725 // If a forwarded-for header is found, switch the source address
726 if (handshakeData
.headers
['x-forwarded-for']) {
727 // Check we're connecting from a whitelisted proxy
728 if (!kiwi
.config
.http_proxies
729 || kiwi
.config
.http_proxies
.indexOf(address
) < 0)
731 console
.log('Unlisted proxy:', address
);
732 websocket
.disconnect();
736 // We're sent from a whitelisted proxy, replace the hosts
737 address
= handshakeData
.headers
['x-forwarded-for'];
741 if (typeof kiwi
.connections
[address
] === 'undefined') {
742 kiwi
.connections
[address
] = {count
: 0, sockets
: []};
744 callback(null, true);
745 }).on('connection', kiwi
.websocketConnection
);
746 io
.of('/kiwi').on('error', console
.log
);
755 this.websocketConnection = function (websocket
) {
758 address
= websocket
.handshake
.address
.address
;
760 // If a forwarded-for header is found, switch the source address
761 if (websocket
.handshake
.headers
['x-forwarded-for']) {
762 // Check we're connecting from a whitelisted proxy
763 if (!kiwi
.config
.http_proxies
764 || kiwi
.config
.http_proxies
.indexOf(address
) < 0)
766 console
.log('Unlisted proxy:', address
);
767 websocket
.disconnect();
771 // We're sent from a whitelisted proxy, replace the hosts
772 address
= websocket
.handshake
.headers
['x-forwarded-for'];
775 kiwi
.log('New connection! ' + address
);
777 websocket
.kiwi
= {address
: address
, buffer
: {list
: []}};
778 con
= kiwi
.connections
[websocket
.kiwi
.address
];
780 if (con
.count
>= kiwi
.config
.max_client_conns
) {
781 websocket
.emit('too_many_connections');
782 websocket
.disconnect();
785 con
.sockets
.push(websocket
);
787 websocket
.sendClientEvent = function (event_name
, data
) {
788 var ev
= kiwi
.kiwi_mod
.run(event_name
, data
, {websocket
: this});
793 //data.event = event_name;
794 websocket
.emit('irc', {command
:event_name
, data
:data
});
797 websocket
.sendServerLine = function (data
, eol
, callback
) {
798 if ((arguments
.length
< 3) && (typeof eol
=== 'function')) {
801 eol
= (typeof eol
!== 'string') ? '\r\n' : eol
;
804 websocket
.ircConnection
.write(data
+ eol
, 'utf-8', callback
);
808 websocket
.on('kiwi', kiwi
.websocketKiwiMessage
);
809 websocket
.on('irc', kiwi
.websocketMessage
);
810 websocket
.on('disconnect', kiwi
.websocketDisconnect
);
811 websocket
.on('error', console
.log
);
816 this.IRCConnection = function (websocket
, nick
, host
, port
, ssl
, password
, callback
) {
823 events
.EventEmitter
.call(this);
825 onConnectHandler = function () {
827 // <dux0r> y4ry65yer56ytr
828 var realname
= '[www.kiwiirc.com] ' + nick
;
829 var username
= nick
.replace(/[^0-9a-zA-Z\-_.]/, '');
831 that
.IRC
.nick
= nick
;
832 // Send the login data
833 dns
.reverse(websocket
.kiwi
.address
, function (err
, domains
) {
834 websocket
.kiwi
.hostname
= (err
) ? websocket
.kiwi
.address
: _
.first(domains
);
836 // Check if we need to pass the users IP as its realname
837 if (kiwi
.config
.ip_as_username
&& kiwi
.config
.ip_as_username
.indexOf(host
) > -1) {
838 // Get a hex value of the clients IP
839 username
= websocket
.kiwi
.address
.split('.').map(function(i
, idx
){
840 return parseInt(i
, 10).toString(16);
844 // Do we have a WEBIRC password for this?
845 if ((kiwi
.config
.webirc
) && (kiwi
.config
.webirc_pass
[host
])) {
846 websocket
.sendServerLine('WEBIRC ' + kiwi
.config
.webirc_pass
[host
] + ' KiwiIRC ' + websocket
.kiwi
.hostname
+ ' ' + websocket
.kiwi
.address
);
849 // If the user specified a password
851 websocket
.sendServerLine('PASS ' + password
);
854 websocket
.sendServerLine('CAP LS');
855 websocket
.sendServerLine('NICK ' + nick
);
856 websocket
.sendServerLine('USER ' + username
+ ' 0 0 :' + realname
);
857 console
.log('Username:', username
, 'Realname:', realname
);
859 that
.connected
= true;
860 that
.emit('connect');
865 ircSocket
= net
.createConnection(port
, host
);
866 ircSocket
.on('connect', onConnectHandler
);
868 ircSocket
= tls
.connect(port
, host
, {}, onConnectHandler
);
871 ircSocket
.setEncoding('utf-8');
872 this.IRC
= {options
: {}, CAP
: {negotiating
: true, requested
: [], enabled
: []}, registered
: false};
874 this.on('error', function (e
) {
875 if (that
.IRC
.registered
) {
876 websocket
.emit('disconnect');
878 websocket
.emit('error', e
.message
);
882 ircSocket
.on('error', function (e
) {
883 that
.connected
= false;
884 that
.emit('error', e
);
888 if (typeof callback
=== 'function') {
889 this.on('connect', callback
);
892 regex
= /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
893 ircSocket
.holdLast
= false;
895 ircSocket
.on('data', function (data
) {
897 if ((ircSocket
.holdLast
) && (ircSocket
.held
!== '')) {
898 data
= ircSocket
.held
+ data
;
899 ircSocket
.holdLast
= false;
902 if (data
.substr(-1) !== '\n') {
903 ircSocket
.holdLast
= true;
905 data
= data
.split("\n");
906 for (i
= 0; i
< data
.length
; i
++) {
908 if ((ircSocket
.holdLast
) && (i
=== data
.length
- 1)) {
909 ircSocket
.held
= data
[i
];
913 // We have a complete line of data, parse it!
914 msg
= regex
.exec(data
[i
].replace(/^\r+|\r+$/, ''));
920 hostname
: msg
[4] || '',
922 params
: msg
[6] || '',
923 trailing
: (msg
[7]) ? msg
[7].trim() : ''
925 that
.emit('irc_' + msg
.command
.toUpperCase(), msg
);
926 if (that
.listeners('irc_' + msg
.command
.toUpperCase()).length
< 1) {
927 kiwi
.log("Unknown command (" + String(msg
.command
).toUpperCase() + ")");
930 kiwi
.log("Malformed IRC line: " + data
[i
].replace(/^\r+|\r+$/, ''));
937 ircSocket
.on('connect', callback
);
940 ircSocket
.on('end', function () {
941 that
.connected
= false;
942 that
.emit('disconnect', false);
945 ircSocket
.on('close', function (had_error
) {
946 that
.connected
= false;
947 that
.emit('disconnect', had_error
);
950 ircSocket
.on('timeout', function () {
952 that
.connected
= false;
953 that
.emit('error', {message
: 'Connection timed out'});
956 ircSocket
.on('drain', function () {
960 this.write = function (data
, encoding
, callback
) {
961 ircSocket
.write(data
, encoding
, callback
);
964 this.end = function (data
, encoding
, callback
) {
965 that
.connected
= false;
966 ircSocket
.end(data
, encoding
, callback
);
969 this.destroySoon = function () {
970 ircSocket
.destroySoon();
973 bound_events
= kiwi
.bindIRCCommands(this, websocket
);
975 this.rebindIRCCommands = function () {
976 _
.each(bound_events
, function (event
) {
977 that
.removeListener(event
.command
, event
.listener
);
979 bound_events
= kiwi
.bindIRCCommands(that
, websocket
);
982 that
.on('error', console
.log
);
988 this.websocketKiwiMessage = function (websocket
, msg
, callback
) {
989 websocket
.ircConnection
= new kiwi
.IRCConnection(websocket
, msg
.nick
, msg
.hostname
, msg
.port
, msg
.ssl
, msg
.password
, callback
);
993 this.websocketMessage = function (websocket
, msg
, callback
) {
994 var args
, obj
, channels
, keys
;
996 if ((callback
) && (typeof (callback
) !== 'function')) {
1000 msg
.data
= JSON
.parse(msg
.data
);
1002 kiwi
.log('[app.websocketMessage] JSON parsing error ' + msg
.data
);
1005 args
= msg
.data
.args
;
1006 switch (msg
.data
.method
) {
1008 if ((args
.target
) && (args
.msg
)) {
1009 obj
= kiwi
.kiwi_mod
.run('msgsend', args
, {websocket
: websocket
});
1011 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ' :' + args
.msg
, callback
);
1016 if ((args
.target
) && (args
.type
)) {
1018 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ' :' + String
.fromCharCode(1) + args
.type
.toUpperCase() + ' ' + args
.params
+ String
.fromCharCode(1), callback
);
1020 websocket
.sendServerLine('NOTICE ' + args
.target
+ ' :' + String
.fromCharCode(1) + args
.type
.toUpperCase() + ' ' + args
.params
+ String
.fromCharCode(1), callback
);
1026 websocket
.sendServerLine(args
.data
, callback
);
1031 channels
= args
.channel
.split(",");
1032 keys
= (args
.key
) ? args
.key
.split(",") : [];
1033 _
.each(channels
, function (chan
, index
) {
1034 websocket
.sendServerLine('JOIN ' + chan
+ ' ' + (keys
[index
] || ''), callback
);
1041 _
.each(args
.channel
.split(","), function (chan
) {
1042 websocket
.sendServerLine('PART ' + chan
, callback
);
1050 websocket
.sendServerLine('TOPIC ' + args
.channel
+ ' :' + args
.topic
, callback
);
1052 websocket
.sendServerLine('TOPIC ' + args
.channel
, callback
);
1058 if ((args
.channel
) && (args
.nick
)) {
1059 websocket
.sendServerLine('KICK ' + args
.channel
+ ' ' + args
.nick
+ ':' + args
.reason
, callback
);
1064 websocket
.ircConnection
.end('QUIT :' + args
.message
+ '\r\n');
1065 websocket
.sentQUIT
= true;
1066 websocket
.ircConnection
.destroySoon();
1067 websocket
.disconnect();
1071 if ((args
.target
) && (args
.msg
)) {
1072 websocket
.sendServerLine('NOTICE ' + args
.target
+ ' :' + args
.msg
, callback
);
1077 if ((args
.target
) && (args
.mode
)) {
1078 websocket
.sendServerLine('MODE ' + args
.target
+ ' ' + args
.mode
+ ' ' + args
.params
, callback
);
1084 websocket
.sendServerLine('NICK ' + args
.nick
, callback
);
1089 if ((args
.target
) && (args
.data
)) {
1090 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ': ' + String
.fromCharCode(1) + 'KIWI ' + args
.data
+ String
.fromCharCode(1), callback
);
1096 // kiwi.log("Caught error (app.websocketMessage): " + e);
1102 this.websocketDisconnect = function (websocket
) {
1105 if ((!websocket
.sentQUIT
) && (websocket
.ircConnection
.connected
)) {
1107 websocket
.ircConnection
.end('QUIT :' + kiwi
.config
.quit_message
+ '\r\n');
1108 websocket
.sentQUIT
= true;
1109 websocket
.ircConnection
.destroySoon();
1113 con
= kiwi
.connections
[websocket
.kiwi
.address
];
1115 con
.sockets
= _
.reject(con
.sockets
, function (sock
) {
1116 return sock
=== websocket
;
1125 this.rehash = function () {
1127 reload_config
= kiwi
.loadConfig();
1129 // If loading the new config errored out, dont attempt any changes
1130 if (reload_config
=== false) {
1134 // We just want the settings that have been changed
1135 changes
= reload_config
[1];
1137 if (Object
.keys(changes
).length
!== 0) {
1138 kiwi
.log('%s config changes: \n', Object
.keys(changes
).length
, changes
);
1139 for (i
in changes
) {
1142 kiwi
.websocketListen(kiwi
.config
.servers
, kiwi
.httpHandler
);
1143 delete changes
.ports
;
1144 delete changes
.bind_address
;
1145 delete changes
.ssl_key
;
1146 delete changes
.ssl_cert
;
1151 delete changes
.user
;
1152 delete changes
.group
;
1156 kiwi
.kiwi_mod
.loadModules(kiwi_root
, kiwi
.config
);
1157 kiwi
.kiwi_mod
.printMods();
1158 delete changes
.module_dir
;
1159 delete changes
.modules
;
1165 // Also clear the kiwi.cached javascript and HTML
1166 if (kiwi
.config
.handle_http
) {
1167 kiwi
.cache
= {alljs
: '', html
: []};
1178 * KiwiIRC controlling via STDIN
1180 this.manageControll = function (data
) {
1181 var parts
= data
.toString().trim().split(' '),
1182 connections_cnt
= 0,
1186 kiwi
.log('Rehashing...');
1187 kiwi
.log(kiwi
.rehash() ? 'Rehash complete' : 'Rehash failed');
1191 kiwi
.log('Recoding...');
1192 kiwi
.log(kiwi
.recode() ? 'Recode complete' : 'Recode failed');
1196 if (parts
[1] === 'reload') {
1198 kiwi
.log('Usage: mod reload module_name');
1202 kiwi
.log('Reloading module (' + parts
[2] + ')..');
1203 kiwi
.kiwi_mod
.reloadModule(parts
[2]);
1204 } else if (parts
[1] === 'list') {
1205 kiwi
.kiwi_mod
.printMods();
1210 if (parts
[1] === 'clear') {
1211 kiwi
.cache
.html
= {};
1212 kiwi
.cache
.alljs
= '';
1213 kiwi
.log('HTML cache cleared');
1218 for (i
in kiwi
.connections
) {
1219 connections_cnt
= connections_cnt
+ parseInt(kiwi
.connections
[i
].count
, 10);
1221 kiwi
.log(connections_cnt
.toString() + ' connected clients');
1225 kiwi
.log('Unknown command \'' + parts
[0] + '\'');