1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-06-12 17:14:46 +02:00

Make spamfilter IDs start with "SPAM" to be more visible. And this also

means shun IDs now start with "H". Update release notes.

This, after i realized that for like *LINEs that are added by spamfilter
the two ID fields in "STATS gline" are a bit confusing as to which ID is
what. Now the spamfilter one starts with "SPAM" so there can be no
confusion. The gline one still starts with "G" as before.

Since I kept the generated ID length the same, this means there is less
bits available for the spamfilter ID, but there are rarely more than 1000
spamfilters, and in that scenario there's just as little birthday attack
collision % as with 200k glines, just to illustrate (~0.0015% vs ~0.0018%)
This commit is contained in:
Bram Matthys
2026-06-10 15:29:26 +02:00
parent faecdd66cd
commit 62f3cda8f2
2 changed files with 61 additions and 40 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ This is work in progress and may not always be a stable version.
### Enhancements: ### Enhancements:
* Server bans and Spamfilters now have a unique ID, like `G7K2MP9WQX3`: * Server bans and Spamfilters now have a unique ID, like `G7K2MP9WQX3`:
* The first letter denotes the type: `G` for gline, `K` for kline, * The first letter denotes the type: `G` for gline, `K` for kline,
`Z` for (g)zline. `Z` for (g)zline, `H` for shun. Spamfilter IDs start with `SPAM`.
* The ID is shown to the affected user, so they can paste the ID back to * The ID is shown to the affected user, so they can paste the ID back to
network staff. It is `$banid` in network staff. It is `$banid` in
[set::reject-message](https://www.unrealircd.org/docs/Set_block#set::reject-message) [set::reject-message](https://www.unrealircd.org/docs/Set_block#set::reject-message)
+60 -39
View File
@@ -137,6 +137,7 @@ struct TKLTypeTable
unsigned tkltype:1; /**< Is a type available in cmd_tkl() and friends */ unsigned tkltype:1; /**< Is a type available in cmd_tkl() and friends */
unsigned exceptiontype:1; /**< Is a type available for exceptions */ unsigned exceptiontype:1; /**< Is a type available for exceptions */
unsigned needip:1; /**< When using this exempt option, only IP addresses are permitted (processed before DNS/ident lookups etc) */ unsigned needip:1; /**< When using this exempt option, only IP addresses are permitted (processed before DNS/ident lookups etc) */
char *id_prefix; /**< Prefix for generated TKL ids, eg "G", "K", "SPAM". NULL for exempt-only options. Note that shun has "H" here while its type.letter is 's'. */
}; };
/** This table which defines all TKL types and TKL exception types. /** This table which defines all TKL types and TKL exception types.
@@ -149,26 +150,26 @@ struct TKLTypeTable
* - more? * - more?
*/ */
TKLTypeTable tkl_types[] = { TKLTypeTable tkl_types[] = {
/* <config name> <letter> <TKL_xxx type> <logging name> <tkl option?> <exempt option?> <ip address only?> */ /* <config name> <letter> <TKL_xxx type> <logging name> <tkl option?> <exempt option?> <ip address only?> <TKLID Prefix> */
{ "gline", 'G', TKL_KILL | TKL_GLOBAL, "G-Line", 1, 1, 0 }, { "gline", 'G', TKL_KILL | TKL_GLOBAL, "G-Line", 1, 1, 0, "G" },
{ "kline", 'k', TKL_KILL, "K-Line", 1, 1, 0 }, { "kline", 'k', TKL_KILL, "K-Line", 1, 1, 0, "K" },
{ "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1, 1 }, { "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1, 1, "Z" },
{ "zline", 'z', TKL_ZAP, "Z-Line", 1, 1, 1 }, { "zline", 'z', TKL_ZAP, "Z-Line", 1, 1, 1, "Z" },
{ "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1, 0 }, { "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1, 0, "SPAM" },
{ "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1, 0 }, { "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1, 0, "Q" },
{ "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0, 0 }, { "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0, 0, "E" },
{ "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1, 0 }, { "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1, 0, "H" },
{ "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0, 0 }, { "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0, 0, "Q" },
{ "local-exception", 'e', TKL_EXCEPTION, "Local Exception", 1, 0, 0 }, { "local-exception", 'e', TKL_EXCEPTION, "Local Exception", 1, 0, 0, "E" },
{ "local-spamfilter", 'f', TKL_SPAMF, "Local Spamfilter", 1, 0, 0 }, { "local-spamfilter", 'f', TKL_SPAMF, "Local Spamfilter", 1, 0, 0, "SPAM" },
{ "blacklist", 'b', TKL_BLACKLIST, "Blacklist", 0, 1, 1 }, { "blacklist", 'b', TKL_BLACKLIST, "Blacklist", 0, 1, 1, NULL },
{ "connect-flood", 'c', TKL_CONNECT_FLOOD, "Connect flood", 0, 1, 0 }, { "connect-flood", 'c', TKL_CONNECT_FLOOD, "Connect flood", 0, 1, 0, NULL },
{ "maxperip", 'm', TKL_MAXPERIP, "Max-per-IP", 0, 1, 0 }, { "maxperip", 'm', TKL_MAXPERIP, "Max-per-IP", 0, 1, 0, NULL },
{ "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD, "Handshake data flood", 0, 1, 1 }, { "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD, "Handshake data flood", 0, 1, 1, NULL },
{ "antirandom", 'r', TKL_ANTIRANDOM, "Antirandom", 0, 1, 0 }, { "antirandom", 'r', TKL_ANTIRANDOM, "Antirandom", 0, 1, 0, NULL },
{ "antimixedutf8", '8', TKL_ANTIMIXEDUTF8, "Antimixedutf8", 0, 1, 0 }, { "antimixedutf8", '8', TKL_ANTIMIXEDUTF8, "Antimixedutf8", 0, 1, 0, NULL },
{ "ban-version", 'v', TKL_BAN_VERSION, "Ban Version", 0, 1, 0 }, { "ban-version", 'v', TKL_BAN_VERSION, "Ban Version", 0, 1, 0, NULL },
{ NULL, '\0', 0, NULL, 0, 0, 0 }, { NULL, '\0', 0, NULL, 0, 0, 0, NULL },
}; };
#define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version" #define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version"
@@ -1331,20 +1332,34 @@ void check_set_spamfilter_utf8_setting_changed(void)
} }
/* === TKL unique IDs === /* === TKL unique IDs ===
* Every TKL has a unique id (struct TKL.id): either assigned (a random id generated * Every TKL typically has a unique id (tkl->id), such as "G7K2MP9WQX3" for a gline.
* by the server that originates the entry, or an id supplied by an external setter * See the comment below about TKL_GENERATED_ID_LEN for more info.
* such as services), or an empty string if none is known. A native (assigned-here) * These TKLIDs are communicated in S2S via @s2s-tkl/id mtag, and they
* id is <prefix><10 x base32>, eg "G7K2MP9WQX3". It is carried over S2S in the * also persist via tkldb.
* s2s-tkl/id message tag and persisted by tkldb; there is no derivation.
*/ */
/** Single-letter prefix for a native TKL id, derived from the TKL type. /* The generated TKLID (not to be confused with the maximum allowed length of TKLID):
* Uppercase, with global/local collapsed (eg gzline and zline both 'Z', spamfilter * This consists of the prefix + the random/hashed base32 chars. Obviously, the idea
* and local-spamfilter both 'F'). gline stays 'G' and kline 'K' as their letters differ. * is that this will never clash, so we have to look at birthday attack collision rates:
* - Spamfilter: "SPAM" prefix + 7 chars = 35 bits = ~0.0015% for 1000 spamfilters.
* - All the rest: 1 letter prefix + 10 chars = 50 bits = ~0.0018% for 200k entries
* And obviously both the 1000 spamfilters and 200k GLINE/whatever are a rather
* absurd number of entries, but possible in odd/attack scenarios.
*/ */
static char tkl_id_prefix(int type) #define TKL_GENERATED_ID_LEN 11
/** The TKL ID prefix string. Eg. "G" for a gline or "SPAM" for a spamfilter. */
static const char *tkl_id_prefix(int type)
{ {
return toupper(_tkl_typetochar(type)); int i;
for (i = 0; tkl_types[i].config_name; i++)
if ((tkl_types[i].type == type) && tkl_types[i].id_prefix)
return tkl_types[i].id_prefix;
#ifdef DEBUGMODE
abort(); /* this should never happen */
#endif
return "?";
} }
/** Find a TKL by its exact id (case-insensitive). Returns NULL on no match or empty id. */ /** Find a TKL by its exact id (case-insensitive). Returns NULL on no match or empty id. */
@@ -1377,16 +1392,18 @@ static void tkl_generate_id(TKL *tkl)
{ {
/* Crockford base32 (no I/L/O/U) so the id survives being read aloud */ /* Crockford base32 (no I/L/O/U) so the id survives being read aloud */
static const char b32[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; static const char b32[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
char prefix = tkl_id_prefix(tkl->type); const char *prefix = tkl_id_prefix(tkl->type);
int prefixlen = strlen(prefix);
int nrandom = TKL_GENERATED_ID_LEN - prefixlen;
TKL *other; TKL *other;
int attempt, i; int attempt, i;
for (attempt = 0; attempt < 16; attempt++) for (attempt = 0; attempt < 16; attempt++)
{ {
tkl->id[0] = prefix; strlcpy(tkl->id, prefix, sizeof(tkl->id));
for (i = 1; i <= 10; i++) for (i = 0; i < nrandom; i++)
tkl->id[i] = b32[getrandom8() % 32]; tkl->id[prefixlen + i] = b32[getrandom8() % 32];
tkl->id[i] = '\0'; tkl->id[prefixlen + nrandom] = '\0';
other = find_tkl_by_id(tkl->id); other = find_tkl_by_id(tkl->id);
if (!other || (other == tkl)) if (!other || (other == tkl))
return; /* unique (or only matches ourselves, if already linked) */ return; /* unique (or only matches ourselves, if already linked) */
@@ -1511,6 +1528,8 @@ static const char *spamfilter_fallback_id(TKL *tkl)
* key just makes the hash deterministic and identical across servers. */ * key just makes the hash deterministic and identical across servers. */
static const char key[16] = "UnrealIRCd rocks"; static const char key[16] = "UnrealIRCd rocks";
static char buf[16]; static char buf[16];
const char *prefix;
int prefixlen;
char content[8192]; char content[8192];
char actbuf[2]; char actbuf[2];
uint64_t h; uint64_t h;
@@ -1526,13 +1545,15 @@ static const char *spamfilter_fallback_id(TKL *tkl)
h = siphash(content, key); h = siphash(content, key);
buf[0] = tkl_id_prefix(tkl->type); prefix = tkl_id_prefix(tkl->type);
for (i = 1; i <= 10; i++) prefixlen = strlen(prefix);
strlcpy(buf, prefix, sizeof(buf));
for (i = 0; i < TKL_GENERATED_ID_LEN - prefixlen; i++)
{ {
buf[i] = b32[h & 31]; buf[prefixlen + i] = b32[h & 31];
h >>= 5; h >>= 5;
} }
buf[i] = '\0'; buf[prefixlen + i] = '\0';
return buf; return buf;
} }