Skip to content

Commit 3e1210c

Browse files
committed
Add support for IPv6 idents and addresses starting with a colon
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) and unmaps IPv4-mapped IPv6 addresses.
1 parent 55b0bb2 commit 3e1210c

File tree

2 files changed

+72
-18
lines changed

2 files changed

+72
-18
lines changed

Diff for: config.example.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ conf.default_gecos = '%n is using a Web IRC client';
8888
* Default ident / username for IRC connections
8989
* %n will be replaced with the users nick
9090
* %h will be replaced with the users hostname
91-
* %i will be replaced with a hexed value of the users IP
91+
* %i will be replaced with a hexed value of the user's IP (for IPv4) or
92+
* a base32-encoded hash of the user's IP (for IPv6).
9293
*/
9394
conf.default_ident = '%i';
9495

Diff for: server/irc/connection.js

+70-17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var net = require('net'),
44
dns = require('dns'),
55
_ = require('lodash'),
66
winston = require('winston'),
7+
crypto = require('crypto'),
78
Socks = require('socksjs'),
89
EventBinder = require('./eventbinder.js'),
910
IrcServer = require('./server.js'),
@@ -599,7 +600,7 @@ IrcConnection.prototype.setDefaultUserDetails = function () {
599600
this.username = (this.username || global.config.default_ident || '%n')
600601
.replace('%n', (this.nick.replace(/[^0-9a-zA-Z\-_.\/]/, '') || 'nick'))
601602
.replace('%h', this.user.hostname)
602-
.replace('%i', ip2Hex(this.user.address) || '00000000');
603+
.replace('%i', getIpIdent(this.user.address));
603604

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

768769

770+
// Convert a IPv4-mapped IPv6 addresses to a regular IPv4 address
771+
function unmapIPv4(address) {
772+
if (address.toLowerCase().indexOf('::ffff:') == 0) {
773+
address = address.substring(7);
774+
}
775+
return address
776+
}
777+
769778

770779
/**
771780
* Load any WEBIRC or alternative settings for this connection
772781
* Called in scope of the IrcConnection instance
773782
*/
774783
function findWebIrc(connect_data) {
775784
var webirc_pass = global.config.webirc_pass,
785+
address = unmapIPv4(this.user.address),
776786
found_webirc_pass, tmp;
777787

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

793808
connect_data.prepend_data = [tmp];
794809
}
@@ -865,25 +880,63 @@ function socketOnData(data) {
865880
}
866881

867882

883+
// Encodes a Buffer of bytes into an RFC 4648 base32 encoded string.
884+
// Does not include padding.
885+
// From https://github.com/agnoster/base32-js
886+
function base32Encode(input) {
887+
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
888+
var skip = 0; // How many bits we will skip from the first byte
889+
var bits = 0; // 5 high bits, carry from one byte to the next
890+
var output = '';
891+
892+
for (var i = 0; i < input.length; ) {
893+
var byte = input[i];
894+
if (skip < 0) { // We have a carry from the previous byte
895+
bits |= byte >> (-skip);
896+
} else { // No carry
897+
bits = (byte << skip) & 0xF8; // 0b11111000
898+
}
868899

869-
function ip2Hex(ip) {
870-
// We can only deal with IPv4 addresses for now
871-
if (!ip.match(/^[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}$/)) {
872-
return;
873-
}
874-
875-
var hexed = ip.split('.').map(function ipSplitMapCb(i){
876-
var hex = parseInt(i, 10).toString(16);
900+
if (skip > 3) {
901+
// Not enough data to produce a character, get us another one
902+
skip -= 8;
903+
++i;
904+
continue;
905+
}
877906

878-
// Pad out the hex value if it's a single char
879-
if (hex.length === 1) {
880-
hex = '0' + hex;
907+
if (skip < 4) {
908+
// Produce a character
909+
output += alphabet[bits >> 3];
910+
skip += 5;
881911
}
912+
}
913+
914+
return output + (skip < 0 ? alphabet[bits >> 3] : '');
915+
}
882916

883-
return hex;
884-
}).join('');
885917

886-
return hexed;
918+
function getIpIdent(ip) {
919+
ip = unmapIPv4(ip);
920+
if (ip.indexOf('.') != -1) { // IPv4
921+
// With IPv4 addresses we can just encode the address in hex
922+
return ip.split('.').map(function ipSplitMapCb(i, idx) {
923+
var hex = parseInt(i, 10).toString(16);
924+
925+
// Pad out the hex value if it's a single char
926+
if (hex.length === 1)
927+
hex = '0' + hex;
928+
929+
return hex;
930+
}).join('');
931+
} else { // IPv6
932+
// Generate a hash of the IP address and encode it in base-32. This
933+
// can have collisions, but the probability should be very low (about
934+
// 1 / 32^x, where x is the max ident length). Why base32? Because
935+
// the ident is compared case-insensitively in hostmasks and is used by
936+
// humans who may confuse similar characters.
937+
return base32Encode(crypto.createHash('sha1')
938+
.update(ip).digest()).substr(0, 10);
939+
}
887940
}
888941

889942

0 commit comments

Comments
 (0)