IRC Server as Tor Hidden Service on OpenBSD

A brief guide on how to set up an IRC server (Ergo) as a Tor v3 Onion Hidden Service on OpenBSD for secret idle parties with your friends.

IRC Server as Tor Hidden Service on OpenBSD

With increasing surveillance by companies as well as government services, many online services that were once fun to use have become a potential threat for one’s privacy. Not only e-mail accounts and social networking websites, but also niche services like the IRC cannot blindly be trusted anymore.

Therefore let’s take a look at how you can set up your own IRC server for you and your friends, in a way that allows you to freely communication without being bothered by big tech or anyone else. We’re going to be using Ergo for this, as it’s an up to date IRC server written in Go that offers bleeding-edge IRCv3 support. We’re also going to use OpenBSD for the base system.

Preparation

Usually when I build OpenBSD-based infrastructure, I use a Vultr VPS instance. Even though Vultr allows running Tor on their service unless it’s an exit node, for this setup I’d rather take a look at a different infrastructure provider that is more focused on privacy and ideally accepts payments via XMR.

Note: I won’t explicitly mention under which user I’m running each command, hence please read carefully. When the prompt shows ircd#, I’m acting as root user, otherwise, when it shows ircd%, I’m the using the _ergo user.

As soon as the OpenBSD VPS has booted, we can log in via SSH and perform a quick update of the system:

ircd# syspatch
ircd# pkg_add -u

After that, let’s begin by installing some handy tools:

ircd# pkg_add git zsh neovim wget mosh rsync htop

What I like to do is link nvim to vim, because typing vim is in my muscle memory. I also like to use zsh as shell. Since that’s all preference, these steps are optional:

ircd# ln -s /usr/local/bin/nvim /usr/local/bin/vim
ircd# chsh -s /usr/local/bin/zsh root

Another optional but useful thing that I like to do is to change the SSH port and disable password authentication / enable pubkey authentication. Make sure you have an authorized_keys entry with your pubkey in place before applying this change:

ircd# sed -i 's/^#Port 22/Port 31231/g;\
              s/^#PubkeyAuthentication .*/PubkeyAuthentication yes/g;\
              s/^#PasswordAuthentication .*/PasswordAuthentication no/g' \
                /etc/ssh/sshd_config
ircd# rcctl restart sshd

Afterwards, disconnect from SSH, and re-connect, ideally using mosh, and launch tmux for the sake of comfort.

Tor

We begin by installing and configuring the Tor hidden service:

ircd# pkg_add tor
ircd# cat /etc/tor/torrc
Log notice syslog
RunAsDaemon 1
DataDirectory /var/tor
HiddenServiceDir /var/tor/hidden_service/
HiddenServicePort 6667 127.0.0.1:6667 unix:/hidden_service_sockets/ergo_tor_sock
User _tor

Next, enable the Tor hidden service:

ircd# rcctl enable tor
ircd# rcctl start tor

We can now check the hidden service’s Onion address:

ircd# cat /var/tor/hidden_service/hostname
xyz.onion

This is the address that our hidden service IRC server will be available at. We will need to add this address to Ergo’s configuration.

Ergo IRCd

First download and install the latest OpenBSD release of Ergo from GitHub2.10.0 as of writing this:

ircd# wget https://github.com/ergochat/ergo/releases/download/v2.10.0/ergo-2.10.0-openbsd-x86_64.tar.gz
ircd# tar -xzf ergo-2.10.0-openbsd-x86_64.tar.gz
ircd# cp ergo-2.10.0-openbsd-x86_64/ergo /usr/local/bin/ergo

Next, add a new system user for Ergo:

ircd# groupadd _ergo
ircd# useradd -d /home/_ergo -m -c "Ergo" -g _ergo -L daemon -s /sbin/nologin _ergo

Now we can create a configuration as well as a MOTD file for Ergo in /etc/:

ircd# cat /etc/ircd.yaml
network:
    name: my-ircd

server:
    name: xyz.onion 
    listeners:
        "127.0.0.1:6667": # (loopback ipv4, localhost-only)
        "/hidden_service_sockets/ergo_tor_sock":
             tor: true
    unix-bind-mode: 0777
    tor-listeners:
        require-sasl: false
        vhost: "xyz.onion"
        max-connections: 1024
        throttle-duration: 10m
        max-connections-per-duration: 64
    sts:
        enabled: false

    casemapping: "ascii"
    enforce-utf8: true
    lookup-hostnames: false
    forward-confirm-hostnames: false
    check-ident: false
    coerce-ident: '~u'

    motd: /etc/ircd.motd
    motd-formatting: false

    relaymsg:
        enabled: false

    proxy-allowed-from:
        - localhost
    max-sendq: 96k
    compatibility:
        force-trailing: true
        send-unprefixed-sasl: true
        allow-truncation: false

    ip-limits:
        count: true
        max-concurrent-connections: 64
        throttle: true
        window: 10m
        max-connections-per-window: 32
        cidr-len-ipv4: 32
        cidr-len-ipv6: 64
        exempted:
            - localhost
        custom-limits:
            #"irccloud":
            #    nets:
            #        - "192.184.9.108"  # highgate.irccloud.com
            #        - "192.184.9.110"  # ealing.irccloud.com
            #        - "192.184.9.112"  # charlton.irccloud.com
            #        - "192.184.10.118" # brockwell.irccloud.com
            #        - "192.184.10.9"   # tooting.irccloud.com
            #        - "192.184.8.73"   # hathersage.irccloud.com
            #        - "192.184.8.103"  # stonehaven.irccloud.com
            #        - "5.254.36.57"    # tinside.irccloud.com
            #        - "5.254.36.56/29" # additional ipv4 net
            #        - "2001:67c:2f08::/48"
            #        - "2a03:5180:f::/64"
            #    max-concurrent-connections: 2048
            #    max-connections-per-window: 2048
    ip-check-script:
        enabled: false
        command: "/usr/local/bin/check-ip-ban"
        args: []
        timeout: 9s
        kill-timeout: 1s
        max-concurrency: 64
        exempt-sasl: false

    ip-cloaking:
        enabled: true
        enabled-for-always-on: true
        netname: "my-ircd"
        cidr-len-ipv4: 32
        cidr-len-ipv6: 64
        num-bits: 64

    secure-nets:
        # - "10.0.0.0/8"

    suppress-lusers: false

accounts:
    authentication-enabled: true
    registration:
        enabled: true
        allow-before-connect: true
        throttling:
            enabled: true
            duration: 10m
            max-attempts: 30

        bcrypt-cost: 4
        verify-timeout: "32h"
        email-verification:
            enabled: false
            password-reset:
                enabled: false

    login-throttling:
        enabled: true
        duration:  1m
        max-attempts: 3

    skip-server-password: false
    login-via-pass-command: true
    require-sasl:
        enabled: false
        exempted:
            - "localhost"
            # - '10.10.0.0/16'

    nick-reservation:
        enabled: true
        additional-nick-limit: 1
        method: strict
        allow-custom-enforcement: false
        guest-nickname-format: "Privateer-*"
        force-guest-format: false
        force-nick-equals-account: false
        forbid-anonymous-nick-changes: false

    multiclient:
        enabled: false

    vhosts:
        enabled: true
        max-length: 64
        valid-regexp: '^[0-9A-Za-z.\-_/]+$'

    default-user-modes: +i

    auth-script:
        enabled: false

channels:
    default-modes: +ntC
    max-channels-per-client: 100
    operator-only-creation: false
    registration:
        enabled: true
        operator-only: false
        max-channels-per-account: 15

    list-delay: 60s
    invite-expiration: 24h

oper-classes:
    "chat-moderator":
        title: Moderator
        capabilities:
            - "kill"      # disconnect user sessions
            - "ban"       # ban IPs, CIDRs, NUH masks, and suspend accounts (UBAN / DLINE / KLINE)
            - "nofakelag" # exempted from "fakelag" restrictions on rate of message sending
            - "relaymsg"  # use RELAYMSG in any channel (see the `relaymsg` config block)
            - "vhosts"    # add and remove vhosts from users
            - "sajoin"    # join arbitrary channels, including private channels
            - "samode"    # modify arbitrary channel and user modes
            - "snomasks"  # subscribe to arbitrary server notice masks
            - "roleplay"  # use the (deprecated) roleplay commands in any channel

    "server-admin":
        title: Admin
        extends: "chat-moderator"
        capabilities:
            - "rehash"       # rehash the server, i.e. reload the config at runtime
            - "accreg"       # modify arbitrary account registrations
            - "chanreg"      # modify arbitrary channel registrations
            - "history"      # modify or delete history messages
            - "defcon"       # use the DEFCON command (restrict server capabilities)
            - "massmessage"  # message all users on the server

opers:
    supervisor:
        class: "server-admin"
        hidden: true
        whois-line: I'm the supervisor, can I get a taxi number? 
        # `ergo genpasswd`.
        password: "$2a$04$..."

logging:
    -
        method: stderr
        type: "* -userinput -useroutput"
        level: info

debug:
    recover-from-errors: true

lock-file: "ircd.lock"

datastore:
    path: ircd.db
    autoupgrade: true
    mysql:
        enabled: false

languages:
    enabled: true
    default: en
    path: languages

limits:
    nicklen: 32
    identlen: 20
    channellen: 64
    awaylen: 390
    kicklen: 390
    topiclen: 390
    monitor-entries: 100
    whowas-entries: 100
    chan-list-modes: 60
    registration-messages: 1024
    multiline:
        max-bytes: 4096 # 0 means disabled
        max-lines: 100  # 0 means no limit

fakelag:
    enabled: true
    window: 1s
    burst-limit: 5
    messages-per-window: 2
    cooldown: 5s

roleplay:
    enabled: false

history:
    enabled: true
    channel-length: 2048
    client-length: 256
    autoresize-window: 2d
    autoreplay-on-join: 20
    chathistory-maxmessages: 1000
    znc-maxmessages: 2048
    restrictions:
        expire-time: 3d
        query-cutoff: 'join-time'
        grace-period: 1h
    persistent:
        enabled: false
    retention:
        allow-individual-delete: false
        enable-account-indexing: false
    tagmsg-storage:
        default: false
        whitelist:
            - "+draft/react"
            - "+react"

allow-environment-overrides: false

Make sure to replace at least my-ircd, xyz.onion, and $2a$04$... with values that make sense for your setup.

Note: This is an example configuration that might work for your use case. It’s nevertheless a good idea to go through each individual setting and make sure it’s what you want.

ircd# cat /etc/ircd.motd 
Put the content of your message-of-the-day file here.

Supervisord

To make sure our IRCd keeps running we’re going to install and configure supervisord:

ircd# pkg_add supervisor
ircd# cat /etc/supervisord.d/ircd.ini
[program:ircd]
command=ergo run --conf /etc/ircd.yaml
autostart=true
startsecs=5
startretries=5
autorestart=true
user=_ergo
environment=HOME="/home/_ergo",LC_ALL="en_US.UTF-8"
directory=/home/_ergo
ircd# rcctl enable supervisord
ircd# rcctl start supervisord

PF

Last but not least, add a basic firewall configuration:

ircd# cat /etc/pf.conf
##########
# Macros #
##########
minefield="1024:9999"
ssh_alternate_port=31231

##########
# Tables #
##########
table <bruteforce> persist
table <troublemakers> persist

###########
# Options #
###########
set skip on lo

#########
# Rules #
#########
block return    # Block stateless traffic
pass            # Establish keep-state

# Block brute-forcers
block quick proto tcp from <bruteforce> \
  to (egress) port $ssh_alternate_port
block quick proto tcp from <troublemakers> \
  to (egress) port $ssh_alternate_port

# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010

# Port build user does not need network
block return out log proto {tcp udp} user _pbuild

# Ergo user does not need network
block return out quick log proto {tcp udp} user _ergo

# Tor user needs network
pass out on egress proto tcp user _tor

# Connections that made it through to the SSH port but abusing it
pass proto tcp from any to (egress) \
  port {$ssh_alternate_port} \
  flags S/SA keep state \
  (max-src-conn 5, max-src-conn-rate 5/5, \
  overload <bruteforce> flush global \
  )

# Port 22 is a dead-giveaway 
# because we know we moved our SSH server
# elsewhere. Anyone poking there is up to no good.
pass in on egress proto tcp \
  from any \
  to (egress) port { \
    telnet, \
    ssh, \
    netbios-ns, \
    netbios-ssn, \
    microsoft-ds \
  } \
  synproxy state \
  tag trouble

# Tag stuff in the range $minefield as trouble ...
pass in on egress proto tcp from any to (egress) port $minefield \
  synproxy state \
  tag trouble

# ... unless it's to our alternate port
pass in proto tcp from any to (egress) port $ssh_alternate_port tag good

# Otherwise add to the troublemakers
pass proto tcp from any to (egress) port $ssh_alternate_port \
  tagged trouble \
  synproxy state \
  (max-src-conn 1, max-src-conn-rate 1/10, \
  overload <troublemakers> flush global \
  )

We’re pretty much done with the basic setup!

Connecting to the IRCd

From a client computer you can now configure your favorite IRC client to connect to your new IRCd by …

  • setting up a Tor client
  • configuring your IRC client to use the Tor client’s SOCKS proxy for connections
  • adding a new connection to your Onion address, on port 6667

I’m not going into these steps as these are different for every operating system and IRC client. What I usually do is that I set up ZNC and Tor on a dedicated VPS instance and use proxychains-ng to make ZNC connect to all its IRC networks via its local Tor SOCKS proxy. This allows me to connect from irssi on my workstation, via a clearnet VPN to ZNC, which in turn connects to multiple IRC networks via Tor Hidden Service connections. IRC networks like for example OFTC allow connection via their Onion addresses.

By using ZNC in between my local IRC client and IRC servers I’m not only increasing my own privacy on the networks but also avoid having to deal with proxy configurations for irssi or the disconnects/timeouts that come with IRC connections via Tor.

Check this dedicated post for more info on that!


Enjoyed this? Support me via Monero, Bitcoin, Lightning, or Ethereum!  More info.