Goodbye ZNC, hello pounce.

2021-02-04

(last time edited: 2021-06-12)

tags: irc, communication, bouncer

Pounce

pounce is a multi-client, TLS-only IRC bouncer. It takes a simple approach, using a multiple-consumer ring buffer and the IRCv3.2 server-time extension to communicate with clients.

Looking around the Void Linux repositories I found out this little gem. It's a simple open-source IRC bouncer program written in C by June Bug, a programmer who's very into IRC communication. There are other bouncers around, including ZNC, which is the top notch bouncer all around. But I was also dissatisfied with the ZNC experience.

I want simpler things, I do not want all features that a bunch of programmers mentally masturbate of. I don't care about API, plugins that would break eventually when programming language interpreters get updated with a new version. I do not care about administration web panels and NGINX proxy pass to configure it. Fuck all that, it's stupid and the attack surface grows larger. Maybe I'm just too dumb to understand the complexity of big spaghetti code, maybe not. Maybe I don't wanna waste my time. Maybe I don't need all those features because I don't make profit by offering web services to Internet people. Maybe I just want to get things done.

Anyways. Pounce is good, pounce is love. Let's get the setup done.

First create a user in your personal server/workstation that will be running the bouncer connected 24/7.

# useradd pounce

or

# adduser pounce

Launch Pounce using a configuration file

It's better to run pounce via configuration files rather than using CLI arguments because we end with really long commands, but I will explain the basic usage for both ways.

local-host = irc.ourdomain.com # default value is localhost, meaning you'll access via IP, but you can choose your own domain
local-port = 6697 # default port for all TLS/SSL irc connections
local-priv = /path/to/letsencrypt/certs/privkey.key # preferably pointing to the letsencrypt certs
local-cert = /path/to/letsencrypt/certs/cert.crt # same as above
local-pass = $6$f246kLc4kvK0gqCy$fsFGbhEefxe..................... # get a hashed password by using pounce -x, you will be prompted to enter a plain text password

host = irc.freenode.net # server we are connecting to
port = 6697 # default port, there are servers who don't accept TLS/SSL connections and you'll have to use 6669
join = ##linux,#voidlinux,#ascii.town # any channel you want the bouncer to join, comma-separated values
nick = your_username
real = your_realname
away = something something not here
quit = leaving

We need to save this configuration file inside this directory ~/.config/pounce. Filename can be whatever you want. ~/.config/pounce/freenode.conf or ~/.config/pounce/myfirstbouncyboomboom.

Once we are done, run pounce as the pounce user.

$ pounce freenode.conf &

The bouncer will now be running in the background. How can we double check if everything is working correctly?

$ pgrep pounce

Now you can connect to your bouncer from your workstation by opening Irssi or a similar IRC client, for example WeeChat. Connect to irc.ourdomain.com using port 6697, TLS activated and the server password will be the unhashed password you entered before in the local-pass directive.

Using Irssi:

/connect -tls irc.ourdomain.com -w hellothisisthepassword

Optional: Launch Pounce using command-line arguments

Some sysadmins prefer to launch their programs via command-line with a bunch of arguments making it really cumbersome to understand at first glimpse, but this method is very useful for scripting situations. It really depends.

The same configuration I posted above can be achieved by running this command.

$ pounce -H irc.ourdomain.com -P 6697 -K /path/to/letsencrypt/privkey.pem -C /path/to/letsencrypt/fullchain.pem -h irc.freenode.net -p 6697 -j ##linux -u your_username

Replacing password authentication with self-signed TLS certificates

As I explained before, when we wanna login to our bouncer instance we input and send a plain-text password to the server, the server receives this password and compares it to the hashed string we obtained before. If the string matches, you get logged in.

Instead of entering a long ass password everytime we want to login to the bouncer, we can generate our client private key.

On your server side, in pounce user account, generate a private key. Warning! Do not share your privkey at all. (I'll explain it how to transfer it to your workstation later.). Optional for multi-users: You can create whatever quantity of clients you want.

$ pounce -g pounceprivkey.pem

Again on your server side, keep the public keys and save them in your ~/.config/pounce directory. Optional for multi-users: You can concatenate whatever quantity of generated public keys you want to into the pouncecert.pem file.

$ openssl x509 -in pounceprivkey.pem > ~/.config/pounce/pouncecert.pem

Now in your pounce configuration directory you can remove the local-pass directive and change it to local-ca.

Notice! Again remember! The local-ca key is a public key. You need this public key in the server otherwise you won't be able to connect.

# ...

local-ca = /home/pounce/.config/pounce/pouncecert.pem

# ...

or as alternative use the command-line argument -A to deploy your pounce.

$ pounce -H irc.ourdomain -P 6697 -K /path/to/letsencrypt/privkey.pem -C /path/to/letsencrypt/fullchain.pem -h irc.freenode.net -p 6697 -j ##linux -u your_username -A pounceprivkey.pem

Now we need to transfer to our workstation the private key pounceprivkey.pem we've made before; otherwise we won't be able to login.

From your workstation do this:

$ scp pounce@ourdomain.com:~/pounceprivkey.pem ~

Now it's very easy to login to our bouncer.

Using catgirl. This is an IRC client also made by June Bug:

$ catgirl -h freenode.yourdomain.com -c /home/your_username/pounceprivkey.pem

The same connection but this time on Irssi:

/connect -tls -tls_cert ~/pounceprivkey.pem irc.ourdomain.com

Identifying to IRC servers using CertFP (Certificate Fingerprints)

You can identify yourself to NickServ using a plain password, or you can opt using CertFP which is a more secure way to do.

First create a private key.

$ pounce -g ~/.config/pounce/freenode-username.pem

Then add this to your instance configuration.

client-cert = /home/pounce/.config/pounce/freenode-username.pem

or alternatively connecting with command line argument using -c /path/to/freenode-username.pem

Login with your password and identify to NickServ.

Then tell NickServ to add your freenode-username.pem certificate fingerprint.

In Irssi:

/query NickServ CERT ADD

You can list all added certificates:

/query NickServ CERT LIST

Launch multiple Pounce instances using Calico

The calico daemon dispatches incoming TLS connections to instances of pounce by Server Name Indication (SNI). Instances of pounce should be configured with -U to bind to UNIX-domain sockets in the directory passed to calico. Note that calico is not a proxy. Incoming connections are passed directly to instances of pounce, which handle TLS negotiation. Instances of pounce and calico can be restarted independently of each other.

This program is also written by June Bug in addition to pounce. It's an extension that helps us deploy multiple bouncers.

I prefer using subdomain wildcard (*.ourdomain.com) certificates for my domain. Since it's an experimental VPS I don't need to be so strict. This simplifies deployment a lot. I don't need to extend my certificates for specific subdomain names everytime I wanna add a bouncer or start a new service. I talk about more about that here.

First log in into your root account and create a new directory. This directory will act as a socket.

# mkdir -p /run/calico

# chown pounce /run/calico

Now run calico under the pounce account. It's not necessary to add a subdomain into the -H (host) argument. Calico will always be running in the background. You can then add and remove pounce instances.

$ calico -H ourdomain.com -P 6697 /run/calico &

Now, once again in the pounce account, if you have already set up multiple configurations launch them via commandline.

$ pounce -U /run/calico -H subdomain.domain.com /path/to/freenode.conf & pounce -U /run/calico -H subdomain.domain.com /path/to/rizon.conf &

Generating Let's Encrypt TLS/SSL certificates with Certbot

Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated websites to enable HTTPS. Certbot is made by the Electronic Frontier Foundation (EFF), a 501(c)3 nonprofit based in San Francisco, CA, that defends digital privacy, free speech, and innovation.

Creating certificates is not a big deal as it may seem. You can use Certbot plugins to deploy the certificates to specific web servers such as Apache or NGINX but we aren't gonna need that. Our bouncer doesn't need of any web server for anything. We just need the cert files. Create them manually with this command.

# certbot certonly --cert-name some_name -d irc.ourdomain.com

They will be placed inside /etc/letsencrypt/live/certs.

Now we encounter a new problem. Our pounce user won't be able to read the certificates. They were generated under root account and placed in root directories under root permissions.

One solution among many is to create a separate UNIX user group. This solves a situation where many non-root programs need to access the certificates, such as uMurmur.

# groupadd certs

or

# addgroup certs

Add the user pounce to the certs group.

# usermod -G certs -a pounce

or

# addgroup pounce certs

Change group recursively to every file and directory inside /etc/letsencrypt.

# chgrp -R certs /etc/letsencrypt

Modify permissions recursively so users inside certs group can only read and execute.

# chmod -R g=rx /etc/letsencrypt

Modify permissions so Others cannot read, write, nor execute. This is done for better security but it's not completely necessary. Just a little detail.

# chmod -R o-rwx /etc/letsencrypt

Don't forget to relog-in into your pounce account in order to refresh permissions.

Creating Runit services for Pounce and Calico (Void Linux specific)

Create a directory for the new calico runit service.

# mkdir /etc/sv/calico

Create a runit run file/script.

# touch /etc/sv/calico/run

Open the run file with a text editor.

# vim /etc/sv/calico/run

Copy and paste the following script. It checks if the temporal calico directory socket is created and then executes the calico daemon.

#!/bin/sh

mkdir -p /run/calico
chown pounce /run/calico

exec chpst -u pounce calico -H yourdomain.com -P 6697 /run/calico

Add executable permissions.

# chmod +x /etc/sv/calico/run

Start the service. Calico is independent of Pounce. Can be initiated before or after Pounce instances are running.

# sv force-restart /var/service/calico

Now create the pounce service for an specific IRC server. The procedure is similar.

First create a service directory.

# mkdir /etc/sv/pounce-freenode

Create a run runit file/script.

# touch /etc/sv/pounce-freenode/run

Open the file using a text editor.

# vim /etc/sv/pounce/run

Add the following text.

#!/bin/sh

sv start calico || exit 1
exec chpst -u pounce:certs pounce -U /run/calico -H subdomain.domain.com /home/pounce/.config/pounce/freenode.conf

Add executable permissions to the run file.

# chmod +x /etc/sv/pounce/run

Repeat the same for other bouncer instances.

Creating OpenRC services for Pounce and Calico (Alpine Linux specific)

For Calico initialization:

First create a service file.

# touch /etc/init.d/calico

Then add the following text. Don't forget to specify your domain, etc.

#!/sbin/openrc-run

command="/usr/bin/calico"
command_user="pounce"
command_args="-H yourdomain.com -P 6697 /run/calico"
command_background="true"
pidfile="/run/calico.pid"

start_pre() {
    mkdir -p /run/calico
    chown pounce /run/calico
}

The procedure for a Pounce service initialization is almost the same. OpenRC is just as easy as using Runit.

First create a service file.

# touch /etc/init.d/someserver-pounce

Then add the following text. Don't forget to change the subdomain and domain specific for your bouncer. Also the name of the .conf file for the bouncer and the pidfile name

#!/sbin/openrc-run

command="/usr/bin/pounce"
command_user="pounce:certs"
command_args="-U /run/calico -H subdomain.domain.com /home/pounce/.config/pounce/someserver.conf"
command_background="true"
pidfile="/run/someserver-pounce.pid"

depend() {
    need calico
}

Starting the service. Calico will be called once pounce starts.

# rc-service example-pounce start

Add the bouncers to the default runlevel.

# rc-update add one-bouncer-pounce default

# rc-update add second-bouncer-pounce default

There is no need to add Calico to the default runlevel. Calico will be up when it's needed by a bouncer.

If you need more help on how to create OpenRC services.

$ man openrc-run

Some tips

Some IRC servers won't let you connect via their subdomain. That is fine. Use their Certificate Common Name instead of using the Subject Alternative Name. For example: euirc.net instead of irc.euirc.net.

Pounce and Calico should always be run as non-root.

Pounce uses only TLS (Transport Layer Security) by default which is a modern and updated implementation of SSL (Secure Sockets Layer). That's why in the tech community they refer it most of the time as TLS/SSL. SSL is deprecated.

Pounce has lots of potential for automation. I haven't seen any running service for multiple users yet but I'm willing to test and create one just for fun.

Save your configuration files somewhere.

Bouncer password and NickServ identification password should be different from each other as a basic security measure.

Happy chatting!