unrealircd

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

securitygroup.c (24715B)

      1 /*
      2  * Mask & security-group routines.
      3  * (C) Copyright 2015-.. Syzop and the UnrealIRCd team
      4  *
      5  * This program is free software; you can redistribute it and/or
      6  * modify it under the terms of the GNU General Public License
      7  * as published by the Free Software Foundation; either version 2
      8  * of the License, or (at your option) any later version.
      9  * 
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  */
     15 
     16 #include "unrealircd.h"
     17 
     18 /* Global variables */
     19 SecurityGroup *securitygroups = NULL;
     20 
     21 /** Free all masks in the mask list */
     22 void unreal_delete_masks(ConfigItem_mask *m)
     23 {
     24 	ConfigItem_mask *m_next;
     25 
     26 	for (; m; m = m_next)
     27 	{
     28 		m_next = m->next;
     29 
     30 		safe_free(m->mask);
     31 
     32 		safe_free(m);
     33 	}
     34 }
     35 
     36 /** Internal function to add one individual mask to the list */
     37 static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce)
     38 {
     39 	ConfigItem_mask *m = safe_alloc(sizeof(ConfigItem_mask));
     40 
     41 	/* Since we allow both mask "xyz"; and mask { abc; def; };... */
     42 	if (ce->value)
     43 		safe_strdup(m->mask, ce->value);
     44 	else
     45 		safe_strdup(m->mask, ce->name);
     46 
     47 	add_ListItem((ListStruct *)m, (ListStruct **)head);
     48 }
     49 
     50 /** Add mask entries from config */
     51 void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce)
     52 {
     53 	if (ce->items)
     54 	{
     55 		ConfigEntry *cep;
     56 		for (cep = ce->items; cep; cep = cep->next)
     57 			unreal_add_mask(head, cep);
     58 	} else
     59 	{
     60 		unreal_add_mask(head, ce);
     61 	}
     62 }
     63 
     64 /** Check if a client matches any of the masks in the mask list.
     65  * The following rules apply:
     66  * - If you have only negating entries, like '!abc' and '!def', then
     67  *   we assume an implicit * rule first, since that is clearly what
     68  *   the user wants.
     69  * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the
     70  *   implicit * is dropped and we assume you only want to match *.com,
     71  *   with the exception of irc1*.com and irc2*.com.
     72  * - If you only have normal entries without ! then things are
     73  *   as they always are.
     74  * @param client	The client to run the mask match against
     75  * @param mask		The mask entry from the config file
     76  * @returns 1 on match, 0 on non-match.
     77  */
     78 int unreal_mask_match(Client *client, ConfigItem_mask *mask)
     79 {
     80 	int retval = 1;
     81 	ConfigItem_mask *m;
     82 
     83 	if (!mask)
     84 		return 0; /* Empty mask block is no match */
     85 
     86 	/* First check normal matches (without ! prefix) */
     87 	for (m = mask; m; m = m->next)
     88 	{
     89 		if (m->mask[0] != '!')
     90 		{
     91 			retval = 0; /* no implicit * */
     92 			if (match_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
     93 			{
     94 				retval = 1;
     95 				break;
     96 			}
     97 		}
     98 	}
     99 
    100 	if (retval)
    101 	{
    102 		/* We matched. Check for exceptions (with ! prefix) */
    103 		for (m = mask; m; m = m->next)
    104 		{
    105 			if ((m->mask[0] == '!') && match_user(m->mask+1, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
    106 				return 0;
    107 		}
    108 	}
    109 
    110 	return retval;
    111 }
    112 
    113 /** Check if a string matches any of the masks in the mask list.
    114  * The following rules apply:
    115  * - If you have only negating entries, like '!abc' and '!def', then
    116  *   we assume an implicit * rule first, since that is clearly what
    117  *   the user wants.
    118  * - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the
    119  *   implicit * is dropped and we assume you only want to match *.com,
    120  *   with the exception of irc1*.com and irc2*.com.
    121  * - If you only have normal entries without ! then things are
    122  *   as they always are.
    123  * @param name	The name to run the mask matching on
    124  * @param mask	The mask entry from the config file
    125  * @returns 1 on match, 0 on non-match.
    126  */
    127 int unreal_mask_match_string(const char *name, ConfigItem_mask *mask)
    128 {
    129 	int retval = 1;
    130 	ConfigItem_mask *m;
    131 
    132 	if (!mask)
    133 		return 0; /* Empty mask block is no match */
    134 
    135 	/* First check normal matches (without ! prefix) */
    136 	for (m = mask; m; m = m->next)
    137 	{
    138 		if (m->mask[0] != '!')
    139 		{
    140 			retval = 0; /* no implicit * */
    141 			if (match_simple(m->mask, name))
    142 			{
    143 				retval = 1;
    144 				break;
    145 			}
    146 		}
    147 	}
    148 
    149 	if (retval)
    150 	{
    151 		/* We matched. Check for exceptions (with ! prefix) */
    152 		for (m = mask; m; m = m->next)
    153 		{
    154 			if ((m->mask[0] == '!') && match_simple(m->mask+1, name))
    155 				return 0;
    156 		}
    157 	}
    158 
    159 	return retval;
    160 }
    161 
    162 #define CheckNullX(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); *errors = *errors + 1; return 0; }
    163 int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors)
    164 {
    165 	ConfigEntry *cepp;
    166 
    167 	if (!strcmp(cep->name, "webirc") || !strcmp(cep->name, "exclude-webirc"))
    168 	{
    169 		CheckNullX(cep);
    170 	} else
    171 	if (!strcmp(cep->name, "websocket") || !strcmp(cep->name, "exclude-websocket"))
    172 	{
    173 		CheckNullX(cep);
    174 	} else
    175 	if (!strcmp(cep->name, "identified") || !strcmp(cep->name, "exclude-identified"))
    176 	{
    177 		CheckNullX(cep);
    178 	} else
    179 	if (!strcmp(cep->name, "tls") || !strcmp(cep->name, "exclude-tls"))
    180 	{
    181 		CheckNullX(cep);
    182 	} else
    183 	if (!strcmp(cep->name, "reputation-score") || !strcmp(cep->name, "exclude-reputation-score"))
    184 	{
    185 		const char *str = cep->value;
    186 		int v;
    187 		CheckNullX(cep);
    188 		if (*str == '<')
    189 			str++;
    190 		v = atoi(str);
    191 		if ((v < 1) || (v > 10000))
    192 		{
    193 			config_error("%s:%i: %s needs to be a value of 1-10000",
    194 				cep->file->filename, cep->line_number, cep->name);
    195 			*errors = *errors + 1;
    196 		}
    197 	} else
    198 	if (!strcmp(cep->name, "connect-time") || !strcmp(cep->name, "exclude-connect-time"))
    199 	{
    200 		const char *str = cep->value;
    201 		long v;
    202 		CheckNullX(cep);
    203 		if (*str == '<')
    204 			str++;
    205 		v = config_checkval(str, CFG_TIME);
    206 		if (v < 1)
    207 		{
    208 			config_error("%s:%i: %s needs to be a time value (and more than 0 seconds)",
    209 				cep->file->filename, cep->line_number, cep->name);
    210 			*errors = *errors + 1;
    211 		}
    212 	} else
    213 	if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "exclude-mask"))
    214 	{
    215 		for (cepp = cep->items; cepp; cepp = cepp->next)
    216 		{
    217 			if (!strcmp(cepp->name, "mask"))
    218 				continue;
    219 			if (cepp->items || cepp->value)
    220 			{
    221 				config_error("%s:%i: security-group::mask should contain hostmasks only. "
    222 				             "Perhaps you meant to use it in security-group { %s ... } directly?",
    223 				             cepp->file->filename, cepp->line_number,
    224 				             cepp->name);
    225 				*errors = *errors + 1;
    226 			}
    227 		}
    228 	} else
    229 	if (!strcmp(cep->name, "ip"))
    230 	{
    231 	} else
    232 	if (!strcmp(cep->name, "security-group") || !strcmp(cep->name, "exclude-security-group"))
    233 	{
    234 	} else
    235 	{
    236 		/* Let's see if an extended server ban exists for this item... */
    237 		Extban *extban;
    238 		if (!strncmp(cep->name, "exclude-", 8))
    239 			extban = findmod_by_bantype_raw(cep->name+8, strlen(cep->name+8));
    240 		else
    241 			extban = findmod_by_bantype_raw(cep->name, strlen(cep->name));
    242 		if (extban && (extban->options & EXTBOPT_TKL) && (extban->is_banned_events & BANCHK_TKL))
    243 		{
    244 			test_extended_list(extban, cep, errors);
    245 			return 1; /* Yup, handled */
    246 		}
    247 		return 0; /* Unhandled: unknown item for us */
    248 	}
    249 	return 1; /* Handled, but there could be errors */
    250 }
    251 
    252 int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out)
    253 {
    254 	int errors = 0;
    255 	ConfigEntry *cep;
    256 
    257 	/* (If there is only a ce->value, trust that it is OK) */
    258 
    259 	/* Test ce->items... */
    260 	for (cep = ce->items; cep; cep = cep->next)
    261 	{
    262 		/* Only complain about things with values,
    263 		 * as valueless things like "10.0.0.0/8" are treated as a mask.
    264 		 */
    265 		if (!test_match_item(conf, cep, &errors) && cep->value)
    266 		{
    267 			config_error_unknown(cep->file->filename, cep->line_number,
    268 				ce->name, cep->name);
    269 			errors++;
    270 			continue;
    271 		}
    272 	}
    273 
    274 	*errors_out = *errors_out + errors;
    275 	return errors ? 0 : 1;
    276 }
    277 
    278 #define tmbbw_is_wildcard(x)	(!strcmp(x, "*") || !strcmp(x, "*@*"))
    279 int test_match_block_too_broad(ConfigFile *conf, ConfigEntry *ce)
    280 {
    281 	ConfigEntry *cep, *cepp;
    282 
    283 	// match *;
    284 	if (ce->value && tmbbw_is_wildcard(ce->value))
    285 		return 1;
    286 
    287 	for (cep = ce->items; cep; cep = cep->next)
    288 	{
    289 		// match { *; }
    290 		if (!cep->value && tmbbw_is_wildcard(cep->name))
    291 			return 1;
    292 		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "ip"))
    293 		{
    294 			// match { mask *; }
    295 			if (cep->value && tmbbw_is_wildcard(cep->value))
    296 				return 1;
    297 			// match { mask { *; } }
    298 			for (cepp = cep->items; cepp; cepp = cepp->next)
    299 				if (tmbbw_is_wildcard(cepp->name))
    300 					return 1;
    301 		}
    302 	}
    303 
    304 	return 0;
    305 }
    306 
    307 int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
    308 {
    309 	int errors = 0;
    310 	ConfigEntry *cep;
    311 
    312 	/* First, check the name of the security group */
    313 	if (!ce->value)
    314 	{
    315 		config_error("%s:%i: security-group block needs a name, eg: security-group web-users {",
    316 			ce->file->filename, ce->line_number);
    317 		errors++;
    318 	} else {
    319 		if (!strcasecmp(ce->value, "unknown-users"))
    320 		{
    321 			config_error("%s:%i: The 'unknown-users' group is a special group that is the "
    322 			             "inverse of 'known-users', you cannot create or adjust it in the "
    323 			             "config file, as it is created automatically by UnrealIRCd.",
    324 			             ce->file->filename, ce->line_number);
    325 			errors++;
    326 			return errors;
    327 		}
    328 		if (!security_group_valid_name(ce->value))
    329 		{
    330 			config_error("%s:%i: security-group block name '%s' contains invalid characters or is too long. "
    331 			             "Only letters, numbers, underscore and hyphen are allowed.",
    332 			             ce->file->filename, ce->line_number, ce->value);
    333 			errors++;
    334 		}
    335 	}
    336 
    337 	for (cep = ce->items; cep; cep = cep->next)
    338 	{
    339 		if (!test_match_item(conf, cep, &errors))
    340 		{
    341 			config_error_unknown(cep->file->filename, cep->line_number,
    342 				"security-group", cep->name);
    343 			errors++;
    344 			continue;
    345 		}
    346 	}
    347 
    348 	return errors;
    349 }
    350 
    351 int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
    352 {
    353 	int errors = 0; /* unused */
    354 	SecurityGroup *s = *block;
    355 
    356 	/* The following code is there so we don't create a security group
    357 	 * unless there is actually a valid config item for it encountered.
    358 	 * This so the security group '*s' can stay NULL if there are zero
    359 	 * items, so we don't waste any CPU if it is unused.
    360 	 */
    361 	if (*block == NULL)
    362 	{
    363 		/* Yeah we call a TEST routine from a CONFIG RUN routine ;). */
    364 		if (!test_match_item(conf, cep, &errors))
    365 			return 0; /* not for us */
    366 		/* If we are still here then we must create the security group */
    367 		*block = s = safe_alloc(sizeof(SecurityGroup));
    368 	}
    369 
    370 	if (!strcmp(cep->name, "webirc"))
    371 		s->webirc = config_checkval(cep->value, CFG_YESNO);
    372 	if (!strcmp(cep->name, "websocket"))
    373 		s->websocket = config_checkval(cep->value, CFG_YESNO);
    374 	else if (!strcmp(cep->name, "identified"))
    375 		s->identified = config_checkval(cep->value, CFG_YESNO);
    376 	else if (!strcmp(cep->name, "tls"))
    377 		s->tls = config_checkval(cep->value, CFG_YESNO);
    378 	else if (!strcmp(cep->name, "reputation-score"))
    379 	{
    380 		if (*cep->value == '<')
    381 			s->reputation_score = 0 - atoi(cep->value+1);
    382 		else
    383 			s->reputation_score = atoi(cep->value);
    384 	}
    385 	else if (!strcmp(cep->name, "connect-time"))
    386 	{
    387 		if (*cep->value == '<')
    388 			s->connect_time = 0 - config_checkval(cep->value+1, CFG_TIME);
    389 		else
    390 			s->connect_time = config_checkval(cep->value, CFG_TIME);
    391 	}
    392 	else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask"))
    393 	{
    394 		unreal_add_masks(&s->mask, cep);
    395 	}
    396 	else if (!strcmp(cep->name, "ip"))
    397 	{
    398 		unreal_add_names(&s->ip, cep);
    399 	}
    400 	else if (!strcmp(cep->name, "security-group"))
    401 	{
    402 		unreal_add_names(&s->security_group, cep);
    403 	}
    404 	else if (!strcmp(cep->name, "exclude-webirc"))
    405 		s->exclude_webirc = config_checkval(cep->value, CFG_YESNO);
    406 	else if (!strcmp(cep->name, "exclude-websocket"))
    407 		s->exclude_websocket = config_checkval(cep->value, CFG_YESNO);
    408 	else if (!strcmp(cep->name, "exclude-identified"))
    409 		s->exclude_identified = config_checkval(cep->value, CFG_YESNO);
    410 	else if (!strcmp(cep->name, "exclude-tls"))
    411 		s->exclude_tls = config_checkval(cep->value, CFG_YESNO);
    412 	else if (!strcmp(cep->name, "exclude-reputation-score"))
    413 	{
    414 		if (*cep->value == '<')
    415 			s->exclude_reputation_score = 0 - atoi(cep->value+1);
    416 		else
    417 			s->exclude_reputation_score = atoi(cep->value);
    418 	}
    419 	else if (!strcmp(cep->name, "exclude-mask"))
    420 	{
    421 		unreal_add_masks(&s->exclude_mask, cep);
    422 	}
    423 	else if (!strcmp(cep->name, "exclude-security-group"))
    424 	{
    425 		unreal_add_names(&s->security_group, cep);
    426 	}
    427 	else
    428 	{
    429 		/* Let's see if an extended server ban exists for this item... this needs to be LAST! */
    430 		Extban *extban;
    431 		const char *name = cep->name;
    432 
    433 		if (!strncmp(cep->name, "exclude-", 8))
    434 		{
    435 			/* Extended (exclusive) ? */
    436 			name = cep->name + 8;
    437 			if (findmod_by_bantype_raw(name, strlen(name)))
    438 				unreal_add_name_values(&s->exclude_extended, name, cep);
    439 			else
    440 				return 0; /* Unhandled */
    441 		} else {
    442 			/* Extended (inclusive) */
    443 			if (findmod_by_bantype_raw(name, strlen(name)))
    444 				unreal_add_name_values(&s->extended, name, cep);
    445 			else
    446 				return 0; /* Unhandled */
    447 		}
    448 	}
    449 
    450 	/* And update the printable list */
    451 	if (cep->items)
    452 	{
    453 		ConfigEntry *cep2;
    454 		for (cep2 = cep->items; cep2; cep2 = cep2->next)
    455 			add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep2->name);
    456 	} else {
    457 		add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value);
    458 	}
    459 
    460 	return 1; /* Handled by us (guaranteed earlier) */
    461 }
    462 
    463 int conf_match_block(ConfigFile *conf, ConfigEntry *ce, SecurityGroup **block)
    464 {
    465 	ConfigEntry *cep;
    466 	SecurityGroup *s = *block;
    467 
    468 	if (*block == NULL)
    469 		*block = s = safe_alloc(sizeof(SecurityGroup));
    470 
    471 	/* Check for simple form: match *; / mask *; */
    472 	if (ce->value)
    473 	{
    474 		unreal_add_masks(&s->mask, ce);
    475 		add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", ce->value);
    476 	}
    477 
    478 	/* Check for long form: match { .... } / mask { .... } */
    479 	for (cep = ce->items; cep; cep = cep->next)
    480 	{
    481 		if (!conf_match_item(conf, cep, &s) && !cep->value && !cep->items)
    482 		{
    483 			/* Valueless? Then it must be a mask like 10.0.0.0/8 */
    484 			unreal_add_masks(&s->mask, cep);
    485 			add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", cep->name);
    486 		}
    487 	}
    488 	return 1;
    489 }
    490 
    491 int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
    492 {
    493 	ConfigEntry *cep;
    494 	SecurityGroup *s = add_security_group(ce->value, 1);
    495 
    496 	for (cep = ce->items; cep; cep = cep->next)
    497 	{
    498 		if (!strcmp(cep->name, "priority"))
    499 		{
    500 			s->priority = atoi(cep->value);
    501 			DelListItem(s, securitygroups);
    502 			AddListItemPrio(s, securitygroups, s->priority);
    503 		} else
    504 			conf_match_item(conf, cep, &s);
    505 	}
    506 	return 1;
    507 }
    508 
    509 /** Check if the name of the security-group contains only valid characters.
    510  * @param name	The name of the group
    511  * @returns 1 if name is valid, 0 if not (eg: illegal characters)
    512  */
    513 int security_group_valid_name(const char *name)
    514 {
    515 	const char *p;
    516 
    517 	if (strlen(name) > SECURITYGROUPLEN)
    518 		return 0; /* Too long */
    519 
    520 	for (p = name; *p; p++)
    521 	{
    522 		if (!isalnum(*p) && !strchr("_-", *p))
    523 			return 0; /* Character not allowed */
    524 	}
    525 	return 1;
    526 }
    527 
    528 /** Find a security-group.
    529  * @param name	The name of the security group
    530  * @returns A SecurityGroup struct, or NULL if not found.
    531  */
    532 SecurityGroup *find_security_group(const char *name)
    533 {
    534 	SecurityGroup *s;
    535 	for (s = securitygroups; s; s = s->next)
    536 		if (!strcasecmp(name, s->name))
    537 			return s;
    538 	return NULL;
    539 }
    540 
    541 /** Checks if a security-group exists.
    542  * This function takes the 'unknown-users' magic group into account as well.
    543  * @param name	The name of the security group
    544  * @returns 1 if it exists, 0 if not
    545  */
    546 int security_group_exists(const char *name)
    547 {
    548 	if (!strcmp(name, "unknown-users") || find_security_group(name))
    549 		return 1;
    550 	return 0;
    551 }
    552 
    553 /** Add a new security-group and add it to the list, but search for existing one first.
    554  * @param name	The name of the security group
    555  * @returns A SecurityGroup struct (already added to the 'securitygroups' linked list)
    556  */
    557 SecurityGroup *add_security_group(const char *name, int priority)
    558 {
    559 	SecurityGroup *s = find_security_group(name);
    560 
    561 	/* Existing? */
    562 	if (s)
    563 		return s;
    564 
    565 	/* Otherwise, create a new entry */
    566 	s = safe_alloc(sizeof(SecurityGroup));
    567 	strlcpy(s->name, name, sizeof(s->name));
    568 	s->priority = priority;
    569 	AddListItemPrio(s, securitygroups, priority);
    570 	return s;
    571 }
    572 
    573 /** Free a SecurityGroup struct */
    574 void free_security_group(SecurityGroup *s)
    575 {
    576 	if (s == NULL)
    577 		return;
    578 	unreal_delete_masks(s->mask);
    579 	unreal_delete_masks(s->exclude_mask);
    580 	free_entire_name_list(s->security_group);
    581 	free_entire_name_list(s->exclude_security_group);
    582 	free_entire_name_list(s->ip);
    583 	free_entire_name_list(s->exclude_ip);
    584 	free_nvplist(s->extended);
    585 	free_nvplist(s->exclude_extended);
    586 	free_nvplist(s->printable_list);
    587 	safe_free(s);
    588 }
    589 
    590 /** Initialize the default security-group blocks */
    591 void set_security_group_defaults(void)
    592 {
    593 	SecurityGroup *s, *s_next;
    594 
    595 	/* First free all security groups */
    596 	for (s = securitygroups; s; s = s_next)
    597 	{
    598 		s_next = s->next;
    599 		free_security_group(s);
    600 	}
    601 	securitygroups = NULL;
    602 
    603 	/* Default group: webirc */
    604 	s = add_security_group("webirc-users", 50);
    605 	s->webirc = 1;
    606 
    607 	/* Default group: websocket */
    608 	s = add_security_group("websocket-users", 51);
    609 	s->websocket = 1;
    610 
    611 	/* Default group: known-users */
    612 	s = add_security_group("known-users", 100);
    613 	s->identified = 1;
    614 	s->reputation_score = 25;
    615 	s->webirc = 0;
    616 
    617 	/* Default group: tls-and-known-users */
    618 	s = add_security_group("tls-and-known-users", 200);
    619 	s->identified = 1;
    620 	s->reputation_score = 25;
    621 	s->webirc = 0;
    622 	s->tls = 1;
    623 
    624 	/* Default group: tls-users */
    625 	s = add_security_group("tls-users", 300);
    626 	s->tls = 1;
    627 }
    628 
    629 int user_matches_extended_list(Client *client, NameValuePrioList *e)
    630 {
    631 	Extban *extban;
    632 	BanContext b;
    633 
    634 	for (; e; e = e->next)
    635 	{
    636 		extban = findmod_by_bantype_raw(e->name, strlen(e->name));
    637 		if (!extban ||
    638 		    !(extban->options & EXTBOPT_TKL) ||
    639 		    !(extban->is_banned_events & BANCHK_TKL))
    640 		{
    641 			continue; /* extban not found or of incorrect type */
    642 		}
    643 
    644 		memset(&b, 0, sizeof(BanContext));
    645 		b.client = client;
    646 		b.banstr = e->value;
    647 		b.ban_check_types = BANCHK_TKL;
    648 		if (extban->is_banned(&b))
    649 			return 1;
    650 	}
    651 
    652 	return 0;
    653 }
    654 
    655 int test_extended_list(Extban *extban, ConfigEntry *cep, int *errors)
    656 {
    657 	BanContext b;
    658 
    659 	if (cep->value)
    660 	{
    661 		memset(&b, 0, sizeof(BanContext));
    662 		b.banstr = cep->value;
    663 		b.ban_check_types = BANCHK_TKL;
    664 		b.what = MODE_ADD;
    665 		if (!extban->conv_param(&b, extban))
    666 		{
    667 			config_error("%s:%i: %s has an invalid value",
    668 			             cep->file->filename, cep->line_number, cep->name);
    669 			*errors = *errors + 1;
    670 			return 0;
    671 		}
    672 	}
    673 
    674 	for (cep = cep->items; cep; cep = cep->next)
    675 	{
    676 		memset(&b, 0, sizeof(BanContext));
    677 		b.banstr = cep->name;
    678 		b.ban_check_types = BANCHK_TKL;
    679 		b.what = MODE_ADD;
    680 		if (!extban->conv_param(&b, extban))
    681 		{
    682 			config_error("%s:%i: %s has an invalid value",
    683 			             cep->file->filename, cep->line_number, cep->name);
    684 			*errors = *errors + 1;
    685 			return 0;
    686 		}
    687 	}
    688 
    689 	return 1;
    690 }
    691 
    692 /** Returns 1 if the user is allowed by any of the security groups in the named list.
    693  * This is only used by security-group::security-group and
    694  * security-group::exclude-security-group.
    695  * @param client	Client to check
    696  * @param l		The NameList
    697  * @returns 1 if any of the security groups match, 0 if none of them matched.
    698  */
    699 int user_allowed_by_security_group_list(Client *client, NameList *l)
    700 {
    701 	for (; l; l = l->next)
    702 		if (user_allowed_by_security_group_name(client, l->name))
    703 			return 1;
    704 	return 0;
    705 }
    706 
    707 /** Returns 1 if the user is OK as far as the security-group is concerned.
    708  * @param client	The client to check
    709  * @param s		The security-group to check against
    710  * @retval 1 if user is allowed by security-group, 0 if not.
    711  */
    712 int user_allowed_by_security_group(Client *client, SecurityGroup *s)
    713 {
    714 	static int recursion_security_group = 0;
    715 
    716 	/* Allow NULL securitygroup, makes it easier in the code elsewhere */
    717 	if (!s)
    718 		return 0;
    719 
    720 	if (recursion_security_group > 8)
    721 	{
    722 		unreal_log(ULOG_WARNING, "main", "SECURITY_GROUP_LOOP_DETECTED", client,
    723 		           "Loop detected while processing security-group '$security_group' -- "
    724 		           "are you perhaps referencing a security-group from a security-group?",
    725 		           log_data_string("security_group", s->name));
    726 		return 0;
    727 	}
    728 	recursion_security_group++;
    729 
    730 	/* DO NOT USE 'return' IN CODE BELOW!!!!!!!!!
    731 	 * - use 'goto user_not_allowed' to reject
    732 	 * - use 'goto user_allowed' to accept
    733 	 */
    734 
    735 	/* Process EXCLUSION criteria first... */
    736 	if (s->exclude_identified && IsLoggedIn(client))
    737 		goto user_not_allowed;
    738 	if (s->exclude_webirc && moddata_client_get(client, "webirc"))
    739 		goto user_not_allowed;
    740 	if (s->exclude_websocket && moddata_client_get(client, "websocket"))
    741 		goto user_not_allowed;
    742 	if ((s->exclude_reputation_score > 0) && (GetReputation(client) >= s->exclude_reputation_score))
    743 		goto user_not_allowed;
    744 	if ((s->exclude_reputation_score < 0) && (GetReputation(client) < 0 - s->exclude_reputation_score))
    745 		goto user_not_allowed;
    746 	if (s->exclude_connect_time != 0)
    747 	{
    748 		long connect_time = get_connected_time(client);
    749 		if ((s->exclude_connect_time > 0) && (connect_time >= s->exclude_connect_time))
    750 			goto user_not_allowed;
    751 		if ((s->exclude_connect_time < 0) && (connect_time < 0 - s->exclude_connect_time))
    752 			goto user_not_allowed;
    753 	}
    754 	if (s->exclude_tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
    755 		goto user_not_allowed;
    756 	if (s->exclude_mask && unreal_mask_match(client, s->exclude_mask))
    757 		goto user_not_allowed;
    758 	if (s->exclude_ip && unreal_match_iplist(client, s->exclude_ip))
    759 		goto user_not_allowed;
    760 	if (s->exclude_security_group && user_allowed_by_security_group_list(client, s->exclude_security_group))
    761 		goto user_not_allowed;
    762 	if (s->exclude_extended && user_matches_extended_list(client, s->exclude_extended))
    763 		goto user_not_allowed;
    764 
    765 	/* Then process INCLUSION criteria... */
    766 	if (s->identified && IsLoggedIn(client))
    767 		goto user_allowed;
    768 	if (s->webirc && moddata_client_get(client, "webirc"))
    769 		goto user_allowed;
    770 	if (s->websocket && moddata_client_get(client, "websocket"))
    771 		goto user_allowed;
    772 	if ((s->reputation_score > 0) && (GetReputation(client) >= s->reputation_score))
    773 		goto user_allowed;
    774 	if ((s->reputation_score < 0) && (GetReputation(client) < 0 - s->reputation_score))
    775 		goto user_allowed;
    776 	if (s->connect_time != 0)
    777 	{
    778 		long connect_time = get_connected_time(client);
    779 		if ((s->connect_time > 0) && (connect_time >= s->connect_time))
    780 			goto user_allowed;
    781 		if ((s->connect_time < 0) && (connect_time < 0 - s->connect_time))
    782 			goto user_allowed;
    783 	}
    784 	if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
    785 		goto user_allowed;
    786 	if (s->mask && unreal_mask_match(client, s->mask))
    787 		goto user_allowed;
    788 	if (s->ip && unreal_match_iplist(client, s->ip))
    789 		goto user_allowed;
    790 	if (s->security_group && user_allowed_by_security_group_list(client, s->security_group))
    791 		goto user_allowed;
    792 	if (s->extended && user_matches_extended_list(client, s->extended))
    793 		goto user_allowed;
    794 
    795 user_not_allowed:
    796 	recursion_security_group--;
    797 	return 0;
    798 
    799 user_allowed:
    800 	recursion_security_group--;
    801 	return 1;
    802 }
    803 
    804 /** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version.
    805  * @param client	The client to check
    806  * @param secgroupname	The name of the security-group to check against
    807  * @retval 1 if user is allowed by security-group, 0 if not.
    808  */
    809 int user_allowed_by_security_group_name(Client *client, const char *secgroupname)
    810 {
    811 	SecurityGroup *s;
    812 
    813 	/* Handle the magical 'unknown-users' case. */
    814 	if (!strcmp(secgroupname, "unknown-users"))
    815 	{
    816 		/* This is simply the inverse of 'known-users' */
    817 		s = find_security_group("known-users");
    818 		if (!s)
    819 			return 0; /* that's weird!? pretty impossible. */
    820 		return !user_allowed_by_security_group(client, s);
    821 	}
    822 
    823 	/* Find the group and evaluate it */
    824 	s = find_security_group(secgroupname);
    825 	if (!s)
    826 		return 0; /* security group not found: no match */
    827 	return user_allowed_by_security_group(client, s);
    828 }
    829 
    830 /** Get comma separated list of matching security groups for 'client'.
    831  * This is usually only used for displaying purposes.
    832  * @returns string like "unknown-users,tls-users" from a static buffer.
    833  */
    834 const char *get_security_groups(Client *client)
    835 {
    836 	SecurityGroup *s;
    837 	static char buf[512];
    838 
    839 	*buf = '\0';
    840 
    841 	/* We put known-users or unknown-users at the beginning.
    842 	 * The latter is special and doesn't actually exist
    843 	 * in the linked list, hence the special code here,
    844 	 * and again later in the for loop to skip it.
    845 	 */
    846 	if (user_allowed_by_security_group_name(client, "known-users"))
    847 		strlcat(buf, "known-users,", sizeof(buf));
    848 	else
    849 		strlcat(buf, "unknown-users,", sizeof(buf));
    850 
    851 	for (s = securitygroups; s; s = s->next)
    852 	{
    853 		if (strcmp(s->name, "known-users") &&
    854 		    user_allowed_by_security_group(client, s))
    855 		{
    856 			strlcat(buf, s->name, sizeof(buf));
    857 			strlcat(buf, ",", sizeof(buf));
    858 		}
    859 	}
    860 
    861 	if (*buf)
    862 		buf[strlen(buf)-1] = '\0';
    863 	return buf;
    864 }