mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-12 17:14:46 +02:00
Compare commits
2 Commits
d5b799d3de
...
62f3cda8f2
| Author | SHA1 | Date | |
|---|---|---|---|
| 62f3cda8f2 | |||
| faecdd66cd |
@@ -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)
|
||||||
@@ -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
|
each individual server and are not network-wide. This allows IRCOps to see
|
||||||
which entries never get any hits and can potentially be removed.
|
which entries never get any hits and can potentially be removed.
|
||||||
* Important exception: config-based spamfilters/bans lose their counters
|
* 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
|
* For non-config TKLs, the hit count and last hit timestamp are preserved
|
||||||
across reboots (via tkldb).
|
across reboots (via tkldb).
|
||||||
* Again, see *Developers and protocol* for the exact STATS field.
|
* Again, see *Developers and protocol* for the exact STATS field.
|
||||||
|
|||||||
@@ -848,6 +848,8 @@ extern MODVAR void (*tkl_del_line)(TKL *tkl);
|
|||||||
extern MODVAR void (*tkl_check_local_remove_shun)(TKL *tmp);
|
extern MODVAR void (*tkl_check_local_remove_shun)(TKL *tmp);
|
||||||
extern MODVAR int (*find_tkline_match)(Client *cptr, int skip_soft);
|
extern MODVAR int (*find_tkline_match)(Client *cptr, int skip_soft);
|
||||||
extern MODVAR void (*tkl_hit)(Client *client, TKL *tkl);
|
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_shun)(Client *cptr);
|
||||||
extern MODVAR int (*find_spamfilter_user)(Client *client, int flags);
|
extern MODVAR int (*find_spamfilter_user)(Client *client, int flags);
|
||||||
extern MODVAR TKL *(*find_qline)(Client *cptr, const char *nick, int *ishold);
|
extern MODVAR TKL *(*find_qline)(Client *cptr, const char *nick, int *ishold);
|
||||||
|
|||||||
@@ -3056,6 +3056,8 @@ enum EfunctionType {
|
|||||||
EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES,
|
EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES,
|
||||||
EFUNC_FLOODPROT_CHECK_MULTILINE_BATCH,
|
EFUNC_FLOODPROT_CHECK_MULTILINE_BATCH,
|
||||||
EFUNC_TKL_HIT,
|
EFUNC_TKL_HIT,
|
||||||
|
EFUNC_REMOVE_CONFIG_TKLS,
|
||||||
|
EFUNC_CONFIG_TKL_HITS_RESTORE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Module flags */
|
/* Module flags */
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ void (*tkl_del_line)(TKL *tkl);
|
|||||||
void (*tkl_check_local_remove_shun)(TKL *tmp);
|
void (*tkl_check_local_remove_shun)(TKL *tmp);
|
||||||
int (*find_tkline_match)(Client *client, int skip_soft);
|
int (*find_tkline_match)(Client *client, int skip_soft);
|
||||||
void (*tkl_hit)(Client *client, TKL *tkl);
|
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_shun)(Client *client);
|
||||||
int(*find_spamfilter_user)(Client *client, int flags);
|
int(*find_spamfilter_user)(Client *client, int flags);
|
||||||
TKL *(*find_qline)(Client *client, const char *nick, int *ishold);
|
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_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_FIND_TKLINE_MATCH, find_tkline_match, NULL, 0);
|
||||||
efunc_init_function(EFUNC_TKL_HIT, tkl_hit, 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_SHUN, find_shun, NULL, 0);
|
||||||
efunc_init_function(EFUNC_FIND_SPAMFILTER_USER, find_spamfilter_user, NULL, 0);
|
efunc_init_function(EFUNC_FIND_SPAMFILTER_USER, find_spamfilter_user, NULL, 0);
|
||||||
efunc_init_function(EFUNC_FIND_QLINE, find_qline, NULL, 0);
|
efunc_init_function(EFUNC_FIND_QLINE, find_qline, NULL, 0);
|
||||||
|
|||||||
+4
-38
@@ -274,7 +274,6 @@ int rehash_internal(Client *client);
|
|||||||
int is_blacklisted_module(const char *name);
|
int is_blacklisted_module(const char *name);
|
||||||
int modules_default_conf_modified(const char *filebuf);
|
int modules_default_conf_modified(const char *filebuf);
|
||||||
int config_item_allowed_for_config_file(const char *resource, const char *item);
|
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);
|
void free_operclass_struct(OperClass *o);
|
||||||
|
|
||||||
/** Return the printable string of a 'cep' location, such as set::something::xyz */
|
/** 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)
|
void free_proxy_block(ConfigItem_proxy *e)
|
||||||
{
|
{
|
||||||
free_security_group(e->mask);
|
free_security_group(e->mask);
|
||||||
@@ -2759,8 +2723,6 @@ void config_rehash()
|
|||||||
safe_free(tld_ptr);
|
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) {
|
for (deny_version_ptr = conf_deny_version; deny_version_ptr; deny_version_ptr = (ConfigItem_deny_version *) next) {
|
||||||
next = (ListStruct *)deny_version_ptr->next;
|
next = (ListStruct *)deny_version_ptr->next;
|
||||||
safe_free(deny_version_ptr->mask);
|
safe_free(deny_version_ptr->mask);
|
||||||
@@ -12306,6 +12268,10 @@ void central_spamfilter_download_complete(OutgoingWebRequest *request, OutgoingW
|
|||||||
|
|
||||||
/* And load the new ones... */
|
/* And load the new ones... */
|
||||||
num_rules = config_run_blocks_generic(cfptr, 0);
|
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();
|
active_rules = count_central_spamfilter_rules();
|
||||||
|
|
||||||
if (iConf.central_spamfilter_verbose > 2)
|
if (iConf.central_spamfilter_verbose > 2)
|
||||||
|
|||||||
+267
-39
@@ -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(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 _match_spamfilter_mtags(Client *client, MessageTag *mtags, const char *cmd);
|
||||||
int check_special_spamfilters_present(void);
|
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);
|
int _join_viruschan(Client *client, TKL *tk, int type);
|
||||||
void _spamfilter_build_user_string(char *buf, const char *nick, Client *client);
|
void _spamfilter_build_user_string(char *buf, const char *nick, Client *client);
|
||||||
int _match_user(const char *rmask, Client *client, int options);
|
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 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.
|
||||||
@@ -144,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"
|
||||||
|
|
||||||
@@ -176,6 +182,27 @@ int confusables_spamfilters_present = 0; /**< Are any spamfilters with input-con
|
|||||||
long previous_spamfilter_utf8 = 0;
|
long previous_spamfilter_utf8 = 0;
|
||||||
static int firstboot = 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
|
/* 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,
|
* 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).
|
* 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);
|
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, _tkl_check_local_remove_shun);
|
||||||
EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
|
EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
|
||||||
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_HIT, _tkl_hit);
|
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_SHUN, _find_shun);
|
||||||
EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
|
EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
|
||||||
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
|
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
|
||||||
@@ -260,6 +289,7 @@ MOD_INIT()
|
|||||||
if (loop.booted == 0)
|
if (loop.booted == 0)
|
||||||
firstboot = 1;
|
firstboot = 1;
|
||||||
LoadPersistentLong(modinfo, previous_spamfilter_utf8);
|
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_spamfilter);
|
||||||
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
|
||||||
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
|
||||||
@@ -285,13 +315,21 @@ MOD_LOAD()
|
|||||||
{
|
{
|
||||||
check_special_spamfilters_present();
|
check_special_spamfilters_present();
|
||||||
check_set_spamfilter_utf8_setting_changed();
|
check_set_spamfilter_utf8_setting_changed();
|
||||||
|
_config_tkl_hits_restore();
|
||||||
EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
|
EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
|
||||||
return MOD_SUCCESS;
|
return MOD_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOD_UNLOAD()
|
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);
|
SavePersistentLong(modinfo, previous_spamfilter_utf8);
|
||||||
|
SavePersistentPointer(modinfo, config_tkl_hits);
|
||||||
return MOD_SUCCESS;
|
return MOD_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,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. */
|
||||||
@@ -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 */
|
/* 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) */
|
||||||
@@ -1474,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;
|
||||||
@@ -1489,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3502,6 +3560,176 @@ void _free_tkl(TKL *tkl)
|
|||||||
safe_free(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.
|
/** Delete a TKL entry from the list and free it.
|
||||||
* @param tkl The TKL entry.
|
* @param tkl The TKL entry.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user