Migration of IRC Services from Hybserv2 to Anope

en

I recently inherited a small IRC network, consisting of a single server and a rather small but still active community. The software on this server was quite outdated, running a version of ircd-hybrid from 8 years ago, and for IRC services a comparably old version of the now-unmaintained hybserv2.

I decided to not even attempt to get this stuff up to date, but rather replace the server entirely with recent and maintained software. The choice fell on InspIRCd 3 and Anope 2. Switching to a new IRC server is not really that much of a challenge, especially in a single-server network. There is some minor tweaking required, such as making user and channel modes compatible with each other (e.g. InspIRCd 3 dropped core support for the halfop (+h) channel mode, which can hovever be reintroduced with the "customprefix" module). The far greater challenge were the IRC services, as there are some major incompatibilities:

  1. Hybserv2 stores password hashes using the original crypt algorithm based on DES. It would support MD5 instead of DES as well, but that would have to have been enabled beforehand. Anope on the other hand, doesn't support DES. They offer various variants of crypt-style hashes as well, but the "least modern" of those is MD5.
  2. Both Hybserv and Anope store their persistent data in plain text files (Anope does support other methods, such as database backends, but recommends the text file), however the formats of these files are incompatible and neither format is (well) documented.

Password Hash Migration

As it turns out, Anope has built-in functionality for password hash migration: When more than one password hashing module (enc_*) is loaded, only the first one is used for hashing new passwords. The other modules are only used for verifying already existing hashes. On top of that, when a user successfully authenticates against NickServ, and the hash is not in the default format (i.e. the first module), Anope will automatically re-hash the password entered by the user using the default method, and write it to disk.

Using this method, the old DES hashes are replaced by more modern ones as each user logs in, until all hashes have been converted (or the NickServ registrations of inactive users have expired). So the only thing left to do is to write an Anope module to verify DES crypt password hashes. This is pretty straight forward, as all that's needed is a thin wrapper around crypt that conforms to the Anope module API. My implementation can be found on Gitlab along with the rest of the sources. To build the module, and including it in your Anope installation, the following steps are required:

  1. Clone the Anope repo from Github: git clone https://github.com/anope/anope
  2. Checkout the exact version of Anope you intend to run. In my case, this was version 2.0.9, so I'd do git checkout 2.0.9. This step is important, because Anope refuses to load modules built for another version.
  3. Place the enc_des.cpp file in modules/encryption/ in the Anope repo and follow the Anope build instructions:
    1. ./Config
    2. cd build
    3. make. Actually, since we only need the DES module, we can run make enc_des.so instead, which will complete a lot faster.
  4. Link against libcrypt. Since I didn't want to deal with all the CMake stuff, I decided to just patch the .so file afterwards: patchelf --add-needed libcrypt.so.1 modules/enc_des.so. Replace the actual version of libcrypt.so by whatever is available on your target system.
  5. Put the built module into your Anope installation's modules directory. If you're using the Anope Debian package, this would be /usr/lib/anope/modules/enc_des.so.

The final step left to do is to configure Anope to load the module:

// Used for new passwords and converting old passwords
module { name = "enc_bcrypt" }
// Used for verification only, passwords are automatically rehashed with bcrypt
module { name = "enc_des" }

I chose to use bcrypt as the new hashing method. You can choose whichever method you like, however you must make sure that enc_des is listed after the chosen module, otherwise all passwords would be rehashed to DES crypt.

And that's about it for password hashes. You should then occasionally check whether there are still any DES hashes left, and once they're all gone, unload and remove the enc_des module.

Understanding Hybserv's Database

Hybserv's "database" consists of multiple files: one for nicks, one for channels, and so on. In this specific case, only NickServ and ChanServ were used, so there was no need to migrate the others.

As mentioned before, there is no documentation of what the content in these files looks like, so we'll have to figure this out on our own.

A user in the nick.db file looks like this:

s3lph 14600 1602803962 1608435182
->PASS EelyBKlmXzO5r
->EMAIL s3lph@example.com
->LASTUH ~s3lph bnc.example.com
->LASTQMSG :So long and thanks for all the fish
->HOST *~s3lph@bnc.example.com
->HOST *~s3lph@127.0.0.*
->TS 1608435182
->LASTSERVER irc.example.com

And this is a channel in the chan.db file:

#example 4 1602804235 1603929600
->FNDR s3lph 1602804235
->PASS wahF8nhZXc4D
->TOPIC :This is an example
->ALVL -1 5 8 5 5 8 10 10 10 8 15 20 25 40 50
->ACCESS s3lph 50 1602804235 1603929600 *As Founder*
->ACCESS s4lph 10 1602803962 1608435256 s3lph

Each item is started with a line consisting of 4 fields: the name, some number and two numbers that look like UNIX timestamps. Looking at Hybserv's source code, we learn that the first number is a set of flags which control the services' behavior in regard to the nick or channel. The source code also tells us that the timestamps are the registration time and the time the resource was last "seen".

The following lines are mostly straight-forward. They all start with ->, which seems to indicate continuation of the object. They feature "somewhat human readable" keys and one or more values separated by spaces. For NickServ registrations, there are values such as the DES password hash, the email address, or which hostmasks the user has successfully identified from before. For ChanServ registrations, we see values such as the channel topic or the founder.

The last few lines were not entirely obvious, so again a look at the source code provided clarification. The ALVL chain of numbers turns out to be a definition of which permissions (e.g. voice, halfop, chanop, founder) are granted starting from which "access level". This also clarifies the format of the ACCESS lines: After the nick comes the assigned access level, then two timestamps (the time access was granted and the time it was last used) and finally the nick of the user granting the access. In the example above, s3lph would be the channel founder, and s4lph would be a halfop.

Understanding Anope's Database

Anope uses a similar approach to storing data. Again, data is stored in plain text, however all data is stored in one file, anope.db, instead of multiple files. The format is actually quite similar, but does feature some crucial differences (empty lines only inserted for readability):

OBJECT NickCore
DATA display s3lph
DATA pass des:EelyBKlmXzO5r
DATA email 
DATA language 
DATA access s3lph@127.0.0.* 
DATA memomax 0
DATA HIDE_EMAIL 1
DATA HIDE_MASK 1
DATA NS_PRIVATE 1
DATA AUTOOP 1
DATA NS_SECURE 1
END

OBJECT NickAlias
DATA nick s3lph
DATA last_quit So long and thanks for all the fish
DATA last_realname Unknown
DATA last_usermask s3lph@bnc.example.com
DATA last_realhost s3lph@bnc.example.com
DATA time_registered 1602803962
DATA last_seen 1608435182
DATA nc s3lph
END


OBJECT ChannelInfo
DATA name #example
DATA founder s3lph
DATA description 
DATA time_registered 1602804235
DATA last_used 1603929600
DATA last_topic This is an example
DATA last_topic_setter s3lph
DATA last_topic_time 1608173683
DATA bantype 2
DATA levels ACCESS_CHANGE 10 ACCESS_LIST 3 AKICK 10 ASSIGN 10001 AUTOHALFOP 4 AUTOOP 5 AUTOOWNER 9999 AUTOPROTECT 10 AUTOVOICE 3 BADWORDS 10 BAN 4 FANTASIA 3 FOUNDER 10000 GETKEY 5 HALFOP 5 HALFOPME 4 INFO 9999 INVITE 5 KICK 4 MEMO 10 MODE 9999 NOKICK 1 OP 5 OPME 5 OWNER 10001 OWNERME 9999 PROTECT 9999 PROTECTME 10 SAY 5 SET 9999 SIGNKICK 9999 TOPIC 5 UNBAN 4 VOICE 4 VOICEME 3 
DATA banexpire 0
DATA memomax 0
DATA PEACE 1
DATA SECUREFOUNDER 1
DATA CS_SECURE 1
DATA SIGNKICK 1
DATA KEEPTOPIC 1
END

OBJECT ChanAccess
DATA provider access/xop
DATA ci #example
DATA mask s4lph
DATA creator s3lph
DATA last_seen 1608435256
DATA created 1602803962
DATA data HOP
END

Due to the similarities, I'll only highlight the main differences:

  • Objects are typed (since all are kept in the same file) and are terminated by an explicit END rather than by the start of the next object.
  • There is no single number containing a lot of flags; instead each flag is represented by its own line (e.g. HIDE_EMAIL, NS_SECURE, ...)
  • NickServ passwords are prefixed with the used hashing module. I chose to use "des" as the prefix for my enc_des module.
  • There are no more ChanServ passwords. Instead, users are automatically granted their permissions once they identify through NickServ.
  • Attributes that can occur multiple times in a Hybserv object are extracted into their own type. For example, channel ACCESS attributes are now represented by ChanAccess objects.
  • ChanAccess objects don't have a numeric access level, but a "data" attribute. This can be one of VOP (voice), HOP (halfop), SOP (chanop) or QOP (founder).

Textfile Database Conversion

With the information gathered above, the conversion from one format to another can be automated. My conversion script can be found on Gitlab. However, you should not use it without careful review as there may be some subtle differences between my setup and yours. Especially the numeric access levels will most likely need to be adapted on both the Hybserv as well as on the Anope side. The conversion can be done by following these steps:

  1. STOP ANOPE. Otherwise Anope will overwrite your changes the next time it flushes changes to disk.
  2. Find your anope.db file. When using the Debian package, it's located under /var/lib/anope/db/anope.db.
  3. MAKE A BACKUP of the anope.db file. The following two commands will append the converted data to anope.db. If something goes wrong, you may lose existing Anope registrations.
  4. ./hybserv2anope.py nickserv .../hybserv/nick.db anope.db
  5. ./hybserv2anope.py chanserv .../hybserv/chan.db anope.db
  6. REVIEW anope.db. Make sure that everything looks as expected.
  7. Start Anope and observe the log for errors.

Conclusion

The two migrations above (password hashes and database files) should allow you to migrate from Hybserv2 to Anope without requiring any action whatsoever from your users. If everything went fine, you should be able to log into NickServ and obtain ChanOp on your channels.

When you look into your anope.db, you will see that your DES password hash was replaced by a bcrypt hash (or whatever method you chose). If it's not visible immediately, you can either wait some time, /msg OperServ UPDATE or restart Anope so that all changes are immediately flushed to disk. Once there are no more DES hashes left, unload the enc_des module.