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:
+8
-18
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user