Exploring the roots and future of Internet Relay Chat

Regex for IRC Operators: Bans, Hostmasks, and Anti-Spam

The first time I needed regex on IRC was 2002. A flood bot was hammering our network with randomized nicknames but a consistent hostmask pattern. The hostnames all ended with a sequence of digits followed by a specific ISP suffix. I needed a ban that would catch the pattern without blocking legitimate users from that ISP. I didn't know regex. I learned it in about two hours out of sheer necessity, testing patterns against a text file of logged hostmasks. That was the most useful two hours of my IRC career. If you operate a channel or a network, regex is not optional — it's a survival skill.

Why IRC Operators Need Regex

IRC bans work on patterns. A simple ban like *!*@*.badhost.com uses glob-style wildcards. That covers simple cases. But what if you need to ban a range of IPs? Or catch spam messages with varying content? Or match nicknames that follow a pattern but use random characters?

Glob patterns can't express "match any string containing 4 or more consecutive digits" or "match this phrase regardless of spacing or case." Regex can. Both UnrealIRCd and InspIRCd support regex in their spamfilter and ban systems. Learning regex means your bans become surgical instead of carpet-bombing.

A quick reality check: regex on IRC is not the same as regex in Python or Perl. IRCd regex engines vary. UnrealIRCd uses PCRE (Perl-Compatible Regular Expressions). InspIRCd also uses PCRE when the regex module is loaded. Some older IRCds use POSIX regex, which has a different feature set. Always check which regex engine your IRCd uses before writing patterns.

Hostmask Pattern Basics

An IRC hostmask has the format nick!user@host. Standard IRC bans use glob patterns with * and ? wildcards:

# Ban all users from a specific host
/mode #channel +b *!*@evil.host.com

# Ban a specific user regardless of host
/mode #channel +b badnick!*@*

# Ban a user pattern from a subnet
/mode #channel +b *!*@123.45.67.*

These work for static cases. For dynamic patterns, you need regex. Here are common patterns rewritten as regex:

# Match any nick that starts with "flood" followed by 2-5 digits
# Glob:  flood?????!*@*  (imprecise, matches floodabcde too)
# Regex: ^flood\d{2,5}!.*@.*$

# Match any hostname ending with .dynamic.isp.net preceded by a hex sequence
# Glob:  *!*@*.dynamic.isp.net  (too broad, matches all dynamic users)
# Regex: ^.*!.*@[0-9a-f]{8}\.dynamic\.isp\.net$

# Match realname containing URLs (common spam pattern)
# Regex: ^.*!.*@.*$  with realname filter: https?://[^ ]+

In UnrealIRCd, regex bans use the /kline or /gline commands with the regex type specified:

# UnrealIRCd regex-based gline
/gline ~r:^flood\d+!.*@.*$ 1d :Flood bot pattern

# The ~r: prefix tells UnrealIRCd to treat it as regex
# Duration: 1d (one day)
# Reason shown to user: "Flood bot pattern"

CIDR Notation for IP Bans

CIDR (Classless Inter-Domain Routing) notation lets you ban IP ranges efficiently. Instead of banning individual IPs or using wildcards, CIDR expresses a network block.

# CIDR basics
# /32 = single IP:     192.168.1.100
# /24 = 256 IPs:       192.168.1.0/24   (192.168.1.0 - 192.168.1.255)
# /16 = 65,536 IPs:    192.168.0.0/16   (192.168.0.0 - 192.168.255.255)
# /8  = 16.7M IPs:     192.0.0.0/8      (192.0.0.0 - 192.255.255.255)

# UnrealIRCd supports CIDR directly
/kline *@192.168.1.0/24 1d :Abuse from this range
/gline *@10.0.0.0/8 30d :Entire private range blocked

# InspIRCd CIDR support
/zline 192.168.1.0/24 86400 :Abuse from this range

For IPv6, CIDR is even more necessary. You can't wildcard an IPv6 address meaningfully:

# IPv6 CIDR bans
/kline *@2001:db8::/32 7d :Abusive IPv6 range
/gline *@fd00::/8 0 :Block all ULA addresses

# A /64 is a typical ISP allocation to a single customer
/kline *@2001:db8:abcd:1234::/64 1d :Spam from this allocation

When dealing with a flood from a specific ISP, use whois to identify the IP range, then look up the CIDR block. Tools like whois (the command-line tool, not IRC WHOIS) show the network allocation:

# Find the network block for an IP
whois 203.0.113.42 | grep -i "cidr\|route\|inetnum"
# Output might show: CIDR: 203.0.113.0/24
# or: inetnum: 203.0.112.0 - 203.0.115.255 (which is /22)
Tip: Start with the most specific CIDR range that covers the abuse. A /24 ban blocks 256 IPs. A /16 blocks 65,536. If you're not sure about the range, start with /24 and widen only if the attacker shifts to adjacent blocks. Overbroad CIDR bans are how you accidentally block an entire country's mobile carrier.

Spamfilter Patterns

Spamfilters match message content using regex. This is where pattern-writing skill matters most. Spam messages evolve — spammers tweak their text to evade filters. Your patterns need to be flexible enough to catch variants but precise enough to avoid false positives.

Here are real-world patterns I've used in production:

# Match URLs with common spam TLDs
# Catches: http://free-stuff.xyz, https://earn-money.top
https?://[a-z0-9.-]+\.(xyz|top|buzz|click|gq|ml|tk|cf)(/|\s|$)

# Match "free nitro" spam (Discord-related, common in bridged channels)
# Catches: "free nitro", "Free  Nitro", "fr3e n1tro"
# Conservative version:
free\s+nitro
# Aggressive version (catches l33t speak):
fr[e3][e3]\s+n[i1]tr[o0]

# Match repeated characters (flood detection)
# Catches: "aaaaaaaaaa", "HAHAHAHAHA"
(.)\1{9,}

# Match messages that are entirely uppercase (shouting)
# Matches if message has 20+ uppercase chars and no lowercase
^[^a-z]*[A-Z]{20,}[^a-z]*$

# Match common phishing patterns
# "click here" + URL in same message
click\s+here.*https?://|https?://.*click\s+here

In UnrealIRCd, spamfilters are set with:

# /spamfilter add [target] [action] [match-type] [duration] [reason] [pattern]
/spamfilter add cpn gline regex 7d "Spam bot" free\s+nitro.*https?://

# Targets: c=channel, p=private, n=notice, q=quit messages
# Actions: warn, block, kill, gline, zline, shun
# Duration: applies to gline/zline/shun

# Block messages containing suspicious URLs
/spamfilter add cp block regex 0 "Blocked URL" https?://[a-z0-9.-]+\.(xyz|top|buzz|click)(/|$)

# Kill users who spam invite links on join
/spamfilter add c kill regex 0 "Spam" discord\.gg/[a-zA-Z0-9]+|bit\.ly/[a-zA-Z0-9]+

Anti-Flood Patterns

Flood attacks on IRC take several forms. Regex helps with content-based floods where the text varies slightly between messages.

# Match color code floods (mIRC color spam)
# \x03 is the mIRC color code character
# Match messages with more than 5 color code changes
(\x03\d{1,2}(,\d{1,2})?){5,}

# Match CTCP flood patterns
# \x01 is the CTCP delimiter
\x01(VERSION|PING|TIME|FINGER)\x01
# Use this in spamfilter to block mass CTCP to channels

# Match nick-spam (bot joins, spams, parts quickly)
# This is better handled by join rate limiting, but regex
# can catch the message pattern:
# Typical: "Visit  for free !"
visit\s+https?://.*for\s+free\s+

# Match repeated paste floods
# Catches same-ish lines repeated, e.g., a bot pasting
# the same URL with slight variations
(https?://same-domain\.com/[a-z0-9]+\s*){3,}
Warning: Overly broad regex patterns in spamfilters will cause false positives. The pattern .*http.* matches every message containing a URL — including legitimate ones. Always test your patterns against normal conversation logs before deploying them. One false positive that kills a regular user's connection erodes trust far more than letting one spam message through.

IRCd-Specific Implementations

How regex is configured differs between IRCds.

UnrealIRCd has the most mature regex integration. Spamfilters support PCRE natively. The /spamfilter command lets opers add patterns at runtime. Persistent filters go in unrealircd.conf:

# unrealircd.conf spamfilter block
spamfilter {
    regex "free\s+nitro.*https?://";
    target { channel; private; };
    action gline;
    reason "Spam bot detected";
    ban-time 7d;
};

spamfilter {
    regex "(\x03\d{1,2}(,\d{1,2})?){10,}";
    target { channel; };
    action block;
    reason "Color flood";
};

InspIRCd uses the m_filter module with regex support provided by m_regex_pcre:

# inspircd.conf



# Filter rules in a separate file (filter.conf)




# flags: p=privmsg, n=notice, P=part message, q=quit message
# action: block, silent, kill, gline, zline

Regex engine matters. PCRE supports lookaheads, lookbehinds, non-greedy quantifiers, and named groups. POSIX regex (used by some older IRCds) doesn't support lookaheads at all. If your pattern uses (?=...) or (?!...), make sure your IRCd's regex engine supports it.

# PCRE-only: negative lookahead
# Match "free" NOT followed by "dom" (catches "free nitro" but not "freedom")
free(?!dom)\s+

# PCRE-only: non-greedy matching
# Match shortest URL possible
https?://[^ ]+?\.com

# POSIX-compatible: character classes
# Match hex strings (works in both PCRE and POSIX)
[0-9a-fA-F]{8,}

Testing and Debugging Patterns

Never deploy an untested regex pattern to a production IRC network. Here's my workflow:

Step 1: Collect samples. Save actual spam messages and flood patterns to a text file. Include legitimate messages too — these are your false positive test cases.

Step 2: Test locally with grep or a regex tool:

# Test pattern against sample messages
grep -P 'free\s+nitro' spam_samples.txt

# Test for false positives against normal chat
grep -P 'free\s+nitro' normal_chat.log
# Should return nothing if your pattern is precise enough

# Count matches
grep -cP '(.)\1{9,}' all_messages.log

Step 3: Use an online regex tester. Sites like regex101.com let you paste your pattern and test strings, with real-time match highlighting and explanation of what each part of the pattern does. Select the PCRE flavor to match what your IRCd uses.

Step 4: Deploy with warn action first. Instead of killing or banning on match, set the action to warn. Monitor the warnings for a day. If there are no false positives, escalate to block or kill.

# Deploy in warn mode first
/spamfilter add cp warn regex 0 "TEST: spam pattern" your-regex-here

# Monitor oper notices for matches
# After 24-48 hours with zero false positives:
/spamfilter del cp warn regex your-regex-here
/spamfilter add cp gline regex 7d "Spam" your-regex-here

Step 5: Document every production pattern. Keep a file listing each regex, what it matches, when it was added, and why. Six months from now, you won't remember why you added [0-9a-f]{12,}.*\.(tk|ml|cf) and whether removing it is safe.

FAQ

What's the performance impact of regex spamfilters?

Each message sent on the network is tested against every active spamfilter pattern. With 10-20 patterns, the overhead is negligible — PCRE matching is fast. With hundreds of patterns, you might see measurable CPU usage during traffic spikes. Keep your pattern list lean. Remove obsolete patterns. If performance is a concern, use simpler patterns (no backtracking-heavy constructs like nested quantifiers) and test with your IRCd's built-in benchmarking tools.

Can regex patterns match across multiple lines?

IRC messages are single-line by protocol definition. Each PRIVMSG is one line. So multiline regex matching is not relevant for message content filters. However, if you're matching against user realnames, quit messages, or other fields, those are also single-line. Multiline mode ((?m)) changes how ^ and $ work but doesn't apply to IRC filtering in practice.

How do I ban Tor exit nodes with regex?

You don't — not with regex. Tor exit node IPs change frequently. Use a DNSBL (DNS-based Block List) instead. Both UnrealIRCd and InspIRCd support DNSBL lookups. Configure your IRCd to query tor.dnsbl.sectoor.de or a similar Tor exit list. This is more reliable and more maintainable than trying to regex-match Tor hostnames.

Should I use regex or glob patterns for channel bans?

Use glob for simple cases — banning a specific user, a hostname, or a wildcard range. Use regex when glob can't express the pattern you need — matching character classes, quantified repetition, alternation, or anchored patterns. Most day-to-day channel moderation uses glob. Regex is for network-level spamfilters and complex abuse patterns.