unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

timedban.c (14397B)

      1 /*
      2  * timedban - Timed bans that are automatically unset.
      3  * (C) Copyright 2009-2017 Bram Matthys (Syzop) and the UnrealIRCd team.
      4  * License: GPLv2 or later
      5  *
      6  * This module adds an extended ban ~t:time:mask
      7  * Where 'time' is the time in minutes after which the ban will be removed.
      8  * Where 'mask' is any banmask that is normally valid.
      9  *
     10  * Note that this extended ban is rather special in the sense that
     11  * it permits (crazy) triple-extbans to be set, such as:
     12  * +b ~t:1:~q:~a:Account
     13  * (=a temporary 1min ban to mute a user with services account Account)
     14  * +e ~t:1440:~m:moderated:*!*@host
     15  * (=user with *!*@host may speak through +m for the next 1440m / 24h)
     16  *
     17  * The triple-extbans / double-stacking requires special routines that
     18  * are based on parts of the core and special recursion checks.
     19  * If you are looking for inspiration of coding your own extended ban
     20  * then look at another extended ban * module as this module is not a
     21  * good starting point ;)
     22  */
     23    
     24 #include "unrealircd.h"
     25 
     26 /* Maximum time (in minutes) for a ban */
     27 #define TIMEDBAN_MAX_TIME	9999
     28 
     29 /* Maximum length of a ban */
     30 #define MAX_LENGTH 128
     31 
     32 /* Split timeout event in <this> amount of iterations */
     33 #define TIMEDBAN_TIMER_ITERATION_SPLIT 4
     34 
     35 /* Call timeout event every <this> seconds.
     36  * NOTE: until all channels are processed it takes
     37  *       TIMEDBAN_TIMER_ITERATION_SPLIT * TIMEDBAN_TIMER.
     38  */
     39 #define TIMEDBAN_TIMER	2
     40 
     41 /* We allow a ban to (potentially) expire slightly before the deadline.
     42  * For example with TIMEDBAN_TIMER_ITERATION_SPLIT=4 and TIMEDBAN_TIMER=2
     43  * a 1 minute ban would expire at 56-63 seconds, rather than 60-67 seconds.
     44  * This is usually preferred.
     45  */
     46 #define TIMEDBAN_TIMER_DELTA ((TIMEDBAN_TIMER_ITERATION_SPLIT*TIMEDBAN_TIMER)/2)
     47 
     48 ModuleHeader MOD_HEADER
     49   = {
     50 	"extbans/timedban",
     51 	"1.0",
     52 	"ExtBan ~t: automatically removed timed bans",
     53 	"UnrealIRCd Team",
     54 	"unrealircd-6",
     55     };
     56 
     57 /* Forward declarations */
     58 const char *timedban_extban_conv_param(BanContext *b, Extban *extban);
     59 int timedban_extban_is_ok(BanContext *b);
     60 int timedban_is_banned(BanContext *b);
     61 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param);
     62 char *timedban_chanmsg(Client *, Client *, Channel *, char *, int);
     63 
     64 EVENT(timedban_timeout);
     65 
     66 MOD_TEST()
     67 {
     68 	return MOD_SUCCESS;
     69 }
     70 
     71 MOD_INIT()
     72 {
     73 	ExtbanInfo extban;
     74 
     75 	MARK_AS_OFFICIAL_MODULE(modinfo);
     76 
     77 	memset(&extban, 0, sizeof(ExtbanInfo));
     78 	extban.letter = 't';
     79 	extban.name = "time";
     80 	extban.options |= EXTBOPT_ACTMODIFIER; /* not really, but ours shouldn't be stacked from group 1 */
     81 	extban.options |= EXTBOPT_INVEX; /* also permit timed invite-only exceptions (+I) */
     82 	extban.conv_param = timedban_extban_conv_param;
     83 	extban.is_ok = timedban_extban_is_ok;
     84 	extban.is_banned = timedban_is_banned;
     85 	extban.is_banned_events = BANCHK_ALL;
     86 
     87 	if (!ExtbanAdd(modinfo->handle, extban))
     88 	{
     89 		config_error("timedban: unable to register 't' extban type!!");
     90 		return MOD_FAILED;
     91 	}
     92                 
     93 	EventAdd(modinfo->handle, "timedban_timeout", timedban_timeout, NULL, TIMEDBAN_TIMER*1000, 0);
     94 
     95 	return MOD_SUCCESS;
     96 }
     97 
     98 MOD_LOAD()
     99 {
    100 	return MOD_SUCCESS;
    101 }
    102 
    103 MOD_UNLOAD()
    104 {
    105 	return MOD_SUCCESS;
    106 }
    107 
    108 /** Generic helper for our conv_param extban function.
    109  * Mostly copied from clean_ban_mask()
    110  * FIXME: Figure out why we have this one at all and not use conv_param? ;)
    111  */
    112 const char *generic_clean_ban_mask(BanContext *b, Extban *extban)
    113 {
    114 	char *cp, *x;
    115 	static char maskbuf[512];
    116 	char *mask;
    117 
    118 	/* Work on a copy */
    119 	strlcpy(maskbuf, b->banstr, sizeof(maskbuf));
    120 	mask = maskbuf;
    121 
    122 	cp = strchr(mask, ' ');
    123 	if (cp)
    124 		*cp = '\0';
    125 
    126 	/* Strip any ':' at beginning since that would cause a desync */
    127 	for (; (*mask && (*mask == ':')); mask++);
    128 	if (!*mask)
    129 		return NULL;
    130 
    131 	/* Forbid ASCII <= 32 in all bans */
    132 	for (x = mask; *x; x++)
    133 		if (*x <= ' ')
    134 			return NULL;
    135 
    136 	/* Extended ban? */
    137 	if (is_extended_ban(mask))
    138 	{
    139 		const char *nextbanstr;
    140 		Extban *extban = findmod_by_bantype(mask, &nextbanstr);
    141 		if (!extban)
    142 			return NULL; /* reject unknown extban */
    143 		if (extban->conv_param)
    144 		{
    145 			const char *ret;
    146 			static char retbuf[512];
    147 			BanContext *newb = safe_alloc(sizeof(BanContext));
    148 			newb->banstr = nextbanstr;
    149 			newb->conv_options = b->conv_options;
    150 			ret = extban->conv_param(newb, extban);
    151 			ret = prefix_with_extban(ret, newb, extban, retbuf, sizeof(retbuf));
    152 			safe_free(newb);
    153 			return ret;
    154 		}
    155 		/* else, do some basic sanity checks and cut it off at 80 bytes */
    156 		if ((mask[1] != ':') || (mask[2] == '\0'))
    157 		    return NULL; /* require a ":<char>" after extban type */
    158 		if (strlen(mask) > 80)
    159 			mask[80] = '\0';
    160 		return mask;
    161 	}
    162 
    163 	return convert_regular_ban(mask, NULL, 0);
    164 }
    165 
    166 /** Convert ban to an acceptable format (or return NULL to fully reject it) */
    167 const char *timedban_extban_conv_param(BanContext *b, Extban *extban)
    168 {
    169 	static char retbuf[MAX_LENGTH+1];
    170 	char para[MAX_LENGTH+1];
    171 	char tmpmask[MAX_LENGTH+1];
    172 	char *durationstr; /**< Duration, such as '5' */
    173 	int duration;
    174 	char *matchby; /**< Matching method, such as 'n!u@h' */
    175 	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
    176 	static int timedban_extban_conv_param_recursion = 0;
    177 	
    178 	if (timedban_extban_conv_param_recursion)
    179 		return NULL; /* reject: recursion detected! */
    180 
    181 	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
    182 	
    183 	/* ~t:duration:n!u@h   for direct matching
    184 	 * ~t:duration:~x:.... when calling another bantype
    185 	 */
    186 
    187 	durationstr = para;
    188 	matchby = strchr(para, ':');
    189 	if (!matchby || !matchby[1])
    190 		return NULL;
    191 	*matchby++ = '\0';
    192 	
    193 	duration = atoi(durationstr);
    194 
    195 	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
    196 		return NULL;
    197 
    198 	strlcpy(tmpmask, matchby, sizeof(tmpmask));
    199 	timedban_extban_conv_param_recursion++;
    200 	//newmask = extban_conv_param_nuh_or_extban(tmpmask);
    201 	b->banstr = matchby; // this was previously 'tmpmask' but then it's a copy-copy-copy.. :D
    202 	newmask = generic_clean_ban_mask(b, extban);
    203 	timedban_extban_conv_param_recursion--;
    204 	if (!newmask || (strlen(newmask) <= 1))
    205 		return NULL;
    206 
    207 	//snprintf(retbuf, sizeof(retbuf), "~t:%d:%s", duration, newmask);
    208 	snprintf(retbuf, sizeof(retbuf), "%d:%s", duration, newmask);
    209 	return retbuf;
    210 }
    211 
    212 int timedban_extban_syntax(Client *client, int checkt, char *reason)
    213 {
    214 	if (MyUser(client) && (checkt == EXBCHK_PARAM))
    215 	{
    216 		sendnotice(client, "Error when setting timed ban: %s", reason);
    217 		sendnotice(client, " Syntax: +b ~t:duration:mask");
    218 		sendnotice(client, "Example: +b ~t:5:nick!user@host");
    219 		sendnotice(client, "Duration is the time in minutes after which the ban is removed (1-9999)");
    220 		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
    221 	}
    222 	return 0; /* FAIL: ban rejected */
    223 }
    224 
    225 /** Generic helper for sub-bans, used by our "is this ban ok?" function */
    226 int generic_ban_is_ok(BanContext *b)
    227 {
    228 	if ((b->banstr[0] == '~') && MyUser(b->client))
    229 	{
    230 		Extban *extban;
    231 		const char *nextbanstr;
    232 
    233 		/* This portion is copied from clean_ban_mask() */
    234 		if (is_extended_ban(b->banstr) && MyUser(b->client))
    235 		{
    236 			if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL))
    237 			{
    238 				if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
    239 				{
    240 					if (b->is_ok_check == EXBCHK_ACCESS_ERR)
    241 						sendnotice(b->client, "Setting/removing of extended bans has been disabled");
    242 					return 0; /* REJECT */
    243 				}
    244 				if (strchr(RESTRICT_EXTENDEDBANS, b->banstr[1]))
    245 				{
    246 					if (b->is_ok_check == EXBCHK_ACCESS_ERR)
    247 						sendnotice(b->client, "Setting/removing of extended bantypes '%s' has been disabled", RESTRICT_EXTENDEDBANS);
    248 					return 0; /* REJECT */
    249 				}
    250 			}
    251 			/* And next is inspired by cmd_mode */
    252 			extban = findmod_by_bantype(b->banstr, &nextbanstr);
    253 			if (extban && extban->is_ok)
    254 			{
    255 				b->banstr = nextbanstr;
    256 				if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR))
    257 				{
    258 					if (!extban->is_ok(b) &&
    259 					    !ValidatePermissionsForPath("channel:override:mode:extban",b->client,NULL,b->channel,NULL))
    260 					{
    261 						return 0; /* REJECT */
    262 					}
    263 				} else
    264 				if (b->is_ok_check == EXBCHK_PARAM)
    265 				{
    266 					if (!extban->is_ok(b))
    267 					{
    268 						return 0; /* REJECT */
    269 					}
    270 				}
    271 			}
    272 		}
    273 	}
    274 	
    275 	/* ACCEPT:
    276 	 * - not an extban; OR
    277 	 * - extban with NULL is_ok; OR
    278 	 * - non-existing extban character (handled by conv_param?)
    279 	 */
    280 	return 1;
    281 }
    282 
    283 /** Validate ban ("is this ban ok?") */
    284 int timedban_extban_is_ok(BanContext *b)
    285 {
    286 	char para[MAX_LENGTH+1];
    287 	char tmpmask[MAX_LENGTH+1];
    288 	char *durationstr; /**< Duration, such as '5' */
    289 	int duration;
    290 	char *matchby; /**< Matching method, such as 'n!u@h' */
    291 	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
    292 	static int timedban_extban_is_ok_recursion = 0;
    293 	int res;
    294 
    295 	/* Always permit deletion */
    296 	if (b->what == MODE_DEL)
    297 		return 1;
    298 
    299 	if (timedban_extban_is_ok_recursion)
    300 		return 0; /* Recursion detected (~t:1:~t:....) */
    301 
    302 	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
    303 	
    304 	/* ~t:duration:n!u@h   for direct matching
    305 	 * ~t:duration:~x:.... when calling another bantype
    306 	 */
    307 
    308 	durationstr = para;
    309 	matchby = strchr(para, ':');
    310 	if (!matchby || !matchby[1])
    311 		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
    312 	*matchby++ = '\0';
    313 
    314 	duration = atoi(durationstr);
    315 
    316 	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
    317 		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid duration time");
    318 
    319 	strlcpy(tmpmask, matchby, sizeof(tmpmask));
    320 	timedban_extban_is_ok_recursion++;
    321 	//res = extban_is_ok_nuh_extban(b->client, b->channel, tmpmask, b->is_ok_check, b->what, b->ban_type);
    322 	b->banstr = tmpmask;
    323 	res = generic_ban_is_ok(b);
    324 	timedban_extban_is_ok_recursion--;
    325 	if (res == 0)
    326 	{
    327 		/* This could be anything ranging from:
    328 		 * invalid n!u@h syntax, unknown (sub)extbantype,
    329 		 * disabled extban type in conf, too much recursion, etc.
    330 		 */
    331 		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
    332 	}
    333 
    334 	return 1; /* OK */
    335 }
    336 
    337 /** Check if the user is currently banned */
    338 int timedban_is_banned(BanContext *b)
    339 {
    340 	b->banstr = strchr(b->banstr, ':'); /* skip time argument */
    341 	if (!b->banstr)
    342 		return 0; /* invalid fmt */
    343 	b->banstr++; /* skip over final semicolon */
    344 
    345 	return ban_check_mask(b);
    346 }
    347 
    348 /** Helper to check if the ban has been expired.
    349  */
    350 int timedban_has_ban_expired(Ban *ban)
    351 {
    352 	char *banstr = ban->banstr;
    353 	char *p1, *p2;
    354 	int t;
    355 	time_t expire_on;
    356 
    357 	/* The caller has only performed a very light check (string starting
    358 	 * with ~t, in the interest of performance), so we don't know yet if
    359 	 * it REALLY is a timed ban. We check that first here...
    360 	 */
    361 	if (!strncmp(banstr, "~t:", 3))
    362 		p1 = banstr + 3;
    363 	else if (!strncmp(banstr, "~time:", 6))
    364 		p1 = banstr + 6;
    365 	else
    366 		return 0; /* not for us */
    367 	p2 = strchr(p1+1, ':'); /* skip time argument */
    368 	if (!p2)
    369 		return 0; /* invalid fmt */
    370 	*p2 = '\0'; /* danger.. must restore!! */
    371 	t = atoi(p1);
    372 	*p2 = ':'; /* restored.. */
    373 	
    374 	expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA;
    375 	
    376 	if (expire_on < TStime())
    377 		return 1;
    378 	return 0;
    379 }
    380 
    381 static char mbuf[512];
    382 static char pbuf[512];
    383 
    384 /** This removes any expired timedbans */
    385 EVENT(timedban_timeout)
    386 {
    387 	Channel *channel;
    388 	Ban *ban, *nextban;
    389 	static int current_iteration = 0;
    390 
    391 	if (++current_iteration >= TIMEDBAN_TIMER_ITERATION_SPLIT)
    392 		current_iteration = 0;
    393 
    394 	for (channel = channels; channel; channel = channel->nextch)
    395 	{
    396 		/* This is a very quick check, at the cost of it being
    397 		 * biased since there's always a tendency of more channel
    398 		 * names to start with one specific letter. But hashing
    399 		 * is too costly. So we stick with this. It should be
    400 		 * good enough. Alternative would be some channel->id value.
    401 		 */
    402 		if (((unsigned int)channel->name[1] % TIMEDBAN_TIMER_ITERATION_SPLIT) != current_iteration)
    403 			continue; /* not this time, maybe next */
    404 
    405 		*mbuf = *pbuf = '\0';
    406 		for (ban = channel->banlist; ban; ban=nextban)
    407 		{
    408 			nextban = ban->next;
    409 			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
    410 			{
    411 				add_send_mode_param(channel, &me, '-',  'b', ban->banstr);
    412 				del_listmode(&channel->banlist, channel, ban->banstr);
    413 			}
    414 		}
    415 		for (ban = channel->exlist; ban; ban=nextban)
    416 		{
    417 			nextban = ban->next;
    418 			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
    419 			{
    420 				add_send_mode_param(channel, &me, '-',  'e', ban->banstr);
    421 				del_listmode(&channel->exlist, channel, ban->banstr);
    422 			}
    423 		}
    424 		for (ban = channel->invexlist; ban; ban=nextban)
    425 		{
    426 			nextban = ban->next;
    427 			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
    428 			{
    429 				add_send_mode_param(channel, &me, '-',  'I', ban->banstr);
    430 				del_listmode(&channel->invexlist, channel, ban->banstr);
    431 			}
    432 		}
    433 		if (*pbuf)
    434 		{
    435 			MessageTag *mtags = NULL;
    436 			new_message(&me, NULL, &mtags);
    437 			sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf);
    438 			sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf);
    439 			free_message_tags(mtags);
    440 			*pbuf = 0;
    441 		}
    442 	}
    443 }
    444 
    445 #if MODEBUFLEN > 512
    446  #error "add_send_mode_param() is not made for MODEBUFLEN > 512"
    447 #endif
    448 
    449 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param) {
    450 	static char *modes = NULL, lastwhat;
    451 	static short count = 0;
    452 	short send = 0;
    453 	
    454 	if (!modes) modes = mbuf;
    455 	
    456 	if (!mbuf[0]) {
    457 		modes = mbuf;
    458 		*modes++ = what;
    459 		*modes = 0;
    460 		lastwhat = what;
    461 		*pbuf = 0;
    462 		count = 0;
    463 	}
    464 	if (lastwhat != what) {
    465 		*modes++ = what;
    466 		*modes = 0;
    467 		lastwhat = what;
    468 	}
    469 	if (strlen(pbuf) + strlen(param) + 11 < MODEBUFLEN) {
    470 		if (*pbuf) 
    471 			strcat(pbuf, " ");
    472 		strcat(pbuf, param);
    473 		*modes++ = mode;
    474 		*modes = 0;
    475 		count++;
    476 	}
    477 	else if (*pbuf) 
    478 		send = 1;
    479 
    480 	if (count == MAXMODEPARAMS)
    481 		send = 1;
    482 
    483 	if (send)
    484 	{
    485 		MessageTag *mtags = NULL;
    486 
    487 		new_message(&me, NULL, &mtags);
    488 		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf);
    489 		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf);
    490 		free_message_tags(mtags);
    491 		send = 0;
    492 		*pbuf = 0;
    493 		modes = mbuf;
    494 		*modes++ = what;
    495 		lastwhat = what;
    496 		if (count != MAXMODEPARAMS)
    497 		{
    498 			strlcpy(pbuf, param, sizeof(pbuf));
    499 			*modes++ = mode;
    500 			count = 1;
    501 		} else {
    502 			count = 0;
    503 		}
    504 		*modes = 0;
    505 	}
    506 }