Net::XMPP virtual hosting support

By Abhijit Menon-Sen <ams@toroid.org>

2009-07-14

I use a Git post-receive hook that sends commit notifications to a Jabber server using the Net::XMPP Perl module.

For the first year or so of its operation, the hook would connect to the Jabber server (first jabberd 1.4, later Ejabberd) that I ran on oryx.com, and send the commit message as git@oryx.com, and all was well. Recently, we reorganised our servers. The git repository stayed in the same place, but we wanted it to connect to the Jabber server on aox.toroid.org and send notifications as git@aox.org.

I run Ejabberd on toroid.org too, and it was the work of a moment to add aox.org to its list of hostnames and create an account for git@aox.org. But when I tried to make my hook connect to the new server, I discovered that Net::XMPP does not allow me to specify the name of the server independently of the hostname in the Jabber ID:

my $client = new Net::Jabber::Client ();
$client->Connect(
    hostname => 'aox.toroid.org',
) or die "Can't connect: $!\n";

my @r = $client->AuthSend(
    username => 'git',
    password => '...',
    resource => 'aox.git'
);
die "Cannot authenticate (@r)\n" if $r[0] ne "ok";

This code would connect to aox.toroid.org and try to authenticate as git@aox.toroid.org. Changing hostname to "aox.org" didn't work (even though we had SRV records pointing to the right server). Changing the username to "git@aox.org" didn't work either. I spent some time looking through the code and a tcpdump of the session to understand the problem. It turns out that an XMPP session begins with a <stream> element like this:

<stream:stream version='1.0'
 xmlns:stream='http://etherx.jabber.org/streams'
 xmlns='jabber:client'
 to='aox.toroid.org'
 from='git.aox.org'
 xml:lang='en'>

What is needed is to connect to aox.toroid.org and send to='aox.org' in the stream, because Ejabberd rejects connections to a hostname not listed in its configuration.

Net::XMPP and XML::Stream (the underlying module) use the hostname passed to Connect() to decide where to connect and what to send in the <stream>. Furthermore, XML::Stream did support SRV lookups, but it would replace the hostname with the result of the lookup and use that name to compose the stream; and Net::XMPP had no way to enable the SRV lookups anyway.

I whipped up a patch to solve these problems. It adds "servername" and "srv" parameters to the Connect() method, which are passed down to XML::Stream. If the servername is specified, it is used to decide where to connect (and if not, the old behaviour remains unchanged). The srv parameter enables SRV lookups, and the results are used to set (only) the servername; but the hostname is used to compose the stream in every case. Thus the two are separated enough for my hook to work again.

I sent my patch to the module maintainers, but did not receive a reply. The patch is archived here. It applies to Net::XMPP 1.02 and XML::Stream 1.23, but may need minor tweaking to apply cleanly to other versions.