Skip to content

Commit

Permalink
Improve IPv6 support
Browse files Browse the repository at this point in the history
IPv6 idents are now the first ten characters of the base32 encoded
SHA-1 hash of the user's IP.  Unlike IPv4 addresses, there is a
possibility that there will be collisions (i.e., two IPs that get the
same ident).  This is unfourtunate, but it is unavoidable given that
IRCds typically only allow about ten characters for the username.
Fourtunately, the probability of collision is quite small (assuming
SHA-1 produces well distributed output, the probability of even an
8-character ident collising is one in 32^8 or about one in a trillion).

This also fixes addresses starting with a colon (e.g., IPv4-mapped IPv6
addresses), unmaps IPv4-mapped IPv6 addresses, and fixes a place where
it was assumed that an address with a dot was IPv6.
  • Loading branch information
ShadowNinja committed Dec 31, 2016
1 parent 55b0bb2 commit d1002fc
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 20 deletions.
3 changes: 2 additions & 1 deletion config.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ conf.default_gecos = '%n is using a Web IRC client';
* Default ident / username for IRC connections
* %n will be replaced with the users nick
* %h will be replaced with the users hostname
* %i will be replaced with a hexed value of the users IP
* %i will be replaced with a hexed value of the user's IP (for IPv4) or
* a base32-encoded hash of the user's IP (for IPv6).
*/
conf.default_ident = '%i';

Expand Down
87 changes: 70 additions & 17 deletions server/irc/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var net = require('net'),
dns = require('dns'),
_ = require('lodash'),
winston = require('winston'),
crypto = require('crypto'),
Socks = require('socksjs'),
EventBinder = require('./eventbinder.js'),
IrcServer = require('./server.js'),
Expand Down Expand Up @@ -599,7 +600,7 @@ IrcConnection.prototype.setDefaultUserDetails = function () {
this.username = (this.username || global.config.default_ident || '%n')
.replace('%n', (this.nick.replace(/[^0-9a-zA-Z\-_.\/]/, '') || 'nick'))
.replace('%h', this.user.hostname)
.replace('%i', ip2Hex(this.user.address) || '00000000');
.replace('%i', getIpIdent(this.user.address));

this.gecos = (this.gecos || global.config.default_gecos || '%n')
.replace('%n', this.nick)
Expand Down Expand Up @@ -766,16 +767,24 @@ var socketConnectHandler = function () {
};


// Convert a IPv4-mapped IPv6 addresses to a regular IPv4 address
function unmapIPv4(address) {
if (address.toLowerCase().indexOf('::ffff:') == 0) {
address = address.substring(7);
}
return address
}


/**
* Load any WEBIRC or alternative settings for this connection
* Called in scope of the IrcConnection instance
*/
function findWebIrc(connect_data) {
var webirc_pass = global.config.webirc_pass,
address = unmapIPv4(this.user.address),
found_webirc_pass, tmp;


// Do we have a single WEBIRC password?
if (typeof webirc_pass === 'string' && webirc_pass) {
found_webirc_pass = webirc_pass;
Expand All @@ -788,7 +797,13 @@ function findWebIrc(connect_data) {
if (found_webirc_pass) {
// Build the WEBIRC line to be sent before IRC registration
tmp = 'WEBIRC ' + found_webirc_pass + ' KiwiIRC ';
tmp += this.user.hostname + ' ' + this.user.address;
var hostname = this.user.hostname;
// Add a 0 in front of IP(v6) addresses starting with a colon
// (otherwise the colon will be interpreted as meaning that the
// rest of the line is a single argument).
if (hostname[0] == ':') hostname = '0' + hostname;
if (address[0] == ':') address = '0' + address;
tmp += hostname + ' ' + address;

connect_data.prepend_data = [tmp];
}
Expand Down Expand Up @@ -865,25 +880,63 @@ function socketOnData(data) {
}


// Encodes a Buffer of bytes into an RFC 4648 base32 encoded string.
// Does not include padding.
// From https://github.com/agnoster/base32-js
function base32Encode(input) {
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var skip = 0; // How many bits we will skip from the first byte
var bits = 0; // 5 high bits, carry from one byte to the next
var output = '';

for (var i = 0; i < input.length; ) {
var byte = input[i];
if (skip < 0) { // We have a carry from the previous byte
bits |= byte >> (-skip);
} else { // No carry
bits = (byte << skip) & 0xF8; // 0b11111000
}

function ip2Hex(ip) {
// We can only deal with IPv4 addresses for now
if (!ip.match(/^[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}$/)) {
return;
}

var hexed = ip.split('.').map(function ipSplitMapCb(i){
var hex = parseInt(i, 10).toString(16);
if (skip > 3) {
// Not enough data to produce a character, get us another one
skip -= 8;
++i;
continue;
}

// Pad out the hex value if it's a single char
if (hex.length === 1) {
hex = '0' + hex;
if (skip < 4) {
// Produce a character
output += alphabet[bits >> 3];
skip += 5;
}
}

return output + (skip < 0 ? alphabet[bits >> 3] : '');
}

return hex;
}).join('');

return hexed;
function getIpIdent(ip) {
ip = unmapIPv4(ip);
if (ip.indexOf('.') != -1) { // IPv4
// With IPv4 addresses we can just encode the address in hex
return ip.split('.').map(function ipSplitMapCb(i, idx) {
var hex = parseInt(i, 10).toString(16);

// Pad out the hex value if it's a single char
if (hex.length === 1)
hex = '0' + hex;

return hex;
}).join('');
} else { // IPv6
// Generate a hash of the IP address and encode it in base-32. This
// can have collisions, but the probability should be very low (about
// 1 / 32^x, where x is the max ident length). Why base32? Because
// the ident is compared case-insensitively in hostmasks and is used by
// humans who may confuse similar characters.
return base32Encode(crypto.createHash('sha1')
.update(ip).digest()).substr(0, 10);
}
}


Expand Down
4 changes: 2 additions & 2 deletions server/weblistener.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ function initialiseSocket(socket, callback) {
address = address.split(',')[0].trim();

// Some reverse proxies (IIS) may include the port, so lets remove that (if ipv4)
if (address.indexOf('.') > -1) {
address = (address || '').split(':')[0];
if (address.match(/^[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}:[0-9]+$/)) {
address = address.split(':')[0];
}
}

Expand Down

0 comments on commit d1002fc

Please sign in to comment.