Merge branch 'winston' of https://github.com/M2Ys4U/KiwiIRC into winston
[KiwiIRC.git] / server / irc / connection.js
1 var net = require('net'),
2 tls = require('tls'),
3 util = require('util'),
4 dns = require('dns'),
5 _ = require('lodash'),
6 winston = require('winston'),
7 EventBinder = require('./eventbinder.js'),
8 IrcServer = require('./server.js'),
9 IrcCommands = require('./commands.js'),
10 IrcChannel = require('./channel.js'),
11 IrcUser = require('./user.js'),
12 EE = require('../ee.js'),
13 iconv = require('iconv-lite'),
14 Proxy = require('../proxy.js'),
15 Socks;
16
17
18 // Break the Node.js version down into usable parts
19 var version_values = process.version.substr(1).split('.').map(function (item) {
20 return parseInt(item, 10);
21 });
22
23 // If we have a suitable Nodejs version, bring int he socks functionality
24 if (version_values[1] >= 10) {
25 Socks = require('socksjs');
26 }
27
28 var IrcConnection = function (hostname, port, ssl, nick, user, options, state, con_num) {
29 var that = this;
30
31 EE.call(this,{
32 wildcard: true,
33 delimiter: ' '
34 });
35 this.setMaxListeners(0);
36
37 options = options || {};
38
39 // Socket state
40 this.connected = false;
41
42 // IRCd write buffers (flood controll)
43 this.write_buffer = [];
44
45 // In process of writing the buffer?
46 this.writing_buffer = false;
47
48 // Max number of lines to write a second
49 this.write_buffer_lines_second = 2;
50
51 // If registeration with the IRCd has completed
52 this.registered = false;
53
54 // If we are in the CAP negotiation stage
55 this.cap_negotiation = true;
56
57 // User information
58 this.nick = nick;
59 this.user = user; // Contains users real hostname and address
60 this.username = this.nick.replace(/[^0-9a-zA-Z\-_.\/]/, '');
61 this.password = options.password || '';
62
63 // Set the passed encoding. or the default if none giving or it fails
64 if (!options.encoding || !this.setEncoding(options.encoding)) {
65 this.setEncoding(global.config.default_encoding);
66 }
67
68 // State object
69 this.state = state;
70
71 // Connection ID in the state
72 this.con_num = con_num;
73
74 // IRC protocol handling
75 this.irc_commands = new IrcCommands(this);
76
77 // IrcServer object
78 this.server = new IrcServer(this, hostname, port);
79
80 // IrcUser objects
81 this.irc_users = Object.create(null);
82
83 // TODO: use `this.nick` instead of `'*'` when using an IrcUser per nick
84 this.irc_users[this.nick] = new IrcUser(this, '*');
85
86 // IrcChannel objects
87 this.irc_channels = Object.create(null);
88
89 // IRC connection information
90 this.irc_host = {hostname: hostname, port: port};
91 this.ssl = !(!ssl);
92
93 // SOCKS proxy details
94 // TODO: Wildcard matching of hostnames and/or CIDR ranges of IP addresses
95 if ((global.config.socks_proxy && global.config.socks_proxy.enabled) && ((global.config.socks_proxy.all) || (_.contains(global.config.socks_proxy.proxy_hosts, this.irc_host.hostname)))) {
96 this.socks = {
97 host: global.config.socks_proxy.address,
98 port: global.config.socks_proxy.port,
99 user: global.config.socks_proxy.user,
100 pass: global.config.socks_proxy.pass
101 };
102 } else {
103 this.socks = false;
104 }
105
106 // Kiwi proxy info may be set within a server module. {port: 7779, host: 'kiwi.proxy.com', ssl: false}
107 this.proxy = false;
108
109 // Net. interface this connection should be made through
110 this.outgoing_interface = false;
111
112 // Options sent by the IRCd
113 this.options = Object.create(null);
114 this.cap = {requested: [], enabled: []};
115
116 // Is SASL supported on the IRCd
117 this.sasl = false;
118
119 // Buffers for data sent from the IRCd
120 this.hold_last = false;
121 this.held_data = null;
122
123 this.applyIrcEvents();
124 };
125 util.inherits(IrcConnection, EE);
126
127 module.exports.IrcConnection = IrcConnection;
128
129
130
131 IrcConnection.prototype.applyIrcEvents = function () {
132 // Listen for events on the IRC connection
133 this.irc_events = {
134 'server * connect': onServerConnect,
135 'channel * join': onChannelJoin,
136
137 // TODO: uncomment when using an IrcUser per nick
138 //'user:*:privmsg': onUserPrivmsg,
139 'user * nick': onUserNick,
140 'channel * part': onUserParts,
141 'channel * quit': onUserParts,
142 'channel * kick': onUserKick
143 };
144
145 EventBinder.bindIrcEvents('', this.irc_events, this, this);
146 };
147
148
149 /**
150 * Start the connection to the IRCd
151 */
152 IrcConnection.prototype.connect = function () {
153 var that = this;
154
155 // The socket connect event to listener for
156 var socket_connect_event_name = 'connect';
157
158 // The destination address
159 var dest_addr;
160 if (this.socks) {
161 dest_addr = this.socks.host;
162 } else if (this.proxy) {
163 dest_addr = this.proxy.host;
164 } else {
165 dest_addr = this.irc_host.hostname;
166 }
167
168 // Make sure we don't already have an open connection
169 this.disposeSocket();
170
171 // Get the IP family for the dest_addr (either socks or IRCd destination)
172 getConnectionFamily(dest_addr, function getConnectionFamilyCb(err, family, host) {
173 var outgoing;
174
175 // Decide which net. interface to make the connection through
176 if (that.outgoing_interface) {
177 // An specific interface has been given for this connection
178 outgoing = this.outgoing_interface;
179
180 } else if (global.config.outgoing_address) {
181 // Pick an interface from the config
182 if ((family === 'IPv6') && (global.config.outgoing_address.IPv6)) {
183 outgoing = global.config.outgoing_address.IPv6;
184 } else {
185 outgoing = global.config.outgoing_address.IPv4 || '0.0.0.0';
186
187 // We don't have an IPv6 interface but dest_addr may still resolve to
188 // an IPv4 address. Reset `host` and try connecting anyway, letting it
189 // fail if an IPv4 resolved address is not found
190 host = dest_addr;
191 }
192
193 // If we have an array of interfaces, select a random one
194 if (typeof outgoing !== 'string' && outgoing.length) {
195 outgoing = outgoing[Math.floor(Math.random() * outgoing.length)];
196 }
197
198 // Make sure we have a valid interface address
199 if (typeof outgoing !== 'string')
200 outgoing = '0.0.0.0';
201
202 } else {
203 // No config was found so use the default
204 outgoing = '0.0.0.0';
205 }
206
207 // Are we connecting through a SOCKS proxy?
208 if (that.socks) {
209 that.socket = Socks.connect({
210 host: that.irc_host.host,
211 port: that.irc_host.port,
212 ssl: that.ssl,
213 rejectUnauthorized: global.config.reject_unauthorised_certificates
214 }, {host: host,
215 port: that.socks.port,
216 user: that.socks.user,
217 pass: that.socks.pass,
218 localAddress: outgoing
219 });
220
221 } else if (that.proxy) {
222 that.socket = new Proxy.ProxySocket(that.proxy.port, host, {
223 username: that.username,
224 interface: that.proxy.interface
225 }, {ssl: that.proxy.ssl});
226
227 if (that.ssl) {
228 that.socket.connectTls(that.irc_host.port, that.irc_host.hostname);
229 } else {
230 that.socket.connect(that.irc_host.port, that.irc_host.hostname);
231 }
232
233 } else {
234 // No socks connection, connect directly to the IRCd
235
236 if (that.ssl) {
237 that.socket = tls.connect({
238 host: host,
239 port: that.irc_host.port,
240 rejectUnauthorized: global.config.reject_unauthorised_certificates,
241 localAddress: outgoing
242 });
243
244 // We need the raw socket connect event
245 that.socket.socket.on('connect', function() { rawSocketConnect.call(that, this); });
246
247 socket_connect_event_name = 'secureConnect';
248
249 } else {
250 that.socket = net.connect({
251 host: host,
252 port: that.irc_host.port,
253 localAddress: outgoing
254 });
255 }
256 }
257
258 // Apply the socket listeners
259 that.socket.on(socket_connect_event_name, function socketConnectCb() {
260
261 // TLS connections have the actual socket as a property
262 var is_tls = (typeof this.socket !== 'undefined') ?
263 true :
264 false;
265
266 // TLS sockets have already called this
267 if (!is_tls)
268 rawSocketConnect.call(that, this);
269
270 that.connected = true;
271
272 socketConnectHandler.call(that);
273 });
274
275 that.socket.on('error', function socketErrorCb(event) {
276 that.emit('error', event);
277 });
278
279 that.socket.on('data', function () {
280 socketOnData.apply(that, arguments);
281 });
282
283 that.socket.on('close', function socketCloseCb(had_error) {
284 that.connected = false;
285
286 // Remove this socket form the identd lookup
287 if (that.identd_port_pair) {
288 delete global.clients.port_pairs[that.identd_port_pair];
289 }
290
291 that.emit('close');
292
293 // Close the whole socket down
294 that.disposeSocket();
295 });
296 });
297 };
298
299 /**
300 * Send an event to the client
301 */
302 IrcConnection.prototype.clientEvent = function (event_name, data, callback) {
303 data.server = this.con_num;
304 this.state.sendIrcCommand(event_name, data, callback);
305 };
306
307 /**
308 * Write a line of data to the IRCd
309 * @param data The line of data to be sent
310 * @param force Write the data now, ignoring any write queue
311 */
312 IrcConnection.prototype.write = function (data, force) {
313 //ENCODE string to encoding of the server
314 encoded_buffer = iconv.encode(data + '\r\n', this.encoding);
315
316 if (force) {
317 this.socket.write(encoded_buffer);
318 return;
319 }
320
321 this.write_buffer.push(encoded_buffer);
322
323 // Only flush if we're not writing already
324 if (!this.writing_buffer)
325 this.flushWriteBuffer();
326 };
327
328
329
330 /**
331 * Flush the write buffer to the server in a throttled fashion
332 */
333 IrcConnection.prototype.flushWriteBuffer = function () {
334
335 // In case the socket closed between writing our queue.. clean up
336 if (!this.connected) {
337 this.write_buffer = [];
338 this.writing_buffer = false;
339 return;
340 }
341
342 this.writing_buffer = true;
343
344 // Disabled write buffer? Send everything we have
345 if (!this.write_buffer_lines_second) {
346 this.write_buffer.forEach(function(buffer, idx) {
347 this.socket.write(buffer);
348 this.write_buffer = null;
349 });
350
351 this.write_buffer = [];
352 this.writing_buffer = false;
353
354 return;
355 }
356
357 // Nothing to write? Stop writing and leave
358 if (this.write_buffer.length === 0) {
359 this.writing_buffer = false;
360 return;
361 }
362
363 this.socket.write(this.write_buffer[0]);
364 this.write_buffer = this.write_buffer.slice(1);
365
366 // Call this function again at some point if we still have data to write
367 if (this.write_buffer.length > 0) {
368 setTimeout(this.flushWriteBuffer.bind(this), 1000 / this.write_buffer_lines_second);
369 } else {
370 // No more buffers to write.. so we've finished
371 this.writing_buffer = false;
372 }
373 };
374
375
376
377 /**
378 * Close the connection to the IRCd after forcing one last line
379 */
380 IrcConnection.prototype.end = function (data, callback) {
381 if (!this.socket)
382 return;
383
384 if (data)
385 this.write(data, true);
386
387 this.socket.end();
388 };
389
390
391
392 /**
393 * Clean up this IrcConnection instance and any sockets
394 */
395 IrcConnection.prototype.dispose = function () {
396 // If we're still connected, wait until the socket is closed before disposing
397 // so that all the events are still correctly triggered
398 if (this.socket && this.connected) {
399 this.end();
400 return;
401 }
402
403 if (this.socket) {
404 this.disposeSocket();
405 }
406
407 _.each(this.irc_users, function (user) {
408 user.dispose();
409 });
410 _.each(this.irc_channels, function (chan) {
411 chan.dispose();
412 });
413 this.irc_users = undefined;
414 this.irc_channels = undefined;
415
416 this.server.dispose();
417 this.server = undefined;
418
419 this.irc_commands = undefined;
420
421 EventBinder.unbindIrcEvents('', this.irc_events, this);
422
423 this.removeAllListeners();
424 };
425
426
427
428 /**
429 * Clean up any sockets for this IrcConnection
430 */
431 IrcConnection.prototype.disposeSocket = function () {
432 if (this.socket) {
433 this.socket.end();
434 this.socket.removeAllListeners();
435 this.socket = null;
436 }
437 };
438
439 /**
440 * Set a new encoding for this connection
441 * Return true in case of success
442 */
443
444 IrcConnection.prototype.setEncoding = function (encoding) {
445 var encoded_test;
446
447 try {
448 encoded_test = iconv.encode("TEST", encoding);
449 //This test is done to check if this encoding also supports
450 //the ASCII charset required by the IRC protocols
451 //(Avoid the use of base64 or incompatible encodings)
452 if (encoded_test == "TEST") {
453 this.encoding = encoding;
454 return true;
455 }
456 return false;
457 } catch (err) {
458 return false;
459 }
460 };
461
462 function getConnectionFamily(host, callback) {
463 if (net.isIP(host)) {
464 if (net.isIPv4(host)) {
465 callback(null, 'IPv4', host);
466 } else {
467 callback(null, 'IPv6', host);
468 }
469 } else {
470 dns.resolve6(host, function resolve6Cb(err, addresses) {
471 if (!err) {
472 callback(null, 'IPv6', addresses[0]);
473 } else {
474 dns.resolve4(host, function resolve4Cb(err, addresses) {
475 if (!err) {
476 callback(null, 'IPv4',addresses[0]);
477 } else {
478 callback(err);
479 }
480 });
481 }
482 });
483 }
484 }
485
486
487 function onChannelJoin(event) {
488 var chan;
489
490 // Only deal with ourselves joining a channel
491 if (event.nick !== this.nick)
492 return;
493
494 // We should only ever get a JOIN command for a channel
495 // we're not already a member of.. but check we don't
496 // have this channel in case something went wrong somewhere
497 // at an earlier point
498 if (!this.irc_channels[event.channel]) {
499 chan = new IrcChannel(this, event.channel);
500 this.irc_channels[event.channel] = chan;
501 chan.irc_events.join.call(chan, event);
502 }
503 }
504
505
506 function onServerConnect(event) {
507 this.nick = event.nick;
508 }
509
510
511 function onUserPrivmsg(event) {
512 var user;
513
514 // Only deal with messages targetted to us
515 if (event.channel !== this.nick)
516 return;
517
518 if (!this.irc_users[event.nick]) {
519 user = new IrcUser(this, event.nick);
520 this.irc_users[event.nick] = user;
521 user.irc_events.privmsg.call(user, event);
522 }
523 }
524
525
526 function onUserNick(event) {
527 var user;
528
529 // Only deal with messages targetted to us
530 if (event.nick !== this.nick)
531 return;
532
533 this.nick = event.newnick;
534 }
535
536
537 function onUserParts(event) {
538 // Only deal with ourselves leaving a channel
539 if (event.nick !== this.nick)
540 return;
541
542 if (this.irc_channels[event.channel]) {
543 this.irc_channels[event.channel].dispose();
544 delete this.irc_channels[event.channel];
545 }
546 }
547
548 function onUserKick(event){
549 // Only deal with ourselves being kicked from a channel
550 if (event.kicked !== this.nick)
551 return;
552
553 if (this.irc_channels[event.channel]) {
554 this.irc_channels[event.channel].dispose();
555 delete this.irc_channels[event.channel];
556 }
557
558 }
559
560
561
562 /**
563 * When a socket connects to an IRCd
564 * May be called before any socket handshake are complete (eg. TLS)
565 */
566 var rawSocketConnect = function(socket) {
567 // Make note of the port numbers for any identd lookups
568 // Nodejs < 0.9.6 has no socket.localPort so check this first
569 if (typeof socket.localPort != 'undefined') {
570 this.identd_port_pair = socket.localPort.toString() + '_' + socket.remotePort.toString();
571 global.clients.port_pairs[this.identd_port_pair] = this;
572 }
573 };
574
575
576 /**
577 * Handle the socket connect event, starting the IRCd registration
578 */
579 var socketConnectHandler = function () {
580 var that = this,
581 connect_data;
582
583 // Build up data to be used for webirc/etc detection
584 connect_data = {
585 connection: this,
586
587 // Array of lines to be sent to the IRCd before anything else
588 prepend_data: []
589 };
590
591 // Let the webirc/etc detection modify any required parameters
592 connect_data = findWebIrc.call(this, connect_data);
593
594 global.modules.emit('irc authorize', connect_data).done(function ircAuthorizeCb() {
595 var gecos = '[www.kiwiirc.com] ' + that.nick;
596
597 if (global.config.default_gecos) {
598 gecos = global.config.default_gecos.toString().replace('%n', that.nick);
599 gecos = gecos.toString().replace('%h', that.user.hostname);
600 }
601
602 // Send any initial data for webirc/etc
603 if (connect_data.prepend_data) {
604 _.each(connect_data.prepend_data, function(data) {
605 that.write(data);
606 });
607 }
608
609 that.write('CAP LS');
610
611 if (that.password)
612 that.write('PASS ' + that.password);
613
614 that.write('NICK ' + that.nick);
615 that.write('USER ' + that.username + ' 0 0 :' + gecos);
616
617 that.emit('connected');
618 });
619 };
620
621
622
623 /**
624 * Load any WEBIRC or alternative settings for this connection
625 * Called in scope of the IrcConnection instance
626 */
627 function findWebIrc(connect_data) {
628 var webirc_pass = global.config.webirc_pass,
629 ip_as_username = global.config.ip_as_username,
630 tmp;
631
632
633 // Do we have a WEBIRC password for this?
634 if (webirc_pass && webirc_pass[this.irc_host.hostname]) {
635 // Build the WEBIRC line to be sent before IRC registration
636 tmp = 'WEBIRC ' + webirc_pass[this.irc_host.hostname] + ' KiwiIRC ';
637 tmp += this.user.hostname + ' ' + this.user.address;
638
639 connect_data.prepend_data = [tmp];
640 }
641
642
643 // Check if we need to pass the users IP as its username/ident
644 if (ip_as_username && ip_as_username.indexOf(this.irc_host.hostname) > -1) {
645 // Get a hex value of the clients IP
646 this.username = this.user.address.split('.').map(function ipSplitMapCb(i, idx){
647 var hex = parseInt(i, 10).toString(16);
648
649 // Pad out the hex value if it's a single char
650 if (hex.length === 1)
651 hex = '0' + hex;
652
653 return hex;
654 }).join('');
655
656 }
657
658 return connect_data;
659 }
660
661
662 /**
663 * Buffer any data we get from the IRCd until we have complete lines.
664 */
665 function socketOnData(data) {
666 var data_pos, // Current position within the data Buffer
667 line_start = 0,
668 lines = [],
669 line = '',
670 max_buffer_size = 1024; // 1024 bytes is the maximum length of two RFC1459 IRC messages.
671 // May need tweaking when IRCv3 message tags are more widespread
672
673 // Split data chunk into individual lines
674 for (data_pos = 0; data_pos < data.length; data_pos++) {
675 if (data[data_pos] === 0x0A) { // Check if byte is a line feed
676 lines.push(data.slice(line_start, data_pos));
677 line_start = data_pos + 1;
678 }
679 }
680
681 // No complete lines of data? Check to see if buffering the data would exceed the max buffer size
682 if (!lines[0]) {
683 if ((this.held_data ? this.held_data.length : 0 ) + data.length > max_buffer_size) {
684 // Buffering this data would exeed our max buffer size
685 this.emit('error', 'Message buffer too large');
686 this.socket.destroy();
687
688 } else {
689
690 // Append the incomplete line to our held_data and wait for more
691 if (this.held_data) {
692 this.held_data = Buffer.concat([this.held_data, data], this.held_data.length + data.length);
693 } else {
694 this.held_data = data;
695 }
696 }
697
698 // No complete lines to process..
699 return;
700 }
701
702 // If we have an incomplete line held from the previous chunk of data
703 // merge it with the first line from this chunk of data
704 if (this.hold_last && this.held_data !== null) {
705 lines[0] = Buffer.concat([this.held_data, lines[0]], this.held_data.length + lines[0].length);
706 this.hold_last = false;
707 this.held_data = null;
708 }
709
710 // If the last line of data in this chunk is not complete, hold it so
711 // it can be merged with the first line from the next chunk
712 if (line_start < data_pos) {
713 if ((data.length - line_start) > max_buffer_size) {
714 // Buffering this data would exeed our max buffer size
715 this.emit('error', 'Message buffer too large');
716 this.socket.destroy();
717 return;
718 }
719
720 this.hold_last = true;
721 this.held_data = new Buffer(data.length - line_start);
722 data.copy(this.held_data, 0, line_start);
723 }
724
725 // Process our data line by line
726 for (i = 0; i < lines.length; i++)
727 parseIrcLine.call(this, lines[i]);
728
729 }
730
731
732
733 /**
734 * The regex that parses a line of data from the IRCd
735 * Deviates from the RFC a little to support the '/' character now used in some
736 * IRCds
737 */
738 var parse_regex = /^(?:(?:(?:@([^ ]+) )?):(?:([^\s!]+)|([^\s!]+)!([^\s@]+)@?([^\s]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.*))?$/i;
739
740 function parseIrcLine(buffer_line) {
741 var msg,
742 i,
743 tags = [],
744 tag,
745 line = '',
746 msg_obj;
747
748 // Decode server encoding
749 line = iconv.decode(buffer_line, this.encoding);
750 if (!line) {
751 return;
752 }
753
754 // Parse the complete line, removing any carriage returns
755 msg = parse_regex.exec(line.replace(/^\r+|\r+$/, ''));
756
757 if (!msg) {
758 // The line was not parsed correctly, must be malformed
759 //console.log("Malformed IRC line: " + line.replace(/^\r+|\r+$/, ''));
760 winston.warn('Malformed IRC line: %s', line.replace(/^\r+|\r+$/, ''));
761 return;
762 }
763
764 // Extract any tags (msg[1])
765 if (msg[1]) {
766 tags = msg[1].split(';');
767
768 for (i = 0; i < tags.length; i++) {
769 tag = tags[i].split('=');
770 tags[i] = {tag: tag[0], value: tag[1]};
771 }
772 }
773
774 msg_obj = {
775 tags: tags,
776 prefix: msg[2],
777 nick: msg[3],
778 ident: msg[4],
779 hostname: msg[5] || '',
780 command: msg[6],
781 params: msg[7] ? msg[7].split(/ +/) : []
782 };
783
784 if (msg[8]) {
785 msg_obj.params.push(msg[8].trim());
786 }
787
788 this.irc_commands.dispatch(msg_obj.command.toUpperCase(), msg_obj);
789 }