Confused beginner's ZNC quickstart
So GnuTLS had a pretty bad CVE a while ago, and I was told by some distrobuilder friends that it doesn't otherwise have a great track record.
I use WeeChat as my IRC client, which links against GnuTLS for IRC-over-TLS functionality. However, I didn't have a great deal of confidence in GnuTLS as a result of this CVE, so I wasn't comfortable having it as the thing standing between my IRC conversations and the rest of the internet. Unfortunately, WeeChat doesn't directly support any other TLS libraries, however that doesn't mean that you can't put something between WeeChat and the rest of the world to do TLS on WeeChat's behalf.
One option is to just use a TCP proxy such as stunnel or an assemblage of tools such as s6-networking to encapsulate an IRC connection from WeeChat to the IRC server in TLS. Alternatively, one could use a fully-fledged IRC bouncer as a more sophisticated application-layer proxy, which is what I chose to do.
The bouncer of choice here is ZNC, which until recently I'd never really used before. As packaged in Debian, however, ZNC is linked against OpenSSL, which meets my requirements. ZNC has been around for a while, and has accumulated a pretty broad amount of functionality: while it functions just fine as a simple IRC client proxy, more sophisticated configurations allow you to set it up as a limited store-and-forward relay (by buffering messages in ZNC and then replaying them to a client), or as a client multiplexor, to allow multiple clients to connect to IRC over a single connection (e.g. a mobile and desktop client).
While I technically only needed this to work as a simple IRC proxy, I set this up as a client muxer, as insurance against me wanting to use that kind of setup in the future. The ZNC wiki helpfully has a handy page on how to set this up, which is a useful starting point and the basis of what's described below. Note that I don't intend to use the web admin interface, and instead want to configure it solely through the IRC-based controlpanel
module (which is otherwise enabled by default).
ZNC setup
I installed ZNC and the tools required to build third party ZNC modules, and created a new user under which to run the ZNC instance.
# apt-get install znc znc-dev
# useradd -m -s /bin/bash znc
Switching to the new znc
user, I then cloned the the git repositories for the clientbuffer
and chanfilter
third party modules (as linked on the aforementioned wiki page). The znc-buildmod
command is used to compile the modules into loadable shared library objects.
# su -l znc
$ mkdir src; cd src
$ git clone https://github.com/CyberShadow/znc-clientbuffer
$ git clone https://github.com/jpnurmi/znc-chanfilter
$ (cd znc-clientbuffer; znc-buildmod clientbuffer.cpp)
$ (cd znc-chanfilter; znc-buildmod chanfilter.cpp)
ZNC requires some first-time interactive setup, which I did by running znc -f -d ~/bouncerdata --makeconf
as the znc
user:
-
Bind to port 6667. (It prompts twice, due to complications involved with running the web interface on port 6667, but I'm going to be binding ZNC to localhost.)
-
Don't listen with TLS.
-
Don't bind on both IPv6 and IPv4. (I'm just using the IPv4 loopback address for this.)
-
Set up an admin user for the ZNC. Set a suitable password, nick, backup nick and realname. The "bind host" is the source address to be used when making outbound connections, so I set this to
127.0.0.1
for the admin user, which I didn't intend to be making any outbound connections with. -
Don't start ZNC immediately.
I then copied the compiled third-party modules into a modules
subdirectory of the configuration directory.
$ mkdir ~/bouncerdata/modules
$ cp ~/src/znc-clientbuffer/clientbuffer.so ~/src/znc-chanfilter/chanfilter.so \
> ~/bouncerdata/modules
Then, I opened the ZNC configuration file at ~znc/bouncerdata/configs/znc.conf
, found the Listener
section, and then added the line Host = localhost
so that ZNC only listens on the loopback address and is not otherwise externally reachable.
I configured ZNC to be run in a systemd
user session, using the unit file definition below as ~znc/.local/share/systemd/user/znc.service
:
[Unit]
Description=znc
[Service]
ExecStart=/usr/bin/znc -d /home/znc/bouncerdata -f
[Install]
WantedBy=default.target
systemd
user sessions are convenient for per-user process supervision. This requires enabling "lingering" mode for the ZNC user with loginctl enable-linger znc
as root, which means that any processes owned by the ZNC user will persist after they log out. This service definition can then be started with the following dance:
# su -l znc
$ export XDG_RUNTIME_DIR=/run/user/$(id -u)
$ systemctl --user start znc
I haven't set up ZNC to start at boot time (with systemctl --user enable
) however, as I want to manually control when it gets started, so I can connect my WeeChat to it at the same time.
ZNC configuration
Switching tabs over to WeeChat, I then added a server for the ZNC admin user.
/server add znc localhost/6667
/set irc.server.znc.nicks zncadmin,zncadmin_
/set irc.server.znc.username zncadmin
/set irc.server.znc.realname zncadmin
/set irc.server.znc.password <password>
/set irc.server.znc.autoconnect on
ZNC uses the contents of the "username" field to associate clients with users, and networks those users are connected to; this follows the format of username, slash, network name. For example, connecting a client to the ZNC with the username set to sysvinit/freenode
would result in the client being recognised as belonging to the user "sysvinit" and connected to the "freenode" network defined for that user. In this case, I didn't specify a network in the username, as I'm just interested at getting access to the IRC control interface.
I then set the default IRCv3 client capabilities to request when connecting in my WeeChat to the values recommended in the ZNC wiki page.
I connected to the "znc" network I just defined in WeeChat, which greeted me with a NOTICE from the *status
pseudo-user saying that I don't have any networks configured. ZNC exposes an interface to internal functions through virtual IRC users; ZNC intercepts messages sent to these users instead of proxying them to the IRC server, so one can control ZNC in-band from an IRC client. By default, these virtual users' names start with an asterisk (though this is configurable). The *status
user is the main ZNC control interface — you can get a listing of usage information using /query *status help
(or similar), formatted in a convenient ASCII art table (though the table is too wide to display properly on my laptop's screen). Each individual module exposes functionality via a virtual user with the same name as the module; for example, the nickserv
module can be configured by sending messages to the *nickserv
user.
Next, I performed some preliminary setup. I got rid of the web admin module, as I don't intend to use it, by sending unloadmod webadmin
to *status
. I then added and set up a user for connecting to external networks, using the controlpanel
module.
First I added a user for myself, which involves setting the password which I have to present upon connecting to ZNC.
<zncadmin> adduser sysvinit REDACTEDPASSWORD
<*controlpanel> User sysvinit added!
I then set various defaults for parameters used when making connections to IRC servers.
<zncadmin> set nick sysvinit sysvinit
<*controlpanel> Nick = sysvinit
<zncadmin> set realname sysvinit Molly Miller
<*controlpanel> RealName = Molly Miller
<zncadmin> set ident sysvinit sysvinit
<*controlpanel> Ident = sysvinit
<zncadmin> set altnick sysvinit sysxinit
<*controlpanel> AltNick = sysxinit
<zncadmin> set quitmsg sysvinit bye
<*controlpanel> QuitMsg = bye
I set the bind address (used when making outbound connections) to the IPv4 wildcard address, as the server I'm running on this box only has proper reverse DNS set on its IPv4 address (there are reasons for this), and I care about having a pretty hostmask being visible to other users on IRC.
<zncadmin> set bindhost sysvinit 0.0.0.0
<*controlpanel> BindHost = 0.0.0.0
There are a couple of settings which need to be changed from the defaults.
<zncadmin> set autoclearchanbuffer sysvinit false
<*controlpanel> AutoClearChanBuffer = false
<zncadmin> set autoclearquerybuffer sysvinit false
<*controlpanel> AutoClearQueryBuffer = false
I set the AutoClearChanBuffer
and AutoClearQueryBuffer
settings to false, because ZNC buffers lines received from a server before sending them on to a client (in case it's not connected when the lines are received), and by default clears lines in the buffer once they've been relayed. This breaks with multiple clients, as clearing lines from the buffer once they've been sent to one client means that they're not available for another client, so both of these settings need to be turned off.
Additionally, I enabled the chansaver
module for my user, as this means that ZNC will remember the channels that I'm in between client disconnects and reconnects, and ZNC restarts. Many IRC clients have autojoin functionality which does much the same thing, but ZNC can manage this too, which is useful in the multi-client use case.
<zncadmin> loadmodule sysvinit chansaver
<*controlpanel> Loaded module chansaver
I then added a network for this newly created user, and enable some network modules which provide useful functionality.
<zncadmin> addnetwork sysvinit freenode
<*controlpanel> Network freenode added to user sysvinit.
<zncadmin> loadnetmodule sysvinit freenode route_replies
<*controlpanel> Loaded module route_replies
<zncadmin> loadnetmodule sysvinit freenode chanfilter
<*controlpanel> Loaded module chanfilter
<zncadmin> loadnetmodule sysvinit freenode clientbuffer autoadd
<*controlpanel> Loaded module clientbuffer
<zncadmin> loadnetmodule sysvinit freenode nickserv REDACTEDPASSWORD
<*controlpanel> Loaded module nickserv
This defined a network called "freenode", and enabled the route_replies
, chanfilter
, clientbuffer
, and nickserv
modules. These are all described in full on the ZNC wiki, but in brief:
-
route_replies
ensures that replies to commands such as WHOIS are sent to the correct client that requested them, to prevent spurious replies being sent to the wrong client. -
chanfilter
allows different clients to receive messages from different subsets of joined channels. This is useful if you don't want e.g. your phone to send you a notification every time that really chatty switch generates an SNMP trap which gets sent to the monitoring IRC channel. -
clientbuffer
stores per-client history buffers, so ZNC knows which client has seen what messages, which is useful if you want per-client backlog replay. I'm not making use of this directly, but I'm including it for completeness, with the option to automatically add new clients it hasn't seen before. -
nickserv
handles identification with nick registration services on networks where it's supported (which is most places).
The two modules which perform client tracking require you to connect to ZNC with a unique client identifier in your IRC username; to identify my WeeChat to ZNC when connecting as the user "sysvinit" to the network "freenode", I would set my username to sysvinit@weechat/freenode
.
Now, at this point ZNC wasn't yet connected to "freenode", as no servers have been defined yet, but it automatically connects once this happens. So I then set up my WeeChat to connect via ZNC before switching things over.
WeeChat (re)configuration
Most of the settings I had in my WeeChat didn't need to be modified, but there were a couple of connection settings which needed to be modified for connecting to ZNC instead of a remote IRC server.
First, I needed to connect to localhost instead of an external address for this network.
/set irc.server.freenode.addresses localhost/6667
/set irc.server.freenode.local_hostname 127.0.0.1
I then changed the username to an identifying string which ZNC will understand, and set the password required to access the ZNC user.
/set irc.server.freenode.username sysvinit@weechat/freenode
/set irc.server.freenode.password REDACTEDPASSWORD
I also didn't need SSL enabled, as I was connecting in plaintext over the loopback, I cleared the autojoin list, as that part's handled by ZNC, and I removed the post-connection command which I used for authenticating to NickServ.
/set irc.server.freenode.ssl null
/set irc.server.freenode.autojoin null
/set irc.server.freenode.command null
At this point, everything was ready to go. So the last thing to do was to reconnect through ZNC. I first changed nick (so that I wouldn't collide with the nick I set ZNC to use), and I then added the server to the network defined in ZNC for my user:
<zncadmin> addserver sysvinit freenode chat.freenode.net +6697
<*controlpanel> Added IRC Server chat.freenode.net +6697 to network freenode for user sysvinit.
Eventually, a new "sysvinit" user showed up on IRC, so ZNC managed to connect successfully, so I then ran a /reconnect
in WeeChat, to make it pick up the new connection settings. Conveniently enough, it all worked, which is pretty neat given that I'd never used this software before.
Remarks
Having ZNC around is reasonably convenient for little things like appearing to stay connected to IRC when restarting my WeeChat for updates. There are also applications for putting IRC bots behind a bouncer, for similar means of concealing restarts, especially during development and testing. (I believe I've seen one IRC bot once where each piece of functionality was implemented in separate client processes behind a ZNC, and used ZNC to mux these all over a single client connection to the IRC server.)
A minor nit is that ZNC gets a bit noisy if it disconnects and reconnects, and it gets particularly upset if the connection to the server becomes unstable, though I've only had that happen once or twice when the IRC servers in question were having a particularly bad (and netsplit-ful) day.