$_ BSDHowTo.ch
How To... Why not..? Scripts Patches RSS logo

How to run IRC bouncer pounce?

Last update: 2024-04-28

Introduction

In this article I show you how you can run the IRC bouncer pounce(1) on a public reachable server. pounce is part of the IRC suite developed by June McEnroe. You may want to give the other pieces of the suite a try, especially the IRC client catgirl(1). In case you are looking for information about IRC - Internet Relay Chat - Wikipedia is a good starting point.

Requirements

In this piece I assume that you have the following:

Beside that you will need to install git on the system. One of the components - kitd(1) - is not available (yet) as package on OpenBSD.

Naming scheme and entries in DNS

The architecture of the software requires you to run a single pounce process per IRC network you want to connect to. Following this I will use a naming scheme for the instances in DNS. Let’s say your domain is example.net. In this article I create a subdomain irc.example.net. In this subdomain I create A and/or AAAA records for the IP address(es) the service will listen on. Assuming all pounce processes will run on the same host. Then I create a CNAME record for each network name of one pounce process that points to the subdomain name. In the zone file of example.net this looks like this:

irc         A       192.0.2.97
            AAAA    2001:db8::c0:0:2:61
libera.irc  CNAME   irc
oftc.irc    CNAME   irc

In this example I will run pounces twice: one to connect to libera.chat and one to connect to OFTC. Please make sure you publish the names in DNS first because later you will use acme-client(1) to obtain a certificate from Let’s Encrypt for these.

Software installation

Next you will want to install the required packages on your system. As mentioned in the introduction kitd(1) is not available as a package. So first install the ones that are available:

$ doas pkg_add pounce git

Next, clone the git repository of kitd(1) and install it the old way:

$ git clone https://git.causal.agency/kitd
$ cd kitd/
$ make
$ doas make install

By the way, pounce brings along calico(1) which we will use to dispatch incoming clients to the right pounce process depending on the hostname the client wishes to connect to. Unlike most packages pounce doesn’t create a user, group or rc.d script for itself. That is because there are several different ways to use pounce, and not all of them require these elements. This means you must do some of the work manually.

User account for pounce

The user account under which pounce processes will be run should be as restricted as possible. You don’t want to use that account for logins of any kind. First, you should create a login class for the account. Using the login class, you can tune certain parameters for the account if necessary. Create the file /etc/login.conf.d/pounce with the following content:

pounce:\
    :hushlogin:\
    :umask=027:\
    :tc=default:

Then you can create the account using the following command:

# useradd -c "IRC bouncer" -g =uid -k /var/empty -L pounce -m -s /sbin/nologin -u 6697 _pounce

Make sure any mails for this user get redirected to root (or any other account that receives mails):

# echo "_pounce:    root" >> /etc/mail/aliases
# newaliases

Some directories are required for pounce and calico, which must be owned by the new user:

# mkdir /var/run/calico
# chown _pounce:_pounce /var/run/calico
# chmod 0750 /var/run/calico
# mkdir -p /home/_pounce/.config/pounce
# mkdir -p /home/_pounce/.local/share/pounce

Server certificate for pounce

As pounce only supports connections over TLS you must obtain a valid certificate for the host names you intend to use. Instead of creating one certificate for each hostname (= IRC network) you can create one for the base domain irc.example.net and make all the host names appear as subject alternative names in it. I use acme-client(1) to obtain the certificate from Let’s Encrypt. For this add a new block to /etc/acme-client.conf:

domain irc.example.net {
    alternative names {
        libera.irc.example.net
        oftc.irc.example.net
    }
    domain key "/home/_pounce/.config/pounce/irc.example.net.key"
    domain full chain certificate "/home/_pounce/.config/pounce/irc.example.net.pem"
    sign with letsencrypt
}

I assume that you already have a working acme-client.conf(5) on your system. If not, you can use /etc/examples/acme-client.conf as starting point. In that case you should carefully read acme-client(1). Obtain the certificate for the first time, thereby verifying that your DNS entries are published:

# acme-client irc.example.net

If everything worked as expected, you must create a cronjob for root which takes care of renewing the certificate as well as informing pounce whenever a new certificate is available:

# crontab -e
~   *   *   *   *   acme-client irc.example.net && pkill -USR1 pounce

Make sure that pounce can read the private key of the certificate:

# chown _pounce /home/_pounce/.config/pounce/irc.example.net.key

Configuration of pounce

For each instance of pounce (= each IRC network to connect to) you will require a configuration file. The example here shows the config file to connect to libera.chat using the nick sample. Create the file /home/_pounce/.config/pounce/libera.conf with the following content:

client-cert = sample.pem
host = irc.libera.chat
join = #openbsd
local-ca = auth.pem
local-cert = irc.example.net.pem
local-host = libera.irc.example.net
local-path = /var/run/calico
local-priv = irc.example.net.key
nick = sample
sasl-external
save = libera.buffer

The file sample.pem is the user certificate for logging in as sample at libera.chat. Place this file in /home/_pounce/.config/pounce/ and make it owned by _pounce:

$ doas -u _pounce cp sample.pem /home/_pounce/.config/pounce/

The other file, /home/_pounce/.config/pounce/auth.pem, is created in the next chapter.

Client certificates for pounce

Your client will use CertFP to identify themselves to pounce. With the method shown here certificate management gets easy for a handful of clients. If you need more, you probably want to implement a proper CA. For each client you need to execute the following steps to create and activate a certificate for it:

$ pounce -g client.example.net.pem
$ openssl x509 -subject -in client.example.net.pem | doas -u _pounce tee -a /home/_pounce/.config/pounce/auth.pem > /dev/null

The file client.example.net.pem created above contains the private key, so you want to handle it with care. You want to move it from your server to the client system it belongs to as soon as possible.

Repeat the steps above for each client you want be able to connect to pounce. In case you want to / have to revoke a certificate just remove it from /home/_pounce/.config/pounce/auth.pem. Each certificate in the file can easily be identified because it is headed by a line containing its subject:

$ doas -u _pounce vi /home/_pounce/.config/pounce/auth.pem
subject= /CN=client
-----BEGIN CERTIFICATE-----
MIIEojCCAooCCQDS/rz9gvX5QTAN...
-----END CERTIFICATE-----

After modifying the file /home/_pounce/.config/pounce/auth.pem you must notify all running pounce instances about the changes. Simply send SIGUSR1 to the processes so each of them reloads the file:

$ doas -u _pounce pkill -USR1 pounce

Just to be sure you want to adjust the permissions on all the files in the home directory of _pounce:

$ doas -u _pounce chmod -R o-rwx /home/_pounce

Running pounce

Once the connection to the IRC server closes pounce exits. It will not attempt to reestablish the connection itself. That is why you need kitd. It will restart each pounce process if necessary, applying timeouts between restarts if these happen too often. You need to create each instance of pounce with an individual name. I prefer to use the IRC network name the instance connects to, making it easier to distinguish the instances. Use the following commands to create an instance of pounce, e.g. for libera.chat:

# ln -s kitd /etc/rc.d/pounce_libera
# rcctl enable pounce_libera
# rcctl set pounce_libera user _pounce
# rcctl set pounce_libera flags pounce libera.conf
# rcctl start pounce_libera

One you’ve setup all the instances for all the IRC networks you want there is just calico left. All the instances of pounce are listening on sockets in /var/run/calico. Now we need one instance of calico which will dispatch incoming connections on port 6697/tcp to the right instance of pounce:

# ln -s kitd /etc/rc.d/calico
# rcctl enable calico
# rcctl set calico user _pounce
# rcctl set calico flags calico -H irc.example.net /var/run/calico
# rcctl start calico

Open the firewall

So far nobody can connect to your bouncer because pf(4) is blocking incomming connections, right? Make sure port 6697/tcp is reachable from the outside. Add the following line to /etc/pf.conf:

pass in on egress proto tcp from any to egress port 6697 keep state

The rule above misses any rate limiting for the connections to the port. That is because rate limiting values can bite if your clients sit behind the same NAT device.

Make sure your new rule is active:

# pfctl -f /etc/pf.conf

Configuration of a client

This chapter shows an example confiugraiton for the IRC client catgirl. First move the client certificate you’ve created on the pounce server to ~/.config/catgirl:

$ mv client.example.net.pem .config/catgirl/

Next create a config file for each of the pounce instances you want to connect to. Like pounce catgirl only ever connects to one host. Use a terminal multiplexer like tmux(1) if you want to connect to more than one server at the same time. The content of the config file ~/.config/catgirl/libera.conf should look like this:

cert = client.example.net.pem
host = libera.irc.example.net
join = #openbsd
nick = sample
sasl-external
user = client

Please note that the username in user = is used by pounce to identify which client is connecting to it. Make sure you provide an individual username to each client because pounce keeps track of the position in the buffer said username.