Theresa O’Connor

Freenode cloaking and ERC

IRC clients often provide a way for their users to automatically identify themselves to NickServ, and ERC is no exception. To enable erc-services-mode in your ~/.ercrc.el, the following should do the trick:

(require 'erc-services nil t)
(erc-services-mode 1)
(setq erc-prompt-for-nickserv-password nil)

Aside: You’ll also need to configure erc-nickserv-passwords with your nick and password information.

IRC clients also usually provide a way to automatically join channels when you connect; ERC enables its erc-autojoin-mode by default. You tell ERC which channels to auto-join by configuring erc-autojoin-channels-alist. Suppose you want to auto-join #emacs on freenode. You’d set erc-autojoin-channels-alist like so:

(setq erc-autojoin-channels-alist '((".*freenode.net" "#emacs")))

Auto-identification and auto-joining are both nice features, but it turns out they can interact in an unfortunate way, when you use IRC hostmask cloaking. Hostmask cloaking is when the IRC server disguises the host that you’re connected to IRC from. Most cloaks get installed when you identify with NickServ. For example, when I identify with freenode’s NickServ, I receive this notice:

-kubrick.freenode.net- NickServ set your hostname to
                       "unaffiliated/hober"

OK, so what's the big deal? The problem is that, when using erc-services-mode and erc-autojoin-mode at the same time, ERC auto-joins channels before your hostmask cloak is installed. People in those channels see your uncloaked host information when you join, thus completely obviating the point of cloaking in the first place.

The fix is to ensure that we only auto-join channels after NickServ has installed our hostmask cloak. Here’s how to do that.

First, disable erc-autojoin-mode in ~/.ercrc.el:

(erc-autojoin-mode 0)

We know NickServ’s installed our cloak when we receive the notice I quoted above. ERC provides a hook for processing notices, erc-server-NOTICE-functions, which we can use.

(add-hook 'erc-server-NOTICE-functions 'ted-post-cloak-autojoin)

Of course, now we have to write ted-post-cloak-autojoin.

Now we need to understand how ted-post-cloak-autojoin will be called from erc-server-NOTICE-functions. Like most ERC callbacks, members of erc-server-NOTICE-functions are passed two parameters: proc, an object representing the network connection, and parsed, which contains the message received from the server.

(defun ted-post-cloak-autojoin (proc parsed)
  "Autojoin iff NickServ tells us to."
  ...)

We want to auto-join when we receive one of these cloak notices. We can inspect parsed to see if it’s what we’re looking for:

(defun ted-post-cloak-autojoin (proc parsed)
  "Autojoin iff NickServ tells us to."
  (when (and (string-equal "irc.freenode.net"
                           (erc-response.sender parsed))
             (string-match ".*NickServ set your hostname to.*"
                           (erc-response.contents parsed)))
    ...))

To actually perform the auto-join, we can call the function erc-autojoin-channels, which takes two arguments, server and nick. But where do we get values for server and nick from?

In Emacs Lisp, process objects (like our proc) have associated buffers (called process buffers). ERC stores session-specific information in buffer-local variables within proc’s buffer. We can use with-current-buffer to temporarily switch to proc’s buffer.

A value for server can be found in the variable erc-session-server, and the function erc-current-nick returns your current nick on that server.

(with-current-buffer (process-buffer proc)
  ...
  (erc-autojoin-channels erc-session-server (erc-current-nick))
  ...)

There’s one last issue to note: erc-server-NOTICE-functions’s docstring says if the function returns non-nil, stop processing the hook. We don’t want to prevent other hook functions from running, so we should always return nil.

Putting it all together, here’s what I’ve come up with:

(defun ted-post-cloak-autojoin (proc parsed)
  "Autojoin iff NickServ tells us to."
  (with-current-buffer (process-buffer proc)
    (when (and (string-equal "irc.freenode.net"
                             (erc-response.sender parsed))
               (string-match ".*NickServ set your hostname to.*"
                             (erc-response.contents parsed)))
      (erc-autojoin-channels erc-session-server (erc-current-nick))
      nil)))

There you go. Now auto-join isn’t triggered until we’re hiding behind our hostmask cloak, and hopefully you've learned something useful about ERC customization and Emacs Lisp too!