diff --git a/Changes b/Changes index 19aa4b161..40d3d4297 100644 --- a/Changes +++ b/Changes @@ -589,3 +589,7 @@ - Fixed a bug with channel alias{}'s where using the format syntax caused a crash (#0002323) reported by Snake. - Made channel mode +S strip RGB color codes. +- Added channelmode +j (jointhrottle), syntax: /mode #chan +j X:Y, and then it will + throttle the number of joins per-user to X in Y seconds. Idea from Angrywolf (who + wrote a module that did this before). This might need some more testing :). + It's enabled by default but can be #undef'ed in include/config.h (line 449). diff --git a/doc/unreal32docs.html b/doc/unreal32docs.html index 18b23d327..dde5f9e76 100644 --- a/doc/unreal32docs.html +++ b/doc/unreal32docs.html @@ -2375,6 +2375,10 @@ set { Makes channel G rated. Checks for words listed in the Badword Blocks, and replaces them with the words specified + +
j <joins:seconds>
+ Throttles joins per-user to joins per seconds seconds +
M
A registered nickname (+r) is required to talk diff --git a/help.conf b/help.conf index d5bdfbb4f..3a2d62c2a 100644 --- a/help.conf +++ b/help.conf @@ -165,6 +165,7 @@ help Chmodes { " I = Overrides +i for matching users [h]"; " f = Flood protection (for more info see /HELPOP CHMODEF) [o]"; " i = A user must be invited to join the channel [h]"; + " j = Throttle joins per-user to 'joins' per 'sec' seconds"; " k = Users must specify to join [h]"; " l = Channel may hold at most of users [o]"; " m = Moderated channel (only +vhoaq users may speak) [h]"; diff --git a/include/config.h b/include/config.h index f5c37f935..2284fac31 100644 --- a/include/config.h +++ b/include/config.h @@ -431,6 +431,7 @@ /* * Extended channel modes. This extends the channel modes with yet another * 32 possible modes which can also be used in modules. + * This is now pretty much required. */ #define EXTCMODE @@ -440,6 +441,13 @@ */ #define NEWCHFLOODPROT +/* JoinThrottle (chanmode +j): +j x:y throttles users to X joins per Y seconds (per-user). + * In peak situations (eg: just after a server restart with thousand clients joining + * hundreds of channels) it can use like ~200k, but in normal circumstances you should + * count on just ~10-50k. + */ +#define JOINTHROTTLE + /* ------------------------- END CONFIGURATION SECTION -------------------- */ #define MOTD MPATH #define RULES RPATH diff --git a/include/h.h b/include/h.h index 76a7a045c..ab0a12bdf 100644 --- a/include/h.h +++ b/include/h.h @@ -467,6 +467,7 @@ extern MODVAR Cmode_t EXTMODE_NONOTICE; #ifdef STRIPBADWORDS extern MODVAR Cmode_t EXTMODE_STRIPBADWORDS; #endif +extern MODVAR Cmode_t EXTMODE_JOINTHROTTLE; #endif #ifndef HAVE_STRLCPY @@ -739,3 +740,9 @@ extern char *clean_ban_mask(char *, int, aClient *); extern void chanfloodtimer_stopchantimers(aChannel *chptr); extern int find_invex(aChannel *chptr, aClient *sptr); extern void DoMD5(unsigned char *mdout, unsigned char *src, unsigned long n); +#ifdef JOINTHROTTLE +aJFlood *cmodej_addentry(aClient *cptr, aChannel *chptr); +void cmodej_delentry(aJFlood *e); +void cmodej_deluserentries(aClient *cptr); +void cmodej_delchannelentries(aChannel *chptr); +#endif diff --git a/include/numeric.h b/include/numeric.h index 1c7c9727e..277d08cde 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -129,6 +129,7 @@ #define ERR_CHANOWNPRIVNEEDED 499 +#define ERR_TOOMANYJOINS 500 #define ERR_UMODEUNKNOWNFLAG 501 #define ERR_USERSDONTMATCH 502 diff --git a/include/struct.h b/include/struct.h index 7dd77abba..4ce7b2cd4 100644 --- a/include/struct.h +++ b/include/struct.h @@ -143,6 +143,7 @@ typedef struct _cmdoverride Cmdoverride; typedef struct SMember Member; typedef struct SMembership Membership; typedef struct SMembershipL MembershipL; +typedef struct JFlood aJFlood; #ifdef ZIP_LINKS typedef struct Zdata aZdata; @@ -754,6 +755,9 @@ struct User { unsigned char away_c; /* number of times away has been set */ #endif } flood; +#ifdef JOINTHROTTLE + aJFlood *jflood; +#endif }; struct Server { @@ -873,6 +877,12 @@ extern Cmode *CmodeAdd(Module *reserved, CmodeInfo req, Cmode_t *mode); extern void CmodeDel(Cmode *cmode); #endif +typedef struct { + EXTCM_PAR_HEADER + unsigned short num; + unsigned short t; +} aModejEntry; + #define LISTENER_NORMAL 0x000001 #define LISTENER_CLIENTSONLY 0x000002 #define LISTENER_SERVERSONLY 0x000004 @@ -1486,6 +1496,9 @@ struct Channel { Ban *banlist; Ban *exlist; /* exceptions */ Ban *invexlist; /* invite list */ +#ifdef JOINTHROTTLE + aJFlood *jflood; +#endif char chname[1]; }; @@ -1735,6 +1748,42 @@ typedef struct { unsigned parameters : 1; } aCtab; +#ifdef JOINTHROTTLE +/** A jointhrottle item, this is a double linked list. + * prev_u Previous entry of user + * next_u Next entry of user + * prev_c Previous entry of channel + * next_c Next entry of channel + * chptr The channel this entry applies to + * cptr The user this entry applies to + * firstjoin Timestamp of "first join" (since last timer reset) + * numjoin Number of joins since that period + * CLARIFICATION: + * Why a double linked list? Well, the following operations need to be performed: + * - if user quits, entry must be removed + * - if channel is destroyed, entry must be removed + * (and of course, more, but these are the most important ones affecting this decision) + * While it would be possible to have a linked list only by user (for example), + * that would mean that upon channel destroy ALL entries would have to be searched + * trough, which might mean for example 800*8=6400 entries in a peak situation + * (such as after a server restart and hundreds of clients connecting&joining). + * For obvious reasons, that would be a very bad idea :). + * So this costs us 2 pointers (8b on ia32) per entry, but in case of channel destroy + * it means we only have for example 20 entries to scan trough rather than 2000. + * Worth the extra memory :). -- Syzop + * Note that in normal situations it won't be that bad since we will try to + * regulary free up some entries. + */ +struct JFlood { + aJFlood *prev_u, *next_u; + aJFlood *prev_c, *next_c; + aChannel *chptr; + aClient *cptr; + time_t firstjoin; + unsigned short numjoins; +}; +#endif + void init_throttling_hash(); int hash_throttling(struct IN_ADDR *in); struct ThrottlingBucket *find_throttling_bucket(struct IN_ADDR *in); diff --git a/src/channel.c b/src/channel.c index b717d2cd6..75c43047d 100644 --- a/src/channel.c +++ b/src/channel.c @@ -1523,6 +1523,9 @@ void sub1_from_channel(aChannel *chptr) chanfloodtimer_stopchantimers(chptr); if (chptr->mode.floodprot) MyFree(chptr->mode.floodprot); +#endif +#ifdef JOINTHROTTLE + cmodej_delchannelentries(chptr); #endif if (chptr->topic) MyFree(chptr->topic); diff --git a/src/events.c b/src/events.c index ed0650b3d..d13bc8b7c 100644 --- a/src/events.c +++ b/src/events.c @@ -45,6 +45,10 @@ ID_Copyright("(C) Carsten Munk 2001"); MODVAR Event *events = NULL; +#ifdef JOINTHROTTLE +extern EVENT(cmodej_cleanup_structs); +#endif + void LockEventSystem(void) { } @@ -213,6 +217,9 @@ void SetupEvents(void) EventAddEx(NULL, "loop", 0, 0, loop_event, NULL); #ifndef NO_FDLIST EventAddEx(NULL, "fdlistcheck", 1, 0, e_check_fdlists, NULL); +#endif +#ifdef JOINTHROTTLE + EventAddEx(NULL, "cmodej_cleanup_structs", 60, 0, cmodej_cleanup_structs, NULL); #endif UnlockEventSystem(); } diff --git a/src/extcmodes.c b/src/extcmodes.c index 94aad5371..1c1ee2ae2 100644 --- a/src/extcmodes.c +++ b/src/extcmodes.c @@ -58,6 +58,18 @@ Cmode_t EXTMODE_NONOTICE = 0L; Cmode_t EXTMODE_STRIPBADWORDS = 0L; #endif +#ifdef JOINTHROTTLE +/* cmode j stuff... */ +Cmode_t EXTMODE_JOINTHROTTLE = 0L; +int cmodej_is_ok(aClient *sptr, aChannel *chptr, char *para, int type, int what); +CmodeParam *cmodej_put_param(CmodeParam *r_in, char *param); +char *cmodej_get_param(CmodeParam *r_in); +char *cmodej_conv_param(char *param_in); +void cmodej_free_param(CmodeParam *r); +CmodeParam *cmodej_dup_struct(CmodeParam *r_in); +int cmodej_sjoin_check(aChannel *chptr, CmodeParam *ourx, CmodeParam *theirx); +#endif + void make_extcmodestr() { char *p; @@ -99,6 +111,21 @@ static void load_extendedchanmodes(void) req.flag = 'G'; CmodeAdd(NULL, req, &EXTMODE_STRIPBADWORDS); #endif + +#ifdef JOINTHROTTLE + /* +j */ + memset(&req, 0, sizeof(req)); + req.paracount = 1; + req.is_ok = cmodej_is_ok; + req.flag = 'j'; + req.put_param = cmodej_put_param; + req.get_param = cmodej_get_param; + req.conv_param = cmodej_conv_param; + req.free_param = cmodej_free_param; + req.dup_struct = cmodej_dup_struct; + req.sjoin_check = cmodej_sjoin_check; + CmodeAdd(NULL, req, &EXTMODE_JOINTHROTTLE); +#endif } void extcmode_init(void) @@ -268,4 +295,152 @@ int extcmode_default_requirehalfop(aClient *cptr, aChannel *chptr, char *para, i return EX_DENY; } +#ifdef JOINTHROTTLE +/*** CHANNEL MODE +j STUFF ******/ +int cmodej_is_ok(aClient *sptr, aChannel *chptr, char *para, int type, int what) +{ + if ((type == EXCHK_ACCESS) || (type == EXCHK_ACCESS_ERR)) + { + if (IsPerson(sptr) && is_chan_op(sptr, chptr)) + return EX_ALLOW; + return EX_DENY; + } else + if (type == EXCHK_PARAM) + { + /* Check parameter.. syntax should be X:Y, X should be 1-255, Y should be 1-999 */ + char buf[32], *p; + int num, t, fail = 0; + + strlcpy(buf, para, sizeof(buf)); + p = strchr(buf, ':'); + if (!p) + { + fail = 1; + } else { + *p++ = '\0'; + num = atoi(buf); + t = atoi(p); + if ((num < 1) || (num > 255) || (t < 1) || (t > 999)) + fail = 1; + } + if (fail) + { + sendnotice(sptr, "Error in setting +j, syntax: +j :, where must be 1-255, and 1-999"); + return EX_DENY; + } + return EX_ALLOW; + } + + /* falltrough -- should not be used */ + return EX_DENY; +} + +CmodeParam *cmodej_put_param(CmodeParam *r_in, char *param) +{ +aModejEntry *r = (aModejEntry *)r_in; +char buf[32], *p; +int num, t; + + if (!r) + { + /* Need to create one */ + r = (aModejEntry *)malloc(sizeof(aModejEntry)); + memset(r, 0, sizeof(aModejEntry)); + r->flag = 'j'; + } + strlcpy(buf, param, sizeof(buf)); + p = strchr(buf, ':'); + if (p) + { + *p++ = '\0'; + num = atoi(buf); + t = atoi(p); + if (num < 1) num = 1; + if (num > 255) num = 255; + if (t < 1) t = 1; + if (t > 999) t = 999; + r->num = num; + r->t = t; + } else { + r->num = 0; + r->t = 0; + } + return (CmodeParam *)r; +} + +char *cmodej_get_param(CmodeParam *r_in) +{ +aModejEntry *r = (aModejEntry *)r_in; +static char retbuf[16]; + + if (!r) + return NULL; + + snprintf(retbuf, sizeof(retbuf), "%hu:%hu", r->num, r->t); + return retbuf; +} + +char *cmodej_conv_param(char *param_in) +{ +static char retbuf[32]; +char param[32], *p; +int num, t, fail = 0; + + strlcpy(param, param_in, sizeof(param)); + p = strchr(param, ':'); + if (!p) + return NULL; + *p++ = '\0'; + num = atoi(param); + t = atoi(p); + if (num < 1) + num = 1; + if (num > 255) + num = 255; + if (t < 1) + t = 1; + if (t > 999) + t = 999; + + snprintf(retbuf, sizeof(retbuf), "%d:%d", num, t); + return retbuf; +} + +void cmodej_free_param(CmodeParam *r) +{ + MyFree(r); +} + +CmodeParam *cmodej_dup_struct(CmodeParam *r_in) +{ +aModejEntry *r = (aModejEntry *)r_in; +aModejEntry *w = (aModejEntry *)MyMalloc(sizeof(aModejEntry)); + + memcpy(w, r, sizeof(aModejEntry)); + return (CmodeParam *)w; +} + +int cmodej_sjoin_check(aChannel *chptr, CmodeParam *ourx, CmodeParam *theirx) +{ +aModejEntry *our = (aModejEntry *)ourx; +aModejEntry *their = (aModejEntry *)theirx; + + if (our->t != their->t) + { + if (our->t > their->t) + return EXSJ_WEWON; + else + return EXSJ_THEYWON; + } + else if (our->num != their->num) + { + if (our->num > their->num) + return EXSJ_WEWON; + else + return EXSJ_THEYWON; + } else + return EXSJ_SAME; +} +#endif /* JOINTHROTTLE */ + #endif /* EXTCMODE */ diff --git a/src/ircd.c b/src/ircd.c index 9c40897e1..1ca752234 100644 --- a/src/ircd.c +++ b/src/ircd.c @@ -1208,7 +1208,7 @@ int InitwIRCD(int argc, char *argv[]) #ifndef _WIN32 fprintf(stderr, "%s", unreallogo); fprintf(stderr, " v%s\n", VERSIONONLY); - fprintf(stderr, " using TRE %s\n", tre_version()); + fprintf(stderr, " using %s\n", tre_version()); #ifdef USE_SSL fprintf(stderr, " using %s\n", OPENSSL_VERSION_TEXT); #endif diff --git a/src/list.c b/src/list.c index ac1fa7689..0128b4b31 100644 --- a/src/list.c +++ b/src/list.c @@ -550,5 +550,154 @@ ListStruct *del_ListItem(ListStruct *item, ListStruct **list) { } return NULL; } + +#ifdef JOINTHROTTLE +/** Adds a aJFlood entry to user & channel and returns entry. + * NOTE: Does not check for already-existing-entry + */ +aJFlood *cmodej_addentry(aClient *cptr, aChannel *chptr) +{ +aJFlood *e; + +#ifdef DEBUGMODE + if (!IsPerson(cptr)) + abort(); + + for (e=cptr->user->jflood; e; e=e->next_u) + if (e->chptr == chptr) + abort(); + + for (e=chptr->jflood; e; e=e->next_c) + if (e->cptr == cptr) + abort(); +#endif + + e = MyMallocEx(sizeof(aJFlood)); + e->cptr = cptr; + e->chptr = chptr; + e->prev_u = e->prev_c = NULL; + e->next_u = cptr->user->jflood; + e->next_c = chptr->jflood; + cptr->user->jflood = chptr->jflood = e; + + return e; +} + +/** Removes an individual entry from list and frees it. + */ +void cmodej_delentry(aJFlood *e) +{ + /* remove from user.. */ + if (e->prev_u) + e->prev_u->next_u = e->next_u; + else + e->cptr->user->jflood = e->next_u; /* new head */ + if (e->next_u) + e->next_u->prev_u = e->prev_u; + + /* remove from channel.. */ + if (e->prev_c) + e->prev_c->next_c = e->next_c; + else + e->chptr->jflood = e->next_c; /* new head */ + if (e->next_c) + e->next_c->prev_c = e->prev_c; + + /* actually free it */ + MyFree(e); +} + +/** Removes all entries belonging to user from all lists and free them. */ +void cmodej_deluserentries(aClient *cptr) +{ +aJFlood *e, *e_next; + + for (e=cptr->user->jflood; e; e=e_next) + { + e_next = e->next_u; + + /* remove from channel.. */ + if (e->prev_c) + e->prev_c->next_c = e->next_c; + else + e->chptr->jflood = e->next_c; /* new head */ + if (e->next_c) + e->next_c->prev_c = e->prev_c; + + /* actually free it */ + MyFree(e); + } +} + +/** Removes all entries belonging to channel from all lists and free them. */ +void cmodej_delchannelentries(aChannel *chptr) +{ +aJFlood *e, *e_next; + + for (e=chptr->jflood; e; e=e_next) + { + e_next = e->next_c; + /* remove from user.. */ + if (e->prev_u) + e->prev_u->next_u = e->next_u; + else + e->cptr->user->jflood = e->next_u; /* new head */ + if (e->next_u) + e->next_u->prev_u = e->prev_u; + + /* actually free it */ + MyFree(e); + } +} + +/** Regulary cleans up cmode-j user/chan structs */ +EVENT(cmodej_cleanup_structs) +{ +aJFlood *e, *e_next; +int i; +aClient *cptr; +aChannel *chptr; +int t; +CmodeParam *cmp; +#ifdef DEBUGMODE +int freed=0; +#endif + + for (chptr = channel; chptr; chptr=chptr->nextch) + { + if (!chptr->jflood) + continue; + t=0; + /* t will be kept at 0 if not found or if mode not set, + * but DO still check since there are entries left as indicated by ->jflood! + */ + if (chptr->mode.extmode & EXTMODE_JOINTHROTTLE) + { + for (cmp = chptr->mode.extmodeparam; cmp; cmp=cmp->next) + if (cmp->flag == 'j') + t = ((aModejEntry *)cmp)->t; + } + + for (e = chptr->jflood; e; e = e_next) + { + e_next = e->next_c; + + if (e->firstjoin + t < TStime()) + { + cmodej_delentry(e); +#ifdef DEBUGMODE + freed++; +#endif + } + } + } + +#ifdef DEBUGMODE + if (freed) + ircd_log(LOG_ERROR, "cmodej_cleanup_structs: %d entries freed [%d bytes]", freed, freed * sizeof(aJFlood)); +#endif +} + +#endif diff --git a/src/modules/m_join.c b/src/modules/m_join.c index f040a4ec6..4eb3eca79 100644 --- a/src/modules/m_join.c +++ b/src/modules/m_join.c @@ -51,6 +51,10 @@ DLLFUNC CMD_FUNC(_do_join); DLLFUNC int _can_join(aClient *cptr, aClient *sptr, aChannel *chptr, char *key, char *link, char *parv[]); static int extended_operoverride(aClient *sptr, aChannel *chptr, char *key, int mval, char mchar); #define MAXBOUNCE 5 /** Most sensible */ +#ifdef JOINTHROTTLE +static int isjthrottled(aClient *cptr, aChannel *chptr); +static void cmodej_increase_usercounter(aClient *cptr, aChannel *chptr); +#endif /* Externs */ extern MODVAR int spamf_ugly_vchanoverride; @@ -220,9 +224,97 @@ Ban *banned; #endif #endif +#ifdef JOINTHROTTLE + if (!IsAnOper(cptr) && + (chptr->mode.extmode & EXTMODE_JOINTHROTTLE) && isjthrottled(cptr, chptr)) + return ERR_TOOMANYJOINS; +#endif + return 0; } +#ifdef JOINTHROTTLE +static int isjthrottled(aClient *cptr, aChannel *chptr) +{ +CmodeParam *m; +aJFlood *e; +int num=0, t=0; + + if (!MyClient(cptr)) + return 0; + + for (m = chptr->mode.extmodeparam; m; m=m->next) + if (m->flag == 'j') + { + num = ((aModejEntry *)m)->num; + t = ((aModejEntry *)m)->t; + break; + } + + if (!num || !t) + return 0; + + /* Grab user<->chan entry.. */ + for (e = cptr->user->jflood; e; e=e->next_u) + if (e->chptr == chptr) + 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) < t) && (e->numjoins == num)) + return 1; /* Throttled */ + + return 0; +} + +static void cmodej_increase_usercounter(aClient *cptr, aChannel *chptr) +{ +CmodeParam *m; +aJFlood *e; +int num=0, t=0; + + if (!MyClient(cptr)) + return; + + for (m = chptr->mode.extmodeparam; m; m=m->next) + if (m->flag == 'j') + { + num = ((aModejEntry *)m)->num; + t = ((aModejEntry *)m)->t; + break; + } + + if (!num || !t) + return; + + /* Grab user<->chan entry.. */ + for (e = cptr->user->jflood; e; e=e->next_u) + if (e->chptr == chptr) + break; + + if (!e) + { + /* Allocate one */ + e = cmodej_addentry(cptr, chptr); + e->firstjoin = TStime(); + e->numjoins = 1; + } else + if ((TStime() - e->firstjoin) < t) /* still valid? */ + { + e->numjoins++; + } else { + /* reset :p */ + e->firstjoin = TStime(); + e->numjoins = 1; + } +} + +#endif + /* ** m_join ** parv[0] = sender prefix @@ -617,6 +709,9 @@ DLLFUNC CMD_FUNC(_do_join) me.name, parv[0], name); continue; } +#ifdef JOINTHROTTLE + cmodej_increase_usercounter(cptr, chptr); +#endif } join_channel(chptr, cptr, sptr, flags); diff --git a/src/s_err.c b/src/s_err.c index 61899023d..626cd2275 100644 --- a/src/s_err.c +++ b/src/s_err.c @@ -556,7 +556,7 @@ static char *replies[] = { /* 497 */ NULL, /* ircu */ /* 498 */ NULL, /* ircu */ /* 499 ERR_CHANOWNPRIVNEEDED */ ":%s 499 %s %s :You're not a channel owner", -/* 500 */ NULL, +/* 500 ERR_TOOMANYJOINS */ ":%s 500 %s %s :Too many join requests. Please wait a while and try again.", /* 501 ERR_UMODEUNKNOWNFLAG */ ":%s 501 %s :Unknown MODE flag", /* 502 ERR_USERSDONTMATCH */ ":%s 502 %s :Cant change mode for other users", /* 503 */ NULL, /* austhex */ diff --git a/src/s_misc.c b/src/s_misc.c index ebf6be008..17d12fec6 100644 --- a/src/s_misc.c +++ b/src/s_misc.c @@ -727,6 +727,9 @@ static void exit_one_client(aClient *cptr, aClient *sptr, aClient *from, char *c { RunHook2(HOOKTYPE_REMOTE_QUIT, sptr, comment); } +#ifdef JOINTHROTTLE + cmodej_deluserentries(sptr); +#endif while ((mp = sptr->user->channel)) remove_user_from_channel(sptr, mp->chptr);