xBNC — The Next Generation of IRC Bouncers Wiki v1.0.0

xBNC 1.0.0

Your IRC, always connected.

A modern IRC bouncer for Undernet, Libera.Chat, Rizon, EvilNet, IRCnet and any other network. This wiki explains everything it does and how to use it, from installation to the API.

  • 24 modules
  • 18 plugins
  • 85 API routes
  • 63 *status commands
HexChat — undernet
Connecting to bnc.example.org:6697 (TLS)…
*statusWelcome to xBNC 1.0.0 Made by y2k. Network: undernet. Last seen: 9 hours ago.
*statusWhile you were away: 3 private messages (bob, carol) and 2 mentions in #xbnc.
bob[02:14] y2k, what time is the meeting tomorrow?
carol[07:52] good morning
y2k/msg *status version
*statusxBNC 1.0.0 Made by y2k

What xBNC is

xBNC is an IRC bouncer: a program that stays connected to your IRC networks around the clock, even when you close your client or switch off your computer. When you come back, it gives you what you missed, exactly once on each device.

You install it on a server of your own (a VPS, a Raspberry Pi, a computer at home), use it with your usual IRC client and configure everything from a web panel, or from IRC with /msg *status. It is a single program written in Rust, with the panel built in.

24native modules, configured per network
18official WebAssembly plugins
2+languages: English and Spanish built in, and any other with a translation file

What it includes

  • Bouncer: permanent connections to several networks per user, with verified TLS, SASL PLAIN and EXTERNAL, automatic reconnection with growing delays and flood protection towards the server.
  • Several devices at once: what you write on one appears on the others, and each one keeps its own account of what it has read.
  • History: channels and private messages with full-text search, filters, TXT and JSON export, general and per-channel retention, and per-user quotas.
  • Full IRCv3 with servers and clients (complete list); classic clients see plain old IRC.
  • Web chat in the panel, a mentions view and read markers shared between devices.
  • Notifications of mentions and private messages by Telegram, Discord, e-mail, a web address of your own or browser and phone push notifications.
  • 24 modules (services login, automatic away, auto op, FiSH encryption, detached channels, scheduled tasks…) and 18 plugins already installed (trivia, polls, reminders, weather…).
  • Multi-user: five roles, quotas, suspension, audit log, two-step verification, passkeys, OpenID Connect and LDAP.
  • Data: SQLite or PostgreSQL, active/standby high availability, backups from the panel or the command line, migration from another bouncer.
  • Administration: web panel, REST API with tokens, WebSocket, Prometheus metrics with a Grafana dashboard, alerts in the panel, over IRC and by webhook.
  • Security: Argon2id, encrypted credentials, brute-force blocking, CSRF, CSP, an unprivileged service, sandboxed plugins and continuous fuzzing.

How it works

How xBNC works Your devices connect to xBNC; xBNC keeps a permanent connection to each IRC network and stores the history. Your devices Laptop · HexChat Phone · Goguma Browser · panel off: nothing is lost xBNC always connected history per device modules and plugins web panel and API IRC networks Undernet Libera.Chat Rizon IRCnet… TLS · user/network@device TLS · SASL · services

Your IRC client no longer connects to the network but to xBNC. xBNC keeps one connection per network that is not cut when you close the client, and presents itself to each device as if it were that network's server.

  1. You connect with the server password user/network@device:password. The device name (@laptop, @phone) is optional, but it lets each device keep its own account of what it has read.
  2. xBNC brings you up to date: it joins your channels for you, sends the topic and the user list, and plays back the messages that arrived since your last connection on that device, without repeating what you already saw.
  3. While you are away, xBNC stays in the channels, stores the history, answers CTCP, marks you as away and runs your modules and plugins.
  4. All your devices stay in sync: what you write on the phone appears on the laptop, and read markers are shared.

What you get when you connect

  1. xBNC's welcome with the parameters (ISUPPORT) of the real network.
  2. The network's MOTD (unless you turn on Hide the MOTD), followed by xBNC's name and version.
  3. The channels you are in, with their topic and user list.
  4. The messages this device has not seen. If the client does not support server-time, each message starts with the date and time in brackets.
  5. If you missed private messages or mentions, a summary from *status (module Automatic away).
  6. If the network is disconnected, a notice from *status with the reason.
  7. This server's message of the day, if a superadministrator has set one (/msg *status setmotd).

History without duplicates

  • Each device (the @name of the login, or default if you give none) has a cursor per network: the last message delivered to it. Two connections with the same name share the cursor.
  • When it connects it receives the messages after its cursor, up to playback_max (1000 by default). A new device receives the most recent new_client_playback messages (100 by default).
  • Cursors are saved on disconnection and every 5 seconds, and never go backwards. If the process stops abruptly, at most a few seconds are repeated.
  • Clients with draft/chathistory get no automatic playback: they ask for what they need with CHATHISTORY (up to chathistory_max, 500 by default).

The connection to each network

  • States: disconnected, registering and connected. During registration the IRCv3 capabilities, SASL and the nick (with an alternative if it is taken) are negotiated.
  • Maximum registration time: 60 seconds. If the server is silent for 90 seconds it is sent a PING, and after 180 the connection is considered lost.
  • Automatic reconnection with delays growing from 5 seconds to 5 minutes, with a random ±20 % variation so that thousands of networks do not come back in the same second. If the network says you are reconnecting too fast, xBNC waits at least 2 minutes.
  • Output to the server goes through a rate limiter (a burst of 9 lines, then 2 per second) so the network does not throw you out for flooding.
  • The server's TLS certificate is always verified against the system's root certificates. For a network with its own or an expired certificate you can trust that particular certificate by its SHA-256 fingerprint (/msg *status addfingerprint); there is no option to turn verification off.
  • Each network can have backup servers (/msg *status addserver). If connecting to a server fails, xBNC tries the next one a second later; after going through the whole list it goes back to the growing delays. If a server that was connected drops the connection, xBNC first goes back to that same server.
  • The local address used to connect (vhost) can be set per network or per user (/msg *status setbindhost, administrators only).
  • If a line is not UTF-8 it is read as Windows-1252, so accented letters from old clients show correctly.

Virtual users

xBNC adds “users” whose names start with *. They do not exist on the network: they are the way to talk to xBNC from any client. *status, *notes, *watch, *crypt, *ignore and one per plugin (*trivia, *remind…). See IRC commands.

Installation

Requirements

  • Linux with systemd: Debian 12, Ubuntu 22.04 or later, Fedora, Rocky or Alma.
  • Rust 1.89 or later (rustup.rs) and a C compiler: sudo apt install build-essential or sudo dnf install gcc.
  • 1 CPU and 512 MB of RAM for personal use. Measured: 2,000 users with their networks and clients in about 150 MB (see Performance).
  • Ports: 6697 (IRC with TLS), 6667 (IRC without TLS, local network only), 8443 (panel). With Let's Encrypt, also 80; with Nginx, 443.

The web panel comes already built inside the source code: Node.js and npm are not needed.

Install

# 1. Install Rust (once, as a normal user)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 2. Build xBNC (as a normal user)
unzip xbnc-1.0.0.zip && cd xbnc
cargo build --release

# 3. Install the service
sudo ./scripts/install.sh

Run in a terminal without options, the installer asks whether you want to use a domain, the e-mail address for Let's Encrypt and whether you want the panel at https://yourdomain with Nginx. At the end it shows the panel address and the one-time code to create your account:

  Panel:   https://bnc.example.org:8443
  IRC:     bnc.example.org, port 6697 (SSL)
  Code to create your account:  K7QM-2XPA-9DWE

Open the panel, enter the code and create the first administrator (it will be a superadministrator). To see the address and ports again: sudo xbncd info; to see the code again: sudo xbncd setup-code.

What the installer does

  1. Checks the system, the built binary and, if you use a domain, that its DNS points to this machine and which ports you must open on your router.
  2. Creates the unprivileged system user xbnc and the folders with their permissions.
  3. Installs the binary in /usr/local/bin/xbncd and the systemd service with isolation measures (ProtectSystem=strict, NoNewPrivileges, no capabilities, a system call filter).
  4. Creates the certificate: self-signed for the IP (or the domain), or from Let's Encrypt with automatic renewal. Renewal reloads xBNC (and Nginx, if used).
  5. Writes /etc/xbnc/xbnc.toml with the version in its header, and creates the database and the secret key.
  6. With --nginx, installs and configures Nginx: redirection from port 80, the panel on 443, WebSockets and uploads of plugins and backups.
  7. Opens the ports in ufw or firewalld and starts the service.

Installer options

OptionWhat it does
--domain NAMEUse a domain (bnc.example.org) instead of the IP.
--letsencrypt EMAILWith --domain: a free Let's Encrypt certificate with automatic renewal. Needs port 80 reachable from the internet.
--nginxWith --domain and --letsencrypt: installs and configures Nginx to serve the panel at https://DOMAIN (port 443).
--ip ADDRESSIP to use if the one detected is not right (several interfaces or a VPN).
--language es|enLanguage of the installer and default language of xBNC.
--behind-proxyPanel on 127.0.0.1:8080, to publish it with your own proxy.
--web-port NPanel port (8443 by default).
--irc-port NIRC port with SSL (6697 by default).
--plain-port N|offIRC port without SSL, local network only (6667 by default); off removes it.
--no-firewallDo not open ports in ufw or firewalld.
--reconfigureRedo the configuration and the certificate of an existing installation (keeps a copy of the previous one). Does not touch users, networks or history.
--binary PATHBinary to install (target/release/xbncd by default).
--no-startInstall without starting the service.
--non-interactiveAsk nothing (by default it asks when run in a terminal).

Examples

# By IP, with a self-signed certificate
sudo ./scripts/install.sh

# With a domain and Let's Encrypt, panel at https://bnc.example.org:8443
sudo ./scripts/install.sh --domain bnc.example.org --letsencrypt you@example.org

# The same, with the panel at https://bnc.example.org behind Nginx
sudo ./scripts/install.sh --domain bnc.example.org --letsencrypt you@example.org --nginx

# Move an existing installation to a domain
sudo ./scripts/install.sh --reconfigure --domain bnc.example.org --letsencrypt you@example.org

# Change the detected IP
sudo ./scripts/install.sh --reconfigure --ip 192.168.1.50

Where everything goes

PathContents
/usr/local/bin/xbncdThe program.
/etc/xbnc/xbnc.tomlProcess configuration (root:xbnc, 0640).
/etc/xbnc/tls/TLS certificate: fullchain.pem and privkey.pem (root:xbnc, 0750).
/etc/xbnc/locales/Extra languages and translation corrections (see Translating xBNC).
/var/lib/xbnc/Database and secret key (xbnc:xbnc, 0700). Back up secret.key: without it the stored passwords cannot be decrypted.
/var/lib/xbnc/logs/Log files of the log module (folders 0700, files 0600).
/var/lib/xbnc/plugins/Installed plugins (0600). In dev/, those of development mode.
/var/lib/xbnc/backups/Backups made from the panel.

The service runs as the unprivileged user xbnc. sudo systemctl status xbnc, sudo systemctl restart xbnc; sudo systemctl reload xbnc makes it re-read networks, channels and users changed from the command line, and the language files. The log: sudo journalctl -u xbnc -f.

Using it away from home

  • Forward port 6697 on your router to the xBNC machine and connect to your public IP or to a dynamic DNS name (DuckDNS and the like). Port 6667 need not be opened: xBNC refuses it from outside.
  • If you do not want to open the panel (8443) to the internet, manage it through an SSH tunnel: ssh -L 8443:127.0.0.1:8443 user@your-public-ip and open https://localhost:8443.
  • With a domain pointing to your home, reinstall with --reconfigure --domain … --letsencrypt … to get rid of the certificate warnings.

Panel behind your own Nginx

If you already run Nginx and prefer to configure it yourself, install with --domain bnc.example.org --behind-proxy and use the included example:

sudo apt install -y nginx
echo 'map $http_upgrade $connection_upgrade { default upgrade; "" close; }' | sudo tee /etc/nginx/conf.d/upgrade.conf
sudo cp packaging/nginx/xbnc.conf /etc/nginx/sites-available/xbnc   # change server_name and certificates
sudo ln -s ../sites-available/xbnc /etc/nginx/sites-enabled/xbnc
sudo nginx -t && sudo systemctl reload nginx

Contents of packaging/nginx/xbnc.conf:

# Nginx reverse proxy for the xBNC web panel.
# Copy to /etc/nginx/sites-available/xbnc, adjust server_name and the
# certificate paths, then: ln -s ../sites-available/xbnc /etc/nginx/sites-enabled/
# In xbnc.toml: [web] address = "127.0.0.1:8080", public_url = "https://bnc.example.org",
# secure_cookies = true, trust_proxy = true.

server {
    listen 80;
    listen [::]:80;
    server_name bnc.example.org;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name bnc.example.org;

    ssl_certificate     /etc/letsencrypt/live/bnc.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/bnc.example.org/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Plugins are up to 4 MB.
    client_max_body_size 8m;

    # Restoring a backup uploads the whole archive.
    location = /api/v1/backups/restore {
        client_max_body_size 4g;
        proxy_request_buffering off;
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSockets: live dashboard, web chat, plugin log
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
    }
}

# Needed once per Nginx configuration (e.g. in /etc/nginx/conf.d/upgrade.conf):
# map $http_upgrade $connection_upgrade { default upgrade; '' close; }

In xbnc.toml, with the panel behind Nginx: address = "127.0.0.1:8080", public_url = "https://bnc.example.org", secure_cookies = true and trust_proxy = true.

Certificate from another authority

Copy your certificate and key to /etc/xbnc/tls/fullchain.pem and /etc/xbnc/tls/privkey.pem (owner root:xbnc, the key with permissions 0640) and restart with sudo systemctl restart xbnc. The certificate is read at start; reload is not enough.

Docker

mkdir -p tls
cp /path/fullchain.pem /path/privkey.pem tls/
# Edit XBNC_HOSTNAME and XBNC_PUBLIC_URL in docker-compose.yml
docker compose up -d --build
docker compose logs xbnc | grep "setup code"

Open https://bnc.example.org:8443/, enter the code and create your account. The command line: docker compose exec xbnc xbncd user add admin --admin. The data lives in the xbnc-data volume. The container runs as the unprivileged user xbnc (uid 10001), with a read-only file system, no capabilities and no-new-privileges.

VariableUse
XBNC_HOSTNAMEServer name (only on the first start).
XBNC_TLS_CERT, XBNC_TLS_KEYPaths of the certificate and key inside the container; they turn on TLS on 6697 (IRC) and 8443 (panel).
XBNC_INSECURE=1Plain text on 6667 (IRC) and 8080 (panel), for testing only.
XBNC_PUBLIC_URLPublic URL of the panel.
XBNC_WEB=0Turns the panel off.
XBNC_LOCALES_DIRFolder of extra languages (see Translating xBNC).
RUST_LOGLog level, for example info or debug.

Updating

unzip xbnc-new.zip && cd xbnc     # or git pull
cargo build --release
sudo ./scripts/update.sh

update.sh checks the new binary and the configuration, makes a backup, stops the service, swaps the binary and starts it. If it does not start, it goes back to the previous binary and to the database from before the update (a new binary may have upgraded the schema, and an old one refuses to open a newer schema). Nothing is ever updated on its own. With --binary PATH it installs another binary.

If you also want to redo the configuration (for example, to move to a domain), run sudo ./scripts/install.sh --reconfigure afterwards.

Uninstalling

sudo ./scripts/uninstall.sh            # removes the service and the binary; keeps /etc/xbnc and /var/lib/xbnc
sudo ./scripts/uninstall.sh --purge    # also deletes the configuration, the data and the xbnc user

First steps

  1. Create your account in the panel with the one-time code. The first account is a superadministrator.
  2. Add a network in Networks → Add network. The wizard has four steps: choose the network (the well-known ones come ready), your nick and name, the identification (SASL, X, Q…) and the channels.
  3. Connect your client. The network page has the card “Connect your IRC client” with your exact details and the steps for HexChat, mIRC, WeeChat and irssi, with buttons to copy them.
  4. Turn on modules and plugins in Modules. Choose the network at the top; with many networks, “Copy to other networks” repeats the configuration.
  5. Choose your time zone and language in Your account: the time zone is used in the log files and in scheduled tasks.
  6. Turn on two-step verification and, if you like, a passkey, in Your account.

Networks ready in the wizard

NetworkServerPortTLSIdentificationDescription
Undernetirc.undernet.org6667noX with Login on ConnectClassic network with all kinds of channels
Libera.Chatirc.libera.chat6697yesSASLFree software communities and open source projects
freenodeirc.freenode.net6697yesSASLHistoric network of projects and communities
Rizonirc.rizon.net6697yesSASL or NickServAnime, games and all kinds of communities
EvilNetirc.evilnet.org6697yesAuthServ (X3)Network with X3 services
IRCnetopen.ircnet.net6697yesno NickServHistoric network, very popular in Europe
OFTCirc.oftc.net6697yesNickServDebian, Tor, the Linux kernel and other projects
EFnetirc.efnet.org6697yesno servicesOne of the oldest networks
QuakeNetirc.quakenet.org6667noQGames and European communities
DALnetirc.dal.net6697yesSASLSocial and support communities
hackintirc.hackint.org6697yesSASLHackerspaces and technical communities

The same from the command line

sudo -u xbnc xbncd user add admin --admin
sudo -u xbnc xbncd network add admin libera --host irc.libera.chat --port 6697 --sasl-user myaccount
sudo -u xbnc xbncd channel add admin libera '#xbnc'
sudo systemctl reload xbnc

The SASL password is asked for at that moment (or read from XBNC_SASL_PASSWORD) and stored encrypted.

Connecting your IRC client

Any client that lets you set the server, port and server password works. No plugin is needed.

What you wantServer password
A user with a single networkalice:password
Choose the networkalice/undernet:password
Choose the network and name the devicealice/undernet@laptop:password
The same login in another orderalice@laptop/undernet:password
  • Server: your domain or IP (sudo xbncd info shows it).
  • Port: 6697 with SSL from anywhere; 6667 without SSL only from your local network.
  • Password: the same one you use for the panel. You can also put alice/undernet@laptop in the user field (USER) and the password alone in PASS, or sign in with SASL PLAIN from the client.
  • One connection is one network: for two networks, create two servers in your client (alice/undernet and alice/libera). Clients with soju.im/bouncer-networks (Goguma, gamja, Halloy) see all your networks in a single connection.
  • Device: the @device part is optional but recommended. Each device (@laptop, @phone, @work) gets its own pending history.
  • If you installed by IP, the certificate is self-signed: turn on “accept invalid certificates” in your client.
Network List → Add: "xBNC Undernet"
Edit → Server: bnc.example.org/6697
Tick "Use SSL for all the servers on this network"
Login method: Server password (/PASS password)
Password: alice/undernet@hexchat:your-password
With a self-signed certificate: tick "Accept invalid SSL certificates"

If you get the port or SSL wrong

xBNC spots the usual mistakes and tells you in the client itself:

What you see in the clientWhat is happeningWhat to do
Closing link: this port uses SSL/TLS. Enable SSL in your IRC client, or connect to port 6667 without SSL.You connect to 6697 without SSL.Turn on SSL or use 6667 from home.
Closing link: this port only accepts connections from the local network...You use 6667 from outside your network.Use 6697 with SSL.
Certificate error or “invalid certificate”Self-signed certificate (installation by IP).Accept the certificate or move to a domain with Let's Encrypt.
464 Password incorrectWrong user, network or password.Check user/network@device:password.
Choose a network by logging in as user/<network>You have several networks and did not say which.Add /network to the user.
ERROR :Too many failed login attemptsYour IP is blocked after failed attempts.Wait 15 minutes or ask an administrator to unblock it in Security.

When the client connects with SSL to a port without SSL it cannot show any message: the service log says so (client sent TLS to a plain-text port).

Networks without SASL (Undernet, QuakeNet)

Some networks do not use SASL: you log in with a services bot, X on Undernet or Q on QuakeNet. xBNC does it for you every time it connects. Turn on the Services login module (in the wizard, the Account step) with your account and password. If you prefer to do it by hand, write the commands in Settings → Commands on connect, one per line, as in your client:

/mode $nick +x
/msg x@channels.undernet.org login YOUR_ACCOUNT YOUR_PASSWORD

They are sent before joining channels, so your IP is already hidden (+x) when you join, and they are stored encrypted.

Client compatibility

The automatic tests use a standard IRC client, a client with every IRCv3 capability and a classic client without any. HexChat, mIRC, WeeChat and irssi have been used by hand with xBNC; the other clients in the list follow the same standards but have not been checked one by one. If something does not work with a particular client, report it with the client's name and version (see Testing the beta).

Networks

Settings of a network

In the panel, Networks → (your network) → Configuration. Also with the API (PATCH /networks/{id}) or the command line.

SettingWhat it is
Short nameThe one you use to log in (alice/undernet). Changing it does not cut the connection.
Server and portAddress of the network. Port 6697 with TLS by default.
TLSEncrypted connection to the network. The server's certificate is always verified.
NickYour nick on the network. If it is taken when connecting, xBNC adds “_” and, with Keep nick, goes back to it as soon as it is free.
User (ident) and real nameThe USER fields. By default, the nick.
Server passwordSent with PASS when connecting. Stored encrypted and never shown again.
SASL account and passwordSASL PLAIN identification. For SASL EXTERNAL, use a client certificate.
EnabledWhen off, xBNC does not connect to that network.
Commands on connectLines sent after connecting and before joining the channels (see below).
ChannelsSaved channels, with their key (encrypted). A /join or /part from the client is saved on its own.
Retention per channelHow long a channel's history is kept: the server setting, nothing, forever or N days.
Backup serversOther servers of the same network that xBNC tries if the current one fails. With /msg *status addserver (see IRC commands).
Trusted certificatesSHA-256 fingerprints of server certificates accepted even if they do not validate (self-signed or expired). With /msg *status addfingerprint.
Local address (vhost)The IP of this server used to connect to the network. With /msg *status setbindhost (administrators only).
Disabled channels and orderA disabled channel stays saved but is not joined; the order is the joining order when connecting. With /msg *status disablechan and movechan.

Commands on connect

They are written as in your client, one per line (up to 20, of 510 characters each), and stored encrypted because they often contain passwords. $nick is replaced by your current nick. Allowed: /msg (or /privmsg, /query), /notice, /quote (or /raw) and any IRC command with a slash (/mode, /away…). A line without a slash is sent as it is.

/mode $nick +x
/msg x@channels.undernet.org login YOUR_USER YOUR_PASSWORD
AWAY :Connected from xBNC

To identify with X, NickServ, Q or AuthServ the Services login module is better: it waits for the network to confirm the login before joining the channels, and on Undernet it uses Login on Connect.

Undernet

Undernet uses the X service and has neither SASL nor TLS. Turn on Services login with your X account: with Login on Connect the account travels in the server password (+x! account password), so your real IP is never seen and you join the channels already as account.users.undernet.org.

*** Your host is now y2k.users.undernet.org
* y2k has joined #xbnc
*statusxBNC 1.0.0 Made by y2k
  • With Invite and unban, if you cannot join one of your channels, xBNC asks X for INVITE or UNBAN.
  • Ignore uses SILENCE: messages from the people you ignore do not even reach xBNC.
  • Nicks have at most 12 characters; the away nick is shortened automatically.
  • Many Spanish-speaking channels use Latin-1: xBNC always understands it, and with Character encoding you can write to them in Latin-1.
  • The connection to Undernet is unencrypted because the network offers no TLS; the X password travels in clear text to the server, as with any client.

Libera.Chat and freenode

Identification by SASL: put your account and password in the network configuration. On Libera you can also use a certificate (Client certificate) with SASL EXTERNAL: generate the certificate in the panel, connect identified, type /msg NickServ CERT ADD and reconnect. If your nick is taken when connecting, Services login with “Recover my nick” sends REGAIN (Libera) or GHOST (freenode).

Rizon

NickServ (Anope), by SASL or with Services login. ChanServ handles invitations and unbans.

EvilNet

X3 services: you identify with AuthServ (AUTH account password) and mode +x hides your host (numeric 396). Choose AuthServ in Services login, or “Automatic”.

QuakeNet

The Q service (AUTH to Q@CServe.quakenet.org) and +x. Services login does it for you; invite and unban use INVITE and UNBANME.

IRCnet and EFnet

They have no NickServ: anyone could take that nick. That is why, in “Automatic”, xBNC never sends your services password on these networks and says so in *status and in the log. IRCnet offers optional SASL with accounts from sasl.ircnet.com. To keep op without services, use Get op back when left alone.

OFTC and other networks with NickServ

Services login with NickServ, or commands on connect. For the client certificate, OFTC uses the SHA-1 fingerprint and Libera the SHA-512 one: the panel shows all three fingerprints (SHA-1, SHA-256 and SHA-512).

IRC commands

You type them in your client as /msg *service command. The answers come in your language.

*status

It is the main service: everything you can do with a network in the panel can also be done here. /msg *status help lists the commands grouped and with their names in bold, only those your account may use; /msg *status help <command> explains one with an example, and /msg *status help <word> searches (for example help channel). Names are not case-sensitive, and the names used by other bouncers are accepted too (ListChans, ListMods, AddTrustedServerFingerprint…), listed under each command as “Also accepted”.

*statusxBNC *status commands
*statusWrite them as /msg *status <command>. Details and an example: help <command>.
*statusChannels
*status channels Your channels: users, modes and state
*status nicks <#channel> Everyone in a channel, with their rank
*status topics The topic of every channel

Unless stated otherwise, any account may use a command. The others say who may: accounts allowed to make changes (every role except “limited”), administrators (administrator and superadministrator) or superadministrators. Administrative actions are recorded in the audit log.

General

CommandWhat it does
help [command]This list, or the details of one command. Without a command, lists every command you can use. With one, shows how it works and an example. You can also search: help chan.
Example: /msg *status help addserver
versionxBNC version.
Example: /msg *status version
uptimeHow long xBNC and this network have been running.
Example: /msg *status uptime
language <code>Your language, also for the web panel. Changes the language of xBNC's messages, the plugins and the web panel at once. Without a code it lists the installed languages.
Example: /msg *status language en
seenWhen you were last connected.
Example: /msg *status seen
showmotdThis server's message of the day.
Example: /msg *status showmotd
clients [user]Devices connected to this network. Administrators can name a user to see all of theirs.
Example: /msg *status clients
Also accepted: listclients

Networks and connection

CommandWhat it does
statusState of this network: server, TLS, nick, capabilities.
Example: /msg *status status
networksYour networks and their state.
Example: /msg *status networks
Also accepted: listnetworks
addnetwork <name> <server> [[+]port]Add a network to your account. A + before the port means TLS. Without a port: 6697 with TLS. Your nick is the one of this network. Then connect with user/name.
Example: /msg *status addnetwork libera irc.libera.chat +6697
delnetwork <name> confirmDelete one of your networks and its history. Asks for confirmation: repeat the command adding "confirm". It cannot be undone.
Example: /msg *status delnetwork test confirm
setnick <nick>Change the nick saved for this network.
Example: /msg *status setnick y2k
Only accounts allowed to make changes (not the limited role).
connectConnect to the network again.
Example: /msg *status connect
Only accounts allowed to make changes (not the limited role).
disconnectDisconnect and stay disconnected. xBNC stays disconnected from this network until you use connect.
Example: /msg *status disconnect
Only accounts allowed to make changes (not the limited role).
jump [server]Reconnect, to the next server or to the one you name. Without a server, goes to the next one of the list (servers). With one, connects to it.
Example: /msg *status jump irc2.undernet.org
Only accounts allowed to make changes (not the limited role).

Servers, certificates and addresses

CommandWhat it does
serversServers of this network (main and backups). If a server fails, xBNC tries the next one in turn.
Example: /msg *status servers
Also accepted: listservers
addserver <server> [[+]port] [password]Add a backup server. A + before the port means TLS. Without a port: the same as the main server. Up to 20.
Example: /msg *status addserver irc2.undernet.org 6667
Only accounts allowed to make changes (not the limited role).
delserver <server> [port]Remove a server. If you remove the main server, the first backup becomes the main one.
Example: /msg *status delserver irc2.undernet.org
Only accounts allowed to make changes (not the limited role).
addfingerprint <SHA-256>Trust a server certificate by its fingerprint. For servers with a self-signed or expired certificate. 64 hex digits, with or without colons. Applies on the next connection.
Example: /msg *status addfingerprint 3F:9A:...:C2:1E
Also accepted: addtrustedserverfingerprint
Only accounts allowed to make changes (not the limited role).
delfingerprint <SHA-256>Stop trusting a certificate.
Example: /msg *status delfingerprint 3F:9A:...:C2:1E
Also accepted: deltrustedserverfingerprint
Only accounts allowed to make changes (not the limited role).
fingerprintsTrusted certificates of this network.
Example: /msg *status fingerprints
Also accepted: listtrustedserverfingerprints
showbindhostLocal address used to connect.
Example: /msg *status showbindhost
setbindhost <IP>Local address for this network (vhost). One of this server's IP addresses. Applies on the next connection (jump).
Example: /msg *status setbindhost 203.0.113.25
Administrators only.
clearbindhostUse the automatic address for this network.
Example: /msg *status clearbindhost
Administrators only.
setuserbindhost <IP>Default local address for all your networks. A network's own address (setbindhost) takes priority.
Example: /msg *status setuserbindhost 2001:db8::25
Administrators only.
clearuserbindhostRemove your default local address.
Example: /msg *status clearuserbindhost
Administrators only.

Channels

CommandWhat it does
channelsYour channels: users, modes and state.
Example: /msg *status channels
Also accepted: listchans
nicks <#channel>Everyone in a channel, with their rank.
Example: /msg *status nicks #xbnc
Also accepted: listnicks
topicsThe topic of every channel.
Example: /msg *status topics
enablechan <#channels>Join saved channels that were disabled. Accepts several channels and the wildcards * and ?.
Example: /msg *status enablechan #xbnc #xbnc-dev
Only accounts allowed to make changes (not the limited role).
disablechan <#channels>Leave channels but keep them saved. They are not joined again until enablechan. Accepts * and ?.
Example: /msg *status disablechan #xbnc*
Only accounts allowed to make changes (not the limited role).
attach <#channels>Show detached channels again.
Example: /msg *status attach #xbnc
detach <#channels>Hide channels from your client (they keep recording). A mention brings the channel back. Accepts * and ?.
Example: /msg *status detach #xbnc
Only accounts allowed to make changes (not the limited role).
movechan <#channel> <position>Change a channel's place in the order. The order is the one in which xBNC joins and lists your channels.
Example: /msg *status movechan #xbnc 1
Only accounts allowed to make changes (not the limited role).
swapchans <#channel1> <#channel2>Swap the places of two channels.
Example: /msg *status swapchans #xbnc #xbnc-dev
Only accounts allowed to make changes (not the limited role).

History (buffers)

CommandWhat it does
playbuffer <#channel|nick> [lines]Play back the history of a conversation. Sends you the last lines (50 by default, up to 1000).
Example: /msg *status playbuffer #xbnc 100
clearbuffer <#channel|nick>Delete the history of a conversation. Accepts * and ?.
Example: /msg *status clearbuffer bob
Also accepted: clearhistory
clearallbuffersDelete all the history of this network.
Example: /msg *status clearallbuffers
clearallchannelbuffersDelete the history of every channel.
Example: /msg *status clearallchannelbuffers
clearallquerybuffersDelete the history of every private conversation.
Example: /msg *status clearallquerybuffers
setbuffer <#channel> <days|forever|none|default>How long a channel's history is kept. forever: never deleted; none: not stored; default: the server setting; or a number of days.
Example: /msg *status setbuffer #xbnc 90
Only accounts allowed to make changes (not the limited role).

Modules and plugins

CommandWhat it does
modulesModules and plugins of this network and their state.
Example: /msg *status modules
Also accepted: listmods
availmodsEvery module and plugin, with what it does.
Example: /msg *status availmods
Also accepted: listavailmods
enable <module|plugin>Turn on a module or plugin on this network.
Example: /msg *status enable away
Also accepted: loadmod
Only accounts allowed to make changes (not the limited role).
disable <module|plugin>Turn it off.
Example: /msg *status disable away
Also accepted: unloadmod
Only accounts allowed to make changes (not the limited role).
reloadmod <module|plugin>Restart a module or plugin on this network.
Example: /msg *status reloadmod trivia
Also accepted: updatemod
Only accounts allowed to make changes (not the limited role).
get <module|plugin>Show its settings.
Example: /msg *status get away
set <module> <setting> <value>Change a setting. For yes/no settings: on/off. See the names with get.
Example: /msg *status set away delay 60
Only accounts allowed to make changes (not the limited role).
addline <module> <setting> <text>Add a line to a list setting.
Example: /msg *status addline watch patterns *xbnc*
Only accounts allowed to make changes (not the limited role).
dellines <module> <setting>Empty a list setting.
Example: /msg *status dellines watch patterns
Only accounts allowed to make changes (not the limited role).

Administration

CommandWhat it does
usersEvery account and whether it is connected.
Example: /msg *status users
Also accepted: listusers
Administrators only.
allnetworksEvery network of every user, with its state.
Example: /msg *status allnetworks
Also accepted: listallusernetworks
Administrators only.
trafficTraffic and messages since xBNC started.
Example: /msg *status traffic
Administrators only.
broadcast <message>Send a notice to every connected user.
Example: /msg *status broadcast Restart at 23:00
Administrators only.
movenetwork <user> <network> <new user> [new name]Move a network to another user. The network reconnects as the new user, with its history.
Example: /msg *status movenetwork ana libera bob
Administrators only.
rehashRe-read users, networks, channels and languages. For changes made with the xbncd command line. Changes in xbnc.toml need a restart.
Example: /msg *status rehash
Also accepted: reload
Administrators only.
listportsPorts xBNC listens on. Ports are set in xbnc.toml (or with the installer) and need a restart.
Example: /msg *status listports
Administrators only.
saveconfigSave the settings. Not needed: xBNC saves every change at once.
Example: /msg *status saveconfig
Administrators only.
setmotd <text>Set this server's message of the day. Users receive it from *status when they connect. Up to 20 lines.
Example: /msg *status setmotd Welcome to xBNC
Superadministrators only.
addmotd <text>Add a line to the message of the day.
Example: /msg *status addmotd Rules: /msg *status help
Superadministrators only.
clearmotdRemove the message of the day.
Example: /msg *status clearmotd
Superadministrators only.
restart [message]Restart xBNC. Every network reconnects. The message, if any, is sent to everyone first.
Example: /msg *status restart Updating
Superadministrators only.
shutdown [message]Stop xBNC completely. It stays stopped until it is started again on the server (systemctl start xbnc).
Example: /msg *status shutdown Maintenance
Superadministrators only.

Notes on these commands

  • Backup servers. If connecting to a server fails, xBNC tries the next one on the list a second later, and after going through the whole list it waits as usual again (5 s, 10 s, 20 s… up to 5 min). If a server that was connected drops the connection, xBNC first goes back to that same server. Passwords of backup servers are stored encrypted. jump alone moves to the next one; jump server or jump server:port, to a particular one.
  • Trusted certificates. For networks with a self-signed or expired certificate, or one with another name. xBNC still checks everything else about TLS and accepts only that particular certificate (its SHA-256 fingerprint). To see a server's fingerprint: openssl s_client -connect irc.example.org:6697 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
  • Local address (vhost). Administrators only, because it uses the server's IP addresses. xBNC checks that the address belongs to the machine before saving it. The network's address takes priority over the user's. It applies on the next connection.
  • Disabled channels. They stay saved with their key and settings, but xBNC does not join them when connecting. The panel marks them as “Disabled”. enablechan joins them straight away.
  • Channel order. It is the order in which xBNC joins when connecting and in which channels and the panel list them. New channels go at the end.
  • Wildcards. enablechan, disablechan, attach, detach and clearbuffer accept several names separated by spaces or commas, and the wildcards * and ?: disablechan #xbnc* #xbnc-dev.
  • Message of the day. A superadministrator sets it and every user receives it, as a notice from *status, each time a client connects. Up to 20 lines of 400 characters. Shown with showmotd.
  • Notices to everyone. broadcast sends “[Administrator notice] text” to every running network; every connected client receives it. restart and shutdown with a message send it first.
  • Restarting and stopping. restart restarts xBNC inside the same process (the networks reconnect). shutdown stops it completely and systemd does not start it again: it has to be started with sudo systemctl start xbnc.
  • Ports and xbnc.toml. Ports and the rest of xbnc.toml are not changed from IRC: listports shows them and rehash re-reads users, networks, channels and languages (the same as sudo systemctl reload xbnc). saveconfig exists out of habit: xBNC saves every change at once.

*notes

CommandWhat it does
listYour notes.
get <key>Show a note.
add <key> <text>Save a note (up to 400 characters).
del <key>Delete a note.

They are the same notes as in Your account → Notes.

*watch

Matches of the Watch words module arrive here. /msg *watch help reminds you how to configure it.

*crypt

CommandWhat it does
setkey <#channel|nick> [cbc:]<key>Save the FiSH key of a channel or private conversation (cbc: for CBC mode). Stored encrypted.
delkey <#channel|nick>Delete a key.
keyx <nick>Agree on a key in private with DH1080 (the other person needs FiSH).
listConversations with a key.

Type +p before a message to send it unencrypted. See Message encryption.

*ignore

CommandWhat it does
add <nick or mask>Ignore someone (a nick, or nick!user@host with *).
del <nick or mask>Stop ignoring.
listList of ignored people.

One service per plugin

Each active plugin answers on *id (*trivia, *remind, *poll…). With help or any text it does not understand, it explains how it is used. If a plugin has nothing to say, xBNC answers with its name, version, description and usage.

Client commands

CommandWhat it does
/quote DETACH #channelDetaches a channel: it keeps recording, but does not appear in your client.
/quote ATTACH #channel or /join #channelShows a detached channel again, with the latest messages.
/quote <alias> [arguments]Runs an alias if your client does not send commands it does not know.

Web panel

PageWhat it is for
HomeLive state of your networks (updated every 2 seconds) and, for administrators, memory, CPU, traffic, connections, blocked IPs, active alerts and new version notices.
ChatWeb IRC client: channels and private conversations of all your networks, with history. The panel counts as one of your devices.
MentionsEverything said to you in private or where your nick was written, newest first.
NetworksAdd (four-step wizard) and edit networks; control (connect, disconnect, reconnect); channels; connected devices (with a button to disconnect one); history with search and export; commands on connect; client certificate; the card “Connect your IRC client”.
ModulesModules and plugins of each network. A network selector that works with any number of networks, and “Copy to other networks” (modules and plugins, except the certificate).
PluginsOfficial and remote catalogue, one-click installation or uploading a .wasm, permissions and approved sites, turning them on or off on the server, uninstalling, live log and development mode (superadministrators).
UsersNew accounts, roles, quotas of networks, devices and history, suspension, password reset, usage of each account and import from another bouncer.
SecuritySign-in attempts (IRC, panel and tokens), blocked IPs with a button to unblock them, open sessions and an audit log of every action.
SettingsHistory, limits, access protection, default language, alerts, backups, updates and restarting the service.
Your accountLanguage, time zone, notifications, password, two-step verification, passkeys, linked external accounts, notes, API tokens, test report and open sessions.

Live settings

What you change in Settings is stored in the database, applies without restarting and takes priority over xbnc.toml. The ↺ icon next to a field goes back to the value of xbnc.toml.

GroupSettingsDefault
HistoryDays of retention (0: forever); messages played back on reconnection; messages for a new device; CHATHISTORY limit.30 days; 1000; 100; 500
LimitsTotal connections; devices per user; networks per user; queue per client (lines before a slow client is disconnected). Each user can have exceptions in Users.1000; 10; 10; 4096
Access protectionFailed attempts before blocking; window; length of the block; time to complete the login.5; 600 s; 900 s; 30 s
GeneralDefault language for people who have not chosen one in Your account. It is saved and applied when clicked, also to whoever changes it.the one in xbnc.toml
AlertsSee Monitoring.
BackupsAutomatic yes/no, every how many hours (1–720) and how many to keep (1–365).no; 24 h; 7

What is read at start (ports, certificates, database, secret key, the panel itself and the log level) is changed in xbnc.toml and needs sudo systemctl restart xbnc. The page says so. Superadministrators also have a button to restart the service.

Accessibility and use on the phone

  • Keyboard navigation with visible focus; dialogs close with Escape.
  • Respects the system's “reduce motion”.
  • On narrow screens the side bar opens with the menu button.
  • The theme (light, or dark in black, yellow and grey) follows the system the first time; the moon or sun button changes it and remembers it in that browser.
  • It can be installed as an app (PWA) to get push notifications.
  • In English and Spanish, and in any language installed as a translation file (see Translating xBNC). The language is changed on the sign-in page, in Your account or (the server default) in Settings, and applies at once to the panel and to xBNC's messages on IRC, also with the client connected. From IRC it is changed with /msg *status language en.

History and search

xBNC stores the messages of channels and private conversations (and, with the Events module, joins, parts, kicks, nick changes, topics and modes). Each message carries the server's time if the network sends it.

Searching

In Networks → (network) → History, or with the API. The search uses a full-text index:

  • It finds whole words and word beginnings: deplo finds “deployment”.
  • It ignores case and accents: cafe finds “Café”.
  • Several words must all appear, in any order. In quotes, the exact phrase is searched: "out of service".
  • If nothing matches by word, it also looks for pieces in the middle of a word: ployment finds “deployment”.
  • It combines with the conversation, sender and date filters.

Export and delete

It exports to TXT or JSON (everything, or one conversation). A conversation or the whole history of the network is deleted from the panel, with /msg *status clearbuffer (or clearallbuffers, clearallchannelbuffers, clearallquerybuffers) or with the API.

Retention and quotas

  • General: Settings → History, days of retention (30 by default; 0, forever).
  • Per channel: in the channels of each network, or with /msg *status setbuffer: the server setting, store nothing, forever or N days.
  • Per user: an administrator can set a history space quota (max_history_mb) in Users; usage shows on each user's page.
  • Space: about 150 bytes per stored message, including the search index.

Log files

Besides the history, the Log files module writes daily text files in the classic IRC format to /var/lib/xbnc/logs/user/network/#channel/YYYY-MM-DD.log, with times in your time zone.

Notifications

In Your account → Notifications, xBNC tells you about mentions and private messages.

OptionWhat it doesDefault
Turn on notificationsMain switch.no
Only when I have no client connectedNo notice if you have an IRC client or the panel chat open.yes
When someone writes my nick in a channelMentions.yes
When I get a private messagePrivate messages.yes
ChannelWhat you need
Browser and phone (push)Click “Turn on in this device” from the panel open on that device. On the phone, install the panel as an app first. It can be turned on on several devices.
TelegramCreate a bot with @BotFather, write to it once and enter its token and your chat id (@userinfobot tells you). The panel never shows the full token again.
DiscordA channel webhook: Channel settings → Integrations → Webhooks. Notifications mention nobody.
E-mailYour address. The administrator has to configure the [smtp] section of xbnc.toml.
Your own web addressAn https URL that receives a POST with the notification in JSON.

“Send a test notification” sends one to every configured channel. So as not to flood you, xBNC groups notifications: if many arrive in a row, those not sent are counted and the next notification says how many were skipped.

Format of the notification at your own web address

{
  "network": "undernet",
  "network_id": 1,
  "conversation": "#xbnc",
  "from": "bob",
  "text": "y2k, what time is the meeting tomorrow?",
  "private": false,
  "ts": 1762851240000,
  "skipped": 0
}

With push, tapping the notification opens the panel chat in that conversation.

Modules

Modules are native xBNC features, built into the program, that are turned on and configured per network: in the panel (Modules), with the API or from IRC with /msg *status. Changes apply at once, without reconnecting. Every setting is validated (types, ranges, lengths, no control characters). Passwords and keys are stored encrypted and the panel never shows them again: to change them, type them again.

/msg *status modules                         list and state
/msg *status enable keepnick                 turn on (disable to turn off)
/msg *status get away                        show settings
/msg *status set away delay 60               change a setting
/msg *status addline watch patterns *xbnc*   add a line to a list
/msg *status dellines watch patterns         empty a list
/msg *status availmods                       every module and plugin, with what it does

Setting types: yes/no (in *status, true, on or 1 for yes and false, off or 0 for no), number (with a minimum and maximum), text, list (one entry per line), choice (one of the values given) and secret (stored encrypted).

Connection and account

Services login

servicesnew in 1.0

Logs you in to X (Undernet), NickServ, Q or AuthServ when connecting and hides your IP before joining channels.

On Undernet it uses Login on Connect: the account goes in the server password and your real IP is never shown. If the network already has a server password, it logs in with /msg X login after connecting. Channels wait until the network confirms the login or the time set below passes. The password is stored encrypted and never shown again.

SettingTypeDefaultDetail
Service
service
choiceautoOptions: auto Automatic, by network, x X (Undernet), nickserv NickServ (Libera, freenode, Rizon…), q Q (QuakeNet), authserv AuthServ (EvilNet, GameSurge).
Account
account
textemptyYour account name on the service. With NickServ it can be empty if it is your nick.
Password
password
secretempty
Hide my IP (mode +x)
hide_host
yes/noyesOn X, Q and AuthServ. Other networks do it themselves.
Log in while connecting (Login on Connect)
login_on_connect
yes/noyesUndernet only: the real IP is never shown.
Wait before joining channels (seconds)
wait
number (0–120)15Until the network confirms the login. 0: do not wait.
Get my nick back if it is taken
recover_nick
yes/noyesWith NickServ: asks for the nick (REGAIN or GHOST) after logging in.
Example
Connecting to irc.undernet.org:6667 with Login on Connect…
*** Your host is now y2k.users.undernet.org
* y2k has joined #xbnc

From IRC: /msg *status enable services and /msg *status get services

Invite and unban

invitenew in 1.0

If you cannot join a channel (+i, +k, +l or a ban), asks X or ChanServ to invite or unban you and joins again.

It tries at most three times every ten minutes per channel. You need access in the channel for the service to listen.

SettingTypeDefaultDetail
Channel service
service
choiceautoOptions: auto Automatic, by network, x X (Undernet), chanserv ChanServ, q Q (QuakeNet).
Ask to remove my ban
unban
yes/noyes
Join when invited to one of my channels
join_invites
yes/noyes
Example
#xbnc: cannot join channel (+i)
*statusAsking X to let you into #xbnc (+i).
* y2k has joined #xbnc

From IRC: /msg *status enable invite and /msg *status get invite

Client certificate (CertFP)

cert

Identifies you on the network with a certificate instead of a password. Enables SASL EXTERNAL.

Generate a certificate, add its fingerprint to your network account and reconnect. On Libera.Chat: connect identified and type /msg NickServ CERT ADD. Only works with TLS.

SettingTypeDefaultDetail
Use SASL EXTERNAL if the network offers it
sasl_external
yes/noyes
Example
y2k/msg NickServ CERT ADD
NickServAdded fingerprint 3f9a…c21e to your fingerprint list.

From IRC: /msg *status enable cert and /msg *status get cert

Keep nick

keepnickon by default

If your nick is taken when connecting, xBNC takes it back as soon as it is free.

Checks every 30 seconds whether your nick is free and takes it back as soon as whoever holds it disconnects. If you change nick by hand from your client, it pauses until the next connection.

Example
*statusYour nick y2k is taken; connected as y2k_. I will take it back as soon as it is free.
* y2k_ is now known as y2k

From IRC: /msg *status enable keepnick and /msg *status get keepnick

Channels

Sticky channels

stickychan

Channels you cannot leave by mistake: xBNC blocks /part and rejoins if you leave.

SettingTypeDefaultDetail
Channels
channels
listemptyOne per line.
Example
#xbnc
#xbnc-dev

If you type /part #xbnc, xBNC does not leave the channel.

From IRC: /msg *status enable stickychan and /msg *status get stickychan

Rejoin after a kick

kickrejoin

Rejoins the channel if you are kicked. Stops trying if you are kicked more than 3 times in a minute.

SettingTypeDefaultDetail
Wait before rejoining (seconds)
delay
number (0–600)10
Example
* bob has kicked you from #xbnc (test)
* y2k has joined #xbnc

From IRC: /msg *status enable kickrejoin and /msg *status get kickrejoin

Automatic op and voice

autoop

Gives op, halfop or voice to the people you choose when they join your channels (if you have op).

SettingTypeDefaultDetail
Wait before giving the mode (seconds)
delay
number (0–30)3
People
entries
listemptyOne per line: mode (o, h or v), mask and, optionally, comma-separated channels (or *) and a key. With a key, xBNC sends a challenge and only gives the mode if the other person answers with the same key from their own xBNC.
Example
o *!*@home.example.org #xbnc
v friend!*@* *
o bob!*@* #xbnc shared-key

From IRC: /msg *status enable autoop and /msg *status get autoop

Get op back when left alone

autocyclenew in 1.0

If you are left alone in a channel without op, leaves and joins again to get it. Never in +i or keyed channels.

SettingTypeDefaultDetail
Only in these channels
channels
listemptyOne per line. Empty: in all of them.
Example
* bob has left #xbnc
*statusYou are alone in #xbnc without op: leaving and joining again.
* y2k has joined #xbnc (with op)

From IRC: /msg *status enable autocycle and /msg *status get autocycle

Detached channels

detachnew in 1.0

Channels that keep recording everything but do not appear in your client. They come back if someone mentions you.

From your client: /quote DETACH #channel to detach and /join #channel to see it again. The latest messages are shown when it comes back.

SettingTypeDefaultDetail
Channels
channels
listemptyOne per line. * works (for example #*-bots).
Show the channel if someone mentions my nick
on_mention
yes/noyes
Show the channel if one of these nicks writes
nicks
listemptyOne per line.
Example
/quote DETACH #xbnc     detaches the channel (it keeps recording)
/quote ATTACH #xbnc     or /join #xbnc: shows it again

If someone writes your nick in #xbnc, the channel comes back with the latest messages.

From IRC: /msg *status enable detach and /msg *status get detach

Hide flooding channels

flooddetachnew in 1.0

If a channel suddenly fills with messages, it is hidden for a while so your client does not freeze. It keeps recording.

SettingTypeDefaultDetail
Messages
messages
number (3–1000)25How many messages…
…in how many seconds
seconds
number (1–600)10
Quiet seconds before showing it again
calm
number (10–3600)60
Example
Messages: 25
…in how many seconds: 10
Quiet seconds before showing it again: 60

From IRC: /msg *status enable flooddetach and /msg *status get flooddetach

Messages

Automatic away

awayon by default

Marks you away when you close all your clients and, when you come back, sums up what you missed.

SettingTypeDefaultDetail
Away message
message
textemptyEmpty: “Away” in your language.
Wait before marking you away (seconds)
delay
number (0–3600)60
When I come back, tell me about private messages and mentions received
summary
yes/noyes
Nick while you are away
nick
textemptyEmpty: unchanged. {nick} is your nick, for example {nick}
Example
Away message: Back soon
Nick while away: {nick}|away

When you return, *status sums up: “While you were away: 3 private messages (bob, carol) and 2 mentions in #xbnc”.

From IRC: /msg *status enable away and /msg *status get away

Watch words

watchon by default

Copies messages containing what you care about to the *watch conversation, even if you were not connected.

SettingTypeDefaultDetail
Also tell me when someone mentions my nick
mentions
yes/nono
Patterns
patterns
listemptyOne per line. * matches any text: *deploy* finds “the deploy failed”. You can add a channel or nick after it to narrow it down.
Example
*deploy*
*xbnc* #xbnc
*urgent* bob

Matches arrive in the *watch conversation.

From IRC: /msg *status enable watch and /msg *status get watch

Aliases

aliason by default

Your own commands: you type /ghost bob and xBNC sends /msg NickServ GHOST bob.

SettingTypeDefaultDetail
Aliases
aliases
listemptyOne per line: name and command. $1, $2… are the arguments, $1- all of them from the first, and $nick your nick.
Example
ghost /msg NickServ GHOST $1
op /mode $1 +o $nick
greet /msg $1 hi, $1, how are you?

In your client: /ghost y2k   (or /quote ghost y2k if your client does not send it)

From IRC: /msg *status enable alias and /msg *status get alias

Ignore

ignorenew in 1.0

Messages from people you ignore never reach any client or the history. On Undernet it uses SILENCE so they are not even sent.

On IRC: /msg *ignore add nick, /msg *ignore del nick and /msg *ignore list.

SettingTypeDefaultDetail
Ignore these
masks
listemptyOne per line: a nick or a nick!user@host mask (* works).
Use SILENCE when the network allows it
silence
yes/noyes
Example
/msg *ignore add pest
/msg *ignore add *!*@spam.example
/msg *ignore list
/msg *ignore del pest

From IRC: /msg *status enable ignore and /msg *status get ignore

Remove colours

stripcontrolsnew in 1.0

Removes colours, bold, underline and blinking from messages, also in the history.

SettingTypeDefaultDetail
Only in these channels
channels
listemptyOne per line. Empty: in all of them.
Example
Only in these channels:
#xbnc

Before: \x0304,01red\x03 \x02bold\x02   After: red bold

From IRC: /msg *status enable stripcontrols and /msg *status get stripcontrols

Character encoding

charsetnew in 1.0

Accents show correctly even when a channel uses Latin-1, and you can write in Latin-1 where needed.

xBNC always understands Latin-1 messages that are not UTF-8. This module picks the exact table and the channels or private chats where your messages are sent in that encoding (for people with very old clients).

SettingTypeDefaultDetail
Legacy encoding
legacy
choicewindows-1252Options: windows-1252 Windows-1252 (the most common), iso-8859-15 ISO-8859-15 (with €), iso-8859-1 ISO-8859-1 (Latin-1).
Send in that encoding to
send_legacy
listemptyChannels or nicks, one per line. * for all. Empty: always UTF-8.
Example
Legacy encoding: Windows-1252
Send in that encoding to:
#xbnc
grandpa

From IRC: /msg *status enable charset and /msg *status get charset

Message encryption (FiSH)

cryptnew in 1.0

Encrypts your messages in channels and private chats with shared keys, compatible with FiSH for mIRC, HexChat and irssi.

On IRC: /msg *crypt setkey #channel key, /msg *crypt keyx nick to agree a key in private and +p before a message to send it unencrypted. Keys are stored encrypted. FiSH protects from the network's servers; it is not a substitute for end-to-end encryption.

SettingTypeDefaultDetail
Keys
keys
secret listemptyOne per line: channel or nick and key (cbc: first for CBC mode). They are stored encrypted: write all of them again to change them.
Mark for decrypted messages
prefix
textemptyFor example [encrypted] . Empty: no mark.
Accept key exchanges (DH1080)
key_exchange
yes/noyesWhen someone proposes a key in private.
Example
/msg *crypt setkey #xbnc cbc:a-long-key
/msg *crypt keyx bob          agree on a key in private (DH1080)
/msg *crypt list
/msg *crypt delkey #xbnc
+p hello                      sends this message unencrypted

From IRC: /msg *status enable crypt and /msg *status get crypt

Scheduled tasks

schedulenew in 1.0

Commands at a fixed time or every so often, in your time zone.

Pick the time zone in Your account. Tasks only run while the network is connected.

SettingTypeDefaultDetail
Tasks
tasks
listemptyOne per line: time and command (08:00 /away Working), days and time (mon-fri 18:00 /msg #channel bye) or interval (every 30m /msg #channel still here). Days: mon tue wed thu fri sat sun, weekdays, weekend.
Example
mon-fri 08:00 /away Working
mon-fri 15:00 /away
weekend 10:30 /msg #xbnc Good morning!
every 2h /msg #xbnc-dev still here

From IRC: /msg *status enable schedule and /msg *status get schedule

CTCP replies

ctcpon by default

Answers VERSION, PING and TIME when no client is connected, stops CTCP floods and lets you hide your version.

SettingTypeDefaultDetail
CTCP requests allowed every 10 seconds
flood_limit
number (1–100)5
VERSION reply
version
textemptyEmpty: your client's, or xBNC's if none is connected.
CTCPs to ignore
block
listemptyOne per line, for example TIME or FINGER.
Example
CTCP requests allowed every 10 seconds: 5
Reply to VERSION: mIRC v7.77
CTCP to ignore:
FINGER
TIME

From IRC: /msg *status enable ctcp and /msg *status get ctcp

Hide the MOTD

block_motd

Does not show the server's message of the day to your clients.

Example
No settings. When on, your clients do not receive the server's message of the day.

From IRC: /msg *status enable block_motd and /msg *status get block_motd

History and DCC

Events in the history

eventson by default

Keeps joins, parts, kicks, nick changes, topics and modes next to the messages.

Modern clients get them as real events; the rest as messages from *events when the history is played back.

SettingTypeDefaultDetail
Keep joins, parts and quits
joins
yes/noyesIn big channels they make a lot of noise.
Keep kicks, nick changes, topics and modes
other
yes/noyes
Example
With “Store kicks, nick changes, topics and modes” on, when the history is played back
you also see: “bob changed the topic to: Meeting at 20:00”.

From IRC: /msg *status enable events and /msg *status get events

Log files

log

Writes every conversation to daily text files, in the classic IRC log format.

The files stay on the server, in the logs folder next to the database (for example /var/lib/xbnc/logs/user/network/#channel/2026-11-08.log). Times are in your time zone (Your account).

SettingTypeDefaultDetail
Delete files older than (days)
retention_days
number (0–3650)00 keeps them forever.
Include events (joins, parts, kicks…)
events
yes/noyes
Example
/var/lib/xbnc/logs/y2k/undernet/#xbnc/2026-11-11.log

[18:30:12] <bob> hello everyone
[18:30:20] * carol has joined

From IRC: /msg *status enable log and /msg *status get log

Replies to whoever asked

route_replieson by default

Replies to WHOIS, WHO, LIST, NAMES or ban lists go only to the device that asked, not to all of them.

Example
If you run /whois bob from your phone, the answer reaches only the phone,
not the laptop that is also connected.

From IRC: /msg *status enable route_replies and /msg *status get route_replies

DCC through xBNC

dcc

DCC file transfers and chats go through xBNC: the other person does not see your IP and they work even behind a router.

xBNC opens a port for each transfer and joins the two connections; nothing is stored on disk. On a server with a public IP it works with no setup; behind NAT, set the public IP in [dcc] public_ip in xbnc.toml and open the [dcc] ports in the firewall.

SettingTypeDefaultDetail
Largest transfer (MB)
max_size_mb
number (0–1000000)40960 = no limit.
Seconds to accept a transfer
wait
number (30–900)180
Allow transfers from private addresses
private_addresses
yes/nonoWhen off, xBNC does not connect to internal networks (10.x, 192.168.x…) even if someone on IRC asks.
Example
# xbnc.toml, only if the server is behind a router (NAT):
[dcc]
public_ip = "203.0.113.10"
ports = "50000-50100"

From IRC: /msg *status enable dcc and /msg *status get dcc

Always on

Core features that need no turning on.

FeatureWhere it is configured
Commands on connectIn each network's configuration.
Remember channelsAlways on: your /join and /part are remembered.
SASL and NickServIn each network's configuration.
Brute-force blockingIn Settings and Security.
NotesIn Your account or with /msg *notes.
Last seenIn Users and with /msg *status seen.
Per-device historyAlways on, with search on every network.
Web and IRC administrationThis panel and /msg *status.

Plugins

Plugins add features for the whole channel. They run in sandboxed WebAssembly: they cannot read files or see other networks, they have CPU and memory limits, and they only do what their permissions allow, which are shown before installing them. Those that go out to the internet can only connect to approved sites.

  1. The 18 official plugins come installed: xBNC installs the missing ones on the first start and after each update, and updates those with an old version. They stay off on every network. One that a superadministrator uninstalls on purpose does not come back until the next version of xBNC (or until it is installed again from Plugins → Catalogue).
  2. A superadministrator can install others from the remote catalogue or by uploading a .wasm file, and approves the websites of plugins that go out to the internet (“Sites it may connect to”): link titles, weather and news.
  3. Each user turns them on in their networks from Modules, in the Plugins section, and adjusts their settings.
  4. They answer in your xBNC language (or the one of their “Language” setting) and also work with your own messages. The official plugins speak English and Spanish; in another language they answer in English.
PermissionWhat it allows
sendSend messages and notices.
rawIRC commands such as KICK, MODE or INVITE. Never QUIT, PASS, USER, NICK, CAP, AUTHENTICATE, OPER, SERVER or SQUIT.
timerReceive an event every minute (or every 5–60 seconds if the plugin asks for it).
presenceSee joins, parts, kicks, nick changes and the user list of the channels.
httpRequest https pages, only from the domains it declares (up to 10) and that the superadministrator approves.

Trivia

trivia 1.0.0new in 1.0

A quiz game for your channels, with hints, points and a ranking.

How to use it

In a channel: !trivia to start, !trivia stop, !trivia top (also as !trivial). Answer by writing in the channel. /msg *trivia add Question? | answer adds your own questions; list and del N manage them.

Example
carol!trivia
y2kTrivia starts! 10 questions; answer by writing in the channel.
y2kQuestion 1/10: What is the capital of Japan?
bobtokyo
y2kCorrect, bob! It was “Tokyo”. You have 1 point.
SettingDefaultDetail
Command
trigger
!trivia
Questions per game
rounds
10
Seconds to answer
seconds
40A hint appears halfway.
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, timer. Help from IRC: /msg *trivia help

Welcome

greet 1.0.0new in 1.0

Greets people who join your channels, with a different message the first time and personal greetings.

How to use it

Automatic. /msg *greet test #channel nick shows what would be said. {nick} and {channel} are replaced in the messages.

Example
* carol has joined #xbnc
y2kWelcome to #xbnc, carol! It's your first time here.
SettingDefaultDetail
First time
first
Welcome to {channel}, {nick}! It's your first time here.For people who have never been in the channel. {nick} and {channel} are replaced.
When they come back
message
Hi {nick}, welcome back to {channel}.Empty: returning people are not greeted.
Personal greetings
personal
emptyOne per line: nick and message. Replaces the usual one for that person.
Greet privately (notice)
notice
noOff: the greeting is written in the channel.
Minutes before greeting the same person again
cooldown
30
Never greet
ignore
emptyNicks, one per line (bots, services).
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, joins and parts. Help from IRC: /msg *greet help

Link titles

urltitle 1.0.0new in 1.0

Shows the title of YouTube, Vimeo, SoundCloud, Dailymotion and Spotify links, Wikipedia articles and GitHub repositories pasted in your channels.

How to use it

Automatic. An administrator approves each site in the panel (Plugins → «Sites it may connect to»).

Example
boblook at this https://www.youtube.com/watch?v=…
y2kYouTube: How to set up an IRC bouncer in 5 minutes
SettingDefaultDetail
Seconds between titles in a channel
cooldown
5
Also for your own links
own
yes
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, web access to approved sites. Help from IRC: /msg *urltitle help

Definitions

factoids 1.0.0new in 1.0

The channel's memory: short answers to frequent questions that you and your editors keep up to date.

How to use it

In a channel: !def word (or ?word), !def word = text, !def -word, !defs. Here: /msg *factoids word.

Example
y2k!def rules = Be respectful and do not flood.
carol?rules
y2krules: Be respectful and do not flood.
SettingDefaultDetail
Command
trigger
!def
Answer ?word too
question_mark
yes
Who else can change definitions
editors
emptyNicks, one per line. You always can.
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages. Help from IRC: /msg *factoids help

Weather

weather 1.0.0new in 1.0

The weather now and today's forecast for any city, from Open-Meteo (free, no key).

How to use it

In a channel: !weather London (also !tiempo or !clima). Here: /msg *weather Paris.

Example
bob!weather London
y2kLondon (United Kingdom): 14 °C, clear sky, wind 9 km/h, humidity 60 %. Today between 9 and 16 °C.
SettingDefaultDetail
Command
trigger
!weather
Default city
city
emptyUsed when no city is written.
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, web access to approved sites. Help from IRC: /msg *weather help

News

news 1.0.0new in 1.0

Posts new entries from RSS and Atom feeds (news sites, blogs, YouTube channels, GitHub releases) in your channels.

How to use it

Set the feeds in the panel: one per line, "#channel https://...". /msg *news list, /msg *news check.

Example
y2k[Hacker News] Show HN: an IRC bouncer written in Rust — https://…
SettingDefaultDetail
Feeds
feeds
emptyOne per line: channel and address, for example #xbnc https://hnrss.org/frontpage. An administrator approves the sites.
Minutes between checks
interval
15
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, timer, web access to approved sites. Help from IRC: /msg *news help

Karma

karma 1.0.0new in 1.0

Points between people: nick++ and nick--, with a ranking.

How to use it

In a channel: nick++, nick--, !karma nick, !karma top. Here: /msg *karma nick.

Example
bobcarol++
y2kcarol has 1 karma.
carol!karma top
SettingDefaultDetail
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages. Help from IRC: /msg *karma help

Quotes

quotes 1.0.0new in 1.0

Memorable lines of the channel: save them, find them and pull a random one.

How to use it

In a channel: !quote, !quote 12, !quote search word, !quote add text, !quote del 12 (also as !cita).

Example
bob!quote add today is a great day
y2kQuote 1 saved.
carol!quote 1
y2k#1: today is a great day
SettingDefaultDetail
Command
trigger
!quote
Anybody can add quotes
anyone_adds
yesOff: only you and the editors.
Editors
editors
emptyNicks who can add and delete quotes, one per line.
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages. Help from IRC: /msg *quotes help

Forbidden words

badwords 1.0.0new in 1.0

Warns, kicks and finally bans people who write forbidden words in your channels (you need op).

How to use it

Set the words in the panel. /msg *badwords test text tells whether a text would be caught.

Example
carolyou are a dummy
y2kcarol: that word is not allowed here. Next time, kick.
SettingDefaultDetail
Forbidden words
words
emptyOne per line. Case and accents do not matter; a * at the end matches the start of a word.
Ban length (minutes)
ban_minutes
100: the ban stays.
Kick reason
reason
empty
Warn privately (notice)
warn_private
no
Never act on
exempt
emptyNicks, one per line.
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, IRC commands (kick, ban), timer. Help from IRC: /msg *badwords help

Games

games 1.0.0new in 1.0

Hangman, magic 8 ball and rock-paper-scissors for your channels.

How to use it

In a channel: !8ball question, !rps rock, !hangman and !g letter (or the whole word) to play.

Example
bob!rps rock
y2krock against scissors: you win, bob!
bob!8ball will it work?
y2kThe 8 ball says: “Ask again later”.
SettingDefaultDetail
Only in these channels
channels
emptyOne per line. Empty: in every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages. Help from IRC: /msg *games help

Dice

dice 1.1.0

Rolls dice in channels and private messages: !dado, !dado 2d6, !dado d20.

How to use it

In a channel or private message: !dado (one six-sided die), !dado 2d6, !dado d20. Also !roll and !dice.

/msg *dice 3d8 rolls for you alone.

Example
bob!roll 2d6
y2kbob rolls 2 6-sided dice: 3 and 5 (total 8).
SettingDefaultDetail
Extra command
trigger
!roll!dado, !dados, !roll and !dice always work.
Most dice per roll
max_dice
10
Answer in private messages
in_queries
yes
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages. Help from IRC: /msg *dice help

Polls

poll 1.1.0

!poll Question? | option | option, then !vote N. Closes on its own and announces the winner.

How to use it

In a channel: !poll Pizza or sushi? | pizza | sushi (up to 10 options) — vote with !vote 1 — !poll shows the count — !poll end closes it (who started it, or you). It closes on its own after the minutes in the settings.

Example
carol!poll Coffee or tea? | coffee | tea
bob!vote 2
*pollVote recorded in #xbnc: 2) tea. You can change it by voting again.
SettingDefaultDetail
Command
trigger
!poll
Vote command
vote_trigger
!vote
Minutes until it closes
minutes
60
Other people can start polls
public
yesOff: only you start them; everyone can vote.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, timer. Help from IRC: /msg *poll help

Reminders

remind 1.1.0

Reminds you (or anyone in the channel) after a while or at a time: !remind 10m take the pizza out, !remind 18:30 meeting.

How to use it

In a channel: !remind 10m take the pizza out — !remind 2 hours call Bob — !remind 1h30m … — !remind 18:30 meeting — !remind list — !remind clear. Also !recuerda.

/msg *remind 10m … reminds you privately. Reminders are checked every minute.

Example
bob!remind in 10 minutes take the pizza out
y2kbob: OK, I will remind you in 10 minutes.
y2kbob: take the pizza out
SettingDefaultDetail
Extra command
trigger
!remind!remind and !recuerda always work.
Other people can set reminders
public
yesOff: only your own and /msg *remind.
Pending reminders per person
max_per_nick
10
Time zone for times like 18:30
timezone
Europe/MadridEurope/Madrid, Europe/London, UTC or a difference such as +1.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, timer. Help from IRC: /msg *remind help

Tell

tell 1.1.0

!tell nick message: delivers it in the channel when that person speaks or joins.

How to use it

In a channel: !tell bob the build is fixed — bob gets it in the channel as soon as he writes something or joins. Works for you and for anyone in the channel.

Example
y2k!tell carol the build is fixed
y2ky2k: OK, I will tell carol as soon as she speaks or joins the channel.
* carol has joined #xbnc
y2kcarol: y2k left you a message 5 minutes ago: the build is fixed
SettingDefaultDetail
Command
trigger
!tell
Deliver when they join
on_join
yesOff: only when they write something.
Messages waiting per person
max_per_nick
5
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, joins and parts. Help from IRC: /msg *tell help

Autoresponder

autoresponder 1.1.0

Answers on its own when someone writes a keyword, and (optionally) private messages while you are away.

How to use it

Write the rules in the settings, one per line: keyword = answer. {nick} is who wrote and {channel} the channel. The rule * = text answers any private message.

/msg *autoresponder shows the rules; /msg *autoresponder test <text> tries them.

Example
bobhi, anyone?
y2kHi bob! I am not here right now, leave me your message.
SettingDefaultDetail
Rules
rules
emptyOne per line: keyword = answer. Example: hola = Hello {nick}! — * = answer to any private message.
Seconds before the same rule answers again
cooldown
60
Also answer private messages
in_queries
yes
Only when no client is connected
only_when_away
noSo it answers for you while you are away.
Only in these channels
channels
emptyOne per line. Empty: every channel.
Language of the messages
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages. Help from IRC: /msg *autoresponder help

Channel stats

chanstats 1.1.0

!stats: messages in the channel and who talks most. !stats nick: one person's count.

How to use it

In a channel: !stats shows the messages and who talks most — !stats bob shows one person. /msg *chanstats #channel shows it to you privately. Counts are saved every minute.

Example
bob!stats
y2k#xbnc: 1204 messages since 3 Nov 2026. Most active: carol 312, bob 280, y2k 199.
SettingDefaultDetail
Command
trigger
!stats
People in the ranking
top
5
Answer other people
public
yesOff: only your own !stats.
Only these channels
channels
emptyOne per line. Empty: every channel.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, timer. Help from IRC: /msg *chanstats help

Seen

seen 2.1.0

!seen nick: when someone was last seen and what they were doing, or which channels they are in right now.

How to use it

In a channel: !seen bob — when bob was last seen and whether he is here now. /msg *seen bob, or /msg *seen stats.

Example
bob!seen carol
y2kcarol was last seen 2 hours ago, in #xbnc saying “off to dinner”.
SettingDefaultDetail
Command
trigger
!seenWhat people write before the nick.
Answer other people in channels
public
yesOff: only answers you (your own !seen and /msg *seen).
Seconds between answers in a channel
cooldown
5
Show what they last said
show_text
yesOff: only says where and when.
Answer only in these channels
channels
emptyOne per line. Empty: in every channel. Activity is recorded in all of them.
Language of the answers
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: send messages, joins and parts. Help from IRC: /msg *seen help

Anti-flood

antiflood 1.1.0

Warns, kicks or bans people who flood or repeat lines in your channels (you must be a channel operator).

How to use it

Works by itself in the channels you list in the settings (you must be a channel operator there). /msg *antiflood shows its settings.

Example
spama
spama
spama
* spam was kicked from #xbnc by y2k (flood)
SettingDefaultDetail
Channels to protect
channels
emptyOne per line. It does nothing until you list some.
Most lines allowed
lines
5
In this many seconds
seconds
4
Same line repeated
repeats
3Times in a row; 0 turns it off.
What to do
action
kickwarn (warn first, kick the second time), kick or ban.
Ban length in minutes
ban_minutes
100: the ban is not lifted.
Kick reason
reason
empty
Never act on these nicks
exempt
emptyOne per line.
Language of the messages
lang
emptyEmpty: your language in xBNC. Or es / en.

Permissions: IRC commands (kick, ban), send messages, timer. Help from IRC: /msg *antiflood help

Users, roles and security

Roles

RoleWho it is for
superadminEverything, including system settings, plugins, backups, updates, import and managing other administrators.
adminManages users (except administrators and superadministrators), every network and security; sees the system.
moderatorSees users and the audit log, and can suspend and reactivate normal and limited users.
userManages their own networks, channels and history. It is the default role.
limitedUses networks an administrator prepares for them; manages channels and history, but does not add, edit or delete networks.

What each role can do

Permissionsuperadminadminmoderatoruserlimited
Edit system settings, install plugins, backups, import, updateyes————
See the system, manage users, security and every networkyesyes———
See users and the audit log, suspend usersyesyesyes——
Manage their own networksyesyesyesyes—
Use their networks, channels, history, chat, modules and pluginsyesyesyesyesyes

An administrator only manages roles below their own; nobody can change their own role or promote themselves, and there is always at least one superadministrator. Users do not see other people's networks (the API answers 404).

Accounts

  • User names: 1 to 32 characters A-Z a-z 0-9 . _ -, not case-sensitive. Passwords: at least 10 characters.
  • Per-user quotas: networks (max_networks), devices (max_clients) and history space in MB (max_history_mb). Empty: the server's general value.
  • Suspending an account closes its access and disconnects its networks. Deleting it also removes its networks and history.
  • Changing or resetting the password closes the account's other panel sessions.

Ways to sign in to the panel

  • User and password, with optional two-step verification (TOTP, RFC 6238, with a QR code) using Google Authenticator, Aegis, 1Password or another compatible app. The secret is stored encrypted; a wrong code counts as a failed attempt. IRC clients still sign in with the password only: the protocol has no second step.
  • Passkeys (WebAuthn): sign in with the device's fingerprint, face or PIN. They are added and removed in Your account.
  • OpenID Connect (Google, Microsoft, Keycloak, Authentik…) and LDAP, if the administrator configures them in xbnc.toml (see Configuration). An external account is linked and unlinked in Your account.

Security measures

AreaMeasure
PasswordsArgon2id (m = 19 MiB, t = 2, p = 1). A user that does not exist costs the same to check, so as not to reveal which accounts exist. At most one computation per CPU at a time, so a flood of attempts cannot exhaust the memory.
Stored credentialsSASL and server passwords, channel keys, commands on connect, services passwords, FiSH keys, TOTP secrets and certificate keys, encrypted with XChaCha20-Poly1305. The key lives outside the database (secret.key, permissions 0600 checked at start).
TLSrustls, TLS 1.2 and 1.3 only. Towards the networks the certificate is verified with the system store, with no option to turn it off. A trusted certificate (addfingerprint) accepts only that exact SHA-256 fingerprint, and the signatures of the TLS handshake are still checked.
Plain textForbidden on public addresses unless insecure = true is set explicitly. The default port without TLS only accepts the local network.
Brute forceIP blocked after 5 failures in 10 minutes for 15 minutes (configurable), shared between IRC (PASS and SASL), the panel and API tokens. At most 16 unauthenticated IRC connections per IP.
LogsPasswords, AUTHENTICATE, OPER and messages to services (NickServ IDENTIFY, X LOGIN, Q AUTH…) are never logged, at any level.
InputLines limited to 8703 bytes; any CR or LF inside a line cuts it, to prevent command injection; nicks, names and texts of modules and plugins validated.
ResourcesLimits on total connections and on clients and networks per user; bounded queues per client and towards the server; maximum registration time.
SQLEvery query uses bound parameters.
PrivilegesThe service runs as the unprivileged xbnc user, with systemd isolation. In Docker: unprivileged user, read-only, no capabilities.
Panel: sessionsA random 256-bit token in an HttpOnly, SameSite=Strict and Secure cookie; the database only holds its SHA-256. Sliding expiry (12 h by default).
Panel: CSRF and originCSRF token required on every request that changes something; requests and WebSockets with another site's Origin are refused.
Panel: headersStrict CSP without external or inline scripts, X-Frame-Options: DENY, nosniff, Referrer-Policy: no-referrer, HSTS with TLS and Cache-Control: no-store on the API.
Panel: abuse20 requests per second per IP, bodies up to 256 KB (4 MB for plugins), 64 connections per IP and 4,096 in total, closing after 2 minutes without traffic and 10 s for the TLS handshake. With trust_proxy the real IP behind Nginx is used.
First accessThe first account can only be created with a one-time code that the service writes to its log.
Secrets in the APIPasswords are never returned; only whether they exist.
API tokens256 bits with the xbnc_ prefix; only their SHA-256 is stored. Scope, expiry, last use and IP visible, immediate revocation.
ModulesBuilt into the program: no external code is loaded. Auto op: *!*@* is refused; with a key, a random challenge answered with HMAC-SHA256 and compared in constant time.
Log filesPaths built only from sanitised names (a channel cannot leave its folder), folders 0700 and files 0600.
PluginsWebAssembly in the Wasmi interpreter, with fuel (CPU) per event, 16 MB of memory, no access to files or a network of their own. Actions checked against the permissions. Only a superadministrator installs, seeing the SHA-256 fingerprint; a file that changes after installation is not loaded. Five failures in a row stop it.
DCCOffers from IRC cannot make xBNC connect to internal networks or to ports below 1024 (unless allowed). For your own sends it only connects back to your device's IP. Each port accepts a single connection, with a timeout, a size limit and closing when idle.
Services loginOn networks without NickServ (IRCnet, EFnet) automatic mode never sends the password.
TranslationsLanguage files are data, not code: an IRC text with line breaks or control characters, or with placeholders different from the English ones, is ignored. Files of 2 MB at most and 100 languages.
FiSHKey exchange accepts 5 requests per minute and 100 keys per session; decrypted text cannot break lines.
Webhookshttp(s) only, no user in the URL or control characters, certificate verified, no redirects followed, 10 s at most.
ImportSuperadministrators only; it never grants that role; the preview returns no secrets.
Audit logEvery action of the panel, of IRC and of the command line is recorded with date, author and detail (Security → Audit, or xbncd audit).
FuzzingIRC parser, DCC, importer, search, manifests and plugin execution, every week in CI with earlier failures as regressions.

Recommendations

  • Use a real certificate (Let's Encrypt) and do not publish plain-text ports.
  • Keep backups encrypted: they contain the secret key.
  • Every connection to the networks leaves from the server's IP: if a user misbehaves on a network, the server's IP may end up banned.
  • Turn on two-step verification or a passkey on administration accounts.

To report a vulnerability, follow SECURITY.md: do not publish it; use the repository's private advisory. Answer within 3 days at most, assessment within 7 and a fix within 90 at most. The complete threat model is in Threat model.

Configuration (xbnc.toml)

/etc/xbnc/xbnc.toml only holds process settings: users, networks and channels are in the database. The installer creates it for you. After editing it: sudo xbncd check-config and sudo systemctl restart xbnc. An unknown key is an error, so that a typo does not go unnoticed. What you set in Settings in the panel takes priority over [history] and [limits].

General

KeyDefaultWhat it does
hostnamexbncServer name the clients see.
languageenDefault language of the IRC messages and of the panel: en, es or any language installed in locales_dir. Each user can choose their own. The installer sets the one you choose.
locales_dir/etc/xbnc/localesFolder of extra languages and corrections (one <code>.json file per language, see Translating xBNC). Read at start and on reload. The XBNC_LOCALES_DIR environment variable takes priority.
secret_key_filerequiredPath of the 32-byte key that encrypts the credentials. Created by xbncd init with permissions 0600. Back it up: without it they cannot be decrypted.

[database]

KeyDefaultWhat it does
pathrequiredSQLite file. Its folder is also the data folder (plugins, log files, backups).
url—PostgreSQL instead of SQLite: postgres://user:password@host/db. Add ?sslmode=require to encrypt the connection.
hafalseHigh availability (needs url): several nodes share the database; one works and the others wait to take over.

[[listen]] (one per port)

KeyDefaultWhat it does
addressrequiredAddress and port: 0.0.0.0:6697 or [::]:6697.
tls_cert—Certificate chain (PEM). Required unless insecure, lan_only or a loopback address.
tls_key—Private key (PEM).
insecurefalseAllow plain text on a public address (not recommended).
lan_onlyfalseLocal network clients only (loopback, private, link-local, CGNAT/Tailscale, IPv6 ULA). The rest get an explanatory ERROR. May go without TLS.

[web]

KeyDefaultWhat it does
addressrequiredAddress of the panel and the API, for example 0.0.0.0:8443 or 127.0.0.1:8080. Without [web], there is no panel.
tls_cert, tls_key—Certificate of the panel.
insecurefalseUnencrypted HTTP on a public address (only behind a TLS proxy on a trusted network).
secure_cookiestrue with TLSMarks the session cookie as Secure. Set it to true behind a TLS proxy.
public_url—Public URL of the panel (https://bnc.example.org). Used to check the Origin of requests and as the base for OpenID Connect.
session_hours12Session length in hours (sliding).
trust_proxyfalseTrust X-Forwarded-For and X-Real-IP from a proxy on loopback, so blocking uses the real IP.

[history]

KeyDefaultWhat it does
retention_days30Delete messages older than N days. 0, forever.
playback_max1000Maximum messages played back on reconnection.
new_client_playback100Messages for a device that had never connected.
chathistory_max500Maximum limit accepted in CHATHISTORY.

[limits]

KeyDefaultWhat it does
max_connections1000IRC client connections in total.
max_clients_per_user10Devices connected per user.
max_networks_per_user10Networks per user.
login_max_failures5Failures before an IP is blocked.
login_window_seconds600Window in which failures are counted.
login_block_seconds900Length of the block.
registration_timeout_seconds30Time for a client to complete its login.
client_queue4096Lines queued per client before disconnecting it as slow (when it comes back it gets what was pending).

[log]

KeyDefaultWhat it does
levelinfoFilter: error, warn, info, debug, trace, or per module (info,xbncd=debug). The RUST_LOG variable takes priority. Credentials are masked at every level.

[dcc]

KeyDefaultWhat it does
public_ipthe one xBNC uses towards the networkAddress announced on IRC. Needed behind NAT. If the connection to the network is IPv6, the public IPv4 is announced.
portsany free oneRange for the tunnels, for example 50000-50100 (open it in the firewall and the router).

[smtp] (e-mail notifications)

KeyDefaultWhat it does
hostrequiredMail server.
port587Port.
securitystarttlsstarttls, tls (port 465) or none (local relays only).
username, password—Credentials.
fromrequiredSender, for example xBNC <xbnc@example.org>.

[oidc] (OpenID Connect)

KeyDefaultWhat it does
name—Text of the button (Google, Keycloak…).
issuerrequiredURL of the issuer; its /.well-known/openid-configuration is read.
client_id, client_secretrequiredDetails of the application registered with the provider. The return address is https://YOUR-PANEL/api/v1/auth/oidc/callback (uses public_url).
scopesopenid profile emailScopes requested.
username_claimpreferred_usernameClaim used as the name of automatically created accounts.
auto_createfalseCreate the xBNC account the first time someone signs in.
link_existingfalseLink with a local account of the same name (only with providers you control).
default_roleuserRole of the accounts created.
allowed_groups[] (everyone)If given, only members of one of these groups may sign in.
groups_claimgroupsClaim with the groups.

[ldap]

KeyDefaultWhat it does
urlrequiredldaps://host:636, or ldap://host:389 with starttls = true.
starttlsfalseUse STARTTLS.
bind_dnrequiredDN used to bind; {{username}} is replaced by the name typed.
ca_filesystem certificatesPEM file with the authority of the LDAP server.
auto_create, link_existing, default_rolefalse, false, userAs in [oidc].

[updates]

KeyDefaultWhat it does
checktrueCheck once a day whether there is a new version (and when an administrator asks). Downloads nothing.
repository—GitHub repository with the releases (owner/name).
url—Or any https URL answering {{"version", "url", "notes", "packages"}}.
public_key—Ed25519 public key of the releases. With it, and xBNC installed from a package, a superadministrator can update from the panel.

[catalog] (remote plugin catalogue)

KeyDefaultWhat it does
url—https:// address of the JSON index.
public_key—Ed25519 public key (base64) that signs the index.

Complete example

hostname = "bnc.example.org"
language = "en"
secret_key_file = "/var/lib/xbnc/secret.key"
# locales_dir = "/etc/xbnc/locales"

[database]
path = "/var/lib/xbnc/xbnc.db"
# url = "postgres://xbnc:password@127.0.0.1/xbnc?sslmode=require"
# ha = true

[[listen]]
address = "0.0.0.0:6697"
tls_cert = "/etc/xbnc/tls/fullchain.pem"
tls_key = "/etc/xbnc/tls/privkey.pem"

[[listen]]
address = "0.0.0.0:6667"
lan_only = true

[web]
address = "0.0.0.0:8443"
tls_cert = "/etc/xbnc/tls/fullchain.pem"
tls_key = "/etc/xbnc/tls/privkey.pem"
public_url = "https://bnc.example.org:8443"
session_hours = 12

[history]
retention_days = 30
playback_max = 1000
new_client_playback = 100
chathistory_max = 500

[limits]
max_connections = 1000
max_clients_per_user = 10
max_networks_per_user = 10
login_max_failures = 5
login_window_seconds = 600
login_block_seconds = 900
registration_timeout_seconds = 30
client_queue = 4096

[log]
level = "info"

[dcc]
public_ip = "203.0.113.10"
ports = "50000-50100"

[smtp]
host = "smtp.example.org"
port = 587
security = "starttls"
username = "xbnc@example.org"
password = "..."
from = "xBNC <xbnc@example.org>"

[oidc]
name = "Keycloak"
issuer = "https://sso.example.org/realms/home"
client_id = "xbnc"
client_secret = "..."
auto_create = true
default_role = "user"
allowed_groups = ["irc"]

[ldap]
url = "ldaps://ldap.example.org:636"
bind_dn = "uid={username},ou=people,dc=example,dc=org"

[updates]
repository = "owner/xbnc"

[catalog]
url = "https://plugins.example.org/index.json"
public_key = "BASE64..."

Command line

Every command accepts -c PATH (or the XBNC_CONFIG variable; /etc/xbnc/xbnc.toml by default), -h for help and -V for the version. Those that write to the database are run as the service user: sudo -u xbnc xbncd … (in Docker, docker compose exec xbnc xbncd …). Afterwards, sudo systemctl reload xbnc so the service applies the changes to users, networks and channels. Every action is recorded in the audit log.

CommandWhat it doesOptions
xbncd runRuns the bouncer in the foreground (the service does this). SIGHUP (systemctl reload) re-reads networks, channels, users and languages.—
xbncd initCreates the database and the secret key.—
xbncd check-configChecks the configuration file and exits.—
xbncd infoShows the panel address and how clients connect.—
xbncd setup-codeShows the one-time code to create the first administrator.—
xbncd user add NAMECreates a user. The password is asked twice, read from standard input with --password-stdin or from XBNC_PASSWORD.--role superadmin|admin|moderator|user|limited (user by default); --admin (shortcut for --role superadmin); --password-stdin
xbncd user listLists the users.—
xbncd user passwd NAMEChanges the password.--password-stdin
xbncd user role NAME ROLEChanges the role.—
xbncd user enable|disable NAMEReactivates or suspends an account (after a reload, its networks disconnect).—
xbncd user reset-2fa NAMERemoves two-step verification (lost phone).—
xbncd user delete NAME --yesDeletes the user, their networks and their history.--yes (confirm)
xbncd network add USER NAME --host SERVERAdds a network. SASL and server passwords are asked for or read from XBNC_SASL_PASSWORD and XBNC_SERVER_PASSWORD.--port (6697); --no-tls; --nick; --username; --realname; --server-password; --sasl-user ACCOUNT
xbncd network list USERLists a user's networks.—
xbncd network enable|disable USER NAMEEnables or disables a network.—
xbncd network delete USER NAME --yesDeletes a network.--yes
xbncd channel add USER NETWORK CHANNELSaves a channel to join automatically.--key (asks for the channel key)
xbncd channel list USER NETWORKLists the saved channels.—
xbncd channel delete USER NETWORK CHANNELRemoves a saved channel.—
xbncd plugin install FILE.wasmInstalls or updates a plugin; each user turns it on in their networks.--yes (accept the permissions it asks for)
xbncd plugin listLists the installed plugins.—
xbncd plugin remove IDUninstalls a plugin and deletes its settings on every network.—
xbncd plugin check FILE.wasmChecks a plugin and shows its manifest.—
xbncd plugin pack PLUGIN.wasm MANIFEST.json -o OUTPUT.wasmPuts the manifest (JSON) inside a compiled plugin.-o, --output (required)
xbncd plugin catalog-keygen -o KEYTo publish a catalogue: creates the signing key (keep it safe).-o, --output
xbncd plugin catalog-entry FILE.wasm URLTo publish a catalogue: prints the index entry of a plugin published at URL (https://).—
xbncd plugin catalog-sign --key KEY INDEXTo publish a catalogue: signs the index (writes INDEX.sig).-k, --key
xbncd backup DESTINATIONConsistent copy of the database, with xBNC running.—
xbncd migrate-to-postgres URLCopies the SQLite database to a new, empty PostgreSQL one. Stop xBNC first; then set database.url.—
xbncd import PATHBrings users, networks, channels and modules from another bouncer (its data folder, with configs/ and users/, or its configuration file). Without --apply nothing is written.--apply; --logs (also the log files); --utc-offset ZONE (+02:00 or Europe/Madrid; +00:00 by default)
xbncd import-logs USER NETWORK FOLDERImports text logs (one folder per channel with YYYY-MM-DD.log files) into a network's history. Can be repeated without duplicating.--utc-offset ZONE
xbncd auditLatest administrative actions.--limit N (50)
xbncd release keygenTo publish releases: creates the signing key pair. The public key goes in [updates] public_key.--out FILE (xbnc-release.key)
xbncd release sign FILES…Writes FILE.sig next to each package. The private key is read from XBNC_RELEASE_KEY or from --key.--key
xbncd release verify --public-key KEY FILES…Checks the .sig signatures.—
xbncd apply-updateInstalls an update requested from the panel (run as root by the xbnc-update service; checks the signature again).—
xbncd locales check FILES…Checks language files against English: missing texts, texts xBNC does not use, broken {0} or <b> tags. Does not need the configuration.--missing (also lists what is missing)
xbncd locales listLanguages this server offers, with the share translated of each.—

Examples

sudo xbncd info
sudo xbncd setup-code
sudo -u xbnc xbncd user add alice
sudo -u xbnc xbncd user add carla --role moderator
echo 'a-long-password' | sudo -u xbnc xbncd user add bob --password-stdin
sudo -u xbnc xbncd user role carla admin
sudo -u xbnc xbncd user reset-2fa alice
sudo -u xbnc xbncd network add alice libera --host irc.libera.chat --sasl-user alice
sudo -u xbnc xbncd network add alice undernet --host irc.undernet.org --port 6667 --no-tls
sudo -u xbnc xbncd channel add alice libera '#xbnc' --key
sudo -u xbnc xbncd backup /var/lib/xbnc/copy.db
sudo -u xbnc xbncd import /home/old/.bouncer --logs --utc-offset Europe/London             # preview
sudo -u xbnc xbncd import /home/old/.bouncer --logs --utc-offset Europe/London --apply     # import
xbncd locales check pt.json --missing
sudo systemctl reload xbnc

Scripts

ScriptUse
scripts/install.shInstalls or reconfigures the service (see Installation).
scripts/update.sh [--binary PATH]Updates the binary with a backup first and automatic rollback.
scripts/backup.sh [FOLDER] [--keep N]Live backup: database, secret key and /etc/xbnc in a 0600 .tar.gz (in /var/backups/xbnc, keeps 14).
scripts/restore.sh ARCHIVE [--with-config] [--yes]Restores a backup.sh copy. Keeps the current files as *.pre-restore-DATE.
scripts/uninstall.sh [--purge]Removes the service and the binary; with --purge, also configuration, data and the xbnc user.

Backups, databases and migration

Backups from the panel

In Settings → Backups (superadministrators): make a backup now, download it, delete it, restore it and schedule automatic backups (every N hours, keeping the last M). Each backup is a .tar.gz with the database (a live copy with an integrity check), the secret key and the installed plugins, stored in /var/lib/xbnc/backups/.

Restoring leaves the backup staged (one can also be uploaded from your computer) and it is applied when xBNC restarts; until then it can be cancelled. Backups contain the secret key: keep them encrypted.

Backups from the command line

sudo ./scripts/backup.sh                       # in /var/backups/xbnc, keeps 14
sudo ./scripts/backup.sh /mnt/backups --keep 30

# Every night at 3:30
echo '30 3 * * * root /path/to/xbnc/scripts/backup.sh >/dev/null' | sudo tee /etc/cron.d/xbnc-backup

# Restore (stops the service, keeps the current files as *.pre-restore-DATE, restores and starts)
sudo ./scripts/restore.sh /var/backups/xbnc/xbnc-20261108T033000Z.tar.gz
sudo ./scripts/restore.sh ARCHIVE --with-config    # also /etc/xbnc

# Only the database
sudo -u xbnc xbncd backup /var/lib/xbnc/copy.db

PostgreSQL

sudo systemctl stop xbnc
sudo -u xbnc xbncd migrate-to-postgres postgres://xbnc:password@127.0.0.1/xbnc
# in /etc/xbnc/xbnc.toml:
#   [database]
#   url = "postgres://xbnc:password@127.0.0.1/xbnc"
sudo systemctl start xbnc

The target database must be empty. With ?sslmode=require the connection is encrypted. Full-text search works the same with SQLite and PostgreSQL.

High availability

With ha = true, several servers run xBNC against the same PostgreSQL database. The one holding a PostgreSQL lock works and the others wait. If the active one goes down, its connection closes, the lock is released and another takes over within seconds. An active node that loses the database stops at once, so there are never two at the same time. Clients must be able to reach the active node (a floating IP or a DNS name that changes).

Migrating from another bouncer

xBNC brings over what you had in your previous bouncer if it uses the classic block configuration format (<User name>, <Network name>, <Chan #channel>).

WhatHow it ends up in xBNC
UsersWith their name. Administrators become admin (never superadmin). Users that already exist in xBNC are not touched.
PasswordsThose stored with salted SHA-256 keep working and move to Argon2id the first time each user signs in. Those in another format (MD5) get a new random password, shown only once.
NetworksServer, port, TLS, server password, nick, ident and real name. Disabled networks stay disabled. If there were several servers, the first is used (add the others afterwards as backup servers with /msg *status addserver). IPv6 servers are supported.
ChannelsWith their keys (encrypted). Disabled ones are not imported.
SASL, NickServ and commands on connectSASL goes to the network; the NickServ password and the commands on connect go to “Commands on connect” (encrypted). They need the data folder.
Client certificateImported if it is in the data folder and its key matches.
ModulesThe xBNC equivalent is turned on (table below).
LogsThe daily text files go into each network's history, with search. Can be repeated without duplicating.

From the panel (Users → Import from another bouncer, superadministrators): choose the .conf file or paste its contents, click “See what will be imported” (nothing is written) and then “Import”. From the panel only the configuration is read; SASL, NickServ, commands on connect, certificates and logs are in the data folder and are imported on the server.

# Preview: changes nothing
sudo -u xbnc xbncd import /path/to/the/folder
# Import, with the logs (which were in London time)
sudo -u xbnc xbncd import /path/to/the/folder --apply --logs --utc-offset Europe/London
sudo systemctl reload xbnc

# Logs of one particular network, separately
sudo -u xbnc xbncd import-logs alice libera /path/to/logs/libera --utc-offset America/New_York

The logs folder has one sub-folder per channel or conversation (#xbnc/, bob/) with YYYY-MM-DD.log files. Messages, actions (* nick …), notices (-nick- …) and joins, parts, quits, kicks, nick changes, topics and modes are recognised. With a named time zone (Europe/London) each day's daylight saving time is applied.

Module of the previous bouncerIn xBNC
keepnickKeep nick
simple_away, away, autoaway, awaynickAutomatic away (with the delay and message of simple_away)
autoopAutomatic op and voice (add the entries again: the syntax is different)
kickrejoinRejoin after a kick (with its delay)
stickychanSticky channels (with the list)
buffextrasEvents in the history
logLog files
route_repliesReplies to whoever asked
certClient certificate
alias, watchAliases and Watch words (add the entries again)
block_motdHide the MOTD
ctcprepliesCTCP replies (with the reply to VERSION)
perform, nickserv, saslCommands on connect and the network's SASL
chansaver, savebuff, playback, clientbuffer, webadmin, controlpanel, fail2ban, lastseen, notes, clearbufferonmsg, simple_disconnect, missingmotd, certauth, listsockets, identfile, partylineAlready included in xBNC, always on

Any other module shows in the preview as “no equivalent”. After importing: networks with several servers use only the first; networks without TLS are flagged; if you used the same ports, stop the previous bouncer before starting xBNC; and configure your clients with the user/network format.

Alerts and monitoring

Alerts

xBNC checks these conditions every 30 seconds. They are adjusted in Settings → Alerts (0 turns each one off):

AlertFires whenDefault
network_downA network has been trying to connect for more than N minutes without success (those you disconnect do not count). Up to 10,080 (a week).15 minutes
failed_loginsThere are N or more failed sign-ins (IRC, panel and tokens) in the last 10 minutes.30
memoryThe process memory goes over N MB.off
databaseThe database goes over N MB.off

When an alert starts and when it is resolved:

  • It appears (or disappears) on the panel's Home page for administrators.
  • It is recorded in the audit log (action alert).
  • With “Notify administrators over IRC” (on by default), every connected administrator and superadministrator gets a notice from *status in their language.
  • With a webhook (only a superadministrator configures it), xBNC sends a POST with JSON. It has 10 seconds to answer with 2xx; if it fails, it is noted in the service log.
{
  "source": "xbnc",
  "server": "bnc.example.org",
  "state": "firing",
  "alert": "network_down",
  "subject": "alice/libera",
  "message": "Network alice/libera has been disconnected for more than 15 minutes",
  "since": 1762600000000,
  "text": "[xBNC bnc.example.org] firing: Network alice/libera ...",
  "content": "[xBNC bnc.example.org] firing: Network alice/libera ..."
}

state is firing or resolved. The text and content fields make it work as it is with the incoming webhooks of Slack and Mattermost (text) and Discord (content).

Prometheus metrics

GET /api/v1/metrics in Prometheus format, with an administrator's session or a token with the “metrics” scope:

scrape_configs:
  - job_name: xbnc
    scheme: https
    metrics_path: /api/v1/metrics
    authorization:
      credentials: xbnc_...
    static_configs:
      - targets: ["bnc.example.org:8443"]
MetricTypeWhat it measures
xbnc_infogaugeServer version (label version).
xbnc_uptime_secondsgaugeSeconds since it started.
xbnc_usersgaugeAccounts.
xbnc_networksgaugeConfigured networks.
xbnc_networks_running{state}gaugeConnections by state: connected, connecting, disconnected, reconnecting.
xbnc_irc_clientsgaugeConnected IRC clients.
xbnc_connectionsgaugeIRC connections open to the bouncer, also unauthenticated ones.
xbnc_blocked_ipsgaugeAddresses blocked after failures.
xbnc_server_bytes_received_total, xbnc_server_bytes_sent_totalcounterBytes received from and sent to the IRC servers.
xbnc_client_bytes_received_total, xbnc_client_bytes_sent_totalcounterBytes received from and sent to the clients.
xbnc_server_lines_totalcounterLines received from the servers.
xbnc_messages_stored_totalcounterMessages stored in the history.
xbnc_reconnects_totalcounterReconnections to IRC servers.
xbnc_client_logins_totalcounterSuccessful IRC client logins.
xbnc_failed_logins_totalcounterFailed logins (IRC and panel).
xbnc_history_messagesgaugeMessages stored in the history.
xbnc_database_bytesgaugeSize of the database.
xbnc_resident_memory_bytesgaugeProcess memory.
xbnc_log_lines_dropped_totalcounterLines of the log module dropped because the disk could not keep up.
xbnc_dcc_relays_totalcounterDCC sends and chats relayed by xBNC.
xbnc_dcc_bytes_totalcounterDCC bytes relayed by xBNC.
xbnc_alerts_firing{alert}gaugeActive alerts by type.

The same counters are shown on IRC with /msg *status traffic (administrators).

Grafana and Prometheus rules

In packaging/monitoring/:

  • grafana-dashboard.json: a dashboard ready to import with connected and disconnected networks, IRC clients, users, memory, uptime, messages per second, traffic, networks by state, logins, reconnections, storage, history and active alerts.
  • prometheus-rules.yml: alerts XbncDown, XbncNetworksDisconnected, XbncManyReconnects, XbncFailedLogins, XbncBlockedAddresses, XbncMemoryHigh, XbncDatabaseLarge, XbncLogLinesDropped and XbncInternalAlert.

Health check

GET /api/v1/health answers without a session; it is meant for load balancers and external monitors.

REST API

Everything the panel does is in the API. Base: /api/v1, at the same address as the panel. JSON format; errors are {"error": "code", "message": "text"} with the right HTTP status. The OpenAPI 3.1 specification is at GET /api/v1/openapi.json (and in docs/openapi.json); a test checks that it documents every route.

API tokens

Create them in Your account → API tokens. The full token (xbnc_ and 43 characters) is only shown when created: xBNC stores its SHA-256. Send it in the header Authorization: Bearer xbnc_…; with a token no CSRF is needed.

ScopeCan
readGET requests only.
writeEverything your account can do, except signing out, changing the password, two-step verification, passkeys and creating or revoking tokens (those need a panel session and answer 403 session_required).
metricsGET /metrics only.

They expire after 30, 90 or 365 days, or never; at most 50 per user. The list shows the prefix, the last use and the last IP. A wrong token counts as a failed attempt (the same blocks as IRC and the panel). Creating and revoking is recorded in the audit log.

Panel session

  1. POST /auth/login with {"username", "password"} (and totp_code if the account has two-step verification). The answer brings the xbnc_session cookie and, in the body, csrf.
  2. Send the cookie with every request, and with POST, PUT, PATCH and DELETE the header X-CSRF-Token with that value (GET /auth/me returns it again).
  3. Requests with an Origin header from another site are refused; curl and scripts do not send it.

Examples

B=https://bnc.example.org:8443/api/v1
T="Authorization: Bearer xbnc_..."

# Networks and their state
curl -s -H "$T" $B/networks

# Search the history of network 1
curl -s -H "$T" "$B/networks/1/history?target=%23xbnc&q=meeting&limit=20"

# Reconnect a network (write token)
curl -s -X POST -H "$T" -H 'Content-Type: application/json' -d '{"action":"jump"}' $B/networks/1/control

# Turn on a module
curl -s -X PUT -H "$T" -H 'Content-Type: application/json' \
  -d '{"enabled":true,"config":{"delay":60,"summary":true}}' $B/networks/1/modules/away

# Languages and the Spanish panel texts (no session needed)
curl -s $B/i18n
curl -s $B/i18n/es

# With a session instead of a token: create a user and give them a network
curl -s -c jar -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"your-password"}' $B/auth/login > login.json
CSRF=$(python3 -c 'import json;print(json.load(open("login.json"))["csrf"])')
curl -s -b jar -H "X-CSRF-Token: $CSRF" -H 'Content-Type: application/json' \
  -d '{"username":"bruno","password":"a-long-password","role":"user","max_networks":3}' $B/users
curl -s -b jar -H "X-CSRF-Token: $CSRF" -H 'Content-Type: application/json' \
  -d '{"user_id":2,"name":"libera","host":"irc.libera.chat","port":6697,"tls":true,"nick":"bruno",
       "sasl_username":"bruno","sasl_password":"secret","channels":["#xbnc","#xbnc-dev"]}' $B/networks

All routes

MethodRouteWhat it doesWho
GET/healthHealth check.no session
GET/i18nInstalled languages (code, name, share translated, translators) and the server's default language.no session
GET/i18n/{code}Panel texts in one language: English text → translation.no session
GET, POST/setupFirst setup with the one-time code.no session
POST/auth/loginSign in: {username, password, totp_code}. Returns the xbnc_session cookie and the csrf field.no session
POST/auth/logoutClose the session.session
GET/auth/meYour account, your permissions and the CSRF token.session or token
GET/auth/methodsWays of signing in the server offers (passkeys, OpenID Connect, LDAP).no session
GET/auth/oidc/startRedirects to the OpenID Connect provider.no session
GET/auth/oidc/callbackReturn from the OpenID Connect provider.no session
POST/auth/passkey/optionsStarts signing in with a passkey (WebAuthn challenge).no session
POST/auth/passkeySigns in with the passkey's answer.no session
GET/dashboardSummary of the Home page (global for administrators).session or token
GET/presetsWell-known networks for the wizard.session or token
GET, POST/networksList (?all=true: every network, administrators) and create networks: {user_id, name, host, port, tls, nick, username, realname, server_password, sasl_username, sasl_password, channels, on_connect}.create: user or above
GET, PATCH, DELETE/networks/{id}Show with live state, edit (an omitted secret is kept, "" deletes it) and delete.owner or admin
POST/networks/{id}/control{action: connect | disconnect | jump}.owner or admin
GET, POST/networks/{id}/channelsChannels, with disabled and position (joining order); add {name, key}.owner or admin
DELETE/networks/{id}/channels/{name}Remove a channel (encode # as %23).owner or admin
PUT/networks/{id}/channels/{name}/historyRetention of the channel: {retention_days: null (server setting) | -1 (do not store) | 0 (forever) | N days}.owner or admin
DELETE/networks/{id}/clients/{client}Disconnect a device.owner or admin
GET/networks/{id}/conversationsConversations with their number of messages.owner or admin
GET, DELETE/networks/{id}/historySearch: target, q, sender, before_id, from, to, limit (up to 500), newest first. DELETE deletes (?target= one conversation).owner or admin
GET/networks/{id}/history/exportDownload: ?format=txt|json and ?target=.owner or admin
GET/networks/{id}/chatWebSocket of the panel chat: the panel as one more IRC client. Messages {line: "PRIVMSG #xbnc :hello"} in both directions.owner only, session
GET, PUT/networks/{id}/on-connectCommands on connect: {commands: "/msg X login …"}; empty deletes them.owner or admin
GET, PUT, DELETE/networks/{id}/certClient certificate: SHA-1/256/512 fingerprints; upload {certificate, private_key} in PEM; delete. The key is never returned.owner or admin
POST/networks/{id}/cert/generateGenerate a new certificate.owner or admin
GET/modulesModule catalogue: ids, settings with type and limits.session or token
GET/networks/{id}/modulesState of a network's modules (secrets empty, plus secrets_set).owner or admin
PUT/networks/{id}/modules/{module}{enabled, config}: validated and applied at once. An empty secret keeps the stored one; null deletes it.owner or admin
GET/networks/{id}/pluginsPlugins of a network.owner or admin
PUT/networks/{id}/plugins/{plugin}{enabled, config}.owner or admin
GET/mentionsMessages that mention you, newest first (?before=ms&limit=).session or token
GET/notesYour notes.session or token
PUT, DELETE/notes/{key}Save {text} (up to 400 characters) or delete a note.session or token
PUT/me/language{language: code of an installed language (en, es…) | ""} (empty: the server's).session or token
PUT/me/timezone{timezone: "Europe/London"} (IANA name; empty: UTC).session or token
PUT/me/passwordChange your password (closes your other sessions).session only
POST/me/totp/setupStart turning on two-step verification (secret and QR code).session only
POST/me/totp/enableTurn it on with a code.session only
POST/me/totp/disableTurn it off.session only
GET, POST/me/passkeysYour passkeys; register one.session only
POST/me/passkeys/optionsStart registering a passkey.session only
DELETE/me/passkeys/{id}Remove a passkey.session only
GET, DELETE/me/identitiesLinked external accounts (OpenID Connect, LDAP); unlink.session only
POST/me/identities/oidcStart linking your OpenID Connect account.session only
GET, PUT/me/notificationsNotification settings (the Telegram token comes back hidden).session or token
POST/me/notifications/testSend a test notification to every channel.session or token
GET, POST, DELETE/me/pushDevices with push; subscribe {endpoint, keys: {p256dh, auth}, label}; remove {endpoint}.session or token
GET/push/keyVAPID public key for Web Push.session or token
GET/diagnosticsTest report: what each of your networks supports and how xBNC handled it (no secrets or messages).session or token
GET, POST/tokensYour tokens; create {name, scope: read | write | metrics, expires_days: 0–3650} (0: never expires). The token only comes in the answer.session only
DELETE/tokens/{id}Revoke a token.session only
GET/sessionsPanel sessions (?all=true: all of them, administrators).session
DELETE/sessions/{id}Close a session.session
GET, POST/usersList and create {username, password, role, max_networks, max_clients}.see: moderator; create: admin
GET, PATCH, DELETE/users/{id}Show, change {role, enabled, max_networks, max_clients, max_history_mb} (null removes the limit) and delete.depends on the role
POST/users/{id}/passwordReset the password (closes their sessions).admin
GET/users/{id}/usageNetworks, clients and history space used against their quotas.moderator
GET/securitySign-ins and blocked IPs.moderator
DELETE/security/blocked/{ip}Unblock an IP.admin
GET/auditAudit log.moderator
GET, PUT/settingsLive settings.see: admin; edit: superadmin
GET/systemVersion, database, listeners and counters.admin
POST/system/restartRestart xBNC (the service starts it again).superadmin
GET/alertsActive alerts.admin
GET/metricsMetrics in Prometheus format.admin or metrics token
GET/wsWebSocket of the Home page: a {type: "stats"} message every 2 seconds with the state of the networks and, for administrators, memory, CPU, clients, connections, blocked IPs and counters; {type: "logout"} when the session ends.session only
GET, POST/backupsBackups, staged restore and settings of the automatic ones; make a backup now.superadmin
GET, DELETE/backups/{name}Download or delete a backup.superadmin
POST/backups/{name}/restoreStage the restore of a backup on the server.superadmin
POST, DELETE/backups/restoreUpload a backup (.tar.gz) and stage it; cancel the staged restore.superadmin
GET/pluginsInstalled plugins.session or token
POST/pluginsInstall or update: the body is the .wasm (application/wasm, up to 4 MB).superadmin, session
POST/plugins/inspectCheck a .wasm without installing it.superadmin, session
PATCH, DELETE/plugins/{id}{enabled} on the server and approved domains; uninstall.superadmin, session
GET/plugins/catalogCatalogue: official plugins and signed remote catalogue.session or token
POST/plugins/catalog/{id}One-click install from the catalogue.superadmin, session
GET, PUT/plugins/devDevelopment mode: state and folder; turn it on or off.superadmin, session
GET/plugins/logsWebSocket with the plugins' log: last lines and new ones.superadmin, session
GET/updatesInstalled and latest published version, whether it can be updated from the panel and the result of the last update.admin
POST/updates/checkCheck now whether there is a new version.admin, session
POST/updates/installDownload, verify and install the signed package of the latest version.superadmin, session
POST/import/previewPreview of the import: {config: "file contents"}. Writes nothing.superadmin, session
POST/import/applyImport.superadmin, session

“owner or admin”: the owner of the network or an administrator. “session only”: not accepted with a token. Passwords are never returned: networks say has_sasl_password and has_server_password.

Versions and updates

New version notices

With [updates] repository (or url) in xbnc.toml, xBNC checks the latest published version once a day and says so on the Home page and in Settings → Updates. On its own it downloads and installs nothing.

Updating

In this beta, xBNC is installed from the source code: it is updated with scripts/update.sh (see Installation).

Updating from the panel (package installations)

xBNC also includes one-click updates for when it is installed from signed .deb or .rpm packages. With [updates] public_key, Settings → Updates shows “Update now” to superadministrators. xBNC downloads the package and its .sig signature, checks the Ed25519 signature and leaves it in /var/lib/xbnc/updates/. The system service xbnc-update checks everything again as root without trusting that folder (it verifies with the key in /etc/xbnc/xbnc.toml, which xBNC cannot change, and refuses equal or older versions), installs it with dpkg or rpm and xBNC restarts. A package without a signature, signed with another key or modified is never installed. Without public_key, or without a package installation, the panel explains how to update by hand.

Publishing signed releases

# Once: create the key (the private one is secret; the public one goes in [updates] public_key)
xbncd release keygen --out xbnc-release.key

# For each release: sign the packages (writes FILE.sig)
XBNC_RELEASE_KEY="$(cat xbnc-release.key)" xbncd release sign xbnc_1.0.0_amd64.deb xbnc-1.0.0-1.x86_64.rpm

# Check
xbncd release verify --public-key "BASE64..." xbnc_1.0.0_amd64.deb

The repository includes a workflow that builds and signs .deb and .rpm packages and a container image (.github/workflows/release.yml). It needs the key as a repository secret (XBNC_RELEASE_KEY) and the public key as a variable (XBNC_RELEASE_PUBLIC_KEY). During the beta xBNC is published only as a source .zip, so the workflow only runs when it is started by hand from the Actions tab.

Publishing on GitHub

scripts/publish-github.sh publishes the source code, a release with the .zip and its checksum, and the wiki as a website of its own (GitHub Pages), from the maintainer's computer and with the maintainer's GitHub account. The complete guide is docs/PUBLISHING.md.

Writing plugins

A plugin is a WebAssembly file with a JSON manifest inside. There are SDKs for C (sdk/c, with the 18 official plugins as examples and the xbnc_util.h helper) and for Rust (sdk/rust). Any language that compiles to WebAssembly without WASI works if it follows the interface.

Manifest

FieldRequiredWhat it is
idyes1–32 characters a-z 0-9 _ -, starting with a letter. Cannot be status, notes, watch, events, xbnc, bnc, plugin or plugins. It is the name of its service (*id).
nameyesVisible name, 1–60 characters.
versionyesVersion, 1–20 characters.
descriptionnoWhat it does.
usagenoHow it is used (up to 1000 characters). The panel shows it under “How to use it” and xBNC answers with it if the plugin does not answer a command.
authornoAuthor.
homepagenoPage of the plugin (https://).
abinoInterface version (1).
permissionsnoList of send, raw, timer, presence and http.
http_domainswith http1 to 10 domains it asks permission to connect to.
timer_secsnoWith timer: receive the timer every 5–60 seconds instead of every minute.
settingsnoUp to 20 settings: {key, type (bool, int, text or lines), label, help, default, min, max}. The panel shows them as a form.
translationsnoBy language code (es, pt, fr…): name, description, usage and, per setting, label and help. The texts at the root of the manifest are the base, in English.
{
  "id": "hello",
  "name": "Hello",
  "version": "1.0.0",
  "description": "Answers !hello.",
  "usage": "In a channel: !hello",
  "author": "your-name",
  "permissions": ["send"],
  "settings": [
    { "key": "greeting", "type": "text", "label": "Greeting", "default": "Hello!" },
    { "key": "lang", "type": "text", "label": "Language", "help": "Empty: your xBNC language.", "default": "" }
  ],
  "translations": {
    "es": {
      "name": "Hola",
      "description": "Responde a !hello.",
      "usage": "En un canal: !hello",
      "settings": { "greeting": { "label": "Saludo" } }
    }
  }
}

Interface (ABI 1)

The module exports:

ExportSignatureWhat it does
memorymemoryLinear memory of the plugin.
xbnc_alloc(len: i32) -> i32Reserves len bytes for the event and returns their address.
xbnc_on_event(ptr: i32, len: i32) -> i64Receives the event (UTF-8 JSON) and returns (address << 32) | length of the answer (JSON), or 0 if it does not answer.

And it can only import from the xbnc module (neither WASI nor anything else):

ImportWhat it does
log(ptr, len)Writes a line to the log (at most 10 per event and 500 characters).
now_ms() -> i64Milliseconds since 1970.
kv_get, kv_set, kv_del, kv_listKey-value store of the plugin on that network: keys up to 200 bytes, values up to 64 KB, 16 MB and 200,000 keys in total, up to 2,000 writes per event (saved together).

Events

{
  "event": "message",
  "network": "undernet", "nick": "y2k", "connected": true, "clients": 1, "lang": "en",
  "config": { "trigger": "!roll" },
  "state": null,
  "command": "PRIVMSG", "conversation": "#xbnc", "channel": true,
  "from": "bob", "from_mask": "bob!b@host", "text": "!roll 2d6",
  "action": false, "outgoing": false
}
EventWhenOwn fields
loadThe plugin starts on the network (when turned on or when xBNC starts).—
connectedxBNC finishes connecting to the network.—
messageA message or notice in a channel or private conversation, yours too.command, conversation, channel, from, from_mask, text, action, outgoing
commandSomeone writes /msg *id text.text
timerEvery minute, or every timer_secs (timer permission).—
join, part, kick, quit, nick, namesMovements in the channels (presence permission).the data of each movement
httpThe answer to a web request (http permission).id, status, body, error

Common fields: network, nick (your current nick), connected, clients (connected clients), lang (your language code: en, es or any installed language), config (its settings) and state (what it saved).

Answer

{
  "actions": [
    { "reply": "text in the same conversation" },
    { "tell": "text for you only, as *id" },
    { "privmsg": { "target": "#channel", "text": "..." } },
    { "notice": { "target": "nick", "text": "..." } },
    { "raw": "MODE #channel +o bob" },
    { "http": { "id": "weather", "method": "GET", "url": "https://api.open-meteo.com/..." } }
  ],
  "state": { "whatever you want to keep": 1 }
}
  • Single-line texts. Actions without permission or badly formed are dropped and noted in the log.
  • state replaces the plugin's saved state on that network; it comes with every event and survives restarts. The instance is kept between events while the network is running.
  • raw can never send QUIT, PASS, USER, NICK, CAP, AUTHENTICATE, OPER, SERVER or SQUIT.
  • http: ('id', 'url', 'method', 'body'). https only, to approved domains; methods GET, POST, PUT and DELETE; body up to 16 KB (text or JSON); up to 3 requests at a time and 30 per minute per plugin and network. The answer arrives as an http event with id, status, body (up to 256 KB) and, if it failed, error.

Limits

LimitValue
File size4 MB
Memory16 MB
CPU per eventabout 20 million instructions (50 million when starting)
Answer64 KB and 20 actions; 5 messages per event
Saved state64 KB
Key-value store16 MB and 200,000 keys per plugin and network
Failures in a row before it stops on a network5

C SDK

sdk/c/xbnc_plugin.h takes care of the interface. Main functions:

FunctionWhat it does
void on_event(const xbnc_event *ev)The function you write: it receives each event.
xbnc_get(ev, "field", buf, max)Reads a text field of the event.
xbnc_is(ev, "field")Reads a yes/no field.
xbnc_config(ev, "key", buf, max), xbnc_config_int, xbnc_config_boolReads a setting.
xbnc_get_state(ev)The saved state (JSON).
xbnc_reply(text), xbnc_tell(text)Answer in the conversation or to you only.
xbnc_privmsg(target, text), xbnc_notice(target, text)Message or notice to whoever you want.
xbnc_raw(line)IRC command (raw permission).
xbnc_http_get(id, url), xbnc_http_post_json(id, url, json)Web requests (http permission).
xbnc_state(json)Save the state.
xbnc_kv_get, xbnc_kv_set, xbnc_kv_del, xbnc_kv_list (and the _str variants)Key-value store.
xbnc_log(text)Write to the log.

sdk/c/xbnc_util.h adds what the official plugins use: language of the answer (u_lang), reading commands with aliases (u_command), walking the store (u_next_key), dates, time zones with European daylight saving time and text building.

#include "xbnc_plugin.h"

void on_event(const xbnc_event *ev) {
    static char kind[16], text[256], greeting[128];
    xbnc_get(ev, "event", kind, sizeof kind);
    if (!xbnc_streq(kind, xbnc_strlen(kind), "message")) return;
    xbnc_get(ev, "text", text, sizeof text);
    if (!xbnc_streq(text, xbnc_strlen(text), "!hello")) return;
    xbnc_config(ev, "greeting", greeting, sizeof greeting);
    xbnc_reply(greeting[0] ? greeting : "Hello!");
}
cd sdk/c
clang --target=wasm32 -O2 -nostdlib -fno-builtin -I. \
  -Wl,--no-entry -Wl,--export-dynamic -Wl,--strip-debug -Wl,-z,stack-size=65536 \
  -o hello.raw.wasm hello.c
xbncd plugin pack hello.raw.wasm hello.json -o hello.wasm
xbncd plugin check hello.wasm
sudo -u xbnc xbncd plugin install hello.wasm

# The official examples: make (or make bundle to copy them to the catalogue)

It needs clang and wasm-ld (LLVM 15 or later) and llvm-objcopy (or xbncd plugin pack).

Rust SDK

The xbnc-plugin crate (sdk/rust) provides the Plugin trait, the Event type (with kind(), text(), conversation(), is_channel(), outgoing(), config_str(), state()…), Response (with reply(), tell(), state()…) and the plugin! macro. Included example (sdk/rust/examples/stats.rs):

//! Example plugin: counts messages per channel and answers "!stats".
//! The counts survive restarts thanks to the persisted state.
//!
//! Build: cargo build -p xbnc-plugin --release --target wasm32-unknown-unknown --example stats
//! then: xbncd plugin pack target/wasm32-unknown-unknown/release/examples/stats.wasm \
//!         sdk/rust/examples/stats.json -o stats.wasm

use xbnc_plugin::{plugin, Event, Plugin, Response};

#[derive(Default)]
struct Stats;

impl Plugin for Stats {
    fn on_event(&mut self, ev: &Event) -> Response {
        let mut r = Response::new();
        let mut counts = ev.state().as_object().cloned().unwrap_or_default();
        match ev.kind() {
            "message" if ev.is_channel() && !ev.outgoing() => {
                let chan = ev.conversation().to_lowercase();
                let n = counts.get(&chan).and_then(|v| v.as_u64()).unwrap_or(0) + 1;
                counts.insert(chan, n.into());
                if ev.text().trim() == ev.config_str("trigger") {
                    r.reply(format!("{} messages counted in {}", n, ev.conversation()));
                }
                r.state(counts.into());
            }
            "command" => {
                if counts.is_empty() {
                    r.tell("Nothing counted yet.");
                }
                let mut v: Vec<_> = counts.iter().collect();
                v.sort_by_key(|e| std::cmp::Reverse(e.1.as_u64()));
                for (chan, n) in v.into_iter().take(10) {
                    r.tell(format!("{chan}: {n}"));
                }
            }
            _ => {}
        }
        r
    }
}

plugin!(Stats);

Development mode

In Plugins → Development (superadministrators): with the mode on, xBNC watches /var/lib/xbnc/plugins/dev/. A .wasm saved there is checked and installed as soon as it changes and every network reloads it. If you put name.json next to name.wasm, it packs it for you (no need for xbncd plugin pack). A new plugin is turned on in the networks of whoever turned the mode on. The results and what the plugin writes are shown in the page's live log.

sudo cp hello.wasm hello.json /var/lib/xbnc/plugins/dev/

Publishing your own catalogue

Besides the official ones, xBNC can offer the plugins of a signed remote catalogue ([catalog] in xbnc.toml). The index is a JSON {"plugins": [{"id", "version", "url", "sha256", "size", "manifest"}]} and its Ed25519 signature in base64 is published in index.sig. Each plugin is checked against the signed SHA-256 before it is installed.

xbncd plugin catalog-keygen -o catalogue.key        # once; the public key goes in [catalog] public_key
xbncd plugin catalog-entry hello.wasm https://plugins.example.org/hello.wasm   # entry for the index
xbncd plugin catalog-sign --key catalogue.key index.json                       # writes index.json.sig

Translating xBNC

xBNC can speak any language. Every text it shows lives in one file per language: the web panel, the sign-in page and everything it says over IRC (*status, its help, notices and module messages). You translate one file and the language appears in every language selector of xBNC, without programming or compiling anything.

The files

FileWhat it is
locales/en.jsonEnglish. It is the base: it holds every text of xBNC.
locales/es.jsonSpanish.
locales/<code>.jsonAny other language: pt.json, fr.json, pt-BR.json…

English and Spanish are built into the program. Other languages, and corrections, go in /etc/xbnc/locales/ (locales_dir in xbnc.toml, or the XBNC_LOCALES_DIR variable). xBNC reads them at start and on reload (sudo systemctl reload xbnc or /msg *status rehash).

{
  "language": {
    "code": "pt",
    "name": "Português",
    "english_name": "Portuguese",
    "locale": "pt-PT",
    "translators": ["Your name or nick"]
  },
  "irc": {
    "connected_to": "Ligado a {0} como {1}.",
    "help.group.channels": "Canais"
  },
  "panel": {
    "Joined": "Dentro",
    "Delete {username}": "Apagar {username}"
  }
}
PartWhat it is
languagecode: the language code (two or three letters, with a region if needed: pt, pt-BR); the file has the same name. name: the name of the language in the language itself, as shown in the selectors. locale: for dates and numbers. translators: shown by xbncd locales list.
ircWhat xBNC says over IRC. The key on the left is a name; translate only the text on the right.
panelThe web panel. The key on the left is the English text; translate only the text on the right.

Step by step

  1. Copy en.json as <code>.json (for example pt.json).
  2. Change the language part: code, name, English name, locale and your name.
  3. Translate the texts on the right. Never change the keys on the left.
  4. Check the file: xbncd locales check pt.json (add --missing to see what is left).
  5. Try it: copy it to /etc/xbnc/locales/ and reload xBNC. It appears straight away on the sign-in page, in Your account, in Settings and with /msg *status language pt.
  6. Send it to the project so it is included in the next version.

Rules for the texts

In the English textWhat to do
{0}, {1}… (IRC)Values xBNC fills in (a nick, a number…). Keep all of them; you can move them inside the sentence.
{name}, {count}… (panel)The same: keep them with exactly the same name.
<b>…</b>, <u>…</u> (IRC)Bold and underline in the IRC client. Keep them around the same words, each <b> with its </b>.
Commands and settings (/msg *status help, set away delay 60)Do not translate them: they are what people type.
$1, $nickVariables of the alias module: keep them as they are.
Disabled|network state (panel key)What follows the | is a hint about where the text is shown (for gender, for example). Translate only the text, without the hint.
PluralsTexts ending in .one are used with 1 and those ending in .other with any other number (unit.network.one = “{{0}} network”).
Line breaksNot allowed in IRC texts.

A text that breaks these rules is ignored and shown in English; xbncd locales check reports it as a problem. This also makes it impossible for a language file to inject IRC commands (line breaks are forbidden).

Correcting a built-in language

A file for a language that already exists (for example es.json in /etc/xbnc/locales/) only needs the texts you want to change: the rest stays as it is.

What a file does not translate

  • Plugin replies. Their name, description and settings are translated in their manifest (translations). What a plugin answers in a channel is written in the plugin itself; in a language it does not include, it answers in English. The official plugins speak English and Spanish.
  • The installer speaks English and Spanish.
  • The server log is in English.

Translating this wiki

The wiki is a project of its own, separate from the app (xbnc-wiki), so that anyone can read it without installing xBNC. It is generated with build.py from one content file per language (content/en.py is the base) and the xBNC source code. To translate it, copy en.py as <code>.py and translate the texts between quotes without touching the HTML tags or calls such as code(…). The tables of modules, plugins and commands translate themselves with xBNC's language files. When the wiki is built, each language appears in the selector at the top.

For developers

  • In the panel, texts are written in English inside t("…"), tk("…") or tx("…"). npm run i18n:check (in web/) lists what is missing from en.json, and node scripts/i18n-check.mjs --sync adds it.
  • In the server, the text is added to the irc section of locales/en.json and used with self.t("key", &[]), tr!(self, "key", arg1, arg2) or i18n::tr(lang, "key", &[…]). The tests check that every key used exists and that es.json translates all of them with the same placeholders.

The complete guide is in TRANSLATING.md, in the source code.

IRCv3

xBNC negotiates with each server the capabilities it offers and xBNC wants, and offers each client its own. Each client only gets what it asked for: a classic client receives the usual messages (for example, the short JOIN and only the highest rank in the user list).

With the servers

CapabilityWhat for
server-timeReal time of each message.
message-tagsTags on messages.
multi-prefixEvery rank of a user (@+), not only the highest.
extended-joinJoins carry the account and the real name.
away-notifyNotices when someone goes away or comes back.
account-notifyNotices when someone identifies or stops being identified.
account-tagEach message says the account of whoever sent it.
chghostChanges of user or host without leaving and joining.
userhost-in-namesThe user list with nick!user@host.
invite-notifyInvitations of other people to your channels.
setnameReal name changes.
cap-notifyThe server adds or removes capabilities on the fly.
saslSASL identification (PLAIN and EXTERNAL towards the server; PLAIN from the clients).

With the clients

CapabilityWhat for
server-timeReal time of each message.
message-tagsTags on messages.
echo-messageYour own messages come back confirmed by the server.
batchGroups of messages (history, playback).
saslSASL identification (PLAIN and EXTERNAL towards the server; PLAIN from the clients).
draft/chathistoryThe client asks for the history it needs (CHATHISTORY).
draft/event-playbackThe history includes joins, parts, kicks and nick changes as real events.
draft/read-markerRead markers shared between devices.
soju.im/bouncer-networksAll your networks in a single connection.
soju.im/bouncer-networks-notifyNotices of changes in your networks.
extended-joinJoins carry the account and the real name.
away-notifyNotices when someone goes away or comes back.
account-notifyNotices when someone identifies or stops being identified.
account-tagEach message says the account of whoever sent it.
chghostChanges of user or host without leaving and joining.
multi-prefixEvery rank of a user (@+), not only the highest.
userhost-in-namesThe user list with nick!user@host.
invite-notifyInvitations of other people to your channels.
setnameReal name changes.
cap-notifyThe server adds or removes capabilities on the fly.

With CAP LS 302, sasl=PLAIN is announced. If the server does not support a capability a client asks for, xBNC still offers it when it can build it itself (for example, the extended JOIN with the data it knows). The test report records, per network, what the server offers (caps_offered), what xBNC turned on (caps_acked) and what each client asked for.

Test report

In Your account → Test report → Download report (or GET /api/v1/diagnostics) you download a JSON file with what is needed to diagnose a problem with a network, without passwords, messages, other people's nicks or the IPs of your devices.

FieldContents
report, formatxbnc-diagnostics, format version 1.
version, brandxBNC version and “xBNC 1.0.0 Made by y2k”.
os, arch, databaseSystem, architecture and database (sqlite or postgresql).
started_at, generated_atWhen xBNC started and when the report was generated.
timezone, languageYour time zone and language.
countersServer counters (traffic, messages, reconnections, logins).
networks[]Per network: name, host, port, tls, enabled, whether it has SASL or a server password, saved_channels, active modules and plugins, and live.
networks[].livestate, server, network_name, connected_since, last_error, caps_offered, caps_acked, isupport, services_logged_in, host_hidden, channels_joined and clients (device and capabilities of each one).

Architecture

xBNC is a single program (xbncd) with the panel built in. The code is split into crates/xbnc-proto (IRC protocol without I/O: parser and serialiser with IRCv3 tags, prefixes, casemapping, ISUPPORT and CTCP) and crates/xbncd (service, CLI, API and panel), plus the plugin SDKs in sdk/, the panel (React and TypeScript) in web/ and the translations in locales/.

How it works inside

  • Each network of each user is an independent task that exclusively owns its connection to the server, the state of the network and the list of clients. Nobody else touches that state: there are no locks on the path of the messages.
  • Each client is a reading task and a writing task, with a bounded queue. If a client does not read, it is disconnected instead of piling up memory; when it comes back it gets what was pending.
  • The panel, the API and the command line never touch the IRC connections: they write to the database and send orders to the network tasks (state, control, join, part, reload, disconnect a client, alert, broadcast).
  • Every database access goes through a separate thread pool. The messages of every network go through a single writer thread that stores everything waiting (up to 512) in one transaction, keeping each network's order.
  • Modules are code of the program itself, called at specific points (server line, client command, timer, connection, client joining or leaving). Plugins receive their events from the network task and run in the blocking thread pool.
  • When a message arrives: it is parsed, the state is updated (nick, channels, members, modes, topic), it is stored with the server's time and an identifier, and it is sent to each client adapted to its capabilities.
  • Texts are looked up in the loaded languages (built in, plus the files of locales_dir) by key, with English as the fallback.

Modules of the code

Module (crates/xbncd/src)What it does
main.rsThe xbncd command: running the service and the command line (users, networks, backups, import, languages, releases, updates).
configReading and validating xbnc.toml.
db, sqlStorage: versioned migrations, users, networks, channels, messages, cursors, audit log and backups; sql gives SQLite (rusqlite) and PostgreSQL the same interface.
cryptoArgon2id, XChaCha20-Poly1305 encryption of the stored credentials and a Secret type that is never printed.
authThe login format, password checks, the brute-force limiter and the masking of lines in the logs.
utilIRC line codec (length limit, lossy UTF-8, protection against CR/LF injection) and time helpers.
network/The task of each network of each user: connection to the IRC server, state, history and attached clients. mods.rs and extra.rs hold the behaviour of the modules, status.rs and status_more.rs the *status, *notes and *watch commands, plugins.rs delivers events to plugins, dcc.rs decides which DCC offers go through xBNC and readmark.rs keeps the read markers.
downstream, bouncer, eventsClient connections: registration, CAP, SASL, PASS and relaying to the network task; soju.im/bouncer-networks; notices to clients when a network changes.
modulesCatalogue of the modules, schema and validation of their settings and the typed configuration per network.
performTurns client-style commands (/msg, /mode…) into IRC, for the commands on connect, aliases and scheduled tasks.
logsLog files, written in a separate thread.
certsClient certificates (generation, validation and fingerprints) and trusted server certificates.
fishFiSH encryption: Blowfish ECB and CBC and the DH1080 key exchange.
i18nLanguages: the built-in English and Spanish and the files of locales_dir.
netinfoLocal network addresses and the address shown for 0.0.0.0.
server, haShared context, the registry of network tasks, TLS listeners, maintenance and reloading; active/standby high availability with PostgreSQL.
rbac, settingsThe five roles and their permissions; history settings and limits editable live (database over xbnc.toml).
totp, webauthn, ssoTwo-step verification (RFC 6238), passkeys, OpenID Connect and LDAP.
tzTime zones with daylight saving time, with the database built in.
metrics, alertsTraffic and message counters, process memory and CPU; alert rules checked every 30 s, with notices in the panel, over IRC and by webhook.
notify, pushNotifications by Telegram, Discord, e-mail, your own web address and Web Push.
plugins, plugin_dev, catalogWebAssembly plugins: manifest, validation, the Wasmi engine with limits, instances and actions; development mode; the built-in catalogue and the signed remote one.
dccReading and rewriting DCC offers and the one-use relays.
httpA minimal HTTP/1.1 client (verified TLS, bounded size and time) for webhooks, notifications, plugins with web access and update checks.
updates, releaseNew version notices, downloading and checking packages, the xbncd apply-update helper and Ed25519 signatures.
backupBackups from the panel and restoring them.
importMigration from another bouncer: reading its configuration, module data, plan, applying it in one transaction and importing logs.
web/HTTP server (axum): the /api/v1 API, WebSocket, sessions, CSRF, headers and the panel files built into the binary.

The panel (web/) is a React and TypeScript application built with Vite. web/dist is built into the binary, so xBNC stays a single file. The official plugins are built into the binary too (crates/xbncd/catalog/).

State machines

They are kept separate:

  1. Connection to the network: Disconnected → Registering → Connected. While registering, xBNC negotiates the IRCv3 capabilities, SASL and the nick (with an alternative if it is taken). Times: 30 s to connect, 60 s to register, a PING after 90 s of silence and disconnection at 180 s. Reconnection with growing delays from 5 s to 5 minutes with ±20 % random variation, and at least 2 minutes after a throttling notice. If a server fails and the network has backup servers, the next one is tried after 1 second.
  2. Client session: registration (CAP, PASS or SASL, NICK, USER) with a time limit, and then a session attached to a network.
  3. User and bouncer: which network tasks exist and how many clients each user has.

The path of a message

  1. The network task reads a line from the server and parses it.
  2. It updates the state (nick, channels, members, modes, topic).
  3. If it is a PRIVMSG, NOTICE or ACTION from someone (or an event, with the Events module), it stores it in messages with the server's time (server-time) or the local one, and gives it the identifier msgid = x<id>.
  4. It sends it to each attached client adapted to its capabilities (removing the tags the client did not ask for) and moves each client's cursor forward.

What a client sends goes to the server, is stored as outgoing and is relayed to the user's other clients (and to the sender itself if it asked for echo-message).

History without duplicates

  • Each device (@device in the login, default if it is not given) has a cursor per network in client_cursors: the identifier of the last message delivered.
  • When it connects, it gets the messages after its cursor (up to playback_max, 1,000 by default). A new device gets the most recent new_client_playback (100 by default).
  • Cursors are saved when the device disconnects and every 5 seconds; they never go back.
  • Clients with draft/chathistory get no automatic playback: they ask for what they need with CHATHISTORY. Messages already read on another device (draft/read-marker) are not played back again.

Database schema

TableContentsSince migration
usersUser, Argon2id hash, role, limits and quotas, encrypted TOTP secret, language, time zone, bind host, last login and last connection.1 (2, 4, 8, 9, 10)
networksServer, port, TLS, nick, SASL, commands on connect, client certificate, backup servers and trusted certificates; passwords and keys encrypted.1 (3, 4, 10)
channelsSaved channels per network, with an encrypted key, retention, disabled and position.1 (8, 10)
messagesHistory: network, conversation, time, sender, command, text, outgoing and mention.1 (8)
client_cursorsLast message delivered per network and device.1
audit_logAdministrative actions.1
web_sessionsPanel sessions (token hash, CSRF, IP, browser, expiry).2
settingsSettings changed from the panel.2
login_eventsLogin attempts over IRC and in the panel (10,000 at most).2
module_configModules per network: enabled and validated JSON settings.4
notesEach user's notes.4
messages_ftsFull-text index of messages.text, kept in sync by triggers (SQLite; PostgreSQL uses its own text search).5
api_tokensAPI tokens: SHA-256 hash, prefix, scope, expiry, last use and IP.6
pluginsInstalled plugins: manifest, SHA-256, size, enabled on the server, approved web domains and the module itself.7 (8)
plugin_configPlugins per network: enabled, settings and saved state.7
plugin_kvEach plugin's key-value store per network.8
notify_settingsNotification settings of each user.8
push_subscriptionsWeb Push subscriptions of each user's browsers.8
read_markersLast message read in each conversation.8
passkeysPasskeys: credential, public key, counter, name and last use.8
user_identitiesOutside identities (OpenID Connect, LDAP) linked to local users.8

PostgreSQL has the same schema; it is supported from version 8 of the schema (PG_MIGRATIONS), and every new migration is added to both lists. Channel events (JOIN, PART…) are stored in messages with their command. Passwords imported from another bouncer with SHA-256 are kept as legacy-sha256$<hash>$<salt> until the first correct login, when they are replaced by Argon2id. Migrations are applied at start inside a transaction, and a binary refuses to open a newer schema than it knows.

Threat model

What xBNC protects, from whom and with which measure at each point. It is the guide for the external audit and for reviewing each change.

What is protected

AssetWhy it matters
Passwords of the xBNC accountsThey give access to the networks, the history and, for administrators, the whole server.
Network credentials (SASL, server password, channel keys, commands on connect, services passwords, FiSH keys, certificates)They allow impersonating the user on the networks.
History and logsPrivate conversations and channels: personal information.
Panel sessions and API tokensAs good as the password while valid.
The secret key (secret.key)Decrypts every stored credential.
The availability of the serviceIf it goes down, users lose messages and presence.
The reputation of the server's IPEvery connection leaves from it.

Who may attack

ActorWhat they controlWhat they try
Anyone on the internetConnections to the IRC, panel and DCC portsGuess passwords, exhaust resources, exploit the parser.
Someone on an IRC networkMessages, CTCP, DCC, channel names and nicksInject commands, make xBNC connect to internal networks, flood the history.
A malicious IRC serverEverything coming through a network's connectionThe above, plus malformed answers, huge lines and disconnection loops.
An xBNC userTheir account, networks, modules and pluginsRead other people's data, escalate to administrator, abuse the server.
An administratorOther people's users and networksEscalate to superadministrator, change global settings.
The author of a pluginThe code a superadministrator installsEscape the sandbox, read data, act without permission.
The author of a language fileA file an administrator copies to locales_dirInject IRC commands or HTML through the texts (refused: they are validated and treated as plain text).
A malicious websiteThe browser of a signed-in userCSRF, session theft, WebSocket hijacking.
Whoever gets the backupsThe .tar.gz filesCredentials and conversations.

Out of scope: someone who is already root on the machine, the server provider and failures of the operating system or of the IRC networks.

IRC client connections (6697/6667)

ThreatMeasure
Password brute forcePer-IP blocking shared with the panel and tokens; Argon2id; the same cost for users that do not exist.
Exhausting connections or memoryGlobal connection limit, 16 unauthenticated per IP, clients per user, registration time and bounded queues.
Huge or binary lines8703-byte limit per line and lossy UTF-8.
Command injection with CR/LFAny CR or LF cuts the line; texts of modules, plugins and language files are validated without control characters.
Plain text on the internetPorts without TLS only accept the local network or require insecure = true.
Credentials in the logsMasking of PASS, AUTHENTICATE, OPER and messages to services at every level.

Connections to the IRC networks

ThreatMeasure
Server impersonationTLS verified with the system store, with no option to turn it off; a trusted certificate only accepts its exact SHA-256 fingerprint.
Malformed answersOwn parser without unsafe, with tests and fuzzing.
History floodingConfigurable retention, optional events, bounded queues; the log module drops lines if the disk cannot keep up; hide flooding channels.
Disconnection loopsGrowing delays with random variation and a minimum wait after throttling notices.
Being thrown out for floodingRate limiting on the output.
Theft of the services passwordOn networks without NickServ it is never sent in automatic mode.

DCC

ThreatMeasure
Using the bouncer to reach internal networks (SSRF)Offers to internal addresses or ports below 1024 refused unless the user allows them.
Making xBNC connect to a third partyFor your own sends it only connects back to your device's IP.
Ports open indefinitelyOne port per offer, a single connection, a timeout, closing when idle and a size limit.
Hijacking the transferWhen receiving, the port only accepts the client's IP.

Web panel and API

ThreatMeasure
CSRFCSRF token in a header, SameSite=Strict cookie and Origin check.
XSSReact escapes all content; CSP without external or inline scripts; IRC texts are never interpreted as HTML; translations are plain text.
Session theftHttpOnly and Secure cookie; only the token's SHA-256 in the database; sliding expiry; changing the password closes the other sessions.
WebSocket hijackingOrigin check, the session revalidated every 30 s and no API tokens.
Privilege escalationPermissions checked on the server in every route; nobody changes their own role; there is always a superadministrator.
Seeing other users' dataOther people's networks answer 404, not 403.
API abuse and slowloris20 requests/s per IP, bounded bodies, 64 connections per IP and 4,096 in total, closing after 2 minutes without traffic, 10 s for the TLS handshake.
Leaked tokensScope, expiry, last use and IP visible, immediate revocation, only their SHA-256 stored.
Stolen first accessOne-time code, only in the service log.
Webhook to internal sitesOnly a superadministrator configures it; no redirects, no credentials in the URL, 10 s at most.

Plugins

ThreatMeasure
Escaping the sandboxWasmi interpreter without JIT, module validated when loaded, no WASI nor imports other than xbnc's, advanced WebAssembly features turned off.
Blocking or crashing the serverFuel per event, limited memory, execution that never grows the native stack, calls in separate threads, stopping after five failures.
Acting without permissionDeclarative actions checked against the permissions; dangerous commands always forbidden; maximum messages per event; web access only to approved domains and with limits.
Reading other people's dataEach instance only receives the events of its network; state and store separated per network.
File changed after installationIts SHA-256 is stored and it is not loaded if it does not match; files 0600.
Malicious plugin installedOnly a superadministrator installs, seeing permissions and fingerprint; recorded in the audit log; signed remote catalogue.

Import from another bouncer

ThreatMeasure
File crafted to create a superadministratorThe import never grants that role.
Weak inherited passwordsSalted SHA-256 compared in constant time and replaced by Argon2id; formats that cannot be checked get a random password.
Abusive paths or sizesFile up to 4 MB, limited nesting, validated names; logs are only read from the folders given.
Leaks in the previewThe preview includes no secrets.

Updates from the panel

ThreatMeasure
Fake package (compromised download server, DNS or network)Ed25519 signature over the package name and its SHA-256; the public key is only in xbnc.toml, owned by root.
Going back to a version with known flawsEqual or older versions than the installed one are refused.
A compromised xbnc process trying to install something as rootThe helper (xbnc-update.service) trusts nothing in the service's folder: it copies the package to /run (0700), checks the signature and the version on that copy, never follows symbolic links, validates the file name and uses each request only once.
Fake update requestOnly superadministrators, from a panel session (not with API tokens) and with CSRF; recorded in the audit log.
Endless downloads or redirects to plain text200 MB and 10 minutes at most; https:// only, 5 redirects at most.

Data at rest and backups

ThreatMeasure
Theft of the databaseNetwork credentials encrypted with XChaCha20-Poly1305; the key outside the database, with 0600 permissions checked at start.
Theft of a backup0600 file; it includes the key, so it must be stored encrypted.
Access from other users of the systemService run as the unprivileged xbnc user, 0700 folders and systemd hardening.

Accepted risks

  • The history is stored unencrypted in the database: encrypting it would prevent search and playback. It is protected by the system's permissions.
  • IRC clients have no second factor: the bouncer password is their only protection (hence per-IP blocking and Argon2id).
  • A superadministrator can install a plugin with permission to send messages on behalf of whoever turns it on: it is a decision of trust that the panel shows before installing.
  • Every connection to the networks leaves from the server's IP.
  • FiSH is old cryptography (Blowfish, without integrity); decrypted messages are stored in the history like the rest.

Review

Each version reviews these tables: a new feature that opens a surface not listed here is added to them. The tests behind each measure are in crates/xbncd/tests/ and in the unit tests of each module; fuzzing covers the IRC parser, DCC, the imported configuration file, search, and the manifests and execution of plugins (see Test environment).

Internal review of version 0.7. The whole code was reviewed with these tables as a guide (it does not replace the external audit):

  • SQL queries: all with bound parameters; the parts built with format! are only fixed column lists and clauses.
  • API authorisation: every route checks the session or token and the permission; network routes check the owner. Fixed: PATCH /users/{id} answered 404 or 403 to accounts without permission depending on whether the user existed.
  • External processes: the only one is dpkg or rpm in the update helper, with fixed arguments and the package already checked.
  • File paths: logs, plugins and updates build paths only from validated names; the update helper never follows symbolic links.
  • Denial of service: added the limit of unauthenticated IRC connections per IP, the panel's connection limits and idle time, and the limit of simultaneous Argon2id calculations.
  • Plugins: the interpreter moved to the loop execution mode after fuzzing found that the chained-call mode could exhaust the native stack in unoptimised builds.
  • unsafe code: only one call to mallopt at start (memory allocator setting).
  • Dependencies: cargo-deny in CI (licences and security advisories).

Internal review of version 1.0. The new code of 1.0 was reviewed and three points were fixed before publishing: on networks without services the password is never sent to whoever uses the nick “NickServ”; the FiSH key exchange has a limit of requests and stored keys; decrypted text cannot break lines. Language files are validated (placeholders, tags, no control characters or line breaks) and a text that fails is shown in English.

Performance

Load test (cargo run --release --example loadtest -- --users 1000 --clients 1 --rate 2 --seconds 60): a simulated IRC server sends messages to every network at once (the worst case) and each client measures how long it takes to receive them, going through the history, modules and plugins. Machine: 2 vCPU Xeon at 2.1 GHz, 7 GB of RAM, network disk, default configuration (SQLite).

Users and networksClientsMessages/s deliveredCPU (of one core)MemoryLatency p50 / p95 / p99Every network back after an outage
5005004925 %52 MB21 / 43 / 46 ms6.2 s
5005004,84743 %56 MB46 / 64 / 72 ms6.2 s
1,0001,0001,95117 %88 MB50 / 80 / 95 ms6.2 s
1,0002,0001,96811 %114 MB44 / 66 / 78 ms6.2 s
2,0002,0001,96819 %153 MB75 / 141 / 155 ms6.3 s
  • No message was lost in any test.
  • Latency grows with the size of the burst: with 2,000 networks receiving the same message at once, the last one is delivered about 150 ms after the first. With real traffic, spread over time, it is a few milliseconds.
  • Memory: about 45 KB per connected network and about 30 KB more per client, not counting the history (on disk).
  • Client logins (Argon2id) go one at a time per CPU: 2,000 clients signing in together take about 27 s on 2 cores, without a memory spike.
  • Disk: about 150 bytes per stored message, including the search index.

Sizing: a small server (1–2 vCPU, 1 GB of RAM) serves hundreds of users with several networks and thousands of messages per second. With many users, check limits.max_connections, the service's LimitNOFILE (65,536 by default) and the history retention.

How it is measured

The test (crates/xbncd/examples/loadtest.rs) starts, in a single process:

  • a simulated IRC server with a channel #xbnc;
  • xBNC with N users, each one with its network connected to that server and joined to #xbnc;
  • one or more IRC clients per user connected to xBNC.

The server sends --rate messages per second to the channel, to every network at the same time (the worst case: simultaneous bursts). Each message carries the time it was sent, and each client measures how long it takes to arrive: IRC server → xBNC (stored in the history, modules, plugins) → client. At the end the server cuts every connection and the test measures how long it takes for every network to come back.

Memory and CPU are those of the whole process, so they include the simulated server and the test clients (xBNC alone uses a little less). The build is --release with the default configuration (history in SQLite with WAL).

Problems found and fixed with this test

ProblemBeforeAfter
Memory after many simultaneous logins: glibc kept in each thread the 19 MB of every Argon2id calculation.1,680 MB with 500 clients55 MB (util::tune_allocator, a fixed mmap threshold)
Hundreds of Argon2id calculations at once (also useful against floods of login attempts).No limitOne per CPU (crypto::with_hash_slot)
One SQLite transaction per stored message.p50 70 ms and 13 % CPU with 500 networksp50 21 ms and 5 % CPU: a single writer thread stores everything waiting in one transaction (Db::insert_message)

Still to do

  • Repeat the test on dedicated hardware and with several networks per user.
  • Measure with real IRC servers far away (the test uses a local one, without network latency).

To repeat it: cargo run --release --example loadtest -- --users 2000 --clients 1 --rate 1 --seconds 60. The options are --users, --clients (per user), --rate (messages per second to the channel) and --seconds.

Test environment

With Docker

testenv/ brings up xBNC with two different IRC servers (Ergo and InspIRCd) on an isolated network, all in plain text on purpose:

cd testenv
docker compose up -d --build
./setup.sh
# IRC: localhost:6667, password test/ergo@laptop:testpassword1
# Panel: http://localhost:8080 (user test, password testpassword1)

Do not publish those ports on a public interface.

Automatic tests

cargo test --workspace                                   # every test
XBNC_TEST_POSTGRES=postgres://postgres@127.0.0.1/postgres cargo test --workspace   # also with PostgreSQL
cargo clippy --workspace --all-targets -- -D warnings
cd web && npm run build && npm run i18n:check            # panel and translations
xbncd locales check locales/*.json                       # language files
python3 ../xbnc-wiki/build.py --app .                   # the wiki (a separate project)

They cover the protocol, the units, the API against a real bouncer, IRC end to end, every *status command, the switch to a backup server, the trusted certificate with a real TLS handshake, a language added as a file, modules and plugins against a simulated IRC server, the panel, SSO and real IRC servers in CI (Ergo, InspIRCd, UnrealIRCd, Solanum and miniircd). Fuzzing (fuzz/) runs every week. testenv/ui/language_check.py checks in a real browser that every way of changing the language works.

Tests on real networks

The test plan per network, module and plugin is in testenv/real/PLAN.md. See Testing the beta.

The test sets

SetWhereWhat it covers
Protocol (16)crates/xbnc-protoParser and serialiser, tag escaping, prefixes, casemapping, ISUPPORT, CTCP.
Units of the service (142)crates/xbncd/srcConfiguration, Argon2id, encryption and key permissions, database and migrations (SQLite and PostgreSQL, and upgrading an old schema), history and search, cursors, sessions, backups, login, brute-force limiter, log masking, codec, CAP, roles, settings, TOTP (RFC 6238 vectors), metrics, tokens, cookies, origin, IP behind a proxy, OpenAPI coverage, HTTP→HTTPS redirect, module catalogue and validation, globs, mentions, aliases, logs, client and trusted certificates (with a real TLS handshake), FiSH, time zones, languages (every key used exists, Spanish complete, files checked and loaded), plugins (permissions, limits, fuzzing regressions), DCC, HTTP client, release signatures, the update helper and connection limits.
API (12)tests/api.rsFirst run with a one-time code, login, sessions, CSRF, origin, headers, brute force, networks, channels, history, export, isolation between users, roles, live settings, 2FA, panel, OpenAPI, modules, certificates, notes, language, tokens, metrics, alerts, import and plugins.
End to end (12)tests/e2e.rsThe whole bouncer against a simulated IRC server (scenarios below).
Modules (16 + 12)tests/modules.rs, tests/modules_v1.rsEach module against the simulated server: away and summary, keep nick, replies to the device that asked, events, CTCP, sticky channels, aliases, auto op, rejoin after a kick, watch words, MOTD and logs; and the modules of 1.0: IRCv3 extras only for clients that asked, services login (X with the hidden host and Login on Connect), invite, ignore and remove colours, FiSH in both directions and DH1080, detached channels, flooding channels, autocycle, Latin-1 and scheduled tasks.
*status commands (12)tests/status_commands.rsGrouped help, other bouncers' command names, backup servers (added, jumped to, promoted and automatic switch), trusted fingerprints, bind hosts, disabled and ordered channels, buffers, MOTD and broadcast, permissions, networks from IRC and a language added as a file.
Plugins (2 + 5 + 7)tests/plugins.rs, tests/plugins_v1.rs, tests/official_plugins.rsPlugins answering on the network and to the user, and each official plugin: seen, trivia, greet, factoids, karma, quotes, games, badwords, web access only to approved sites, reminders, tell, polls, channel stats, anti-flood, dice and autoresponder.
Panel (3)tests/panel.rsChat in the panel, mentions and channel history, plugin development mode and the one-click catalogue.
SSO (3)tests/sso.rsPasskeys, OpenID Connect and LDAP.
PostgreSQL (1)tests/postgres.rsMoving an SQLite database to PostgreSQL (with XBNC_TEST_POSTGRES).
Real IRC server (1)tests/real_ircd.rsA real xBNC against a real IRC server (see below).
Rust SDK (3)sdk/rustEvents and answers of the SDK.

End-to-end scenarios

  1. On connecting, the client gets the welcome, the network's ISUPPORT, its channels, the topic and the list of users.
  2. A message received with the client closed is played back on reconnecting, with its time; your own messages are not played back to the same device and a third connection gets no duplicates.
  3. Two devices connected at once stay in sync; a new device gets the recent history.
  4. CHATHISTORY LATEST, AFTER and errors, batch, the server's server-time kept, msgid and echo-message; capabilities that are not implemented are not announced.
  5. Login with SASL PLAIN.
  6. A wrong password is refused and the IP is blocked after three failures, even with the right password.
  7. Unknown network: the error lists the networks available.
  8. Server outage: the bouncer says so, reconnects with growing delays and joins the channels again.
  9. *status, and PART and JOIN with a key saved (the key is stored encrypted).
  10. Commands on connect are sent before joining channels.
  11. Read markers are shared between devices.
  12. soju.im/bouncer-networks for modern clients.

Real IRC servers

tests/real_ircd.rs connects a real xBNC to a real IRC server (variable XBNC_IRCD=host:port) and checks the list of users, messages in both directions, sync between two devices, playback without duplicates with the client closed, a nick change, leaving a channel and staying connected to the network:

XBNC_IRCD=127.0.0.1:6667 cargo test -p xbncd --test real_ircd -- --ignored

CI runs it against Ergo, InspIRCd, UnrealIRCd, Solanum and miniircd (testenv/ci/start-ircd.sh builds or installs each one with a minimal configuration).

Fuzzing

fuzz/ has six libFuzzer targets:

TargetWhat it receives
irc_messageArbitrary IRC lines: parser, serialiser and the round trip.
dccDCC offers.
import_configConfiguration files of another bouncer.
search_querySearch queries against the full-text index.
plugin_manifestWebAssembly files with arbitrary manifests.
plugin_runtimeArbitrary WebAssembly modules loaded and run as plugins.
cargo install cargo-fuzz          # needs Rust nightly
cd fuzz && mkdir -p corpus/irc_message && cp seeds/irc_message/* corpus/irc_message/
cargo +nightly fuzz run irc_message corpus/irc_message -- -max_total_time=600

CI (.github/workflows/fuzz.yml) runs each target for 20 minutes every week, keeps the corpus between runs and first checks that the failures already found (fuzz/regressions/) are still fixed. The failures found so far, all fixed and with their test:

  1. The parser accepted CR, LF or NUL inside a line.
  2. The parser accepted commands that are not alphanumeric (@ @).
  3. DCC offers accepted control characters.
  4. Embedding a manifest overflowed with sections of impossible size.
  5. A plugin with a long loop exhausted the process's native stack in builds where the compiler does not turn the interpreter's calls into jumps: the interpreter now uses the loop execution mode, which never grows the stack.

Load tests

cargo run --release --example loadtest -- --users 2000 --rate 1 --seconds 60. Results and method in Performance.

The panel in a browser

The panel has been gone through in Chrome with Playwright against a real bouncer with a real IRC client connected: first run, network wizard, channels, history and search, users, security, settings, 2FA, modules, plugins (installation with the permissions preview, per-network settings), import from another bouncer, alerts, updates, backups, chat, the sign-in page, the light and dark themes and the phone, in English and in Spanish, with no errors in the console apart from the expected 401 before signing in. testenv/ui/language_check.py URL USER PASSWORD repeats in a real browser every way of changing the language (sign-in page, Your account, Settings, a language added as a file) and checks that no text is left in another language.

Manual checklist

Use it with testenv/ or with a real installation:

  • The service starts and stops cleanly (systemctl start and stop xbnc; the log shows xBNC stopped).
  • An administrator and several users are created with the command line.
  • A user with two networks is connected to both at once.
  • A classic client (HexChat, WeeChat, irssi or mIRC) connects with user/network@device:password.
  • With the client closed, someone else writes in the channel and in private; on reconnecting those messages arrive exactly once.
  • Two clients of the same user see what the other one writes.
  • docker compose restart ergo (or cutting the network): xBNC says so, reconnects and joins the channels again.
  • /msg *status status shows the right state.
  • Five wrong passwords block the IP for 15 minutes.
  • scripts/backup.sh and scripts/restore.sh bring back users, networks and history.
  • journalctl -u xbnc contains no passwords after logging in, SASL and /msg NickServ IDENTIFY.
  • From the panel: create a user, add a network with the wizard, join a channel and see the history.
  • An ordinary user does not see Users, Security or Settings, nor other people's networks.
  • Turn on two-step verification and sign in with the code.
  • Change the retention in Settings and check in System that it applies without restarting.
  • Memory use with 1 user and 2 networks (systemctl status xbnc): note it down to compare in future versions.

Common problems

Always start with the log and with *status: sudo journalctl -u xbnc -n 100 --no-pager and, from your client, /msg *status status.

You cannot connect

SymptomLikely causeSolution
The client says “invalid certificate”You installed by IP with a self-signed certificate.Accept the certificate in the client or move to a domain: sudo ./scripts/install.sh --reconfigure --domain … --letsencrypt …
464 Password incorrectWrong user, network or password.Format user/network@device:password; check the user with sudo -u xbnc xbncd user list.
ERROR :Too many failed login attemptsIP blocked after failures.Wait 15 minutes, unblock it in Security or restart the service.
Choose a network by logging in as user/<network>You have several networks and did not say which.Add /network to the user.
No active networks for this accountThe network does not exist, is disabled or was not reloaded.sudo -u xbnc xbncd network list user and sudo systemctl reload xbnc.
this port uses SSL/TLS. Enable SSL…You connect to 6697 without SSL.Turn on SSL or use 6667 from your local network.
this port only accepts connections from the local networkYou use 6667 from outside.Use 6697 with SSL.
The client does not connect and nothing appears in the logThe client does not reach the machine (IP, port or firewall).sudo xbncd info; from the client: openssl s_client -connect IP:6697 (Linux) or Test-NetConnection IP -Port 6697 (Windows).
Log: client sent TLS to a plain-text portThe client has SSL on against 6667.Turn SSL off or use 6697.
Log: TLS handshake failedThe client does not accept the self-signed certificate.Turn on “accept invalid certificates” or use --domain with --letsencrypt.

Problems with a network

SymptomLikely causeSolution
*status: connection failed: … invalid peer certificateInvalid IRC server certificate (self-signed or expired) or missing root certificates.sudo apt install ca-certificates; check the host and the TLS port. If the network uses its own certificate, trust it with /msg *status addfingerprint FINGERPRINT.
*status: registration timed outWrong port (TLS against a port without TLS or the other way round).Check the network's port and TLS (Undernet: 6667 without TLS).
SASL authentication failedWrong SASL account or password.Correct them in the network configuration.
SASL is configured but this server does not offer SASL PLAINThe network does not support SASL.Remove SASL and use Services login.
Your host is trying to (re)connect too fast -- throttledThe network slows down repeated reconnections from your IP.Wait; xBNC waits at least 2 minutes. Do not click Connect several times.
On Undernet I join the channels with my real IPNo login before joining.Turn on Services login with “Log in when connecting” and a wait of a few seconds.
The login with X, Q or AuthServ does not workWrong services account or password.Check Services login; the reason arrives through *status.
On IRCnet it does not identifyIRCnet has no NickServ; xBNC does not send the password on purpose.Use SASL from sasl.ircnet.com on the network, or nothing.
The nick ends in “_”The nick was taken when connecting.Turn on Keep nick or type /nick your-nick.
xBNC keeps changing my nickKeep nick goes back to the saved nick./msg *status setnick new, or turn the module off.
I see “�” instead of accented lettersRare: xBNC converts Latin-1 to UTF-8.If it is the other person who cannot see your accents, add their channel or nick in Character encoding.
The client certificate does not identifyThe fingerprint is not in your account, the network does not use TLS or you did not reconnect.Add the fingerprint (SHA-512 on Libera, SHA-1 on OFTC) and reconnect.

Modules and plugins

SymptomLikely causeSolution
/msg *plugin only shows “Usage: MSG <nick> <message>”That is your client's help: you wrote nothing after the name./msg *poll help.
An alias does nothingYour client does not send commands it does not know./quote name arguments.
The autoop module does not give opYou do not have op, the mask does not match or the other person does not answer the challenge.Check the real user@host with /whois, and the key.
A plugin with web access does not answerIts sites are not approved.A superadministrator approves them in Plugins (“Sites it may connect to”).
A plugin has stopped on a networkIt failed five times in a row.Look at the Plugins log; turn it off and on again after fixing it.
No log files appearThe log module is off or there are no permissions on /var/lib/xbnc/logs.Turn it on; the service log says if it cannot write.
Scheduled tasks run at another timeYour time zone is not set.Your account → Time zone.

Panel and service

SymptomLikely causeSolution
The panel does not load (connection refused)[web] is missing or it listens on 127.0.0.1.sudo xbncd info; to redo the configuration, sudo ./scripts/install.sh --reconfigure.
“The page was out of date”Old CSRF token after signing in again.Reload the page.
“Request refused because it came from another site”public_url does not match the browser's address.Correct public_url or use that address.
After signing in you are back at the sign-in pageSecure cookie over HTTP without TLS.Use HTTPS or localhost (or secure_cookies = false, for testing only).
Everyone gets blocked behind NginxWithout trust_proxy, everything comes from 127.0.0.1.trust_proxy = true and the X-Forwarded-For header in Nginx.
secret key file … not found; run xbncd init firstNot initialised.sudo -u xbnc xbncd init.
secret key … is accessible by other usersKey permissions too open.sudo chmod 600 /var/lib/xbnc/secret.key && sudo chown xbnc:xbnc /var/lib/xbnc/secret.key.
listener … has no TLS certificatePublic port without TLS.Add tls_cert and tls_key, use lan_only or 127.0.0.1.
database schema version N is newer than this binary supportsYou went back to an older binary.Use the new binary or restore the previous backup.
Changes made from the command line do not showThe service was not reloaded.sudo systemctl reload xbnc.
A new language does not appear in the panelThe file is not in locales_dir, has another name or xBNC was not reloaded.xbncd locales check FILE; copy it to /etc/xbnc/locales/CODE.json and sudo systemctl reload xbnc. The log says which languages were loaded.
The client disconnects on its own with a lot of trafficThe client's queue filled up.Increase client_queue; when it comes back it gets what was pending.
Playback was truncatedThere were more than playback_max pending messages.Increase playback_max or use a client with draft/chathistory.
You lost the setup code—sudo xbncd setup-code, or create the user from the command line.
You lost the phone with two-step verification—sudo -u xbnc xbncd user reset-2fa user.
You forgot the password of the only administrator—sudo -u xbnc xbncd user passwd user.

More detail in the log

# In /etc/xbnc/xbnc.toml:  [log] level = "info,xbncd=debug"
sudo systemctl restart xbnc
sudo journalctl -u xbnc -f
sudo journalctl -u xbnc --since "1 hour ago" -p warning

With trace every IRC line is logged (with credentials masked). Go back to info when done: with debug, nicks and channels appear.

Testing the beta

Version 1.0.0 is being tested on real networks (Undernet, Libera.Chat, freenode, Rizon, EvilNet, IRCnet and others) before it is declared stable.

  1. Install it and add your networks.
  2. Follow the test plan in testenv/real/PLAN.md: one section per network, module and plugin.
  3. If something fails, download the test report from Your account.
  4. Send it with what you did, what you expected, what happened, the network, the client, the approximate time, the version (/msg *status version) and the warnings of the log (sudo journalctl -u xbnc --since "1 hour ago" | grep -E "WARN|ERROR"; review it before sending it).

If the project is on GitHub, use the “Test report” issue template. Security flaws are not made public: follow SECURITY.md.

Known limits

  • Undernet and QuakeNet do not offer TLS: the connection to those networks is unencrypted.
  • FiSH is old cryptography (Blowfish, without integrity): it protects from the network's servers, it is no replacement for end-to-end encryption. Decrypted messages are stored in your history like the rest.
  • Some clients do not send commands they do not know: aliases need /quote.
  • DCC between an IPv6-only client and an IPv4-only one is not translated.
  • Search by pieces of words is only used when there are no whole-word matches.
  • If the process stops abruptly, a few seconds of history may be repeated.
  • In a language added as a file, the replies of the official plugins come in English (they speak English and Spanish).
  • In this beta installation is from the source code; no .deb or .rpm packages are published.
  • Still to be checked on real networks: login with X, NickServ, Q and AuthServ; invite and unban; plugins with web access; FiSH with real clients; installer with real systemd and Let's Encrypt; DCC between networks; SASL EXTERNAL; importer with real installations.
xBNC 1.0.0 Made by y2kApache License 2.0