unrealircd

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

timedban.c (14490B)

      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->ban_type = b->ban_type;
    150 			newb->conv_options = b->conv_options;
    151 			newb->client = b->client;
    152 			newb->channel = b->channel;
    153 			ret = extban->conv_param(newb, extban);
    154 			ret = prefix_with_extban(ret, newb, extban, retbuf, sizeof(retbuf));
    155 			safe_free(newb);
    156 			return ret;
    157 		}
    158 		/* else, do some basic sanity checks and cut it off at 80 bytes */
    159 		if ((mask[1] != ':') || (mask[2] == '\0'))
    160 		    return NULL; /* require a ":<char>" after extban type */
    161 		if (strlen(mask) > 80)
    162 			mask[80] = '\0';
    163 		return mask;
    164 	}
    165 
    166 	return convert_regular_ban(mask, NULL, 0);
    167 }
    168 
    169 /** Convert ban to an acceptable format (or return NULL to fully reject it) */
    170 const char *timedban_extban_conv_param(BanContext *b, Extban *extban)
    171 {
    172 	static char retbuf[MAX_LENGTH+1];
    173 	char para[MAX_LENGTH+1];
    174 	char tmpmask[MAX_LENGTH+1];
    175 	char *durationstr; /**< Duration, such as '5' */
    176 	int duration;
    177 	char *matchby; /**< Matching method, such as 'n!u@h' */
    178 	const char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
    179 	static int timedban_extban_conv_param_recursion = 0;
    180 	
    181 	if (timedban_extban_conv_param_recursion)
    182 		return NULL; /* reject: recursion detected! */
    183 
    184 	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
    185 	
    186 	/* ~t:duration:n!u@h   for direct matching
    187 	 * ~t:duration:~x:.... when calling another bantype
    188 	 */
    189 
    190 	durationstr = para;
    191 	matchby = strchr(para, ':');
    192 	if (!matchby || !matchby[1])
    193 		return NULL;
    194 	*matchby++ = '\0';
    195 	
    196 	duration = atoi(durationstr);
    197 
    198 	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
    199 		return NULL;
    200 
    201 	strlcpy(tmpmask, matchby, sizeof(tmpmask));
    202 	timedban_extban_conv_param_recursion++;
    203 	//newmask = extban_conv_param_nuh_or_extban(tmpmask);
    204 	b->banstr = matchby; // this was previously 'tmpmask' but then it's a copy-copy-copy.. :D
    205 	newmask = generic_clean_ban_mask(b, extban);
    206 	timedban_extban_conv_param_recursion--;
    207 	if (!newmask || (strlen(newmask) <= 1))
    208 		return NULL;
    209 
    210 	//snprintf(retbuf, sizeof(retbuf), "~t:%d:%s", duration, newmask);
    211 	snprintf(retbuf, sizeof(retbuf), "%d:%s", duration, newmask);
    212 	return retbuf;
    213 }
    214 
    215 int timedban_extban_syntax(Client *client, int checkt, char *reason)
    216 {
    217 	if (MyUser(client) && (checkt == EXBCHK_PARAM))
    218 	{
    219 		sendnotice(client, "Error when setting timed ban: %s", reason);
    220 		sendnotice(client, " Syntax: +b ~t:duration:mask");
    221 		sendnotice(client, "Example: +b ~t:5:nick!user@host");
    222 		sendnotice(client, "Duration is the time in minutes after which the ban is removed (1-9999)");
    223 		sendnotice(client, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
    224 	}
    225 	return 0; /* FAIL: ban rejected */
    226 }
    227 
    228 /** Generic helper for sub-bans, used by our "is this ban ok?" function */
    229 int generic_ban_is_ok(BanContext *b)
    230 {
    231 	if ((b->banstr[0] == '~') && MyUser(b->client))
    232 	{
    233 		Extban *extban;
    234 		const char *nextbanstr;
    235 
    236 		/* This portion is copied from clean_ban_mask() */
    237 		if (is_extended_ban(b->banstr) && MyUser(b->client))
    238 		{
    239 			if (RESTRICT_EXTENDEDBANS && !ValidatePermissionsForPath("immune:restrict-extendedbans",b->client,NULL,NULL,NULL))
    240 			{
    241 				if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
    242 				{
    243 					if (b->is_ok_check == EXBCHK_ACCESS_ERR)
    244 						sendnotice(b->client, "Setting/removing of extended bans has been disabled");
    245 					return 0; /* REJECT */
    246 				}
    247 				if (strchr(RESTRICT_EXTENDEDBANS, b->banstr[1]))
    248 				{
    249 					if (b->is_ok_check == EXBCHK_ACCESS_ERR)
    250 						sendnotice(b->client, "Setting/removing of extended bantypes '%s' has been disabled", RESTRICT_EXTENDEDBANS);
    251 					return 0; /* REJECT */
    252 				}
    253 			}
    254 			/* And next is inspired by cmd_mode */
    255 			extban = findmod_by_bantype(b->banstr, &nextbanstr);
    256 			if (extban && extban->is_ok)
    257 			{
    258 				b->banstr = nextbanstr;
    259 				if ((b->is_ok_check == EXBCHK_ACCESS) || (b->is_ok_check == EXBCHK_ACCESS_ERR))
    260 				{
    261 					if (!extban->is_ok(b) &&
    262 					    !ValidatePermissionsForPath("channel:override:mode:extban",b->client,NULL,b->channel,NULL))
    263 					{
    264 						return 0; /* REJECT */
    265 					}
    266 				} else
    267 				if (b->is_ok_check == EXBCHK_PARAM)
    268 				{
    269 					if (!extban->is_ok(b))
    270 					{
    271 						return 0; /* REJECT */
    272 					}
    273 				}
    274 			}
    275 		}
    276 	}
    277 	
    278 	/* ACCEPT:
    279 	 * - not an extban; OR
    280 	 * - extban with NULL is_ok; OR
    281 	 * - non-existing extban character (handled by conv_param?)
    282 	 */
    283 	return 1;
    284 }
    285 
    286 /** Validate ban ("is this ban ok?") */
    287 int timedban_extban_is_ok(BanContext *b)
    288 {
    289 	char para[MAX_LENGTH+1];
    290 	char tmpmask[MAX_LENGTH+1];
    291 	char *durationstr; /**< Duration, such as '5' */
    292 	int duration;
    293 	char *matchby; /**< Matching method, such as 'n!u@h' */
    294 	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */
    295 	static int timedban_extban_is_ok_recursion = 0;
    296 	int res;
    297 
    298 	/* Always permit deletion */
    299 	if (b->what == MODE_DEL)
    300 		return 1;
    301 
    302 	if (timedban_extban_is_ok_recursion)
    303 		return 0; /* Recursion detected (~t:1:~t:....) */
    304 
    305 	strlcpy(para, b->banstr, sizeof(para)); /* work on a copy (and truncate it) */
    306 	
    307 	/* ~t:duration:n!u@h   for direct matching
    308 	 * ~t:duration:~x:.... when calling another bantype
    309 	 */
    310 
    311 	durationstr = para;
    312 	matchby = strchr(para, ':');
    313 	if (!matchby || !matchby[1])
    314 		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid syntax");
    315 	*matchby++ = '\0';
    316 
    317 	duration = atoi(durationstr);
    318 
    319 	if ((duration <= 0) || (duration > TIMEDBAN_MAX_TIME))
    320 		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid duration time");
    321 
    322 	strlcpy(tmpmask, matchby, sizeof(tmpmask));
    323 	timedban_extban_is_ok_recursion++;
    324 	//res = extban_is_ok_nuh_extban(b->client, b->channel, tmpmask, b->is_ok_check, b->what, b->ban_type);
    325 	b->banstr = tmpmask;
    326 	res = generic_ban_is_ok(b);
    327 	timedban_extban_is_ok_recursion--;
    328 	if (res == 0)
    329 	{
    330 		/* This could be anything ranging from:
    331 		 * invalid n!u@h syntax, unknown (sub)extbantype,
    332 		 * disabled extban type in conf, too much recursion, etc.
    333 		 */
    334 		return timedban_extban_syntax(b->client, b->is_ok_check, "Invalid matcher");
    335 	}
    336 
    337 	return 1; /* OK */
    338 }
    339 
    340 /** Check if the user is currently banned */
    341 int timedban_is_banned(BanContext *b)
    342 {
    343 	b->banstr = strchr(b->banstr, ':'); /* skip time argument */
    344 	if (!b->banstr)
    345 		return 0; /* invalid fmt */
    346 	b->banstr++; /* skip over final semicolon */
    347 
    348 	return ban_check_mask(b);
    349 }
    350 
    351 /** Helper to check if the ban has been expired.
    352  */
    353 int timedban_has_ban_expired(Ban *ban)
    354 {
    355 	char *banstr = ban->banstr;
    356 	char *p1, *p2;
    357 	int t;
    358 	time_t expire_on;
    359 
    360 	/* The caller has only performed a very light check (string starting
    361 	 * with ~t, in the interest of performance), so we don't know yet if
    362 	 * it REALLY is a timed ban. We check that first here...
    363 	 */
    364 	if (!strncmp(banstr, "~t:", 3))
    365 		p1 = banstr + 3;
    366 	else if (!strncmp(banstr, "~time:", 6))
    367 		p1 = banstr + 6;
    368 	else
    369 		return 0; /* not for us */
    370 	p2 = strchr(p1+1, ':'); /* skip time argument */
    371 	if (!p2)
    372 		return 0; /* invalid fmt */
    373 	*p2 = '\0'; /* danger.. must restore!! */
    374 	t = atoi(p1);
    375 	*p2 = ':'; /* restored.. */
    376 	
    377 	expire_on = ban->when + (t * 60) - TIMEDBAN_TIMER_DELTA;
    378 	
    379 	if (expire_on < TStime())
    380 		return 1;
    381 	return 0;
    382 }
    383 
    384 static char mbuf[512];
    385 static char pbuf[512];
    386 
    387 /** This removes any expired timedbans */
    388 EVENT(timedban_timeout)
    389 {
    390 	Channel *channel;
    391 	Ban *ban, *nextban;
    392 	static int current_iteration = 0;
    393 
    394 	if (++current_iteration >= TIMEDBAN_TIMER_ITERATION_SPLIT)
    395 		current_iteration = 0;
    396 
    397 	for (channel = channels; channel; channel = channel->nextch)
    398 	{
    399 		/* This is a very quick check, at the cost of it being
    400 		 * biased since there's always a tendency of more channel
    401 		 * names to start with one specific letter. But hashing
    402 		 * is too costly. So we stick with this. It should be
    403 		 * good enough. Alternative would be some channel->id value.
    404 		 */
    405 		if (((unsigned int)channel->name[1] % TIMEDBAN_TIMER_ITERATION_SPLIT) != current_iteration)
    406 			continue; /* not this time, maybe next */
    407 
    408 		*mbuf = *pbuf = '\0';
    409 		for (ban = channel->banlist; ban; ban=nextban)
    410 		{
    411 			nextban = ban->next;
    412 			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
    413 			{
    414 				add_send_mode_param(channel, &me, '-',  'b', ban->banstr);
    415 				del_listmode(&channel->banlist, channel, ban->banstr);
    416 			}
    417 		}
    418 		for (ban = channel->exlist; ban; ban=nextban)
    419 		{
    420 			nextban = ban->next;
    421 			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
    422 			{
    423 				add_send_mode_param(channel, &me, '-',  'e', ban->banstr);
    424 				del_listmode(&channel->exlist, channel, ban->banstr);
    425 			}
    426 		}
    427 		for (ban = channel->invexlist; ban; ban=nextban)
    428 		{
    429 			nextban = ban->next;
    430 			if (!strncmp(ban->banstr, "~t", 2) && timedban_has_ban_expired(ban))
    431 			{
    432 				add_send_mode_param(channel, &me, '-',  'I', ban->banstr);
    433 				del_listmode(&channel->invexlist, channel, ban->banstr);
    434 			}
    435 		}
    436 		if (*pbuf)
    437 		{
    438 			MessageTag *mtags = NULL;
    439 			new_message(&me, NULL, &mtags);
    440 			sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf);
    441 			sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf);
    442 			free_message_tags(mtags);
    443 			*pbuf = 0;
    444 		}
    445 	}
    446 }
    447 
    448 #if MODEBUFLEN > 512
    449  #error "add_send_mode_param() is not made for MODEBUFLEN > 512"
    450 #endif
    451 
    452 void add_send_mode_param(Channel *channel, Client *from, char what, char mode, char *param) {
    453 	static char *modes = NULL, lastwhat;
    454 	static short count = 0;
    455 	short send = 0;
    456 	
    457 	if (!modes) modes = mbuf;
    458 	
    459 	if (!mbuf[0]) {
    460 		modes = mbuf;
    461 		*modes++ = what;
    462 		*modes = 0;
    463 		lastwhat = what;
    464 		*pbuf = 0;
    465 		count = 0;
    466 	}
    467 	if (lastwhat != what) {
    468 		*modes++ = what;
    469 		*modes = 0;
    470 		lastwhat = what;
    471 	}
    472 	if (strlen(pbuf) + strlen(param) + 11 < MODEBUFLEN) {
    473 		if (*pbuf) 
    474 			strcat(pbuf, " ");
    475 		strcat(pbuf, param);
    476 		*modes++ = mode;
    477 		*modes = 0;
    478 		count++;
    479 	}
    480 	else if (*pbuf) 
    481 		send = 1;
    482 
    483 	if (count == MAXMODEPARAMS)
    484 		send = 1;
    485 
    486 	if (send)
    487 	{
    488 		MessageTag *mtags = NULL;
    489 
    490 		new_message(&me, NULL, &mtags);
    491 		sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags, ":%s MODE %s %s %s", me.name, channel->name, mbuf, pbuf);
    492 		sendto_server(NULL, 0, 0, mtags, ":%s MODE %s %s %s 0", me.id, channel->name, mbuf, pbuf);
    493 		free_message_tags(mtags);
    494 		send = 0;
    495 		*pbuf = 0;
    496 		modes = mbuf;
    497 		*modes++ = what;
    498 		lastwhat = what;
    499 		if (count != MAXMODEPARAMS)
    500 		{
    501 			strlcpy(pbuf, param, sizeof(pbuf));
    502 			*modes++ = mode;
    503 			count = 1;
    504 		} else {
    505 			count = 0;
    506 		}
    507 		*modes = 0;
    508 	}
    509 }