diff --git a/doc/example.conf b/doc/example.conf index 69c894f61..b67cb7d92 100755 --- a/doc/example.conf +++ b/doc/example.conf @@ -514,6 +514,7 @@ general { reject_duration = 5 minutes; throttle_duration = 60; throttle_count = 4; + max_ratelimit_tokens = 30; }; modules { diff --git a/doc/reference.conf b/doc/reference.conf index c0431fa31..bce1ea33f 100755 --- a/doc/reference.conf +++ b/doc/reference.conf @@ -1264,6 +1264,13 @@ general { client_flood_burst_max = 5; client_flood_message_time = 1; client_flood_message_num = 2; + + /* max_ratelimit_tokens: the maximum number of ratelimit tokens that one + * user can accumulate. This attempts to limit the amount of outbound + * bandwidth one user can consume. Do not change unless you know what + * you're doing. + */ + max_ratelimit_tokens = 30; }; modules { diff --git a/include/client.h b/include/client.h index 47725750e..62d3fa0fa 100644 --- a/include/client.h +++ b/include/client.h @@ -268,6 +268,10 @@ struct LocalUser unsigned int targets_free; /* free targets */ time_t target_last; /* last time we cleared a slot */ + /* ratelimit items */ + time_t ratelimit; + unsigned int join_who_credits; + struct ListClient *safelist_data; char *mangledhost; /* non-NULL if host mangling module loaded and diff --git a/include/ratelimit.h b/include/ratelimit.h new file mode 100644 index 000000000..8e39493e9 --- /dev/null +++ b/include/ratelimit.h @@ -0,0 +1,30 @@ +/* + * charybdis: an advanced Internet Relay Chat Daemon(ircd). + * + * Copyright (C) 2012 Keith Buck + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef INCLUDED_ratelimit_h +#define INCLUDED_ratelimit_h + +int ratelimit_client(struct Client *client_p, unsigned int penalty); +int ratelimit_client_who(struct Client *client_p, unsigned int penalty); +void credit_client_join(struct Client *client_p); + +#endif /* INCLUDED_ratelimit_h */ diff --git a/include/s_conf.h b/include/s_conf.h index 8526f06c0..8db55f24a 100644 --- a/include/s_conf.h +++ b/include/s_conf.h @@ -224,6 +224,7 @@ struct config_file_entry int global_snotices; int operspy_dont_care_user_info; int use_propagated_bans; + int max_ratelimit_tokens; int client_flood_max_lines; int client_flood_burst_rate; diff --git a/include/s_stats.h b/include/s_stats.h index f4f45c092..4e88a5054 100644 --- a/include/s_stats.h +++ b/include/s_stats.h @@ -67,6 +67,7 @@ struct ServerStatistics unsigned int is_ssuc; /* successful sasl authentications */ unsigned int is_sbad; /* failed sasl authentications */ unsigned int is_tgch; /* messages blocked due to target change */ + unsigned int is_rl; /* commands blocked due to ratelimit */ }; extern struct ServerStatistics ServerStats; diff --git a/modules/m_info.c b/modules/m_info.c index 5ee13253c..4facb980d 100644 --- a/modules/m_info.c +++ b/modules/m_info.c @@ -518,6 +518,12 @@ static struct InfoStruct info_table[] = { &ConfigFileEntry.use_propagated_bans, "KLINE sets fully propagated bans" }, + { + "max_ratelimit_tokens", + OUTPUT_DECIMAL, + &ConfigFileEntry.max_ratelimit_tokens, + "The maximum number of tokens that can be accumulated for executing rate-limited commands", + }, { "default_split_server_count", OUTPUT_DECIMAL, diff --git a/modules/m_stats.c b/modules/m_stats.c index d5dcd3383..395486875 100644 --- a/modules/m_stats.c +++ b/modules/m_stats.c @@ -944,6 +944,8 @@ stats_tstats (struct Client *source_p) sendto_one_numeric(source_p, RPL_STATSDEBUG, "T :tgchange blocked msgs %u restricted addrs %lu", sp.is_tgch, rb_dlink_list_length(&tgchange_list)); + sendto_one_numeric(source_p, RPL_STATSDEBUG, + "T :ratelimit blocked commands %u", sp.is_rl); sendto_one_numeric(source_p, RPL_STATSDEBUG, "T :auth successes %u fails %u", sp.is_asuc, sp.is_abad); diff --git a/src/Makefile.in b/src/Makefile.in index b01779274..3c4cba597 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -82,6 +82,7 @@ SRCS = \ packet.c \ parse.c \ privilege.c \ + ratelimit.c \ res.c \ reslib.c \ reject.c \ diff --git a/src/newconf.c b/src/newconf.c index 8447201ed..b1cc6a862 100644 --- a/src/newconf.c +++ b/src/newconf.c @@ -2277,6 +2277,7 @@ static struct ConfEntry conf_general_table[] = { "client_flood_burst_max", CF_INT, NULL, 0, &ConfigFileEntry.client_flood_burst_max }, { "client_flood_message_num", CF_INT, NULL, 0, &ConfigFileEntry.client_flood_message_num }, { "client_flood_message_time", CF_INT, NULL, 0, &ConfigFileEntry.client_flood_message_time }, + { "max_ratelimit_tokens", CF_INT, NULL, 0, &ConfigFileEntry.max_ratelimit_tokens }, { "\0", 0, NULL, 0, NULL } }; diff --git a/src/ratelimit.c b/src/ratelimit.c new file mode 100644 index 000000000..ce9efc0bc --- /dev/null +++ b/src/ratelimit.c @@ -0,0 +1,132 @@ +/* + * charybdis: an advanced ircd + * ratelimit.c: Per-client ratelimiting for high-bandwidth commands. + * + * Copyright (c) 2012 Keith Buck + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "stdinc.h" +#include "s_conf.h" +#include "s_stats.h" +#include "ratelimit.h" + +/* + * ratelimit_client(struct Client *client_p, int penalty) + * + * Applies a penalty to a client for executing a rate-limited command. + * + * Inputs: + * - the client to be rate-limited + * - the penalty to apply + * + * Outputs: + * - 1 if the user has been penalized and the command should be + * allowed to execute + * - 0 if the command should not execute and the user has not + * been penalized (they are executing commands too fast and have + * been rate-limited) + * The caller should return RPL_LOAD2HI + * + * Side effects: + * - The ratelimit for the user will be initialized if it hasn't + * been initialized yet. + */ +int ratelimit_client(struct Client *client_p, unsigned int penalty) +{ + s_assert(client_p); + s_assert(MyClient(client_p)); + + if (!client_p->localClient->ratelimit) + { + /* Not initialized yet - do it now. */ + client_p->localClient->ratelimit = rb_current_time() - ConfigFileEntry.max_ratelimit_tokens; + } + + /* Don't make it impossible to execute anything. */ + if (penalty > ConfigFileEntry.max_ratelimit_tokens) + penalty = ConfigFileEntry.max_ratelimit_tokens; + + if (client_p->localClient->ratelimit <= rb_current_time() - ConfigFileEntry.max_ratelimit_tokens) + { + client_p->localClient->ratelimit = rb_current_time() - ConfigFileEntry.max_ratelimit_tokens + penalty; + return 1; + } + + if (client_p->localClient->ratelimit + penalty > rb_current_time()) + { + ServerStats.is_rl++; + return 0; + } + + client_p->localClient->ratelimit += penalty; + + return 1; +} + +/* + * ratelimit_client_who(struct Client *client_p, int penalty) + * + * Rate-limits a client for a WHO query if they have no remaining "free" + * WHO queries to execute. + * + * Inputs: + * - same as ratelimit_client + * + * Outputs: + * - same as ratelimit_client + * + * Side effects: + * - A "free who" token will be removed from the user if one exists. + * If one doesn't exist, the user will be ratelimited as normal. + */ +int ratelimit_client_who(struct Client *client_p, unsigned int penalty) +{ + s_assert(client_p); + s_assert(MyClient(client_p)); + + if (client_p->localClient->join_who_credits) + { + --client_p->localClient->join_who_credits; + return 1; + } + + return ratelimit_client(client_p, penalty); +} + +/* + * credit_client_join(struct Client *client_p) + * + * Gives a user a credit to execute a WHO for joining a channel. + * + * Inputs: + * - the client to be credited + * + * Outputs: + * - (none) + * + * Side effects: + * - (none) + */ +void credit_client_join(struct Client *client_p) +{ + s_assert(client_p); + s_assert(MyClient(client_p)); + + ++client_p->localClient->join_who_credits; +} diff --git a/src/s_conf.c b/src/s_conf.c index 369522534..399351a4c 100644 --- a/src/s_conf.c +++ b/src/s_conf.c @@ -745,6 +745,7 @@ set_default_conf(void) ConfigFileEntry.global_snotices = YES; ConfigFileEntry.operspy_dont_care_user_info = NO; ConfigFileEntry.use_propagated_bans = YES; + ConfigFileEntry.max_ratelimit_tokens = 30; #ifdef HAVE_LIBZ ConfigFileEntry.compression_level = 4;