/* * ================================================================= * Filename: cmdflood.c * Description: Flood protection for commands. * Written by: AngryWolf * Requested by: aaadicted * Documentation: cmdflood.txt (comes with the package) * ================================================================= */ #include "config.h" #include "struct.h" #include "common.h" #include "sys.h" #include "numeric.h" #include "msg.h" #include "channel.h" #include #include #include #include #include #ifdef _WIN32 #include #endif #include #include "h.h" #ifdef STRIPBADWORDS #include "badwords.h" #endif #ifdef _WIN32 #include "version.h" #endif typedef struct _floodsetting FloodSetting; typedef struct _floodinfo FloodInfo; struct _floodsetting { FloodSetting *prev, *next; char *cmd; Cmdoverride *ovr; int count; int period; }; struct _floodinfo { FloodInfo *prev, *next; aClient *cptr; char *cmd; int count; TS last; }; extern void sendto_one(aClient *to, char *pattern, ...); extern void sendto_realops(char *pattern, ...); extern int config_parse_flood(char *orig, int *times, int *period); #define MSG_CMDFLOOD "CMDFLOOD" #define TOK_CMDFLOOD "CF" #define ERR_TOOMANYUSES ":%s 495 %s :Too many uses of command %s. Wait %d seconds before trying again." #define DelOverride(cmd, ovr) if (ovr && CommandExists(cmd)) CmdoverrideDel(ovr); ovr = NULL #define DelHook(x) if (x) HookDel(x); x = NULL #define DelCommand(x) if (x) CommandDel(x); x = NULL #define ircstrdup(x,y) if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y) #define ircfree(x) if (x) MyFree(x); x = NULL static Command *AddCommand(Module *module, char *msg, char *token, iFP func); static Cmdoverride *AddOverride(char *msg, iFP cb); static int m_cmdflood(aClient *cptr, aClient *sptr, int parc, char *parv[]); static int override_cmd(Cmdoverride *, aClient *, aClient *, int, char *[]); static int cb_config_test(ConfigFile *, ConfigEntry *, int, int *); static int cb_config_run(ConfigFile *, ConfigEntry *, int); static int cb_config_rehash(); static int cb_quit(aClient *, char *); static FloodSetting *find_FloodSetting(char *cmd); static FloodInfo *find_FloodInfo(aClient *cptr, char *cmd); static void FreeFloodSettings(); static void FreeFloodInfoList(); static void InitConf(); static void FreeConf(); #ifdef HOOKTYPE_REHASH_COMPLETE static int cb_rehash_complete(); #else static EVENT(LoadOverrides); #endif static Command *CmdCmdflood; static Hook *HookConfTest, *HookConfRun, *HookConfRehash; static Hook *HookQuit; #ifdef STATIC_LINKING #ifdef HOOKTYPE_REHASH_COMPLETE static Hook *HookRehashDone; #else static u_int event_added; #endif #endif static FloodSetting *FloodSettings; static FloodInfo *FloodInfoList; #ifndef STATIC_LINKING static ModuleInfo *MyModInfo; #define MyMod MyModInfo->handle #define SAVE_MODINFO MyModInfo = modinfo; #else #define MyMod NULL #define SAVE_MODINFO #endif ModuleHeader MOD_HEADER(cmdflood) = { "cmdflood", "$Id: cmdflood.c,v 1.8 2004/05/17 19:58:06 angrywolf Exp $", "Flood protection for commands", "3.2-b8-1", NULL }; DLLFUNC int MOD_TEST(cmdflood)(ModuleInfo *modinfo) { SAVE_MODINFO HookConfTest = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, cb_config_test); return MOD_SUCCESS; } DLLFUNC int MOD_INIT(cmdflood)(ModuleInfo *modinfo) { SAVE_MODINFO FloodInfoList = NULL; #ifndef STATIC_LINKING ModuleSetOptions(modinfo->handle, MOD_OPT_PERM); #endif InitConf(); HookConfRun = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, cb_config_run); HookConfRehash = HookAddEx(modinfo->handle, HOOKTYPE_REHASH, cb_config_rehash); #if defined(HOOKTYPE_REHASH_COMPLETE) && defined(STATIC_LINKING) HookRehashDone = HookAddEx(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, cb_rehash_complete); #endif HookQuit = HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_QUIT, cb_quit); CmdCmdflood = AddCommand(modinfo->handle, MSG_CMDFLOOD, TOK_CMDFLOOD, m_cmdflood); if (!CmdCmdflood) return MOD_FAILED; return MOD_SUCCESS; } DLLFUNC int MOD_LOAD(cmdflood)(int module_load) { #ifdef HOOKTYPE_REHASH_COMPLETE cb_rehash_complete(); #else LoadOverrides(NULL); #endif return MOD_SUCCESS; } DLLFUNC int MOD_UNLOAD(cmdflood)(int module_unload) { FreeConf(); FreeFloodInfoList(); #if defined(HOOKTYPE_REHASH_COMPLETE) && defined(STATIC_LINKING) DelHook(HookRehashDone); #endif DelHook(HookQuit); DelHook(HookConfRehash); DelHook(HookConfRun); DelHook(HookConfTest); DelCommand(CmdCmdflood); return MOD_SUCCESS; } static void InitConf() { FloodSettings = NULL; #if defined(STATIC_LINKING) && !defined(HOOKTYPE_REHASH_COMPLETE) event_added = 0; #endif } static void FreeConf() { FreeFloodSettings(); } #ifdef HOOKTYPE_REHASH_COMPLETE static int cb_rehash_complete() #else static EVENT(LoadOverrides) #endif { FloodSetting *f; for (f = FloodSettings; f; f = f->next) f->ovr = AddOverride(f->cmd, override_cmd); #ifdef HOOKTYPE_REHASH_COMPLETE return 0; #endif } static FloodSetting *find_FloodSetting(char *cmd) { FloodSetting *f; for (f = FloodSettings; f; f = f->next) if (!stricmp(f->cmd, cmd)) break; return f; } static FloodInfo *find_FloodInfo(aClient *cptr, char *cmd) { FloodInfo *f; for (f = FloodInfoList; f; f = f->next) if (f->cptr == cptr && !stricmp(f->cmd, cmd)) break; return f; } static void FreeFloodSettings() { FloodSetting *f; ListStruct *next; for (f = FloodSettings; f; f = (FloodSetting *) next) { next = (ListStruct *) f->next; DelListItem(f, FloodSettings); DelOverride(f->cmd, f->ovr); MyFree(f->cmd); MyFree(f); } } static void FreeFloodInfoList() { FloodInfo *f; ListStruct *next; for (f = FloodInfoList; f; f = (FloodInfo *) next) { next = (ListStruct *) f->next; DelListItem(f, FloodInfoList); MyFree(f->cmd); MyFree(f); } } static int cb_config_rehash() { FreeConf(); InitConf(); return 1; } #define CHECK_EMPTY(ce, parent) \ if (!(ce)->ce_varname) \ { \ config_error("%s:%i: blank %s item", \ (ce)->ce_fileptr->cf_filename, \ (ce)->ce_varlinenum, (parent)->ce_varname); \ errors++; \ continue; \ } \ if (!(ce)->ce_vardata) \ { \ config_error("%s:%i: %s::%s without contents", \ (ce)->ce_fileptr->cf_filename, \ (ce)->ce_varlinenum, \ (parent)->ce_varname, (ce)->ce_varname); \ errors++; \ continue; \ } static int cb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) { ConfigEntry *cep; int errors = 0; int count, period; if (type != CONFIG_MAIN) return 0; if (!strcmp(ce->ce_varname, "cmdflood")) { for (cep = ce->ce_entries; cep; cep = cep->ce_next) { CHECK_EMPTY(cep, ce) if (!config_parse_flood(cep->ce_vardata, &count, &period) || (count < 1) || (count > 255) || (period < 5)) { config_error("%s:%i: Bad value for cmdflood::%s. Syntax is ':' (ie. 5:60), " "count should be in range 1-255, period greater than 4", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname); errors++; } } *errs = errors; return errors ? -1 : 1; } return 0; } static int cb_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { ConfigEntry *cep; FloodSetting *f; if (type != CONFIG_MAIN) return 0; if (!strcmp(ce->ce_varname, "cmdflood")) { for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (find_FloodSetting(cep->ce_varname)) continue; f = (FloodSetting *) MyMallocEx(sizeof(FloodSetting)); f->cmd = strdup(cep->ce_varname); config_parse_flood(cep->ce_vardata, &f->count, &f->period); AddListItem(f, FloodSettings); #if defined(STATIC_LINKING) && !defined(HOOKTYPE_REHASH_COMPLETE) if (!event_added) { /* Not the best solution, but it works. */ EventAdd("cmdflood_loadoverrides", 0, 1, LoadOverrides, NULL); event_added = 1; } #endif } return 1; } return 0; } static Cmdoverride *AddOverride(char *msg, iFP cb) { Cmdoverride *ovr = CmdoverrideAdd(MyMod, msg, cb); #ifndef STATIC_LINKING if (ModuleGetError(MyMod) != MODERR_NOERROR || !ovr) #else if (!ovr) #endif { #ifndef STATIC_LINKING config_error("Error replacing command %s when loading module %s: %s", msg, MOD_HEADER(cmdflood).name, ModuleGetErrorStr(MyMod)); #else config_error("Error replacing command %s when loading module %s", msg, MOD_HEADER(cmdflood).name); #endif return NULL; } return ovr; } static Command *AddCommand(Module *module, char *msg, char *token, iFP func) { Command *cmd; if (CommandExists(msg)) { config_error("Command %s already exists", msg); return NULL; } if (CommandExists(token)) { config_error("Token %s already exists", token); return NULL; } cmd = CommandAdd(module, msg, token, func, MAXPARA, 0); #ifndef STATIC_LINKING if (ModuleGetError(module) != MODERR_NOERROR || !cmd) #else if (!cmd) #endif { #ifndef STATIC_LINKING config_error("Error adding command %s: %s", msg, ModuleGetErrorStr(module)); #else config_error("Error adding command %s", msg); #endif return NULL; } return cmd; } static int cb_quit(aClient *sptr, char *comment) { FloodInfo *f; ListStruct *next; for (f = FloodInfoList; f; f = (FloodInfo *) next) { next = (ListStruct *) f->next; if (f->cptr == sptr) { DelListItem(f, FloodInfoList); MyFree(f->cmd); MyFree(f); } } return 0; } static int override_cmd(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[]) { FloodInfo *fi; FloodSetting *fs; TS now = TStime(); char *cmd = ovr->command->cmd; if (IsPerson(sptr) && !IsAnOper(sptr)) { if (!(fi = find_FloodInfo(sptr, cmd))) { fi = (FloodInfo *) MyMallocEx(sizeof(FloodInfo)); fi->cptr = sptr; fi->cmd = strdup(cmd); fi->last = now; fi->count = 1; AddListItem(fi, FloodInfoList); } else { fs = find_FloodSetting(cmd); if (!fs) { sendto_realops("*** [BUG] Couldn't find the flood configuration of command %s." " Please report this bug at http://angrywolf.clanintern-irc.de/.", cmd); return CallCmdoverride(ovr, cptr, sptr, parc, parv); } if (now - fi->last >= fs->period) { fi->last = now; fi->count = 1; } else { fi->count++; if (fi->count > fs->count && now - fi->last < fs->period) { sendto_one(sptr, ERR_TOOMANYUSES, me.name, sptr->name, cmd, fi->last + fs->period - now); return 0; } } } } return CallCmdoverride(ovr, cptr, sptr, parc, parv); } static int m_cmdflood(aClient *cptr, aClient *sptr, int parc, char *parv[]) { FloodSetting *f; if (!IsPerson(sptr)) return -1; if (!MyConnect(sptr) || !IsAnOper(sptr)) { sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]); return -1; } for (f = FloodSettings; f; f = f->next) sendto_one(sptr, ":%s %i %s :x %s: %d per %s", me.name, RPL_TEXT, sptr->name, f->cmd, f->count, pretty_time_val(f->period)); sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, sptr->name, 'x'); return 0; }