Hopm Install Guide

In this guide, we'll setup and configure hopm, an open proxy monitor that kills spam bots.

Advantages:

  1. Pure C
  2. Compatible with every IRC server
  3. Fast scanning and DNSBL support.

Disadvantages:

  1. Occasionally bans innocent users because it cannot perform statistical analysis

Before you begin, you must read the README and INSTALL docs.

Installation

Let's create the user hopm:

$ doas useradd -m -g =uid -c "hopm" -d /home/hopm -s /bin/ksh hopm

Then we switch to the user hopm and change to its home folder:

$ doas su hopm
$ cd

We download the latest release, extract it, then build it:

$ ftp https://github.com/ircd-hybrid/hopm/archive/1.1.10.tar.gz
$ tar xvzf 1.1.10.tar.gz
$ cd hopm-1.1.10
$ ./configure
$ make
$ make install

hopm will now be installed in ~/hopm.

/home/hopm/hopm/etc/reference.conf contains a sample template.

We'll create a new /home/hopm/hopm/etc/hopm.conf from scratch to keep it short:

options {
        pidfile = "var/run/hopm.pid";
        command_queue_size = 64;
        command_interval = 10 seconds;
        command_timeout = 180 seconds;
        negcache_rebuild = 12 hours;
        dns_fdlimit = 64;
        dns_timeout = 5 seconds;
        scanlog = "var/log/scan.log";
};

The only thing we change is we uncomment scanlog so that we have a record of all users that connect. It will be stored in /home/hopm/hopm/var/log/scan.log.

irc {
        nick = "MyHopm";
        realname = "Hybrid Open Proxy Monitor";
        username = "hopm";
        server = "127.0.0.1";
        port = 16667;
        tls = no;
        readtimeout = 15 minutes;
        reconnectinterval = 30 seconds;
        nickserv = "SQUERY NickServ :IDENTIFY MyHopm PASSWORD";
        oper = "MyHopm PASSWORD";
        mode = "+BcFiIoqRsw";
        away = "I'm a bot. Your messages will be ignored.";
        channel {
                name = "#hopm";
                key = "somekey";
                invite = "SQUERY ChanServ :INVITE #hopm";
        };

connregex = "Client connecting: ([^ ]+) \\(([^@]+)@([^\\)]+)\\) \\[([0-9a-f\\.:]+)\\].*";

        kline = "KLINE *@%h 3600 :Open proxy found on your host. Please contact support@example.com if this is in error.";
        notice = "To prevent spam and abuse, we scan users for open proxies.";
};

Change the nick, realname, username. server, port, tls specify how to connect to your ircd. You will want to use 127.0.0.1 port 16667 with no TLS.

ngircd uses SQUERY for nickserv: SQUERY NickServ :IDENTIFY MyHopm PASSWORD -- you'll want to replace MyHopm with the real nick and PASSWORD with the real password. For oper: MyHopm PASSWORD -- you'll want to replace MyHopm and PASSWORD with the operator name and password.

We change the mode to +BcFiIoqRsw. I recommend you change channel's name from #hopm to your team channel. invite has been modified to match ngircd; replace #hopm with your team channel.

For kline, make sure to replace *@%i with *@%h (to allow hopm to work with old cloak hostmasks, which have been broken since April 2020. Note that hopm will not work with +x cloaking). You must also change the order for kline: in ngircd, kline expects the hostmask before the time. You will also want to replace support@example.com with your actual support email.

WARNING: You must change the order for kline for ngircd:

kline = "KLINE *@%h 3600 :Open proxy found on your host. Please contact support@example.com if this is in error.";

The hostmask must come before the time.

opm {

In our OPM block, we will define a few blacklists: dronbl, efnet, and ircbl.

        blacklist {  
                name = "dnsbl.dronebl.org";
                address_family = ipv4, ipv6;
                type = "A record reply";
                ban_unknown = no;
                reply {
                        2 = "Sample data used for heuristical analysis";
                        3 = "IRC spam drone (litmus/sdbot/fyle)";
                        5 = "Bottler (experimental)";
                        6 = "Unknown worm or spambot";
                        7 = "DDoS drone";
                        8 = "Open SOCKS proxy";
                        9 = "Open HTTP proxy";
                        10 = "ProxyChain";
                        11 = "Web Page Proxy";
                        12 = "Open DNS Resolver";
                        13 = "Automated dictionary attacks";
                        14 = "Open WINGATE proxy";
                        15 = "Compromised router / gateway";
                        16 = "Autorooting worms";
                        17 = "Automatically determined botnet IPs (experimental)";
                        18 = "Possibly compromised DNS/MX type hostname detected on IRC";
                        19 = "Abused VPN Service";
                        255 = "Uncategorized threat class";
                };
                kline = "KLINE *@%h 3600 :You have a host listed in the DroneBL. For more information, visit https://dronebl.org/lookup_branded?ip=%i&network=<your_network_name>";
        };

The name of the first blacklist is dnsbl.dronebl.org. It supports both ipv4 and ipv6 addresses. We use A record replies. We don't want to ban unknown types.

Note: Replace <your_network_name> with a unique network name. The Network parameter set in ngircd.conf should suffice.

For the kline, we again make sure to put the hostmask before the time (as ngircd requires). We also use i to kline by hostmask instead of by IP, since ngircd may be cloaking user IPs.

        blacklist {
                name = "rbl.efnetrbl.org";
                type = "A record reply";
                ban_unknown = no;
                reply {
                        1 = "Open proxy";
                        2 = "spamtrap666";
                        3 = "spamtrap50";
                        4 = "TOR";
                        5 = "Drones / Flooding";
                };
                kline = "KLINE *@%h 3600 :Blacklisted proxy found. For more information, visit https://rbl.efnetrbl.org/?i=%i";
        };
        blacklist {
                name = "tor.efnetrbl.org";
                type = "A record reply";
                ban_unknown = no;
                reply {
                        1 = "TOR";
                };
                kline = "KLINE *@%h 3600 :TOR exit node found. For more information, visit https://rbl.efnetrbl.org/?i=%i";
        };

The two blacklists from efnet are the same.

        blacklist {
                 name = "rbl.ircbl.org";
                 type = "A record reply";
                 reply {
                         2 = "Open proxy (2)";
                         6 = "Mail or NS server (6)";
                         10 = "D regex pattern (10)";
                         11 = "Drone / compromised (11)";
                         13 = "Join/part flood (13)";
                         14 = "Drone / compromised 2 (14)";
                         16 = "Spam bot (16)";
                         17 = "Drone (17)";
                         18 = "Drone 2 (18)";
                         19 = "Web abuse (19)";
                         20 = "Drone/flood bot (20)";
                         21 = "Compromised host (21)";
                         22 = "Open Proxy (22)";
                         23 = "Open Proxy (23)";
                         24 = "Mass advertising (24)";
                         30 = "Drone (30)";
                         31 = "Drone 2 (31)";
                         32 = "Open proxy (32)";
                         42 = "Open proxy (42)";
                 };
                 ban_unknown = yes;
                 kline = "KLINE 180 *@%h :Compromised host on this IP. See https://ircbl.org/lookup?ip=%i&network=<your_network_name> for more information.";
         };
	blacklist {
		name = "tor-irc.dnsbl.oftc.net";
		type = "A record reply";
		reply {
			2 = "Tor exit server";
		};
		ban_unknown = yes;
		kline = "KLINE *@%h 3600 :Please use our tor onion addresses. If this is in error, please email support@ircnow.org";
	};
};

Note: Again, replace <your_network_name> with a unique network name. The Network parameter set in ngircd.conf should suffice.

This is another blacklist.

Next, we define a scanner block. hopm will try to get the user to connect to the target_ip:target_port using the listed port/protocol. A target_string will be sent through the proxy and then checked. If the data is found, the user is an proxy.

NOTE: target_ip must be an actual ip address. Replace 127.0.0.1 with your public IPv4 address.

scanner {
        name = "default";
        protocol = HTTP:80;
        protocol = HTTP:8080;
        protocol = HTTP:3128;
        protocol = HTTP:6588;
#       protocol = HTTPS:443;
#       protocol = HTTPS:8443;
        protocol = SOCKS4:1080;
        protocol = SOCKS5:1080;
        protocol = ROUTER:23;
        protocol = WINGATE:23;
        protocol = DREAMBOX:23;
        protocol = HTTPPOST:80;
#       protocol = HTTPSPOST:443;
#       protocol = HTTPSPOST:8443;
#       bind = "127.0.0.1";
        fd = 512;
        max_read = 4 kbytes;
        timeout = 30 seconds;
        target_ip = "127.0.0.1";
        target_port = 6667;
        target_string = "NOTICE * :*** Looking up your hostname and checking ident";
};

Two more scanner blocks:

scanner {
        name = "extended";
        protocol = HTTP:81;
        protocol = HTTP:8000;
        protocol = HTTP:8001;
        protocol = HTTP:8081;
        protocol = HTTPPOST:81;
        protocol = HTTPPOST:6588;
        protocol = HTTPPOST:4480;
        protocol = HTTPPOST:8000;
        protocol = HTTPPOST:8001;
        protocol = HTTPPOST:8080;
        protocol = HTTPPOST:8081;
        protocol = SOCKS4:4914;
        protocol = SOCKS4:6826;
        protocol = SOCKS4:7198;
        protocol = SOCKS4:7366;
        protocol = SOCKS4:9036;
        protocol = SOCKS5:4438;
        protocol = SOCKS5:5104;
        protocol = SOCKS5:5113;
        protocol = SOCKS5:5262;
        protocol = SOCKS5:5634;
        protocol = SOCKS5:6552;
        protocol = SOCKS5:6561;
        protocol = SOCKS5:7464;
        protocol = SOCKS5:7810;
        protocol = SOCKS5:8130;
        protocol = SOCKS5:8148;
        protocol = SOCKS5:8520;
        protocol = SOCKS5:8814;
        protocol = SOCKS5:9100;
        protocol = SOCKS5:9186;
        protocol = SOCKS5:9447;
        protocol = SOCKS5:9578;
        protocol = SOCKS5:10000;
        protocol = SOCKS5:64101;
        protocol = SOCKS4:29992;
        protocol = SOCKS4:38884;
        protocol = SOCKS4:18844;
        protocol = SOCKS4:17771;
        protocol = SOCKS4:31121;
        fd = 400;
};
scanner {
        name = "ssh";
        protocol = SSH:22;
        target_string = "SSH-1.99-OpenSSH_5.1";
        target_string = "SSH-2.0-dropbear_0.51";
        target_string = "SSH-2.0-dropbear_0.52";
        target_string = "SSH-2.0-dropbear_0.53.1";
        target_string = "SSH-2.0-dropbear_2012.55";
        target_string = "SSH-2.0-dropbear_2013.62";
        target_string = "SSH-2.0-dropbear_2014.63";
        target_string = "SSH-2.0-OpenSSH_4.3";
        target_string = "SSH-2.0-OpenSSH_5.1";
        target_string = "SSH-2.0-OpenSSH_5.5p1";
        target_string = "SSH-2.0-ROSSSH";
        target_string = "SSH-2.0-SSH_Server";
};
user {
        mask = "*!*@*";
        scanner = "default";
};

user {
        mask = "*!~*@*";
        mask = "*!squid@*";
        mask = "*!nobody@*";
        mask = "*!www-data@*";
        mask = "*!cache@*";
        mask = "*!CacheFlowS@*";
        mask = "*!*@*www*";
        mask = "*!*@*proxy*";
        mask = "*!*@*cache*";
        scanner = "extended";
};

exempt {
        mask = "*!*@127.0.0.1";
};

Run Hopm

$ /home/hopm/hopm/bin/hopm -d

Cronjob

Put this script in /home/hopm/hopm/bin/autohopm

#!/bin/sh
HOPMPATH=/home/hopm/hopm

if test -r $HOPMPATH/var/run/hopm.pid; then
    HOPMPID=$(cat $HOPMPATH/var/run/hopm.pid)
    if $(kill -0 $HOPMPID >/dev/null 2>&1)
    then
        exit 0
    fi
fi
$HOPMPATH/bin/hopm &> /dev/null

Then make sure execute privileges are set:

$ chmod 754 /home/hopm/hopm/bin/autohopm
$ crontab -e
*/5	*	*	*	*	/home/hopm/hopm/bin/autohopm

Troubleshooting

If you see this error:

[2021-01-23T09:59:14-0600] IRC -> connect(): error connecting to username.coconut.ircnow.org: Connection refused                                                                 
[2021-01-23T09:59:14-0600] IRC -> Connection to (username.coconut.ircnow.org) failed, reconnecting.                                                                              
[2021-01-23T09:59:14-0600] IRC -> connect(): error connecting to username.coconut.ircnow.org: Connection refused

This may be due to a configuration issue with ngircd. In particular, if the hostname has an AAAA record, hopm may be trying to connect via IPv6 but ngircd does not listen to IPv6 connections.

$ doas pkg_add torsocks
$ torsocks nc irc.example.com 6667
nick toruser
user toruser * * :toruser

In the #hopm channel, you should see:

23:16 <MyHopm> DNSBL -> toruser!~toruser@vps-16fb7987.vps.ovh.ca [51.79.69.241] appears in BL zone rbl.efnetrbl.org (TOR)

Run Hopm as System Daemon

For this refer to this page
After you've created the rc.d script, append to /etc/rc.conf.local: hopm_user=hopm

  1. Syntax errors when hopm is running in foreground. This is either the result of missing brackets where needed in config file, or that the file has DOS encodings. See https://github.com/ircd-hybrid/hopm/issues/22#issuecomment-301276082 here. The missing brackets where it was needed may come from the previous section, compared to the line/s where it is indicated by hopm when executed.
  2. If the service fails to start, check and make sure /home/hopm/hopm/var/log/hopm.log is owned by hopm.