1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-06 22:03:12 +02:00

Code cleanup: move flood control to generic system

This commit is contained in:
Bram Matthys
2021-05-28 17:59:39 +02:00
parent 3e1f092afc
commit 36b9faa7cd
11 changed files with 132 additions and 118 deletions
+8 -18
View File
@@ -1,6 +1,7 @@
/************************************************************************
* Unreal Internet Relay Chat Daemon, include/dynconf.h
* Copyright (C) 1999 Carsten Munk
* Copyright (C) 1999-2003 Carsten Munk
* Copyright (C) 2003-2021 Bram Matthys
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,6 +23,11 @@
#define DYNCONF_H
typedef struct FloodSettings {
int limit[MAXFLOODOPTIONS];
long period[MAXFLOODOPTIONS];
} FloodSettings;
typedef struct NetworkConfiguration NetworkConfiguration;
struct NetworkConfiguration {
unsigned x_inah:1;
@@ -118,14 +124,7 @@ struct Configuration {
int handshake_data_flood_ban_action;
struct ChMode modes_on_join;
int level_on_join;
unsigned char away_count;
long away_period;
unsigned char nick_count;
long nick_period;
unsigned char invite_count;
long invite_period;
unsigned char knock_count;
long knock_period;
FloodSettings *floodsettings;
unsigned char max_concurrent_conversations_users;
unsigned char max_concurrent_conversations_new_user_every;
int ident_connect_timeout;
@@ -234,15 +233,6 @@ extern MODVAR int ipv6_disabled;
#define MODES_ON_JOIN iConf.modes_on_join.mode
#define LEVEL_ON_JOIN iConf.level_on_join
#define AWAY_PERIOD iConf.away_period
#define AWAY_COUNT iConf.away_count
#define NICK_PERIOD iConf.nick_period
#define NICK_COUNT iConf.nick_count
#define KNOCK_PERIOD iConf.knock_period
#define KNOCK_COUNT iConf.knock_count
#define INVITE_PERIOD iConf.invite_period
#define INVITE_COUNT iConf.invite_count
#define IDENT_CONNECT_TIMEOUT iConf.ident_connect_timeout
#define IDENT_READ_TIMEOUT iConf.ident_read_timeout
+1
View File
@@ -1065,3 +1065,4 @@ extern Secret *secrets;
/* end */
extern int check_password_strength(char *pass, int min_length, int strict, char **err);
extern int valid_secret_password(char *pass, char **err);
extern int flood_limit_exceeded(Client *client, FloodOption opt);
+18 -2
View File
@@ -1210,6 +1210,23 @@ extern void unload_all_unused_moddata(void);
#define TLSFLAG_NOSTARTTLS 0x8
#define TLSFLAG_DISABLECLIENTCERT 0x10
/** Flood counters for local clients */
typedef struct FloodCounter {
int count;
long t;
} FloodCounter;
/** This is the list of different flood counters that we keep for local clients. */
typedef enum FloodOption {
FLD_NICK = 0, /**< nick-flood */
FLD_JOIN = 1, /**< join-flood */
FLD_AWAY = 2, /**< away-flood */
FLD_INVITE = 3, /**< invite-flood */
FLD_KNOCK = 4, /**< knock-flood */
} FloodOption;
#define MAXFLOODOPTIONS 10
/** This shows the Client struct (any client), the User struct (a user), Server (a server) that are commonly accessed both in the core and by 3rd party coders.
* @defgroup CommonStructs Common structs
* @{
@@ -1289,6 +1306,7 @@ struct LocalClient {
struct hostent *hostp; /**< Host record for this client (used by DNS code) */
char sockhost[HOSTLEN + 1]; /**< Hostname from the socket */
u_short port; /**< Remote TCP port of client */
FloodCounter flood[MAXFLOODOPTIONS];
};
/** User information (persons, not servers), you use client->user to access these (see also @link Client @endlink).
@@ -1311,11 +1329,9 @@ struct User {
char *operlogin; /**< Which oper { } block was used to oper up, otherwise NULL - used by oper::maxlogins */
struct {
time_t nick_t; /**< For set::anti-flood::nick-flood: time */
time_t away_t; /**< For set::anti-flood::away-flood: time */
time_t knock_t; /**< For set::anti-flood::knock-flood: time */
time_t invite_t; /**< For set::anti-flood::invite-flood: time */
unsigned char nick_c; /**< For set::anti-flood::nick-flood: counter */
unsigned char away_c; /**< For set::anti-flood::away-flood: counter */
unsigned char knock_c; /**< For set::anti-flood::knock-flood: counter */
unsigned char invite_c; /**< For set::anti-flood::invite-flood: counter */
} flood; /**< Anti-flood counters */
+42 -32
View File
@@ -362,6 +362,36 @@ char *x;
return 1;
}
/** Parses a value like '5:60s' into a flood setting that we can store.
* @param str The string to parse (eg: '5:60s')
* @param settings The FloodSettings block to store the result in
* @param opt The option (eg: FLD_AWAY)
* @returns 1 if OK, 0 for parse error.
*/
int config_parse_flood_generic(const char *str, FloodSettings *settings, FloodOption opt)
{
char buf[64], *p;
/* Work on a copy so we don't destroy 'str' */
strlcpy(buf, str, sizeof(buf));
/* Initialize to zero first */
settings->limit[opt] = 0;
settings->period[opt] = 0;
p = strchr(buf, ':');
/* 'blah', ':blah', '1:' */
if (!p || (p == buf) || (*(p+1) == '\0'))
return 0;
*p++ = '\0';
settings->limit[opt] = atoi(buf);
settings->period[opt] = config_checkval(p, CFG_TIME);
return 1;
}
long config_checkval(char *orig, unsigned short flags) {
char *value = raw_strdup(orig);
char *text;
@@ -1635,11 +1665,6 @@ void config_setdefaultsettings(Configuration *i)
safe_strdup(i->oper_snomask, SNO_DEFOPER);
i->ident_read_timeout = 7;
i->ident_connect_timeout = 3;
i->nick_count = 3; i->nick_period = 60; /* NICK flood protection: max 3 per 60s */
i->away_count = 4; i->away_period = 120; /* AWAY flood protection: max 4 per 120s */
i->invite_count = 4; i->invite_period = 60; /* INVITE flood protection: max 4 per 60s */
i->knock_count = 4; i->knock_period = 120; /* KNOCK protection: max 4 per 120s */
i->throttle_count = 3; i->throttle_period = 60; /* throttle protection: max 3 per 60s */
i->ban_version_tkl_time = 86400; /* 1d */
i->spamfilter_ban_time = 86400; /* 1d */
safe_strdup(i->spamfilter_ban_reason, "Spam/advertising");
@@ -1678,6 +1703,14 @@ void config_setdefaultsettings(Configuration *i)
i->handshake_delay = -1;
i->broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO;
/* Flood options */
i->throttle_count = 3; i->throttle_period = 60; /* throttle protection: max 3 per 60s */
i->floodsettings = safe_alloc(sizeof(FloodCounter) * MAXFLOODOPTIONS);
config_parse_flood_generic("3:60", i->floodsettings, FLD_NICK); /* NICK flood protection: max 3 per 60s */
config_parse_flood_generic("4:120", i->floodsettings, FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
config_parse_flood_generic("4:60", i->floodsettings, FLD_INVITE); /* INVITE flood protection: max 4 per 60s */
config_parse_flood_generic("4:120", i->floodsettings, FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
/* SSL/TLS options */
i->tls_options = safe_alloc(sizeof(TLSOptions));
snprintf(tmp, sizeof(tmp), "%s/tls/server.cert.pem", CONFDIR);
@@ -7543,44 +7576,21 @@ int _conf_set(ConfigFile *conf, ConfigEntry *ce)
tempiConf.handshake_data_flood_ban_action = banact_stringtoval(ceppp->ce_vardata);
}
}
else if (!strcmp(cepp->ce_varname, "away-count"))
tempiConf.away_count = atol(cepp->ce_vardata);
else if (!strcmp(cepp->ce_varname, "away-period"))
tempiConf.away_period = config_checkval(cepp->ce_vardata, CFG_TIME);
else if (!strcmp(cepp->ce_varname, "away-flood"))
{
int cnt, period;
config_parse_flood(cepp->ce_vardata, &cnt, &period);
tempiConf.away_count = cnt;
tempiConf.away_period = period;
config_parse_flood_generic(cepp->ce_vardata, tempiConf.floodsettings, FLD_AWAY);
}
else if (!strcmp(cepp->ce_varname, "nick-flood"))
{
int cnt, period;
config_parse_flood(cepp->ce_vardata, &cnt, &period);
tempiConf.nick_count = cnt;
tempiConf.nick_period = period;
}
else if (!strcmp(cepp->ce_varname, "away-flood"))
{
int cnt, period;
config_parse_flood(cepp->ce_vardata, &cnt, &period);
tempiConf.away_count = cnt;
tempiConf.away_period = period;
config_parse_flood_generic(cepp->ce_vardata, tempiConf.floodsettings, FLD_NICK);
}
else if (!strcmp(cepp->ce_varname, "invite-flood"))
{
int cnt, period;
config_parse_flood(cepp->ce_vardata, &cnt, &period);
tempiConf.invite_count = cnt;
tempiConf.invite_period = period;
config_parse_flood_generic(cepp->ce_vardata, tempiConf.floodsettings, FLD_INVITE);
}
else if (!strcmp(cepp->ce_varname, "knock-flood"))
{
int cnt, period;
config_parse_flood(cepp->ce_vardata, &cnt, &period);
tempiConf.knock_count = cnt;
tempiConf.knock_period = period;
config_parse_flood_generic(cepp->ce_vardata, tempiConf.floodsettings, FLD_KNOCK);
}
else if (!strcmp(cepp->ce_varname, "connect-flood"))
{
+6 -14
View File
@@ -89,21 +89,13 @@ CMD_FUNC(cmd_away)
if (MyUser(client) && match_spamfilter(client, new_reason, SPAMF_AWAY, "AWAY", NULL, 0, NULL))
return;
/* Check set::anti-flood::away-flood */
if (MyUser(client) && AWAY_PERIOD && !ValidatePermissionsForPath("immune:away-flood",client,NULL,NULL,NULL))
/* Check away-flood */
if (MyUser(client) &&
!ValidatePermissionsForPath("immune:away-flood",client,NULL,NULL,NULL) &&
flood_limit_exceeded(client, FLD_AWAY))
{
if ((client->user->flood.away_t + AWAY_PERIOD) <= timeofday)
{
client->user->flood.away_c = 0;
client->user->flood.away_t = timeofday;
}
if (client->user->flood.away_c <= AWAY_COUNT)
client->user->flood.away_c++;
if (client->user->flood.away_c > AWAY_COUNT)
{
sendnumeric(client, ERR_TOOMANYAWAY);
return;
}
sendnumeric(client, ERR_TOOMANYAWAY);
return;
}
/* Obey set::away-length */
+5 -14
View File
@@ -166,25 +166,16 @@ CMD_FUNC(cmd_invite)
return;
}
if (MyConnect(client))
if (MyUser(client))
{
if (target_limit_exceeded(client, target, target->name))
return;
if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL))
if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL) &&
flood_limit_exceeded(client, FLD_INVITE))
{
if ((client->user->flood.invite_t + INVITE_PERIOD) <= timeofday)
{
client->user->flood.invite_c = 0;
client->user->flood.invite_t = timeofday;
}
if (client->user->flood.invite_c <= INVITE_COUNT)
client->user->flood.invite_c++;
if (client->user->flood.invite_c > INVITE_COUNT)
{
sendnumeric(client, RPL_TRYAGAIN, "INVITE");
return;
}
sendnumeric(client, RPL_TRYAGAIN, "INVITE");
return;
}
if (!override)
+5 -14
View File
@@ -132,21 +132,12 @@ CMD_FUNC(cmd_knock)
if (i == HOOK_DENY)
return;
if (MyUser(client) && !ValidatePermissionsForPath("immune:knock-flood",client,NULL,NULL,NULL))
if (MyUser(client) &&
!ValidatePermissionsForPath("immune:knock-flood",client,NULL,NULL,NULL) &&
flood_limit_exceeded(client, FLD_KNOCK))
{
if ((client->user->flood.knock_t + KNOCK_PERIOD) <= timeofday)
{
client->user->flood.knock_c = 0;
client->user->flood.knock_t = timeofday;
}
if (client->user->flood.knock_c <= KNOCK_COUNT)
client->user->flood.knock_c++;
if (client->user->flood.knock_c > KNOCK_COUNT)
{
sendnumeric(client, ERR_CANNOTKNOCK, parv[1],
"You are KNOCK flooding");
return;
}
sendnumeric(client, ERR_CANNOTKNOCK, parv[1], "You are KNOCK flooding");
return;
}
new_message(&me, NULL, &mtags);
+10 -20
View File
@@ -274,19 +274,6 @@ CMD_FUNC(cmd_nick_local)
return;
}
/* set::anti-flood::nick-flood */
if (client->user && !ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
{
if ((client->user->flood.nick_c >= NICK_COUNT) &&
(TStime() - client->user->flood.nick_t < NICK_PERIOD))
{
/* Throttle... */
sendnumeric(client, ERR_NCHANGETOOFAST, nick,
(int)(NICK_PERIOD - (TStime() - client->user->flood.nick_t)));
return;
}
}
/* Check for collisions / in use */
if (!strcasecmp("ircd", nick) || !strcasecmp("irc", nick))
{
@@ -321,6 +308,16 @@ CMD_FUNC(cmd_nick_local)
/* fallthrough for ircops that have sufficient privileges */
}
/* set::anti-flood::nick-flood */
if (client->user &&
!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL) &&
flood_limit_exceeded(client, FLD_NICK))
{
/* Throttle... */
sendnumeric(client, ERR_NCHANGETOOFAST, nick);
return;
}
if (!ValidatePermissionsForPath("immune:nick-flood",client,NULL,NULL,NULL))
cptr->local->since += 3; /* Nick-flood prot. -Donwulff */
@@ -439,13 +436,6 @@ CMD_FUNC(cmd_nick_local)
}
}
if (TStime() - client->user->flood.nick_t >= NICK_PERIOD)
{
client->user->flood.nick_t = TStime();
client->user->flood.nick_c = 1;
} else
client->user->flood.nick_c++;
sendto_snomask(SNO_NICKCHANGE, "*** %s (%s@%s) has changed their nickname to %s",
client->name, client->user->username, client->user->realhost, nick);
+3 -3
View File
@@ -875,9 +875,9 @@ int stats_set(Client *client, char *para)
sendtxtnumeric(client, "anti-flood::handshake-data-flood::amount: %ld bytes", iConf.handshake_data_flood_amount);
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-action: %s", banact_valtostring(iConf.handshake_data_flood_ban_action));
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-time: %s", pretty_time_val(iConf.handshake_data_flood_ban_time));
if (AWAY_PERIOD)
sendtxtnumeric(client, "anti-flood::away-flood: %d per %s", AWAY_COUNT, pretty_time_val(AWAY_PERIOD));
sendtxtnumeric(client, "anti-flood::nick-flood: %d per %s", NICK_COUNT, pretty_time_val(NICK_PERIOD));
//if (AWAY_PERIOD)
// sendtxtnumeric(client, "anti-flood::away-flood: %d per %s", AWAY_COUNT, pretty_time_val(AWAY_PERIOD));
//sendtxtnumeric(client, "anti-flood::nick-flood: %d per %s", NICK_COUNT, pretty_time_val(NICK_PERIOD));
sendtxtnumeric(client, "handshake-timeout: %s", pretty_time_val(iConf.handshake_timeout));
sendtxtnumeric(client, "sasl-timeout: %s", pretty_time_val(iConf.sasl_timeout));
sendtxtnumeric(client, "ident::connect-timeout: %s", pretty_time_val(IDENT_CONNECT_TIMEOUT));
+1 -1
View File
@@ -478,7 +478,7 @@ static char *replies[] = {
/* 435 */ NULL, /* bahamut */
/* 436 ERR_NICKCOLLISION */ "%s :Nickname collision KILL",
/* 437 ERR_BANNICKCHANGE */ "%s :Cannot change nickname while banned on channel",
/* 438 ERR_NCHANGETOOFAST */ "%s :Nick change too fast. Please wait %d seconds",
/* 438 ERR_NCHANGETOOFAST */ "%s :Nick change too fast. Please try again later.",
/* 439 ERR_TARGETTOOFAST */ "%s :Message target change too fast. Please wait %ld seconds",
/* 440 ERR_SERVICESDOWN */ "%s :Services are currently down. Please try again later.",
/* 441 ERR_USERNOTINCHANNEL */ "%s %s :They aren't on that channel",
+33
View File
@@ -925,3 +925,36 @@ char *get_connect_extinfo(Client *client)
return retbuf;
}
/** Is the flood limit exceeded for an option? eg for away-flood.
* @param client The client to check flood for (local user)
* @param opt The flood option (eg FLD_AWAY)
* @note This increments the flood counter as well.
* @returns 1 if exceeded, 0 if not.
*/
int flood_limit_exceeded(Client *client, FloodOption opt)
{
if (!MyUser(client))
return 0;
if ((opt < 0) || (opt >= MAXFLOODOPTIONS))
abort();
/* Is the protection enabled at all? */
if (iConf.floodsettings->limit[opt] == 0)
return 0;
/* Ok, let's do the flood check */
if ((client->local->flood[opt].t + iConf.floodsettings->period[opt]) <= timeofday)
{
/* Time exceeded, reset */
client->local->flood[opt].count = 0;
client->local->flood[opt].t = timeofday;
}
if (client->local->flood[opt].count <= iConf.floodsettings->limit[opt])
client->local->flood[opt].count++;
if (client->local->flood[opt].count > iConf.floodsettings->limit[opt])
return 1; /* Flood limit hit! */
return 0;
}