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

2 Commits

Author SHA1 Message Date
Bram Matthys 62f3cda8f2 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%)
2026-06-10 15:37:20 +02:00
Bram Matthys faecdd66cd Config-file based *LINES/Spamfilter: preserve hit counters between rehashes.
Unlike non-config-based TKLs - which go through tkldb - they are still not
preserved through restarts. But at least they are not lost due to REHASH.
This is done via a save+restore, a bit complicated, but we have little
choice (other than not doing this at all).

This also moves remove_config_tkls() from conf.c to tkl.c
2026-06-10 14:30:39 +02:00
6 changed files with 281 additions and 79 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ This is work in progress and may not always be a stable version.
### Enhancements:
* Server bans and Spamfilters now have a unique ID, like `G7K2MP9WQX3`:
* 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
network staff. It is `$banid` in
[set::reject-message](https://www.unrealircd.org/docs/Set_block#set::reject-message)
@@ -32,7 +32,7 @@ This is work in progress and may not always be a stable version.
each individual server and are not network-wide. This allows IRCOps to see
which entries never get any hits and can potentially be removed.
* Important exception: config-based spamfilters/bans lose their counters
on `REHASH` and restart.
on restart.
* For non-config TKLs, the hit count and last hit timestamp are preserved
across reboots (via tkldb).
* Again, see *Developers and protocol* for the exact STATS field.
+2
View File
@@ -848,6 +848,8 @@ extern MODVAR void (*tkl_del_line)(TKL *tkl);
extern MODVAR void (*tkl_check_local_remove_shun)(TKL *tmp);
extern MODVAR int (*find_tkline_match)(Client *cptr, int skip_soft);
extern MODVAR void (*tkl_hit)(Client *client, TKL *tkl);
extern MODVAR void (*remove_config_tkls)(int flag);
extern MODVAR void (*config_tkl_hits_restore)(void);
extern MODVAR int (*find_shun)(Client *cptr);
extern MODVAR int (*find_spamfilter_user)(Client *client, int flags);
extern MODVAR TKL *(*find_qline)(Client *cptr, const char *nick, int *ishold);
+2
View File
@@ -3056,6 +3056,8 @@ enum EfunctionType {
EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES,
EFUNC_FLOODPROT_CHECK_MULTILINE_BATCH,
EFUNC_TKL_HIT,
EFUNC_REMOVE_CONFIG_TKLS,
EFUNC_CONFIG_TKL_HITS_RESTORE,
};
/* Module flags */
+4
View File
@@ -74,6 +74,8 @@ void (*tkl_del_line)(TKL *tkl);
void (*tkl_check_local_remove_shun)(TKL *tmp);
int (*find_tkline_match)(Client *client, int skip_soft);
void (*tkl_hit)(Client *client, TKL *tkl);
void (*remove_config_tkls)(int flag);
void (*config_tkl_hits_restore)(void);
int (*find_shun)(Client *client);
int(*find_spamfilter_user)(Client *client, int flags);
TKL *(*find_qline)(Client *client, const char *nick, int *ishold);
@@ -424,6 +426,8 @@ void efunctions_init(void)
efunc_init_function(EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, tkl_check_local_remove_shun, NULL, 0);
efunc_init_function(EFUNC_FIND_TKLINE_MATCH, find_tkline_match, NULL, 0);
efunc_init_function(EFUNC_TKL_HIT, tkl_hit, NULL, 0);
efunc_init_function(EFUNC_REMOVE_CONFIG_TKLS, remove_config_tkls, NULL, 0);
efunc_init_function(EFUNC_CONFIG_TKL_HITS_RESTORE, config_tkl_hits_restore, NULL, 0);
efunc_init_function(EFUNC_FIND_SHUN, find_shun, NULL, 0);
efunc_init_function(EFUNC_FIND_SPAMFILTER_USER, find_spamfilter_user, NULL, 0);
efunc_init_function(EFUNC_FIND_QLINE, find_qline, NULL, 0);
+4 -38
View File
@@ -274,7 +274,6 @@ int rehash_internal(Client *client);
int is_blacklisted_module(const char *name);
int modules_default_conf_modified(const char *filebuf);
int config_item_allowed_for_config_file(const char *resource, const char *item);
void remove_config_tkls(int flag);
void free_operclass_struct(OperClass *o);
/** Return the printable string of a 'cep' location, such as set::something::xyz */
@@ -2559,41 +2558,6 @@ int config_read_file(const char *filename, const char *display_name)
}
}
/** Remove all TKL's that were added by the config file(s).
* This is done after config passed testing and right before
* adding the (new) entries.
*/
void remove_config_tkls(int flag)
{
TKL *tk, *tk_next;
int index, index2;
/* IP hashed TKL list */
for (index = 0; index < TKLIPHASHLEN1; index++)
{
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
{
for (tk = tklines_ip_hash[index][index2]; tk; tk = tk_next)
{
tk_next = tk->next;
if (tk->flags & flag)
tkl_del_line(tk);
}
}
}
/* Generic TKL list */
for (index = 0; index < TKLISTLEN; index++)
{
for (tk = tklines[index]; tk; tk = tk_next)
{
tk_next = tk->next;
if (tk->flags & flag)
tkl_del_line(tk);
}
}
}
void free_proxy_block(ConfigItem_proxy *e)
{
free_security_group(e->mask);
@@ -2759,8 +2723,6 @@ void config_rehash()
safe_free(tld_ptr);
}
remove_config_tkls(TKL_FLAG_CONFIG);
for (deny_version_ptr = conf_deny_version; deny_version_ptr; deny_version_ptr = (ConfigItem_deny_version *) next) {
next = (ListStruct *)deny_version_ptr->next;
safe_free(deny_version_ptr->mask);
@@ -12306,6 +12268,10 @@ void central_spamfilter_download_complete(OutgoingWebRequest *request, OutgoingW
/* And load the new ones... */
num_rules = config_run_blocks_generic(cfptr, 0);
/* Restore hit counters onto the freshly re-added central spamfilters (matched
* by key), from the snapshot taken in remove_config_tkls() just above.
*/
config_tkl_hits_restore();
active_rules = count_central_spamfilter_rules();
if (iConf.central_spamfilter_verbose > 2)
+267 -39
View File
@@ -99,6 +99,11 @@ int _take_action(Client *client, BanAction *action, const char *reason, long dur
int _match_spamfilter(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, ClientContext *clictx, TKL **rettk);
int _match_spamfilter_mtags(Client *client, MessageTag *mtags, const char *cmd);
int check_special_spamfilters_present(void);
char *tkl_hits_key(TKL *tkl, char *buf, size_t len);
void config_tkl_hits_free(ModData *m);
void config_tkl_hits_snapshot(TKL *tkl);
void _config_tkl_hits_restore(void);
void _remove_config_tkls(int flag);
int _join_viruschan(Client *client, TKL *tk, int type);
void _spamfilter_build_user_string(char *buf, const char *nick, Client *client);
int _match_user(const char *rmask, Client *client, int options);
@@ -132,6 +137,7 @@ struct TKLTypeTable
unsigned tkltype:1; /**< Is a type available in cmd_tkl() and friends */
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) */
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.
@@ -144,26 +150,26 @@ struct TKLTypeTable
* - more?
*/
TKLTypeTable tkl_types[] = {
/* <config name> <letter> <TKL_xxx type> <logging name> <tkl option?> <exempt option?> <ip address only?> */
{ "gline", 'G', TKL_KILL | TKL_GLOBAL, "G-Line", 1, 1, 0 },
{ "kline", 'k', TKL_KILL, "K-Line", 1, 1, 0 },
{ "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1, 1 },
{ "zline", 'z', TKL_ZAP, "Z-Line", 1, 1, 1 },
{ "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1, 0 },
{ "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1, 0 },
{ "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0, 0 },
{ "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1, 0 },
{ "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0, 0 },
{ "local-exception", 'e', TKL_EXCEPTION, "Local Exception", 1, 0, 0 },
{ "local-spamfilter", 'f', TKL_SPAMF, "Local Spamfilter", 1, 0, 0 },
{ "blacklist", 'b', TKL_BLACKLIST, "Blacklist", 0, 1, 1 },
{ "connect-flood", 'c', TKL_CONNECT_FLOOD, "Connect flood", 0, 1, 0 },
{ "maxperip", 'm', TKL_MAXPERIP, "Max-per-IP", 0, 1, 0 },
{ "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD, "Handshake data flood", 0, 1, 1 },
{ "antirandom", 'r', TKL_ANTIRANDOM, "Antirandom", 0, 1, 0 },
{ "antimixedutf8", '8', TKL_ANTIMIXEDUTF8, "Antimixedutf8", 0, 1, 0 },
{ "ban-version", 'v', TKL_BAN_VERSION, "Ban Version", 0, 1, 0 },
{ NULL, '\0', 0, NULL, 0, 0, 0 },
/* <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, "G" },
{ "kline", 'k', TKL_KILL, "K-Line", 1, 1, 0, "K" },
{ "gzline", 'Z', TKL_ZAP | TKL_GLOBAL, "Global Z-Line", 1, 1, 1, "Z" },
{ "zline", 'z', TKL_ZAP, "Z-Line", 1, 1, 1, "Z" },
{ "spamfilter", 'F', TKL_SPAMF | TKL_GLOBAL, "Spamfilter", 1, 1, 0, "SPAM" },
{ "qline", 'Q', TKL_NAME | TKL_GLOBAL, "Q-Line", 1, 1, 0, "Q" },
{ "except", 'E', TKL_EXCEPTION | TKL_GLOBAL, "Exception", 1, 0, 0, "E" },
{ "shun", 's', TKL_SHUN | TKL_GLOBAL, "Shun", 1, 1, 0, "H" },
{ "local-qline", 'q', TKL_NAME, "Local Q-Line", 1, 0, 0, "Q" },
{ "local-exception", 'e', TKL_EXCEPTION, "Local Exception", 1, 0, 0, "E" },
{ "local-spamfilter", 'f', TKL_SPAMF, "Local Spamfilter", 1, 0, 0, "SPAM" },
{ "blacklist", 'b', TKL_BLACKLIST, "Blacklist", 0, 1, 1, NULL },
{ "connect-flood", 'c', TKL_CONNECT_FLOOD, "Connect flood", 0, 1, 0, NULL },
{ "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, NULL },
{ "antirandom", 'r', TKL_ANTIRANDOM, "Antirandom", 0, 1, 0, NULL },
{ "antimixedutf8", '8', TKL_ANTIMIXEDUTF8, "Antimixedutf8", 0, 1, 0, NULL },
{ "ban-version", 'v', TKL_BAN_VERSION, "Ban Version", 0, 1, 0, NULL },
{ 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"
@@ -176,6 +182,27 @@ int confusables_spamfilters_present = 0; /**< Are any spamfilters with input-con
long previous_spamfilter_utf8 = 0;
static int firstboot = 0;
/* Config-file (and central) TKLs are freed and re-created on every /REHASH (and
* central spamfilters on every feed refresh) since they live in the config, not
* in the tkldb, which would otherwise reset their hit counters. To keep the
* counters we stash them here right before the old entry is freed, keyed by a
* stable per-TKL identity (see tkl_hits_key), and copy them back onto the freshly
* re-added entry with the same key. The list is carried across the rehash
* module-unload by Save/LoadPersistentPointer (same trick connthrottle uses).
* This is rehash/refresh-only; on a full restart the counters still reset.
* Covers config server bans, name bans (qlines) and spamfilters; not exceptions.
*/
typedef struct ConfigTKLHits ConfigTKLHits;
struct ConfigTKLHits {
ConfigTKLHits *prev, *next;
char key[256];
long long hits;
time_t lasthit;
long long hits_except; /* spamfilter only */
time_t lasthit_except; /* spamfilter only */
};
ConfigTKLHits *config_tkl_hits = NULL;
/* s2s-tkl/<field>: per-TKL fields carried over S2S as @s2s-tkl/<field>=<value> on the
* TKL command (modeled on s2s-md/). Old servers ignore unknown tags. To add a field,
* add a row here plus its serialize/unserialize pair (defined further down).
@@ -224,6 +251,8 @@ MOD_TEST()
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, _tkl_check_local_remove_shun);
EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_HIT, _tkl_hit);
EfunctionAddVoid(modinfo->handle, EFUNC_REMOVE_CONFIG_TKLS, _remove_config_tkls);
EfunctionAddVoid(modinfo->handle, EFUNC_CONFIG_TKL_HITS_RESTORE, _config_tkl_hits_restore);
EfunctionAdd(modinfo->handle, EFUNC_FIND_SHUN, _find_shun);
EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
@@ -260,6 +289,7 @@ MOD_INIT()
if (loop.booted == 0)
firstboot = 1;
LoadPersistentLong(modinfo, previous_spamfilter_utf8);
LoadPersistentPointer(modinfo, config_tkl_hits, config_tkl_hits_free);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_spamfilter);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
@@ -285,13 +315,21 @@ MOD_LOAD()
{
check_special_spamfilters_present();
check_set_spamfilter_utf8_setting_changed();
_config_tkl_hits_restore();
EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
/* Free our config TKLs here (rather than from config_rehash) so the same pass
* can snapshot the spamfilter hit counters. Call the local impl: the snapshot
* store is a per-instance global, and we must write to ours (saved just below),
* not the new instance the efunc pointer now points to.
*/
_remove_config_tkls(TKL_FLAG_CONFIG);
SavePersistentLong(modinfo, previous_spamfilter_utf8);
SavePersistentPointer(modinfo, config_tkl_hits);
return MOD_SUCCESS;
}
@@ -1294,20 +1332,34 @@ void check_set_spamfilter_utf8_setting_changed(void)
}
/* === TKL unique IDs ===
* Every TKL has a unique id (struct TKL.id): either assigned (a random id generated
* by the server that originates the entry, or an id supplied by an external setter
* such as services), or an empty string if none is known. A native (assigned-here)
* id is <prefix><10 x base32>, eg "G7K2MP9WQX3". It is carried over S2S in the
* s2s-tkl/id message tag and persisted by tkldb; there is no derivation.
* Every TKL typically has a unique id (tkl->id), such as "G7K2MP9WQX3" for a gline.
* See the comment below about TKL_GENERATED_ID_LEN for more info.
* These TKLIDs are communicated in S2S via @s2s-tkl/id mtag, and they
* also persist via tkldb.
*/
/** Single-letter prefix for a native TKL id, derived from the TKL type.
* Uppercase, with global/local collapsed (eg gzline and zline both 'Z', spamfilter
* and local-spamfilter both 'F'). gline stays 'G' and kline 'K' as their letters differ.
/* The generated TKLID (not to be confused with the maximum allowed length of TKLID):
* This consists of the prefix + the random/hashed base32 chars. Obviously, the idea
* 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. */
@@ -1340,16 +1392,18 @@ static void tkl_generate_id(TKL *tkl)
{
/* Crockford base32 (no I/L/O/U) so the id survives being read aloud */
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;
int attempt, i;
for (attempt = 0; attempt < 16; attempt++)
{
tkl->id[0] = prefix;
for (i = 1; i <= 10; i++)
tkl->id[i] = b32[getrandom8() % 32];
tkl->id[i] = '\0';
strlcpy(tkl->id, prefix, sizeof(tkl->id));
for (i = 0; i < nrandom; i++)
tkl->id[prefixlen + i] = b32[getrandom8() % 32];
tkl->id[prefixlen + nrandom] = '\0';
other = find_tkl_by_id(tkl->id);
if (!other || (other == tkl))
return; /* unique (or only matches ourselves, if already linked) */
@@ -1474,6 +1528,8 @@ static const char *spamfilter_fallback_id(TKL *tkl)
* key just makes the hash deterministic and identical across servers. */
static const char key[16] = "UnrealIRCd rocks";
static char buf[16];
const char *prefix;
int prefixlen;
char content[8192];
char actbuf[2];
uint64_t h;
@@ -1489,13 +1545,15 @@ static const char *spamfilter_fallback_id(TKL *tkl)
h = siphash(content, key);
buf[0] = tkl_id_prefix(tkl->type);
for (i = 1; i <= 10; i++)
prefix = tkl_id_prefix(tkl->type);
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;
}
buf[i] = '\0';
buf[prefixlen + i] = '\0';
return buf;
}
@@ -3502,6 +3560,176 @@ void _free_tkl(TKL *tkl)
safe_free(tkl);
}
/** Build a stable match key for a hit-bearing config/central TKL (server ban,
* name ban or spamfilter), used to pair an old entry with its rebuilt self across
* a rehash / feed refresh. Returns buf, or NULL for types we don't preserve
* (exceptions) or with no usable key. Format: "<typechar> <id|mask|name>", joined
* by a single space (these fields never contain spaces).
*/
char *tkl_hits_key(TKL *tkl, char *buf, size_t len)
{
char tmp[BUFSIZE];
char t = tkl_typetochar(tkl->type);
if (TKLIsSpamfilter(tkl) && tkl->id[0])
snprintf(buf, len, "%c %s", t, tkl->id);
else if (TKLIsServerBan(tkl))
snprintf(buf, len, "%c %s", t, tkl_uhost(tkl, tmp, sizeof(tmp), 0));
else if (TKLIsNameBan(tkl))
snprintf(buf, len, "%c %s", t, tkl->ptr.nameban->name);
else
return NULL;
return buf;
}
/** Stash a config/central TKL's hit counters before it is freed (from the removal
* loop in _remove_config_tkls), so they can be copied back onto the same entry
* after a rehash / feed refresh re-adds it. Covers server bans, name bans and
* spamfilters (whatever tkl_hits_key recognises); skips entries that never hit.
*/
void config_tkl_hits_snapshot(TKL *tkl)
{
ConfigTKLHits *e;
char key[256];
if (!(tkl->flags & (TKL_FLAG_CONFIG | TKL_FLAG_CENTRAL_SPAMFILTER)))
return;
if (!tkl_hits_key(tkl, key, sizeof(key)))
return; /* not a hit-bearing config type */
if (!tkl->hits && !tkl->lasthit &&
(!TKLIsSpamfilter(tkl) ||
(!tkl->ptr.spamfilter->hits_except && !tkl->ptr.spamfilter->lasthit_except)))
return; /* never hit, nothing to keep */
e = safe_alloc(sizeof(ConfigTKLHits));
strlcpy(e->key, key, sizeof(e->key));
e->hits = tkl->hits;
e->lasthit = tkl->lasthit;
if (TKLIsSpamfilter(tkl))
{
e->hits_except = tkl->ptr.spamfilter->hits_except;
e->lasthit_except = tkl->ptr.spamfilter->lasthit_except;
}
AddListItem(e, config_tkl_hits);
}
/** Free the whole snapshot list and forget it. */
void config_tkl_hits_free_all(void)
{
ConfigTKLHits *e, *e_next;
for (e = config_tkl_hits; e; e = e_next)
{
e_next = e->next;
safe_free(e);
}
config_tkl_hits = NULL;
}
/** PersistentPointer free callback: used only if the snapshot is never reloaded
* (e.g. final module unload), otherwise MOD_LOAD consumes and frees it.
*/
void config_tkl_hits_free(ModData *m)
{
config_tkl_hits_free_all();
}
/** Copy stashed counters back onto one re-added TKL, matched by key. */
static void config_tkl_hits_restore_one(TKL *tkl)
{
ConfigTKLHits *e;
char key[256];
if (!(tkl->flags & (TKL_FLAG_CONFIG | TKL_FLAG_CENTRAL_SPAMFILTER)))
return;
if (!tkl_hits_key(tkl, key, sizeof(key)))
return;
for (e = config_tkl_hits; e; e = e->next)
{
if (!strcmp(e->key, key))
{
tkl->hits = e->hits;
tkl->lasthit = e->lasthit;
if (TKLIsSpamfilter(tkl))
{
tkl->ptr.spamfilter->hits_except = e->hits_except;
tkl->ptr.spamfilter->lasthit_except = e->lasthit_except;
}
return;
}
}
}
/** After config/central TKLs have been re-added, copy the stashed hit counters
* back onto the ones with a matching key, then drop the whole stash. Called from
* MOD_LOAD (config rehash) and from the central feed refresh; a no-op on a normal
* boot (the stash is empty).
*/
void _config_tkl_hits_restore(void)
{
TKL *tkl;
int index, index2;
if (!config_tkl_hits)
return;
/* IP-hashed list (zlines, klines, glines) */
for (index = 0; index < TKLIPHASHLEN1; index++)
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
config_tkl_hits_restore_one(tkl);
/* Generic list (name bans, spamfilters, non-ip-hashed server bans) */
for (index = 0; index < TKLISTLEN; index++)
for (tkl = tklines[index]; tkl; tkl = tkl->next)
config_tkl_hits_restore_one(tkl);
/* Matched entries are applied; entries no longer in the config are discarded. */
config_tkl_hits_free_all();
}
/** Remove all TKLs with the given flag (TKL_FLAG_CONFIG or
* TKL_FLAG_CENTRAL_SPAMFILTER). For config/central spamfilters the hit counters
* are snapshotted first, so a later re-add can restore them. Lives here (rather
* than in conf.c) so the snapshot sits right next to the removal it rides on.
*/
void _remove_config_tkls(int flag)
{
TKL *tk, *tk_next;
int index, index2;
/* IP hashed TKL list */
for (index = 0; index < TKLIPHASHLEN1; index++)
{
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
{
for (tk = tklines_ip_hash[index][index2]; tk; tk = tk_next)
{
tk_next = tk->next;
if (tk->flags & flag)
{
config_tkl_hits_snapshot(tk);
tkl_del_line(tk);
}
}
}
}
/* Generic TKL list */
for (index = 0; index < TKLISTLEN; index++)
{
for (tk = tklines[index]; tk; tk = tk_next)
{
tk_next = tk->next;
if (tk->flags & flag)
{
config_tkl_hits_snapshot(tk);
tkl_del_line(tk);
}
}
}
}
/** Delete a TKL entry from the list and free it.
* @param tkl The TKL entry.
*/