mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-12 17:34:46 +02:00
Add TKL IDs via message tags in S2S.
By default - assuming you don't set set::reject-message things by yourself - the *LINE id is appended at the end of the rejection that is shown to the user, like: [ID: G7K2MP9WQX3]. Also new is spamfilter to *LINE mapping, so you can see which *LINE was set by which SPAMFILTER. For this STATS gline and friends were enhanced. In fact, multiple fields were added there, including some that are 0 (zero) placeholders at the moment. These will be set in a future commit. Some things were combined here so we only have to break STATS and tkldb database format once (unless i made a mistake, then the follow up commit will correct that i guess :D). This was requested by Hero in https://bugs.unrealircd.org/view.php?id=4397 in 2015. Again by musk in https://bugs.unrealircd.org/view.php?id=4397 in 2022. And on IRC by Chris and others. As you can see it was not SUPER easy and a lot of thought went into this (and in terms of S2S traffic it is part of something bigger too)
This commit is contained in:
@@ -5,6 +5,28 @@ This is the git version (development version) for future UnrealIRCd 6.2.6.
|
||||
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.
|
||||
* 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)
|
||||
and included by default. If you have a custom set::reject-message in
|
||||
your config file then you will have to add it in manually.
|
||||
* IRCOps see the ID in `STATS gline` and similar and can search for it
|
||||
with e.g. `STATS gline +i G7K2MP9WQX3`.
|
||||
* Spamfilters also have an ID, and we already had something similar that
|
||||
could be used for `SPAMFILTER del <id>` that was only local-server.
|
||||
For new spamfilters this is now a network wide ID as well.
|
||||
* When a server ban was placed by a spamfilter, `STATS gline` shows the
|
||||
originating spamfilter's ID, so it can be traced back.
|
||||
* The `STATS gline` and other TKL stats changed format, see
|
||||
*Developers and protocol* if you use scripts or your client depends
|
||||
on the exact numeric format.
|
||||
* For TKL IDs to reach all servers, all servers need to be on 6.2.6
|
||||
or later, especially the hubs. If there is one server in-between
|
||||
that is older, then TKL IDs don't propagate properly and the ID
|
||||
will be empty.
|
||||
|
||||
### Changes:
|
||||
* Spamfilter regexes now use more sensible defaults in terms of "max effort",
|
||||
@@ -34,6 +56,31 @@ This is work in progress and may not always be a stable version.
|
||||
`update_known_user_cache(client);` to update the known users cache.
|
||||
For example, if you have some authentication module that marks a user
|
||||
as IsLoggedIn.
|
||||
* TKL entries can now use message tags to carry extra data, similar to how
|
||||
`s2s-md` worked for early moddata, we have `s2s-tkl`. We have two at
|
||||
the moment:
|
||||
* `s2s-tkl/id`: network-wide unique ID for the TKL entry
|
||||
* `s2s-tkl/spamfilter_id`: for server bans, if they were set by spamfilter,
|
||||
the spamfilter TKL ID.
|
||||
* Example: `@s2s-tkl/id=xxxx;s2s-tkl/spamfilter_id=yyyy :server TKL .....`
|
||||
* Older servers will not pass these along, so for this functionality to
|
||||
work all servers need to be on latest version, especially your hub.
|
||||
* The TKL `STATS` numerics has more fields now, they are added right before
|
||||
last (so before the `:reason` or `:regex`):
|
||||
* `RPL_STATSGLINE` (223) ends with `hits lasthit spamfilter_id id :reason`
|
||||
* `RPL_STATSQLINE` (217) ends with `hits lasthit id :reason`
|
||||
* `RPL_STATSSPAMF` (229) ends with `lasthit lasthit_except id :regex`
|
||||
(which comes right after `hits hits_except`, which was already there)
|
||||
* `RPL_STATSEXCEPTTKL` (230) ends with `id :reason`
|
||||
* An absent id or spamfilter_id is sent as `-`. The hits/lasthit/lasthit_except
|
||||
fields are reserved and currently always `0`; FIXME in later commit.
|
||||
* `banned_client()` has an extra parameter `const char *tklid` for the
|
||||
TKL ID (or NULL if none).
|
||||
* The tkldb database version is now 6260 and stores the id and spamfilter_id.
|
||||
FIXME: also prepared for hit stats, so update this release note line in later commit.
|
||||
Older databases still load. Downside: you cannot downgrade UnrealIRCd.
|
||||
* JSON for TKL entries (server logs and JSON-RPC) now includes `id`, and
|
||||
`spamfilter_id` for spamfilter-created server bans.
|
||||
|
||||
UnrealIRCd 6.2.5
|
||||
-----------------
|
||||
|
||||
+1
-1
@@ -953,7 +953,7 @@ extern MODVAR int (*decode_authenticate_plain)(const char *param, char **authori
|
||||
extern MODVAR void (*exit_client)(Client *client, MessageTag *recv_mtags, const char *comment);
|
||||
extern MODVAR void (*exit_client_fmt)(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf, 3, 4)));
|
||||
extern MODVAR void (*exit_client_ex)(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment);
|
||||
extern MODVAR void (*banned_client)(Client *client, const char *bantype, const char *reason, int global, int noexit);
|
||||
extern MODVAR void (*banned_client)(Client *client, const char *bantype, const char *reason, const char *tklid, int global, int noexit);
|
||||
extern MODVAR char *(*unreal_expand_string)(const char *str, char *buf, size_t buflen, NameValuePrioList *nvp, int buildvarstring_options, Client *client);
|
||||
extern MODVAR char *(*utf8_convert_confusables)(const char *i, char *obuf, int olen);
|
||||
extern MODVAR const char *(*utf8_get_block_name)(int i);
|
||||
|
||||
+4
-4
@@ -372,17 +372,17 @@
|
||||
#define STR_RPL_STATSCOMMANDS /* 212 */ "%s %u %lu"
|
||||
#define STR_RPL_STATSCLINE /* 213 */ "%c %s * %s %d %d %s"
|
||||
#define STR_RPL_STATSILINE /* 215 */ "I %s %s %d %d %s %s %d"
|
||||
#define STR_RPL_STATSQLINE /* 217 */ "%c %s %lld %lld %s :%s"
|
||||
#define STR_RPL_STATSQLINE /* 217 */ "%c %s %lld %lld %s %lld %lld %s :%s"
|
||||
#define STR_RPL_STATSYLINE /* 218 */ "Y %s %d %d %d %d %d"
|
||||
#define STR_RPL_ENDOFSTATS /* 219 */ "%c :End of /STATS report"
|
||||
#define STR_RPL_UMODEIS /* 221 */ "%s"
|
||||
#define STR_RPL_STATSGLINE /* 223 */ "%c %s %lld %lld %s :%s"
|
||||
#define STR_RPL_STATSGLINE /* 223 */ "%c %s %lld %lld %s %lld %lld %s %s :%s"
|
||||
#define STR_RPL_STATSTLINE /* 224 */ "T %s %s %s"
|
||||
#define STR_RPL_STATSNLINE /* 226 */ "n %s %s"
|
||||
#define STR_RPL_STATSVLINE /* 227 */ "v %s %s %s"
|
||||
#define STR_RPL_STATSBANVER /* 228 */ "%s %s"
|
||||
#define STR_RPL_STATSSPAMF /* 229 */ "%c %s %s %s %lld %lld %lld %s %s %lld %lld :%s"
|
||||
#define STR_RPL_STATSEXCEPTTKL /* 230 */ "%s %s %lld %lld %s :%s"
|
||||
#define STR_RPL_STATSSPAMF /* 229 */ "%c %s %s %s %lld %lld %lld %s %s %lld %lld %lld %lld %s :%s"
|
||||
#define STR_RPL_STATSEXCEPTTKL /* 230 */ "%s %s %lld %lld %s %s :%s"
|
||||
#define STR_RPL_RULES /* 232 */ ":- %s"
|
||||
#define STR_RPL_STATSLLINE /* 241 */ "%c %s * %s %d %d"
|
||||
#define STR_RPL_STATSUPTIME /* 242 */ ":Server Up %lld days, %lld:%02lld:%02lld"
|
||||
|
||||
+4
-1
@@ -204,7 +204,6 @@ typedef OperPermission (*OperClassEntryEvalCallback)(OperClassACLEntryVar* varia
|
||||
#define MAXCCUSERS 20 /* Maximum for set::anti-flood::max-concurrent-conversations */
|
||||
#define BATCHLEN 22
|
||||
#define MAXBATCHREFLEN 48 /* Max length of client-sent batch reference tags */
|
||||
#define MAXSPAMFILTERIDLEN 24
|
||||
|
||||
/*
|
||||
* Watch it - Don't change this unless you also change the ERR_TOOMANYWATCH
|
||||
@@ -1319,6 +1318,8 @@ struct BanException {
|
||||
#define TKL_FLAG_CONFIG 0x0001 /* Entry from configuration file. Cannot be removed by using commands. */
|
||||
#define TKL_FLAG_CENTRAL_SPAMFILTER 0x0002 /* Entry from central spamfilter. */
|
||||
|
||||
#define TKLIDLEN 16 /**< TKL id buffer size: native 'prefix+body' or an external id. Max 15 chars + NUL. */
|
||||
|
||||
/** A TKL entry, such as a KLINE, GLINE, Spamfilter, QLINE, Exception, .. */
|
||||
struct TKL {
|
||||
TKL *prev, *next;
|
||||
@@ -1327,6 +1328,8 @@ struct TKL {
|
||||
char *set_by; /**< By who was this entry added */
|
||||
time_t set_at; /**< When this entry was added */
|
||||
time_t expire_at; /**< When this entry will expire */
|
||||
char id[TKLIDLEN]; /**< Unique ID: assigned (random, generated by originating server) or external (eg from services), or empty string if none */
|
||||
char spamfilter_id[TKLIDLEN]; /**< For server bans created by a spamfilter: that spamfilter's id. Empty otherwise. */
|
||||
union {
|
||||
Spamfilter *spamfilter;
|
||||
ServerBan *serverban;
|
||||
|
||||
@@ -184,7 +184,7 @@ int (*decode_authenticate_plain)(const char *param, char **authorization_id, cha
|
||||
void (*exit_client)(Client *client, MessageTag *recv_mtags, const char *comment);
|
||||
void (*exit_client_fmt)(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...);
|
||||
void (*exit_client_ex)(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment);
|
||||
void (*banned_client)(Client *client, const char *bantype, const char *reason, int global, int noexit);
|
||||
void (*banned_client)(Client *client, const char *bantype, const char *reason, const char *tklid, int global, int noexit);
|
||||
char *(*unreal_expand_string)(const char *str, char *buf, size_t buflen, NameValuePrioList *nvp, int buildvarstring_options, Client *client);
|
||||
char *(*utf8_convert_confusables)(const char *i, char *obuf, int olen);
|
||||
const char *(*utf8_get_block_name)(int i);
|
||||
|
||||
+2
-2
@@ -1935,8 +1935,8 @@ void config_setdefaultsettings(Configuration *i)
|
||||
safe_strdup(i->reject_message_too_many_new_connections_ipv6_range, "Too many new connections from this IPv6 range ($prefix_addr/$prefix_len) [connthrottle]");
|
||||
safe_strdup(i->reject_message_server_full, "This server is full");
|
||||
safe_strdup(i->reject_message_unauthorized, "You are not authorized to connect to this server");
|
||||
safe_strdup(i->reject_message_kline, "You are not welcome on this server. $bantype: $banreason. Email $klineaddr for more information.");
|
||||
safe_strdup(i->reject_message_gline, "You are not welcome on this network. $bantype: $banreason. Email $glineaddr for more information.");
|
||||
safe_strdup(i->reject_message_kline, "You are not welcome on this server. $bantype: $banreason. Email $klineaddr for more information.$banid");
|
||||
safe_strdup(i->reject_message_gline, "You are not welcome on this network. $bantype: $banreason. Email $glineaddr for more information.$banid");
|
||||
|
||||
i->topic_setter = SETTER_NICK_USER_HOST;
|
||||
i->ban_setter = SETTER_NICK_USER_HOST;
|
||||
|
||||
@@ -565,6 +565,8 @@ void json_expand_tkl(json_t *root, const char *key, TKL *tkl, int detail)
|
||||
json_object_set_new(j, "type", json_string_unreal(tkl_type_config_string(tkl))); // Eg 'kline'
|
||||
json_object_set_new(j, "type_string", json_string_unreal(tkl_type_string(tkl))); // Eg 'Soft K-Line'
|
||||
json_object_set_new(j, "set_by", json_string_unreal(tkl->set_by));
|
||||
if (tkl->id[0])
|
||||
json_object_set_new(j, "id", json_string_unreal(tkl->id));
|
||||
json_object_set_new(j, "set_at", json_timestamp(tkl->set_at));
|
||||
json_object_set_new(j, "expire_at", json_timestamp(tkl->expire_at));
|
||||
*buf = '\0';
|
||||
@@ -592,6 +594,8 @@ void json_expand_tkl(json_t *root, const char *key, TKL *tkl, int detail)
|
||||
else
|
||||
json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0)));
|
||||
json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.serverban->reason));
|
||||
if (tkl->spamfilter_id[0])
|
||||
json_object_set_new(j, "spamfilter_id", json_string_unreal(tkl->spamfilter_id));
|
||||
} else
|
||||
if (TKLIsNameBan(tkl))
|
||||
{
|
||||
|
||||
+1
-1
@@ -2967,7 +2967,7 @@ const char *command_issued_by_rpc(MessageTag *mtags)
|
||||
/** Is 's' a valid spamfilter id? A-Z, 0-9 and _ and with a max length. */
|
||||
int valid_spamfilter_id(const char *s)
|
||||
{
|
||||
if (strlen(s) > MAXSPAMFILTERIDLEN)
|
||||
if (strlen(s) >= TKLIDLEN)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ CMD_FUNC(cmd_chgname)
|
||||
if (!ValidatePermissionsForPath("immune:server-ban:ban-realname",target,NULL,NULL,NULL) &&
|
||||
((bconf = find_ban(NULL, target->info, CONF_BAN_REALNAME))))
|
||||
{
|
||||
banned_client(target, "realname", bconf->reason?bconf->reason:"", 0, 0);
|
||||
banned_client(target, "realname", bconf->reason?bconf->reason:"", NULL, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1097,7 +1097,7 @@ int _register_user(Client *client)
|
||||
if ((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME)))
|
||||
{
|
||||
ircstats.is_ref++;
|
||||
banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
|
||||
banned_client(client, "realname", bconf->reason?bconf->reason:"", NULL, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
/* Check G/Z lines before shuns -- kill before quite -- codemastr */
|
||||
|
||||
+19
-8
@@ -38,7 +38,7 @@ CMD_FUNC(cmd_quit);
|
||||
void _exit_client(Client *client, MessageTag *recv_mtags, const char *comment);
|
||||
void _exit_client_fmt(Client *client, MessageTag *recv_mtags, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf, 3, 4)));
|
||||
void _exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, const char *comment);
|
||||
void _banned_client(Client *client, const char *bantype, const char *reason, int global, int noexit);
|
||||
void _banned_client(Client *client, const char *bantype, const char *reason, const char *tklid, int global, int noexit);
|
||||
static void remove_dependents(Client *client, Client *from, MessageTag *mtags, const char *comment, const char *splitstr);
|
||||
static void exit_one_client(Client *, MessageTag *mtags_i, const char *);
|
||||
static int should_hide_ban_reason(Client *client, const char *reason);
|
||||
@@ -491,11 +491,12 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com
|
||||
*
|
||||
* @note This function will call exit_client() appropriately.
|
||||
*/
|
||||
void _banned_client(Client *client, const char *bantype, const char *reason, int global, int noexit)
|
||||
void _banned_client(Client *client, const char *bantype, const char *reason, const char *tklid, int global, int noexit)
|
||||
{
|
||||
char buf[512];
|
||||
char idbuf[64];
|
||||
char *fmt = global ? iConf.reject_message_gline : iConf.reject_message_kline;
|
||||
const char *vars[6], *values[6];
|
||||
const char *vars[7], *values[7];
|
||||
MessageTag *mtags = NULL;
|
||||
|
||||
if (!MyConnect(client))
|
||||
@@ -503,6 +504,14 @@ void _banned_client(Client *client, const char *bantype, const char *reason, int
|
||||
|
||||
RunHook(HOOKTYPE_BANNED_CLIENT, client, bantype, reason, global);
|
||||
|
||||
/* The " [ID: xxx]" fragment, empty when there is no id. Used both as the $banid
|
||||
* reject-message variable and appended to the quit reason / real-quit-reason mtag.
|
||||
*/
|
||||
if (!BadPtr(tklid))
|
||||
snprintf(idbuf, sizeof(idbuf), " [ID: %s]", tklid);
|
||||
else
|
||||
idbuf[0] = '\0';
|
||||
|
||||
/* This was: "You are not welcome on this %s. %s: %s. %s" but is now dynamic: */
|
||||
vars[0] = "bantype";
|
||||
values[0] = bantype;
|
||||
@@ -514,8 +523,10 @@ void _banned_client(Client *client, const char *bantype, const char *reason, int
|
||||
values[3] = GLINE_ADDRESS ? GLINE_ADDRESS : KLINE_ADDRESS; /* fallback to klineaddr */
|
||||
vars[4] = "ip";
|
||||
values[4] = GetIP(client);
|
||||
vars[5] = NULL;
|
||||
values[5] = NULL;
|
||||
vars[5] = "banid";
|
||||
values[5] = idbuf;
|
||||
vars[6] = NULL;
|
||||
values[6] = NULL;
|
||||
buildvarstring(fmt, buf, sizeof(buf), vars, values);
|
||||
|
||||
/* This is a bit extensive but we will send both a YOUAREBANNEDCREEP
|
||||
@@ -537,13 +548,13 @@ void _banned_client(Client *client, const char *bantype, const char *reason, int
|
||||
/* Hide the ban reason, but put the real reason in unrealircd.org/real-quit-reason */
|
||||
MessageTag *m = safe_alloc(sizeof(MessageTag));
|
||||
safe_strdup(m->name, "unrealircd.org/real-quit-reason");
|
||||
snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason);
|
||||
snprintf(buf, sizeof(buf), "Banned (%s): %s%s", bantype, reason, idbuf);
|
||||
safe_strdup(m->value, buf);
|
||||
AddListItem(m, mtags);
|
||||
/* And the quit reason for anyone else, goes here.. */
|
||||
snprintf(buf, sizeof(buf), "Banned (%s)", bantype);
|
||||
snprintf(buf, sizeof(buf), "Banned (%s)%s", bantype, idbuf);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Banned (%s): %s", bantype, reason);
|
||||
snprintf(buf, sizeof(buf), "Banned (%s): %s%s", bantype, reason, idbuf);
|
||||
}
|
||||
|
||||
if (noexit != NO_EXIT_CLIENT)
|
||||
|
||||
@@ -145,7 +145,7 @@ CMD_FUNC(cmd_setname)
|
||||
if (!ValidatePermissionsForPath("immune:server-ban:ban-realname",client,NULL,NULL,NULL) &&
|
||||
((bconf = find_ban(NULL, client->info, CONF_BAN_REALNAME))))
|
||||
{
|
||||
banned_client(client, "realname", bconf->reason?bconf->reason:"", 0, 0);
|
||||
banned_client(client, "realname", bconf->reason?bconf->reason:"", NULL, 0, 0);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
+361
-44
@@ -175,6 +175,25 @@ int confusables_spamfilters_present = 0; /**< Are any spamfilters with input-con
|
||||
long previous_spamfilter_utf8 = 0;
|
||||
static int firstboot = 0;
|
||||
|
||||
/* 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).
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *(*serialize)(TKL *tkl); /**< value to send, or NULL to omit the tag */
|
||||
void (*unserialize)(TKL *tkl, const char *value);
|
||||
} TKLS2SField;
|
||||
static const char *tkl_s2s_get_id(TKL *tkl);
|
||||
static void tkl_s2s_set_id(TKL *tkl, const char *value);
|
||||
static const char *tkl_s2s_get_spamfilter_id(TKL *tkl);
|
||||
static void tkl_s2s_set_spamfilter_id(TKL *tkl, const char *value);
|
||||
static const TKLS2SField tkl_s2s_fields[] = {
|
||||
{ "id", tkl_s2s_get_id, tkl_s2s_set_id },
|
||||
{ "spamfilter_id", tkl_s2s_get_spamfilter_id, tkl_s2s_set_spamfilter_id },
|
||||
{ NULL, NULL, NULL },
|
||||
};
|
||||
|
||||
MOD_TEST()
|
||||
{
|
||||
MARK_AS_OFFICIAL_MODULE(modinfo);
|
||||
@@ -320,7 +339,7 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
|
||||
{
|
||||
config_error("%s:%i: spamfilter::id invalid: maximum size (%d chars) exceeded "
|
||||
"or forbidden characters encountered: only A-Z, 0-9 and _ are permitted.",
|
||||
cep->file->filename, cep->line_number, MAXSPAMFILTERIDLEN);
|
||||
cep->file->filename, cep->line_number, TKLIDLEN-1);
|
||||
errors++;
|
||||
}
|
||||
} else
|
||||
@@ -1272,12 +1291,209 @@ void check_set_spamfilter_utf8_setting_changed(void)
|
||||
previous_spamfilter_utf8 = iConf.spamfilter_utf8;
|
||||
}
|
||||
|
||||
/** Return unique spamfilter id for TKL */
|
||||
char *spamfilter_id(TKL *tk)
|
||||
{
|
||||
static char buf[128];
|
||||
/* === 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.
|
||||
*/
|
||||
|
||||
snprintf(buf, sizeof(buf), "%p", (void *)tk);
|
||||
/** 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.
|
||||
*/
|
||||
static char tkl_id_prefix(int type)
|
||||
{
|
||||
return toupper(_tkl_typetochar(type));
|
||||
}
|
||||
|
||||
/** Find a TKL by its exact id (case-insensitive). Returns NULL on no match or empty id. */
|
||||
static TKL *find_tkl_by_id(const char *id)
|
||||
{
|
||||
TKL *tkl;
|
||||
int index, index2;
|
||||
|
||||
if (BadPtr(id))
|
||||
return NULL;
|
||||
|
||||
for (index = 0; index < TKLIPHASHLEN1; index++)
|
||||
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
|
||||
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
|
||||
if (!strcasecmp(tkl->id, id))
|
||||
return tkl;
|
||||
|
||||
for (index = 0; index < TKLISTLEN; index++)
|
||||
for (tkl = tklines[index]; tkl; tkl = tkl->next)
|
||||
if (!strcasecmp(tkl->id, id))
|
||||
return tkl;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Assign a fresh, locally-unique random id to 'tkl' (overwrites tkl->id).
|
||||
* Called when this server is the origin of the entry.
|
||||
*/
|
||||
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);
|
||||
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';
|
||||
other = find_tkl_by_id(tkl->id);
|
||||
if (!other || (other == tkl))
|
||||
return; /* unique (or only matches ourselves, if already linked) */
|
||||
}
|
||||
/* 16 collisions in a row is astronomically unlikely; keep the last candidate. */
|
||||
}
|
||||
|
||||
/* s2s-tkl/xxx message-tag field handlers */
|
||||
|
||||
static const char *tkl_s2s_get_id(TKL *tkl)
|
||||
{
|
||||
return tkl->id[0] ? tkl->id : NULL;
|
||||
}
|
||||
|
||||
static void tkl_s2s_set_id(TKL *tkl, const char *value)
|
||||
{
|
||||
if (!BadPtr(value) && (strlen(value) < sizeof(tkl->id)))
|
||||
strlcpy(tkl->id, value, sizeof(tkl->id));
|
||||
}
|
||||
|
||||
static const char *tkl_s2s_get_spamfilter_id(TKL *tkl)
|
||||
{
|
||||
return tkl->spamfilter_id[0] ? tkl->spamfilter_id : NULL;
|
||||
}
|
||||
|
||||
static void tkl_s2s_set_spamfilter_id(TKL *tkl, const char *value)
|
||||
{
|
||||
if (!BadPtr(value) && (strlen(value) < sizeof(tkl->spamfilter_id)))
|
||||
strlcpy(tkl->spamfilter_id, value, sizeof(tkl->spamfilter_id));
|
||||
}
|
||||
|
||||
/** Append s2s-tkl/<field> message tags describing 'tkl' onto '*mtags' (for sending over S2S). */
|
||||
static void tkl_add_s2s_mtags(TKL *tkl, MessageTag **mtags)
|
||||
{
|
||||
const TKLS2SField *f;
|
||||
const char *value;
|
||||
MessageTag *m;
|
||||
char name[64];
|
||||
|
||||
for (f = tkl_s2s_fields; f->name; f++)
|
||||
{
|
||||
value = f->serialize(tkl);
|
||||
if (!value)
|
||||
continue;
|
||||
snprintf(name, sizeof(name), "s2s-tkl/%s", f->name);
|
||||
m = safe_alloc(sizeof(MessageTag));
|
||||
safe_strdup(m->name, name);
|
||||
safe_strdup(m->value, value);
|
||||
AddListItem(m, *mtags);
|
||||
}
|
||||
}
|
||||
|
||||
/** Build a one-element s2s-tkl/spamfilter_id message tag from 'spamfilter_id', or NULL if it is empty.
|
||||
* Lets a locally-created server ban carry its spamfilter id through the same path as a remote one.
|
||||
*/
|
||||
static MessageTag *tkl_spamfilter_id_mtag(const char *spamfilter_id)
|
||||
{
|
||||
MessageTag *m;
|
||||
|
||||
if (BadPtr(spamfilter_id))
|
||||
return NULL;
|
||||
m = safe_alloc(sizeof(MessageTag));
|
||||
safe_strdup(m->name, "s2s-tkl/spamfilter_id");
|
||||
safe_strdup(m->value, spamfilter_id);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Read s2s-tkl/<field> message tags from 'mtags' into 'tkl' (on receiving an S2S TKL command). */
|
||||
static void tkl_extract_s2s_mtags(TKL *tkl, MessageTag *mtags)
|
||||
{
|
||||
MessageTag *m;
|
||||
const TKLS2SField *f;
|
||||
|
||||
for (m = mtags; m; m = m->next)
|
||||
{
|
||||
if (strncmp(m->name, "s2s-tkl/", 8))
|
||||
continue;
|
||||
for (f = tkl_s2s_fields; f->name; f++)
|
||||
{
|
||||
if (!strcmp(f->name, m->name + 8))
|
||||
{
|
||||
f->unserialize(tkl, m->value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the value of the s2s-tkl/<field> message tag in 'mtags', or NULL if absent.
|
||||
* Used by the merge logic to peek at an incoming field without overwriting the existing entry.
|
||||
*/
|
||||
static const char *tkl_s2s_mtag_value(MessageTag *mtags, const char *field)
|
||||
{
|
||||
MessageTag *m;
|
||||
char name[64];
|
||||
|
||||
snprintf(name, sizeof(name), "s2s-tkl/%s", field);
|
||||
for (m = mtags; m; m = m->next)
|
||||
if (!strcmp(m->name, name))
|
||||
return m->value;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** A fallback spamfilter id (not the normal randomly generated ID).
|
||||
* This ID is a hash based on (target, action, match method+string). So when those
|
||||
* properties are the same, it will create the same ID. Used for:
|
||||
* - Config spamfilter or Central Spamfilter without an explicit id.
|
||||
* For these it behaves very much like a regular tkl->id, is shown in STATS etc.
|
||||
* These entries never propagate servers, as they are local, but it is still nice
|
||||
* for the ID to be unique on each server if you use the exact same spamfilter
|
||||
* (think remote includes or central spamfilter).
|
||||
* - Global spamfilter with a blank id (eg from an older server).
|
||||
* In this case it is ONLY used for '/SPAMFILTER del' and not in regular STATS
|
||||
* or used as a true id.
|
||||
* And to be clear: for both cases they never appear in S2S and not on disk either
|
||||
* (it is computed, each time).
|
||||
*/
|
||||
static const char *spamfilter_fallback_id(TKL *tkl)
|
||||
{
|
||||
static const char b32[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
||||
/* Fixed, non-secret key: the fallback id is opers-only and not security-sensitive, the
|
||||
* key just makes the hash deterministic and identical across servers. */
|
||||
static const char key[16] = "UnrealIRCd rocks";
|
||||
static char buf[16];
|
||||
char content[8192];
|
||||
char actbuf[2];
|
||||
uint64_t h;
|
||||
int i;
|
||||
|
||||
actbuf[0] = banact_valtochar(tkl->ptr.spamfilter->action->action);
|
||||
actbuf[1] = '\0';
|
||||
snprintf(content, sizeof(content), "%s\t%s\t%s\t%s",
|
||||
spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
|
||||
actbuf,
|
||||
unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
|
||||
tkl->ptr.spamfilter->match->str);
|
||||
|
||||
h = siphash(content, key);
|
||||
|
||||
buf[0] = tkl_id_prefix(tkl->type);
|
||||
for (i = 1; i <= 10; i++)
|
||||
{
|
||||
buf[i] = b32[h & 31];
|
||||
h >>= 5;
|
||||
}
|
||||
buf[i] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -1291,9 +1507,8 @@ static void spamfilter_regex_error(TKL *tkl, const char *regex_error)
|
||||
{
|
||||
unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_REGEX_ERROR", NULL,
|
||||
"[Spamfilter] Regex aborted ($regex_error) for '$tkl'. Possibly too complex regex? "
|
||||
"To delete, use: /SPAMFILTER del $spamfilter_id",
|
||||
"To delete, use: /SPAMFILTER del $tkl.id",
|
||||
log_data_string("regex_error", regex_error),
|
||||
log_data_string("spamfilter_id", spamfilter_id(tkl)),
|
||||
log_data_tkl("tkl", tkl));
|
||||
} else {
|
||||
unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_REGEX_ERROR", NULL,
|
||||
@@ -1308,7 +1523,7 @@ int tkl_ip_change(Client *client, const char *oldip)
|
||||
{
|
||||
TKL *tkl;
|
||||
if ((tkl = find_tkline_match_zap(client)))
|
||||
banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
|
||||
banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, tkl->id, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1317,7 +1532,7 @@ int tkl_accept(Client *client)
|
||||
TKL *tkl;
|
||||
if ((tkl = find_tkline_match_zap(client)))
|
||||
{
|
||||
banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
|
||||
banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, tkl->id, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
|
||||
return 2; // TODO: HOOK_DENY_ALWAYS;
|
||||
}
|
||||
return 0;
|
||||
@@ -2431,7 +2646,8 @@ void spamfilter_del_by_id(Client *client, const char *id)
|
||||
{
|
||||
int index;
|
||||
TKL *tk;
|
||||
int found = 0;
|
||||
int matches = 0;
|
||||
TKL *match = NULL;
|
||||
char mo[32], mo2[32];
|
||||
const char *tkllayer[13] = {
|
||||
me.name, /* 0 server.name */
|
||||
@@ -2453,21 +2669,30 @@ void spamfilter_del_by_id(Client *client, const char *id)
|
||||
{
|
||||
for (tk = tklines[index]; tk; tk = tk->next)
|
||||
{
|
||||
if (((tk->type & (TKL_GLOBAL|TKL_SPAMF)) == (TKL_GLOBAL|TKL_SPAMF)) && !strcmp(spamfilter_id(tk), id))
|
||||
if ((tk->type & (TKL_GLOBAL|TKL_SPAMF)) != (TKL_GLOBAL|TKL_SPAMF))
|
||||
continue;
|
||||
/* Match the real id, or for an id-less spamfilter (eg from an old server)
|
||||
* its locally-computed fallback handle.
|
||||
*/
|
||||
if (tk->id[0] ? !strcmp(tk->id, id) : !strcmp(spamfilter_fallback_id(tk), id))
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
match = tk;
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break; /* break outer loop */
|
||||
}
|
||||
|
||||
if (!tk)
|
||||
if (matches == 0)
|
||||
{
|
||||
sendnotice(client, "Sorry, no spamfilter found with that ID. Did you run '/spamfilter del' to get the appropriate id?");
|
||||
return;
|
||||
}
|
||||
if (matches > 1)
|
||||
{
|
||||
sendnotice(client, "That ID is ambiguous (it matches multiple spamfilters). Please remove the spamfilter by its full definition instead.");
|
||||
return;
|
||||
}
|
||||
tk = match;
|
||||
|
||||
/* Spamfilter found. Now fill the tkllayer */
|
||||
tkllayer[1] = "-";
|
||||
@@ -3000,6 +3225,19 @@ TKL *_tkl_add_spamfilter(int type, const char *id, unsigned short target, BanAct
|
||||
safe_strdup(tkl->ptr.spamfilter->id, id);
|
||||
tkl->ptr.spamfilter->show_message_content_on_hit = show_message_content_on_hit;
|
||||
|
||||
/* Config (and central) spamfilters are local per server, so they never receive
|
||||
* a random network id. Give them a deterministic id instead, identical on every
|
||||
* server: the config/central id label if set, otherwise a content hash. This is
|
||||
* what lets a central spamfilter be referenced by the same id on every server.
|
||||
*/
|
||||
if (flags & (TKL_FLAG_CONFIG | TKL_FLAG_CENTRAL_SPAMFILTER))
|
||||
{
|
||||
if (!BadPtr(id))
|
||||
strlcpy(tkl->id, id, sizeof(tkl->id));
|
||||
else
|
||||
strlcpy(tkl->id, spamfilter_fallback_id(tkl), sizeof(tkl->id));
|
||||
}
|
||||
|
||||
if (tkl->ptr.spamfilter->target & SPAMF_USER)
|
||||
loop.do_bancheck_spamf_user = 1;
|
||||
if (tkl->ptr.spamfilter->target & SPAMF_AWAY)
|
||||
@@ -3679,15 +3917,15 @@ int _find_tkline_match(Client *client, int skip_soft)
|
||||
{
|
||||
ircstats.is_ref++;
|
||||
if (tkl->type & TKL_GLOBAL)
|
||||
banned_client(client, "G-Lined", tkl->ptr.serverban->reason, 1, 0);
|
||||
banned_client(client, "G-Lined", tkl->ptr.serverban->reason, tkl->id, 1, 0);
|
||||
else
|
||||
banned_client(client, "K-Lined", tkl->ptr.serverban->reason, 0, 0);
|
||||
banned_client(client, "K-Lined", tkl->ptr.serverban->reason, tkl->id, 0, 0);
|
||||
return 1; /* killed */
|
||||
} else
|
||||
if (tkl->type & TKL_ZAP)
|
||||
{
|
||||
ircstats.is_ref++;
|
||||
banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
|
||||
banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, tkl->id, (tkl->type & TKL_GLOBAL)?1:0, 0);
|
||||
return 1; /* killed */
|
||||
}
|
||||
|
||||
@@ -3929,12 +4167,15 @@ TKL *_find_tkline_match_zap(Client *client)
|
||||
#define NOT_BY_REASON 0x8
|
||||
#define BY_SETBY 0x10
|
||||
#define NOT_BY_SETBY 0x20
|
||||
#define BY_ID 0x40
|
||||
#define NOT_BY_ID 0x80
|
||||
|
||||
typedef struct {
|
||||
int flags;
|
||||
const char *mask;
|
||||
const char *reason;
|
||||
const char *set_by;
|
||||
const char *id;
|
||||
} TKLFlag;
|
||||
|
||||
/** Parse STATS tkl parameters.
|
||||
@@ -3989,6 +4230,15 @@ static void parse_stats_params(const char *para, TKLFlag *flag)
|
||||
flag->flags |= NOT_BY_SETBY;
|
||||
flag->set_by = tmp;
|
||||
break;
|
||||
case 'i':
|
||||
if (flag->id || !(tmp = strtok(NULL, " ")))
|
||||
continue;
|
||||
if (what == '+')
|
||||
flag->flags |= BY_ID;
|
||||
else
|
||||
flag->flags |= NOT_BY_ID;
|
||||
flag->id = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3998,6 +4248,9 @@ static void parse_stats_params(const char *para, TKLFlag *flag)
|
||||
*/
|
||||
int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklflags, TKL *tkl)
|
||||
{
|
||||
const char *id_str = tkl->id[0] ? tkl->id : "-";
|
||||
const char *spamfilter_id_str = tkl->spamfilter_id[0] ? tkl->spamfilter_id : "-";
|
||||
|
||||
/***** First, handle the selection ******/
|
||||
|
||||
if (!BadPtr(para))
|
||||
@@ -4008,6 +4261,12 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
if (tklflags->flags & NOT_BY_SETBY)
|
||||
if (match_simple(tklflags->set_by, tkl->set_by))
|
||||
return 0;
|
||||
if (tklflags->flags & BY_ID)
|
||||
if (!match_simple(tklflags->id, tkl->id))
|
||||
return 0;
|
||||
if (tklflags->flags & NOT_BY_ID)
|
||||
if (match_simple(tklflags->id, tkl->id))
|
||||
return 0;
|
||||
if (TKLIsServerBan(tkl))
|
||||
{
|
||||
if (tklflags->flags & BY_MASK)
|
||||
@@ -4078,7 +4337,7 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
{
|
||||
sendnumeric(client, RPL_STATSGLINE, 'K', namevalue_nospaces(m),
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, (long long)0, (long long)0, spamfilter_id_str, id_str, tkl->ptr.serverban->reason);
|
||||
|
||||
}
|
||||
} else {
|
||||
@@ -4088,31 +4347,31 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
{
|
||||
sendnumeric(client, RPL_STATSGLINE, 'G', uhost,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, (long long)0, (long long)0, spamfilter_id_str, id_str, tkl->ptr.serverban->reason);
|
||||
} else
|
||||
if (tkl->type == (TKL_ZAP | TKL_GLOBAL))
|
||||
{
|
||||
sendnumeric(client, RPL_STATSGLINE, 'Z', uhost,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, (long long)0, (long long)0, spamfilter_id_str, id_str, tkl->ptr.serverban->reason);
|
||||
} else
|
||||
if (tkl->type == (TKL_SHUN | TKL_GLOBAL))
|
||||
{
|
||||
sendnumeric(client, RPL_STATSGLINE, 's', uhost,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, (long long)0, (long long)0, spamfilter_id_str, id_str, tkl->ptr.serverban->reason);
|
||||
} else
|
||||
if (tkl->type == (TKL_KILL))
|
||||
{
|
||||
sendnumeric(client, RPL_STATSGLINE, 'K', uhost,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, (long long)0, (long long)0, spamfilter_id_str, id_str, tkl->ptr.serverban->reason);
|
||||
} else
|
||||
if (tkl->type == (TKL_ZAP))
|
||||
{
|
||||
sendnumeric(client, RPL_STATSGLINE, 'z', uhost,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, (long long)0, (long long)0, spamfilter_id_str, id_str, tkl->ptr.serverban->reason);
|
||||
}
|
||||
}
|
||||
} else
|
||||
@@ -4130,18 +4389,17 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
tkl->set_by,
|
||||
tkl->ptr.spamfilter->hits,
|
||||
tkl->ptr.spamfilter->hits_except,
|
||||
(long long)0,
|
||||
(long long)0,
|
||||
id_str,
|
||||
tkl->ptr.spamfilter->match->str);
|
||||
if (para && !strcasecmp(para, "del"))
|
||||
{
|
||||
char *hash = spamfilter_id(tkl);
|
||||
if (tkl->type & TKL_GLOBAL)
|
||||
{
|
||||
sendtxtnumeric(client, "To delete this spamfilter, use /SPAMFILTER del %s", hash);
|
||||
sendtxtnumeric(client, "-");
|
||||
} else {
|
||||
if (!(tkl->type & TKL_GLOBAL))
|
||||
sendtxtnumeric(client, "This spamfilter is stored in the configuration file and cannot be removed with /SPAMFILTER del");
|
||||
sendtxtnumeric(client, "-");
|
||||
}
|
||||
else
|
||||
sendtxtnumeric(client, "To delete this spamfilter, use /SPAMFILTER del %s", tkl->id[0] ? tkl->id : spamfilter_fallback_id(tkl));
|
||||
sendtxtnumeric(client, "-");
|
||||
}
|
||||
} else
|
||||
if (TKLIsNameBan(tkl))
|
||||
@@ -4152,6 +4410,9 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at),
|
||||
tkl->set_by,
|
||||
(long long)0,
|
||||
(long long)0,
|
||||
id_str,
|
||||
tkl->ptr.nameban->reason);
|
||||
} else
|
||||
if (TKLIsBanException(tkl))
|
||||
@@ -4165,7 +4426,7 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
sendnumeric(client, RPL_STATSEXCEPTTKL, namevalue_nospaces(m),
|
||||
tkl->ptr.banexception->bantypes,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, id_str, tkl->ptr.banexception->reason);
|
||||
}
|
||||
} else {
|
||||
/* IRC-added: uses simple user/host mask */
|
||||
@@ -4174,7 +4435,7 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
||||
sendnumeric(client, RPL_STATSEXCEPTTKL, uhost,
|
||||
tkl->ptr.banexception->bantypes,
|
||||
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
|
||||
(long long)(TStime() - tkl->set_at), tkl->set_by, id_str, tkl->ptr.banexception->reason);
|
||||
}
|
||||
} else
|
||||
{
|
||||
@@ -4255,6 +4516,7 @@ void _tkl_stats(Client *client, int type, const char *para, int *cnt)
|
||||
*/
|
||||
void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
|
||||
{
|
||||
MessageTag *mtags = NULL;
|
||||
char typ;
|
||||
|
||||
if (!(tkl->type & TKL_GLOBAL))
|
||||
@@ -4262,9 +4524,13 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
|
||||
|
||||
typ = tkl_typetochar(tkl->type);
|
||||
|
||||
/* Carry the s2s-tkl/ fields (id, spamfilter_id) on add; del matches by mask so needs no tags. */
|
||||
if (add)
|
||||
tkl_add_s2s_mtags(tkl, &mtags);
|
||||
|
||||
if (TKLIsServerBan(tkl))
|
||||
{
|
||||
sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld :%s", sender->name,
|
||||
sendto_one(to, mtags, ":%s TKL %c %c %s%s %s %s %lld %lld :%s", sender->name,
|
||||
add ? '+' : '-',
|
||||
typ,
|
||||
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
|
||||
@@ -4275,7 +4541,7 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
|
||||
} else
|
||||
if (TKLIsNameBan(tkl))
|
||||
{
|
||||
sendto_one(to, NULL, ":%s TKL %c %c %c %s %s %lld %lld :%s", sender->name,
|
||||
sendto_one(to, mtags, ":%s TKL %c %c %c %s %s %lld %lld :%s", sender->name,
|
||||
add ? '+' : '-',
|
||||
typ,
|
||||
tkl->ptr.nameban->hold ? 'H' : '*',
|
||||
@@ -4286,7 +4552,7 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
|
||||
} else
|
||||
if (TKLIsSpamfilter(tkl))
|
||||
{
|
||||
sendto_one(to, NULL, ":%s TKL %c %c %s %c %s %lld %lld %lld %s %s :%s", sender->name,
|
||||
sendto_one(to, mtags, ":%s TKL %c %c %s %c %s %lld %lld %lld %s %s :%s", sender->name,
|
||||
add ? '+' : '-',
|
||||
typ,
|
||||
spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
|
||||
@@ -4299,7 +4565,7 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
|
||||
} else
|
||||
if (TKLIsBanException(tkl))
|
||||
{
|
||||
sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld %s :%s", sender->name,
|
||||
sendto_one(to, mtags, ":%s TKL %c %c %s%s %s %s %lld %lld %s :%s", sender->name,
|
||||
add ? '+' : '-',
|
||||
typ,
|
||||
(tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
|
||||
@@ -4316,6 +4582,8 @@ void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
|
||||
log_data_integer("tkl_type_int", typ));
|
||||
abort();
|
||||
}
|
||||
|
||||
safe_free_message_tags(mtags);
|
||||
}
|
||||
|
||||
/** Broadcast a TKL entry.
|
||||
@@ -4822,6 +5090,30 @@ CMD_FUNC(cmd_tkl_add)
|
||||
* Note that we only update common fields,
|
||||
* which is acceptable to me. -- Syzop
|
||||
*/
|
||||
const char *incoming_id = tkl_s2s_mtag_value(recv_mtags, "id");
|
||||
const char *incoming_spamfilter_id = NULL;
|
||||
int changed = 0;
|
||||
|
||||
/* id: an assigned id beats a blank one; two assigned ids tie-break by older
|
||||
* set_at, then lexicographically. The "spamfilter_id" travels with the winning "id".
|
||||
* This is done before set_at is lowered below, so the tie-break sees the original set_at.
|
||||
*/
|
||||
if (!BadPtr(incoming_id) && (strlen(incoming_id) < sizeof(tkl->id)) && strcmp(tkl->id, incoming_id))
|
||||
{
|
||||
if (!tkl->id[0] ||
|
||||
(set_at < tkl->set_at) ||
|
||||
((set_at == tkl->set_at) && (strcmp(incoming_id, tkl->id) < 0)))
|
||||
{
|
||||
strlcpy(tkl->id, incoming_id, sizeof(tkl->id));
|
||||
incoming_spamfilter_id = tkl_s2s_mtag_value(recv_mtags, "spamfilter_id");
|
||||
if (!BadPtr(incoming_spamfilter_id) && (strlen(incoming_spamfilter_id) < sizeof(tkl->spamfilter_id)))
|
||||
strlcpy(tkl->spamfilter_id, incoming_spamfilter_id, sizeof(tkl->spamfilter_id));
|
||||
else
|
||||
tkl->spamfilter_id[0] = '\0';
|
||||
changed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((set_at < tkl->set_at) || (expire_at != tkl->expire_at) || strcmp(tkl->set_by, parv[5]))
|
||||
{
|
||||
/* here's how it goes:
|
||||
@@ -4844,12 +5136,22 @@ CMD_FUNC(cmd_tkl_add)
|
||||
if (strcmp(tkl->set_by, parv[5]) < 0)
|
||||
safe_strdup(tkl->set_by, parv[5]);
|
||||
|
||||
if (type & TKL_GLOBAL)
|
||||
tkl_broadcast_entry(1, client, client, tkl);
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
if (changed && (type & TKL_GLOBAL))
|
||||
tkl_broadcast_entry(1, client, client, tkl);
|
||||
return;
|
||||
}
|
||||
|
||||
/* New entry: assign its unique id. Honor a supplied s2s-tkl/id (and spamfilter_id) from a
|
||||
* remote server or services; if none and we are the originating server, generate a random one;
|
||||
* otherwise leave it blank (eg the tag was dropped by an old server in the path).
|
||||
*/
|
||||
tkl_extract_s2s_mtags(tkl, recv_mtags);
|
||||
if (!tkl->id[0] && IsMe(client))
|
||||
tkl_generate_id(tkl);
|
||||
|
||||
tkl_added(client, tkl);
|
||||
}
|
||||
|
||||
@@ -5169,7 +5471,18 @@ void ban_action_run_all_sets_and_stops(Client *client, BanAction *action, int *s
|
||||
* @note Be sure to check IsDead(client) if return value is 1 and you are
|
||||
* considering to continue processing.
|
||||
*/
|
||||
static int take_action_ex(Client *client, BanAction *actions, const char *reason, long duration, int take_action_flags, int *stopped, const char *spamfilter_id);
|
||||
|
||||
int _take_action(Client *client, BanAction *actions, const char *reason, long duration, int take_action_flags, int *stopped)
|
||||
{
|
||||
return take_action_ex(client, actions, reason, duration, take_action_flags, stopped, NULL);
|
||||
}
|
||||
|
||||
/** Internal variant of take_action() that records the spamfilter id on any
|
||||
* *LINE/SHUN it creates, for the gline->spamfilter trace. Kept internal so the public
|
||||
* take_action() efunc stays free of this spamfilter-specific concept.
|
||||
*/
|
||||
static int take_action_ex(Client *client, BanAction *actions, const char *reason, long duration, int take_action_flags, int *stopped, const char *spamfilter_id)
|
||||
{
|
||||
BanAction *action;
|
||||
int previous_highest = 0;
|
||||
@@ -5244,7 +5557,11 @@ int _take_action(Client *client, BanAction *actions, const char *reason, long du
|
||||
tkllayer[6] = mo;
|
||||
tkllayer[7] = mo2;
|
||||
tkllayer[8] = reason;
|
||||
cmd_tkl(NULL, &me, NULL, 9, tkllayer);
|
||||
{
|
||||
MessageTag *m = tkl_spamfilter_id_mtag(spamfilter_id); /* NULL unless set by a spamfilter */
|
||||
cmd_tkl(NULL, &me, m, 9, tkllayer);
|
||||
safe_free_message_tags(m);
|
||||
}
|
||||
RunHookReturnInt(HOOKTYPE_TAKE_ACTION, !=99, client, action->action, reason, duration);
|
||||
if ((action->action == BAN_ACT_SHUN) || (action->action == BAN_ACT_SOFT_SHUN))
|
||||
{
|
||||
@@ -5754,7 +6071,7 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
||||
|
||||
/* Spamfilter matched */
|
||||
reason = unreal_decodespace(tkl->ptr.spamfilter->tkl_reason);
|
||||
ret = take_action(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration, TAKE_ACTION_SKIP_SET, NULL);
|
||||
ret = take_action_ex(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration, TAKE_ACTION_SKIP_SET, NULL, tkl->id);
|
||||
if (!IsDead(client))
|
||||
{
|
||||
if ((ret == BAN_ACT_BLOCK) || (ret == BAN_ACT_SOFT_BLOCK))
|
||||
|
||||
+49
-5
@@ -29,7 +29,7 @@ ModuleHeader MOD_HEADER = {
|
||||
|
||||
#define TKLDB_MAGIC 0x10101010
|
||||
/* Database version */
|
||||
#define TKLDB_VERSION 4999
|
||||
#define TKLDB_VERSION 6260
|
||||
/* Save tkls to file every <this> seconds */
|
||||
#define TKLDB_SAVE_EVERY 300
|
||||
/* The very first save after boot, apply this delta, this
|
||||
@@ -401,6 +401,15 @@ int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl)
|
||||
W_SAFE(unrealdb_write_str(db, tkl->set_by));
|
||||
W_SAFE(unrealdb_write_int64(db, tkl->set_at));
|
||||
W_SAFE(unrealdb_write_int64(db, tkl->expire_at));
|
||||
W_SAFE(unrealdb_write_str(db, tkl->id)); /* since TKLDB_VERSION 6260 */
|
||||
W_SAFE(unrealdb_write_str(db, tkl->spamfilter_id)); /* since TKLDB_VERSION 6260 */
|
||||
|
||||
/* Reserved hit-stat fields for all TKL types (since TKLDB_VERSION 6260):
|
||||
* hits, lasthit. Written as 0 for now; a later release will populate them
|
||||
* (for non-config TKLs).
|
||||
*/
|
||||
W_SAFE(unrealdb_write_int64(db, 0));
|
||||
W_SAFE(unrealdb_write_int64(db, 0));
|
||||
|
||||
if (TKLIsServerBan(tkl))
|
||||
{
|
||||
@@ -446,6 +455,11 @@ int write_tkline(UnrealDB *db, const char *tmpfname, TKL *tkl)
|
||||
W_SAFE(unrealdb_write_char(db, action));
|
||||
W_SAFE(unrealdb_write_str(db, tkl->ptr.spamfilter->tkl_reason));
|
||||
W_SAFE(unrealdb_write_int64(db, tkl->ptr.spamfilter->tkl_duration));
|
||||
/* Reserved spamfilter-only hit-stat fields (since TKLDB_VERSION 6260):
|
||||
* hits_except, lasthit_except. Written as 0 for now.
|
||||
*/
|
||||
W_SAFE(unrealdb_write_int64(db, 0));
|
||||
W_SAFE(unrealdb_write_int64(db, 0));
|
||||
}
|
||||
|
||||
return 1;
|
||||
@@ -530,6 +544,7 @@ int read_tkldb(void)
|
||||
for (cnt = 0; cnt < tklcount; cnt++)
|
||||
{
|
||||
int do_not_add = 0;
|
||||
TKL *added = NULL;
|
||||
|
||||
tkl = safe_alloc(sizeof(TKL));
|
||||
|
||||
@@ -556,6 +571,22 @@ int read_tkldb(void)
|
||||
R_SAFE(unrealdb_read_int64(db, &v));
|
||||
tkl->expire_at = v;
|
||||
|
||||
/* id and spamfilter_id were added in TKLDB_VERSION 6260 */
|
||||
if (version >= 6260)
|
||||
{
|
||||
R_SAFE(unrealdb_read_str(db, &str));
|
||||
strlcpy(tkl->id, str, sizeof(tkl->id));
|
||||
safe_free(str);
|
||||
R_SAFE(unrealdb_read_str(db, &str));
|
||||
strlcpy(tkl->spamfilter_id, str, sizeof(tkl->spamfilter_id));
|
||||
safe_free(str);
|
||||
/* Reserved hit-stat fields for all TKL types (hits, lasthit).
|
||||
* Read and discarded for now; a later release will use them.
|
||||
*/
|
||||
R_SAFE(unrealdb_read_int64(db, &v));
|
||||
R_SAFE(unrealdb_read_int64(db, &v));
|
||||
}
|
||||
|
||||
/* Save some CPU... if it's already expired then don't bother adding */
|
||||
if (tkl->expire_at != 0 && tkl->expire_at <= TStime())
|
||||
do_not_add = 1;
|
||||
@@ -599,7 +630,7 @@ int read_tkldb(void)
|
||||
|
||||
if (!do_not_add)
|
||||
{
|
||||
tkl_add_serverban(tkl->type, tkl->ptr.serverban->usermask,
|
||||
added = tkl_add_serverban(tkl->type, tkl->ptr.serverban->usermask,
|
||||
tkl->ptr.serverban->hostmask,
|
||||
NULL,
|
||||
tkl->ptr.serverban->reason,
|
||||
@@ -639,7 +670,7 @@ int read_tkldb(void)
|
||||
|
||||
if (!do_not_add)
|
||||
{
|
||||
tkl_add_banexception(tkl->type, tkl->ptr.banexception->usermask,
|
||||
added = tkl_add_banexception(tkl->type, tkl->ptr.banexception->usermask,
|
||||
tkl->ptr.banexception->hostmask,
|
||||
NULL,
|
||||
tkl->ptr.banexception->reason,
|
||||
@@ -668,7 +699,7 @@ int read_tkldb(void)
|
||||
|
||||
if (!do_not_add)
|
||||
{
|
||||
tkl_add_nameban(tkl->type, tkl->ptr.nameban->name,
|
||||
added = tkl_add_nameban(tkl->type, tkl->ptr.nameban->name,
|
||||
tkl->ptr.nameban->hold,
|
||||
tkl->ptr.nameban->reason,
|
||||
tkl->set_by, tkl->expire_at,
|
||||
@@ -726,6 +757,13 @@ int read_tkldb(void)
|
||||
R_SAFE(unrealdb_read_str(db, &tkl->ptr.spamfilter->tkl_reason));
|
||||
R_SAFE(unrealdb_read_int64(db, &v));
|
||||
tkl->ptr.spamfilter->tkl_duration = v;
|
||||
/* Reserved spamfilter-only hit-stat fields (hits_except,
|
||||
* lasthit_except). Read and discarded for now. */
|
||||
if (version >= 6260)
|
||||
{
|
||||
R_SAFE(unrealdb_read_int64(db, &v));
|
||||
R_SAFE(unrealdb_read_int64(db, &v));
|
||||
}
|
||||
|
||||
if (!do_not_add &&
|
||||
find_tkl_spamfilter(tkl->type, tkl->ptr.spamfilter->match->str,
|
||||
@@ -744,7 +782,7 @@ int read_tkldb(void)
|
||||
|
||||
if (!do_not_add)
|
||||
{
|
||||
tkl_add_spamfilter(tkl->type, NULL, tkl->ptr.spamfilter->target,
|
||||
added = tkl_add_spamfilter(tkl->type, NULL, tkl->ptr.spamfilter->target,
|
||||
tkl->ptr.spamfilter->action,
|
||||
tkl->ptr.spamfilter->match,
|
||||
NULL,
|
||||
@@ -771,6 +809,12 @@ int read_tkldb(void)
|
||||
break; /* we MUST stop reading */
|
||||
}
|
||||
|
||||
if (added)
|
||||
{
|
||||
strlcpy(added->id, tkl->id, sizeof(added->id));
|
||||
strlcpy(added->spamfilter_id, tkl->spamfilter_id, sizeof(added->spamfilter_id));
|
||||
}
|
||||
|
||||
if (!do_not_add)
|
||||
added_cnt++;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user