pissircd/src/modules/jointhrottle.c

249 lines
6.2 KiB
C

/*
* Jointhrottle (set::anti-flood::join-flood).
* (C) Copyright 2005-.. Bram Matthys (Syzop) and the UnrealIRCd team
*
* This was PREVIOUSLY channel mode +j but has been moved to the
* set::anti-flood::join-flood block instead since people rarely need
* to tweak this per-channel and it's nice to have this on by default.
*
* 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
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"jointhrottle",
"5.0",
"Join flood protection (set::anti-flood::join-flood)",
"UnrealIRCd Team",
"unrealircd-6",
};
ModuleInfo *ModInfo = NULL;
ModDataInfo *jointhrottle_md; /* Module Data structure which we acquire */
typedef struct JoinFlood JoinFlood;
struct JoinFlood {
JoinFlood *prev, *next;
char name[CHANNELLEN+1];
time_t firstjoin;
unsigned short numjoins;
};
/* Forward declarations */
void jointhrottle_md_free(ModData *m);
int jointhrottle_can_join(Client *client, Channel *channel, const char *key, char **errmsg);
int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags);
static int isjthrottled(Client *client, Channel *channel);
static void jointhrottle_increase_usercounter(Client *client, Channel *channel);
EVENT(jointhrottle_cleanup_structs);
JoinFlood *jointhrottle_addentry(Client *client, Channel *channel);
MOD_TEST()
{
return MOD_SUCCESS;
}
MOD_INIT()
{
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
ModInfo = modinfo;
memset(&mreq, 0, sizeof(mreq));
mreq.name = "jointhrottle";
mreq.free = jointhrottle_md_free;
mreq.serialize = NULL; /* not supported */
mreq.unserialize = NULL; /* not supported */
mreq.sync = 0;
mreq.type = MODDATATYPE_LOCAL_CLIENT;
jointhrottle_md = ModDataAdd(modinfo->handle, mreq);
if (!jointhrottle_md)
abort();
HookAdd(modinfo->handle, HOOKTYPE_CAN_JOIN, 0, jointhrottle_can_join);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_JOIN, 0, jointhrottle_local_join);
return MOD_SUCCESS;
}
MOD_LOAD()
{
EventAdd(ModInfo->handle, "jointhrottle_cleanup_structs", jointhrottle_cleanup_structs, NULL, 60000, 0);
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_FAILED;
}
static int isjthrottled(Client *client, Channel *channel)
{
JoinFlood *e;
FloodSettings *settings = get_floodsettings_for_user(client, FLD_JOIN);
if (!MyUser(client))
return 0;
/* Grab user<->chan entry.. */
for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
if (!strcasecmp(e->name, channel->name))
break;
if (!e)
return 0; /* Not present, so cannot be throttled */
/* Ok... now the actual check:
* if ([timer valid] && [one more join would exceed num])
*/
if (((TStime() - e->firstjoin) < settings->period[FLD_JOIN]) &&
(e->numjoins >= settings->limit[FLD_JOIN]))
return 1; /* Throttled */
return 0;
}
static void jointhrottle_increase_usercounter(Client *client, Channel *channel)
{
JoinFlood *e;
if (!MyUser(client))
return;
/* Grab user<->chan entry.. */
for (e = moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
if (!strcasecmp(e->name, channel->name))
break;
if (!e)
{
/* Allocate one */
e = jointhrottle_addentry(client, channel);
e->firstjoin = TStime();
e->numjoins = 1;
} else
if ((TStime() - e->firstjoin) < iConf.floodsettings->period[FLD_JOIN]) /* still valid? */
{
e->numjoins++;
} else {
/* reset :p */
e->firstjoin = TStime();
e->numjoins = 1;
}
}
int jointhrottle_can_join(Client *client, Channel *channel, const char *key, char **errmsg)
{
if (!ValidatePermissionsForPath("immune:join-flood",client,NULL,channel,NULL) && isjthrottled(client, channel))
{
*errmsg = STR_ERR_TOOMANYJOINS;
return ERR_TOOMANYJOINS;
}
return 0;
}
int jointhrottle_local_join(Client *client, Channel *channel, MessageTag *mtags)
{
jointhrottle_increase_usercounter(client, channel);
return 0;
}
/** Adds a JoinFlood entry to user & channel and returns entry.
* NOTE: Does not check for already-existing-entry
*/
JoinFlood *jointhrottle_addentry(Client *client, Channel *channel)
{
JoinFlood *e;
#ifdef DEBUGMODE
if (!IsUser(client))
abort();
for (e=moddata_local_client(client, jointhrottle_md).ptr; e; e=e->next)
if (!strcasecmp(e->name, channel->name))
abort(); /* already exists -- should never happen */
#endif
e = safe_alloc(sizeof(JoinFlood));
strlcpy(e->name, channel->name, sizeof(e->name));
/* Insert our new entry as (new) head */
if (moddata_local_client(client, jointhrottle_md).ptr)
{
JoinFlood *current_head = moddata_local_client(client, jointhrottle_md).ptr;
current_head->prev = e;
e->next = current_head;
}
moddata_local_client(client, jointhrottle_md).ptr = e;
return e;
}
/** Regularly cleans up user/chan structs */
EVENT(jointhrottle_cleanup_structs)
{
Client *client;
JoinFlood *jf, *jf_next;
list_for_each_entry(client, &lclient_list, lclient_node)
{
if (!MyUser(client))
continue; /* only (local) persons.. */
for (jf = moddata_local_client(client, jointhrottle_md).ptr; jf; jf = jf_next)
{
jf_next = jf->next;
if (jf->firstjoin + iConf.floodsettings->period[FLD_JOIN] > TStime())
continue; /* still valid entry */
if (moddata_local_client(client, jointhrottle_md).ptr == jf)
{
/* change head */
moddata_local_client(client, jointhrottle_md).ptr = jf->next; /* could be set to NULL now */
if (jf->next)
jf->next->prev = NULL;
} else {
/* change non-head entries */
jf->prev->next = jf->next; /* could be set to NULL now */
if (jf->next)
jf->next->prev = jf->prev;
}
safe_free(jf);
}
}
}
void jointhrottle_md_free(ModData *m)
{
JoinFlood *j, *j_next;
if (!m->ptr)
return;
for (j = m->ptr; j; j = j_next)
{
j_next = j->next;
safe_free(j);
}
m->ptr = NULL;
}