X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=server%2Firc%2Fconnection.js;h=49c7c810e5524e73e4e259f84e21a28fb2105724;hb=ad590288aa67bdffef795f005678000d663e9adb;hp=37aa13433ac107a2725c2e67d15e07628fc575a3;hpb=3e01340eed22bacc156992da6279fd28f54fc29c;p=KiwiIRC.git diff --git a/server/irc/connection.js b/server/irc/connection.js index 37aa134..49c7c81 100644 --- a/server/irc/connection.js +++ b/server/irc/connection.js @@ -23,7 +23,7 @@ if (version_values[1] >= 10) { Socks = require('socksjs'); } -var IrcConnection = function (hostname, port, ssl, nick, user, pass, state, con_num) { +var IrcConnection = function (hostname, port, ssl, nick, user, options, state, con_num) { var that = this; EE.call(this,{ @@ -32,9 +32,8 @@ var IrcConnection = function (hostname, port, ssl, nick, user, pass, state, con_ }); this.setMaxListeners(0); - // Set the first configured encoding as the default encoding - this.encoding = global.config.default_encoding; - + options = options || {}; + // Socket state this.connected = false; @@ -57,7 +56,12 @@ var IrcConnection = function (hostname, port, ssl, nick, user, pass, state, con_ this.nick = nick; this.user = user; // Contains users real hostname and address this.username = this.nick.replace(/[^0-9a-zA-Z\-_.\/]/, ''); - this.password = pass; + this.password = options.password || ''; + + // Set the passed encoding. or the default if none giving or it fails + if (!options.encoding || !this.setEncoding(options.encoding)) { + this.setEncoding(global.config.default_encoding); + } // State object this.state = state; @@ -106,7 +110,7 @@ var IrcConnection = function (hostname, port, ssl, nick, user, pass, state, con_ // Buffers for data sent from the IRCd this.hold_last = false; - this.held_data = ''; + this.held_data = null; this.applyIrcEvents(); }; @@ -168,6 +172,15 @@ IrcConnection.prototype.connect = function () { host = dest_addr; } + // If we have an array of interfaces, select a random one + if (typeof outgoing !== 'string' && outgoing.length) { + outgoing = outgoing[Math.floor(Math.random() * outgoing.length)]; + } + + // Make sure we have a valid interface address + if (typeof outgoing !== 'string') + outgoing = '0.0.0.0'; + } else { // No config was found so use the default outgoing = '0.0.0.0'; @@ -198,6 +211,9 @@ IrcConnection.prototype.connect = function () { localAddress: outgoing }); + // We need the raw socket connect event + that.socket.socket.on('connect', function() { rawSocketConnect.call(that, this); }); + socket_connect_event_name = 'secureConnect'; } else { @@ -212,19 +228,16 @@ IrcConnection.prototype.connect = function () { // Apply the socket listeners that.socket.on(socket_connect_event_name, function socketConnectCb() { - // SSL connections have the actual socket as a property - var socket = (typeof this.socket !== 'undefined') ? - this.socket : - this; + // TLS connections have the actual socket as a property + var is_tls = (typeof this.socket !== 'undefined') ? + true : + false; - that.connected = true; + // TLS sockets have already called this + if (!is_tls) + rawSocketConnect.call(that, this); - // Make note of the port numbers for any identd lookups - // Nodejs < 0.9.6 has no socket.localPort so check this first - if (socket.localPort) { - that.identd_port_pair = socket.localPort.toString() + '_' + socket.remotePort.toString(); - global.clients.port_pairs[that.identd_port_pair] = that; - } + that.connected = true; socketConnectHandler.call(that); }); @@ -234,7 +247,7 @@ IrcConnection.prototype.connect = function () { }); that.socket.on('data', function () { - parse.apply(that, arguments); + socketOnData.apply(that, arguments); }); that.socket.on('close', function socketCloseCb(had_error) { @@ -516,6 +529,19 @@ function onUserKick(event){ +/** + * When a socket connects to an IRCd + * May be called before any socket handshake are complete (eg. TLS) + */ +var rawSocketConnect = function(socket) { + // Make note of the port numbers for any identd lookups + // Nodejs < 0.9.6 has no socket.localPort so check this first + if (typeof socket.localPort != 'undefined') { + this.identd_port_pair = socket.localPort.toString() + '_' + socket.remotePort.toString(); + global.clients.port_pairs[this.identd_port_pair] = this; + } +}; + /** * Handle the socket connect event, starting the IRCd registration @@ -536,6 +562,8 @@ var socketConnectHandler = function () { connect_data = findWebIrc.call(this, connect_data); global.modules.emit('irc authorize', connect_data).done(function ircAuthorizeCb() { + var gecos = '[www.kiwiirc.com] ' + that.nick; + // Send any initial data for webirc/etc if (connect_data.prepend_data) { _.each(connect_data.prepend_data, function(data) { @@ -549,7 +577,7 @@ var socketConnectHandler = function () { that.write('PASS ' + that.password); that.write('NICK ' + that.nick); - that.write('USER ' + that.username + ' 0 0 :' + '[www.kiwiirc.com] ' + that.nick); + that.write('USER ' + that.username + ' 0 0 :' + (global.config.default_gecos || gecos)); that.emit('connected'); }); @@ -596,74 +624,125 @@ function findWebIrc(connect_data) { } +/** + * Buffer any data we get from the IRCd until we have complete lines. + */ +function socketOnData(data) { + var data_pos, // Current position within the data Buffer + line_start = 0, + lines = [], + line = '', + max_buffer_size = 1024; // 1024 bytes is the maximum length of two RFC1459 IRC messages. + // May need tweaking when IRCv3 message tags are more widespread + + // Split data chunk into individual lines + for (data_pos = 0; data_pos < data.length; data_pos++) { + if (data[data_pos] === 0x0A) { // Check if byte is a line feed + lines.push(data.slice(line_start, data_pos)); + line_start = data_pos + 1; + } + } + + // No complete lines of data? Check to see if buffering the data would exceed the max buffer size + if (!lines[0]) { + if ((this.held_data ? this.held_data.length : 0 ) + data.length > max_buffer_size) { + // Buffering this data would exeed our max buffer size + this.emit('error', 'Message buffer too large'); + this.socket.destroy(); + + } else { + + // Append the incomplete line to our held_data and wait for more + if (this.held_data) { + this.held_data = Buffer.concat([this.held_data, data], this.held_data.length + data.length); + } else { + this.held_data = data; + } + } + + // No complete lines to process.. + return; + } + + // If we have an incomplete line held from the previous chunk of data + // merge it with the first line from this chunk of data + if (this.hold_last && this.held_data !== null) { + lines[0] = Buffer.concat([this.held_data, lines[0]], this.held_data.length + lines[0].length); + this.hold_last = false; + this.held_data = null; + } + + // If the last line of data in this chunk is not complete, hold it so + // it can be merged with the first line from the next chunk + if (line_start < data_pos) { + if ((data.length - line_start) > max_buffer_size) { + // Buffering this data would exeed our max buffer size + this.emit('error', 'Message buffer too large'); + this.socket.destroy(); + return; + } + + this.hold_last = true; + this.held_data = new Buffer(data.length - line_start); + data.copy(this.held_data, 0, line_start); + } + + // Process our data line by line + for (i = 0; i < lines.length; i++) + parseIrcLine.call(this, lines[i]); + +} + + /** * The regex that parses a line of data from the IRCd * Deviates from the RFC a little to support the '/' character now used in some * IRCds */ -var parse_regex = /^(?:(?:(?:(@[^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-*]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-*]+)!([^\x00\r\n\ ]+?)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; - -var parse = function (data) { - var i, - msg, - msg2, - trm, - j, +var parse_regex = /^(?:(?:(?:@([^ ]+) )?):(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-*]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-*]+)!([^\x00\r\n\ ]+?)@?([a-z0-9\.\-:\/_]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i; + +function parseIrcLine(buffer_line) { + var msg, + i, j, tags = [], - tag; + tag, + line = ''; - //DECODE server encoding - data = iconv.decode(data, this.encoding); + // Decode server encoding + line = iconv.decode(buffer_line, this.encoding); + if (!line) return; - if (this.hold_last && this.held_data !== '') { - data = this.held_data + data; - this.hold_last = false; - this.held_data = ''; - } + // Parse the complete line, removing any carriage returns + msg = parse_regex.exec(line.replace(/^\r+|\r+$/, '')); - // If the last line is incomplete, hold it until we have more data - if (data.substr(-1) !== '\n') { - this.hold_last = true; + if (!msg) { + // The line was not parsed correctly, must be malformed + console.log("Malformed IRC line: " + line.replace(/^\r+|\r+$/, '')); + return; } - // Process our data line by line - data = data.split("\n"); - for (i = 0; i < data.length; i++) { - if (!data[i]) break; - - // If flagged to hold the last line, store it and move on - if (this.hold_last && (i === data.length - 1)) { - this.held_data = data[i]; - break; - } - - // Parse the complete line, removing any carriage returns - msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, '')); - - if (msg) { - if (msg[1]) { - tags = msg[1].split(';'); - for (j = 0; j < tags.length; j++) { - tag = tags[j].split('='); - tags[j] = {tag: tag[0], value: tag[1]}; - } - } - msg = { - tags: tags, - prefix: msg[2], - nick: msg[3], - ident: msg[4], - hostname: msg[5] || '', - command: msg[6], - params: msg[7] || '', - trailing: (msg[8]) ? msg[8].trim() : '' - }; - msg.params = msg.params.split(' '); - this.irc_commands.dispatch(msg.command.toUpperCase(), msg); - } else { - // The line was not parsed correctly, must be malformed - console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, '')); + // Extract any tags (msg[1]) + if (msg[1]) { + tags = msg[1].split(';'); + + for (j = 0; j < tags.length; j++) { + tag = tags[j].split('='); + tags[j] = {tag: tag[0], value: tag[1]}; } } -}; + + msg = { + tags: tags, + prefix: msg[2], + nick: msg[3], + ident: msg[4], + hostname: msg[5] || '', + command: msg[6], + params: msg[7] || '', + trailing: (msg[8]) ? msg[8].trim() : '' + }; + + msg.params = msg.params.split(' '); + this.irc_commands.dispatch(msg.command.toUpperCase(), msg); +}