pissircd/src/securitygroup.c

958 lines
27 KiB
C

/*
* Mask & security-group routines.
* (C) Copyright 2015-.. Syzop and the UnrealIRCd team
*
* 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 2
* of the License, 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.
*/
#include "unrealircd.h"
/* Global variables */
SecurityGroup *securitygroups = NULL;
/** Free all masks in the mask list */
void unreal_delete_masks(ConfigItem_mask *m)
{
ConfigItem_mask *m_next;
for (; m; m = m_next)
{
m_next = m->next;
safe_free(m->mask);
safe_free(m);
}
}
void unreal_add_mask_string(ConfigItem_mask **head, const char *name)
{
ConfigItem_mask *m = safe_alloc(sizeof(ConfigItem_mask));
safe_strdup(m->mask, name);
add_ListItem((ListStruct *)m, (ListStruct **)head);
}
/** Internal function to add one individual mask to the list */
static void unreal_add_mask(ConfigItem_mask **head, ConfigEntry *ce)
{
/* Since we allow both mask "xyz"; and mask { abc; def; };... */
if (ce->value)
unreal_add_mask_string(head, ce->value);
else
unreal_add_mask_string(head, ce->name);
}
/** Add mask entries from config */
void unreal_add_masks(ConfigItem_mask **head, ConfigEntry *ce)
{
if (ce->items)
{
ConfigEntry *cep;
for (cep = ce->items; cep; cep = cep->next)
unreal_add_mask(head, cep);
} else
{
unreal_add_mask(head, ce);
}
}
ConfigItem_mask *unreal_duplicate_masks(ConfigItem_mask *existing)
{
ConfigItem_mask *ret = NULL;
ConfigItem_mask *n;
for (; existing; existing = existing->next)
{
n = safe_alloc(sizeof(ConfigItem_mask));
safe_strdup(n->mask, existing->mask);
AddListItem(n, ret);
}
return ret;
}
/** Check if a client matches any of the masks in the mask list.
* The following rules apply:
* - If you have only negating entries, like '!abc' and '!def', then
* we assume an implicit * rule first, since that is clearly what
* the user wants.
* - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the
* implicit * is dropped and we assume you only want to match *.com,
* with the exception of irc1*.com and irc2*.com.
* - If you only have normal entries without ! then things are
* as they always are.
* @param client The client to run the mask match against
* @param mask The mask entry from the config file
* @returns 1 on match, 0 on non-match.
*/
int unreal_mask_match(Client *client, ConfigItem_mask *mask)
{
int retval = 1;
ConfigItem_mask *m;
if (!mask)
return 0; /* Empty mask block is no match */
/* First check normal matches (without ! prefix) */
for (m = mask; m; m = m->next)
{
if (m->mask[0] != '!')
{
retval = 0; /* no implicit * */
if (match_user(m->mask, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
{
retval = 1;
break;
}
}
}
if (retval)
{
/* We matched. Check for exceptions (with ! prefix) */
for (m = mask; m; m = m->next)
{
if ((m->mask[0] == '!') && match_user(m->mask+1, client, MATCH_CHECK_REAL|MATCH_CHECK_EXTENDED))
return 0;
}
}
return retval;
}
/** Check if a string matches any of the masks in the mask list.
* The following rules apply:
* - If you have only negating entries, like '!abc' and '!def', then
* we assume an implicit * rule first, since that is clearly what
* the user wants.
* - If you have a mix, like '*.com', '!irc1*', '!irc2*' then the
* implicit * is dropped and we assume you only want to match *.com,
* with the exception of irc1*.com and irc2*.com.
* - If you only have normal entries without ! then things are
* as they always are.
* @param name The name to run the mask matching on
* @param mask The mask entry from the config file
* @returns 1 on match, 0 on non-match.
*/
int unreal_mask_match_string(const char *name, ConfigItem_mask *mask)
{
int retval = 1;
ConfigItem_mask *m;
if (!mask)
return 0; /* Empty mask block is no match */
/* First check normal matches (without ! prefix) */
for (m = mask; m; m = m->next)
{
if (m->mask[0] != '!')
{
retval = 0; /* no implicit * */
if (match_simple(m->mask, name))
{
retval = 1;
break;
}
}
}
if (retval)
{
/* We matched. Check for exceptions (with ! prefix) */
for (m = mask; m; m = m->next)
{
if ((m->mask[0] == '!') && match_simple(m->mask+1, name))
return 0;
}
}
return retval;
}
#define CheckNullX(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); (*errors)++; return 0; }
int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors)
{
ConfigEntry *cepp;
if (!strcmp(cep->name, "webirc") || !strcmp(cep->name, "exclude-webirc"))
{
CheckNullX(cep);
} else
if (!strcmp(cep->name, "websocket") || !strcmp(cep->name, "exclude-websocket"))
{
CheckNullX(cep);
} else
if (!strcmp(cep->name, "identified") || !strcmp(cep->name, "exclude-identified"))
{
CheckNullX(cep);
} else
if (!strcmp(cep->name, "tls") || !strcmp(cep->name, "exclude-tls"))
{
CheckNullX(cep);
} else
if (!strcmp(cep->name, "reputation-score") || !strcmp(cep->name, "exclude-reputation-score"))
{
const char *str = cep->value;
int v;
CheckNullX(cep);
if (*str == '<')
str++;
v = atoi(str);
if ((v < 1) || (v > 10000))
{
config_error("%s:%i: %s needs to be a value of 1-10000",
cep->file->filename, cep->line_number, cep->name);
(*errors)++;
}
} else
if (!strcmp(cep->name, "connect-time") || !strcmp(cep->name, "exclude-connect-time"))
{
const char *str = cep->value;
long v;
CheckNullX(cep);
if (*str == '<')
str++;
v = config_checkval(str, CFG_TIME);
if (v < 1)
{
config_error("%s:%i: %s needs to be a time value (and more than 0 seconds)",
cep->file->filename, cep->line_number, cep->name);
(*errors)++;
}
} else
if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "exclude-mask"))
{
for (cepp = cep->items; cepp; cepp = cepp->next)
{
if (!strcmp(cepp->name, "mask"))
continue;
if (cepp->items || cepp->value)
{
config_error("%s:%i: security-group::mask should contain hostmasks only. "
"Perhaps you meant to use it in security-group { %s ... } directly?",
cepp->file->filename, cepp->line_number,
cepp->name);
(*errors)++;
}
}
} else
if (!strcmp(cep->name, "ip"))
{
} else
if (!strcmp(cep->name, "security-group") || !strcmp(cep->name, "exclude-security-group"))
{
} else
if (!strcmp(cep->name, "rule") || !strcmp(cep->name, "exclude-rule"))
{
int val = crule_test(cep->value);
if (val)
{
config_error("%s:%i: rule contains an invalid expression: %s",
cep->file->filename,
cep->line_number,
crule_errstring(val));
(*errors)++;
}
} else
{
/* Let's see if an extended server ban exists for this item... */
Extban *extban;
if (!strncmp(cep->name, "exclude-", 8))
extban = findmod_by_bantype_raw(cep->name+8, strlen(cep->name+8));
else
extban = findmod_by_bantype_raw(cep->name, strlen(cep->name));
if (extban && (extban->options & EXTBOPT_TKL) && (extban->is_banned_events & BANCHK_TKL))
{
test_extended_list(extban, cep, errors);
return 1; /* Yup, handled */
}
return 0; /* Unhandled: unknown item for us */
}
return 1; /* Handled, but there could be errors */
}
int test_match_block(ConfigFile *conf, ConfigEntry *ce, int *errors_out)
{
int errors = 0;
ConfigEntry *cep;
/* (If there is only a ce->value, trust that it is OK) */
/* Test ce->items... */
for (cep = ce->items; cep; cep = cep->next)
{
/* Only complain about things with values,
* as valueless things like "10.0.0.0/8" are treated as a mask.
*/
if (!test_match_item(conf, cep, &errors) && cep->value)
{
config_error_unknown(cep->file->filename, cep->line_number,
ce->name, cep->name);
errors++;
continue;
}
}
*errors_out = *errors_out + errors;
return errors ? 0 : 1;
}
#define tmbbw_is_wildcard(x) (!strcmp(x, "*") || !strcmp(x, "*@*"))
int test_match_block_too_broad(ConfigFile *conf, ConfigEntry *ce)
{
ConfigEntry *cep, *cepp;
// match *;
if (ce->value && tmbbw_is_wildcard(ce->value))
return 1;
for (cep = ce->items; cep; cep = cep->next)
{
// match { *; }
if (!cep->value && tmbbw_is_wildcard(cep->name))
return 1;
if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask") || !strcmp(cep->name, "ip"))
{
// match { mask *; }
if (cep->value && tmbbw_is_wildcard(cep->value))
return 1;
// match { mask { *; } }
for (cepp = cep->items; cepp; cepp = cepp->next)
if (tmbbw_is_wildcard(cepp->name))
return 1;
}
}
return 0;
}
int _test_security_group(ConfigFile *conf, ConfigEntry *ce)
{
int errors = 0;
ConfigEntry *cep;
/* First, check the name of the security group */
if (!ce->value)
{
config_error("%s:%i: security-group block needs a name, eg: security-group web-users {",
ce->file->filename, ce->line_number);
errors++;
} else {
if (!strcasecmp(ce->value, "unknown-users"))
{
config_error("%s:%i: The 'unknown-users' group is a special group that is the "
"inverse of 'known-users', you cannot create or adjust it in the "
"config file, as it is created automatically by UnrealIRCd.",
ce->file->filename, ce->line_number);
errors++;
return errors;
}
if (!security_group_valid_name(ce->value))
{
config_error("%s:%i: security-group block name '%s' contains invalid characters or is too long. "
"Only letters, numbers, underscore and hyphen are allowed.",
ce->file->filename, ce->line_number, ce->value);
errors++;
}
}
for (cep = ce->items; cep; cep = cep->next)
{
if (!test_match_item(conf, cep, &errors))
{
config_error_unknown(cep->file->filename, cep->line_number,
"security-group", cep->name);
errors++;
continue;
}
}
return errors;
}
int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block)
{
int errors = 0; /* unused */
SecurityGroup *s = *block;
/* The following code is there so we don't create a security group
* unless there is actually a valid config item for it encountered.
* This so the security group '*s' can stay NULL if there are zero
* items, so we don't waste any CPU if it is unused.
*/
if (*block == NULL)
{
/* Yeah we call a TEST routine from a CONFIG RUN routine ;). */
if (!test_match_item(conf, cep, &errors))
return 0; /* not for us */
/* If we are still here then we must create the security group */
*block = s = safe_alloc(sizeof(SecurityGroup));
}
if (!strcmp(cep->name, "webirc"))
s->webirc = config_checkval(cep->value, CFG_YESNO);
if (!strcmp(cep->name, "websocket"))
s->websocket = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "identified"))
s->identified = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "tls"))
s->tls = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "reputation-score"))
{
if (*cep->value == '<')
s->reputation_score = 0 - atoi(cep->value+1);
else
s->reputation_score = atoi(cep->value);
}
else if (!strcmp(cep->name, "connect-time"))
{
if (*cep->value == '<')
s->connect_time = 0 - config_checkval(cep->value+1, CFG_TIME);
else
s->connect_time = config_checkval(cep->value, CFG_TIME);
}
else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "include-mask"))
{
unreal_add_masks(&s->mask, cep);
}
else if (!strcmp(cep->name, "ip"))
{
unreal_add_names(&s->ip, cep);
}
else if (!strcmp(cep->name, "security-group"))
{
unreal_add_names(&s->security_group, cep);
}
else if (!strcmp(cep->name, "rule"))
{
safe_strdup(s->prettyrule, cep->value);
s->rule = crule_parse(s->prettyrule);
}
else if (!strcmp(cep->name, "exclude-webirc"))
s->exclude_webirc = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "exclude-websocket"))
s->exclude_websocket = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "exclude-identified"))
s->exclude_identified = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "exclude-tls"))
s->exclude_tls = config_checkval(cep->value, CFG_YESNO);
else if (!strcmp(cep->name, "exclude-reputation-score"))
{
if (*cep->value == '<')
s->exclude_reputation_score = 0 - atoi(cep->value+1);
else
s->exclude_reputation_score = atoi(cep->value);
}
else if (!strcmp(cep->name, "exclude-mask"))
{
unreal_add_masks(&s->exclude_mask, cep);
}
else if (!strcmp(cep->name, "exclude-security-group"))
{
unreal_add_names(&s->exclude_security_group, cep);
}
else if (!strcmp(cep->name, "exclude-rule"))
{
safe_strdup(s->exclude_prettyrule, cep->value);
s->exclude_rule = crule_parse(s->exclude_prettyrule);
}
else
{
/* Let's see if an extended server ban exists for this item... this needs to be LAST! */
Extban *extban;
const char *name = cep->name;
if (!strncmp(cep->name, "exclude-", 8))
{
/* Extended (exclusive) ? */
name = cep->name + 8;
if (findmod_by_bantype_raw(name, strlen(name)))
unreal_add_name_values(&s->exclude_extended, name, cep);
else
return 0; /* Unhandled */
} else {
/* Extended (inclusive) */
if (findmod_by_bantype_raw(name, strlen(name)))
unreal_add_name_values(&s->extended, name, cep);
else
return 0; /* Unhandled */
}
}
/* And update the printable list */
if (cep->items)
{
ConfigEntry *cep2;
for (cep2 = cep->items; cep2; cep2 = cep2->next)
add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep2->name);
} else {
add_nvplist(&s->printable_list, s->printable_list_counter++, cep->name, cep->value);
}
return 1; /* Handled by us (guaranteed earlier) */
}
int conf_match_block(ConfigFile *conf, ConfigEntry *ce, SecurityGroup **block)
{
ConfigEntry *cep;
SecurityGroup *s = *block;
if (*block == NULL)
*block = s = safe_alloc(sizeof(SecurityGroup));
/* Check for simple form: match *; / mask *; */
if (ce->value)
{
unreal_add_masks(&s->mask, ce);
add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", ce->value);
}
/* Check for long form: match { .... } / mask { .... } */
for (cep = ce->items; cep; cep = cep->next)
{
if (!conf_match_item(conf, cep, &s) && !cep->value && !cep->items)
{
/* Valueless? Then it must be a mask like 10.0.0.0/8 */
unreal_add_masks(&s->mask, cep);
add_nvplist(&s->printable_list, s->printable_list_counter++, "mask", cep->name);
}
}
return 1;
}
int _conf_security_group(ConfigFile *conf, ConfigEntry *ce)
{
ConfigEntry *cep;
SecurityGroup *s = add_security_group(ce->value, 1);
for (cep = ce->items; cep; cep = cep->next)
{
if (!strcmp(cep->name, "priority"))
{
s->priority = atoi(cep->value);
DelListItem(s, securitygroups);
AddListItemPrio(s, securitygroups, s->priority);
} else
conf_match_item(conf, cep, &s);
}
return 1;
}
/** Check if the name of the security-group contains only valid characters.
* @param name The name of the group
* @returns 1 if name is valid, 0 if not (eg: illegal characters)
*/
int security_group_valid_name(const char *name)
{
const char *p;
if (strlen(name) > SECURITYGROUPLEN)
return 0; /* Too long */
for (p = name; *p; p++)
{
if (!isalnum(*p) && !strchr("_-", *p))
return 0; /* Character not allowed */
}
return 1;
}
/** Find a security-group.
* @param name The name of the security group
* @returns A SecurityGroup struct, or NULL if not found.
*/
SecurityGroup *find_security_group(const char *name)
{
SecurityGroup *s;
for (s = securitygroups; s; s = s->next)
if (!strcasecmp(name, s->name))
return s;
return NULL;
}
/** Checks if a security-group exists.
* This function takes the 'unknown-users' magic group into account as well.
* @param name The name of the security group
* @returns 1 if it exists, 0 if not
*/
int security_group_exists(const char *name)
{
if (!strcmp(name, "unknown-users") || find_security_group(name))
return 1;
return 0;
}
/** Add a new security-group and add it to the list, but search for existing one first.
* @param name The name of the security group
* @returns A SecurityGroup struct (already added to the 'securitygroups' linked list)
*/
SecurityGroup *add_security_group(const char *name, int priority)
{
SecurityGroup *s = find_security_group(name);
/* Existing? */
if (s)
return s;
/* Otherwise, create a new entry */
s = safe_alloc(sizeof(SecurityGroup));
strlcpy(s->name, name, sizeof(s->name));
s->priority = priority;
init_dynamic_set_block(&s->settings);
AddListItemPrio(s, securitygroups, priority);
return s;
}
/** Free a SecurityGroup struct */
void free_security_group(SecurityGroup *s)
{
if (s == NULL)
return;
// IMPORTANT: if you add anything here,
// then also update duplicate_security_group() !!!!
unreal_delete_masks(s->mask);
unreal_delete_masks(s->exclude_mask);
free_entire_name_list(s->security_group);
free_entire_name_list(s->exclude_security_group);
safe_crule_free(s->rule);
safe_crule_free(s->exclude_rule);
safe_free(s->prettyrule);
safe_free(s->exclude_prettyrule);
free_entire_name_list(s->ip);
free_entire_name_list(s->exclude_ip);
free_nvplist(s->extended);
free_nvplist(s->exclude_extended);
free_nvplist(s->printable_list);
free_dynamic_set_block(&s->settings);
safe_free(s);
}
/** Duplicate a SecurityGroup struct */
SecurityGroup *duplicate_security_group(SecurityGroup *s)
{
SecurityGroup *n;
if (s == NULL)
return NULL;
n = safe_alloc(sizeof(SecurityGroup));
n->mask = unreal_duplicate_masks(s->mask);
n->exclude_mask = unreal_duplicate_masks(s->exclude_mask);
n->security_group = duplicate_name_list(s->security_group);
n->exclude_security_group = duplicate_name_list(s->exclude_security_group);
if (s->prettyrule)
{
safe_strdup(n->prettyrule, s->prettyrule);
n->rule = crule_parse(n->prettyrule);
}
if (s->exclude_prettyrule)
{
safe_strdup(n->exclude_prettyrule, s->exclude_prettyrule);
n->exclude_rule = crule_parse(n->exclude_prettyrule);
}
n->ip = duplicate_name_list(s->exclude_ip);
n->extended = duplicate_nvplist(s->extended);
n->exclude_extended = duplicate_nvplist(s->exclude_extended);
n->printable_list = duplicate_nvplist(s->printable_list);
// not duplicated since it makes no sense: s->settings
return n;
}
/** Initialize the default security-group blocks */
void set_security_group_defaults(void)
{
SecurityGroup *s, *s_next;
/* First free all security groups */
for (s = securitygroups; s; s = s_next)
{
s_next = s->next;
free_security_group(s);
}
securitygroups = NULL;
/* Default group: webirc */
s = add_security_group("webirc-users", 50);
s->webirc = 1;
/* Default group: websocket */
s = add_security_group("websocket-users", 51);
s->websocket = 1;
/* Default group: known-users */
s = add_security_group("known-users", 100);
s->identified = 1;
s->reputation_score = 25;
s->webirc = 0;
/* Default group: tls-and-known-users */
s = add_security_group("tls-and-known-users", 200);
s->identified = 1;
s->reputation_score = 25;
s->webirc = 0;
s->tls = 1;
/* Default group: tls-users */
s = add_security_group("tls-users", 300);
s->tls = 1;
}
int user_matches_extended_list(Client *client, NameValuePrioList *e)
{
Extban *extban;
BanContext b;
for (; e; e = e->next)
{
extban = findmod_by_bantype_raw(e->name, strlen(e->name));
if (!extban ||
!(extban->options & EXTBOPT_TKL) ||
!(extban->is_banned_events & BANCHK_TKL))
{
continue; /* extban not found or of incorrect type */
}
memset(&b, 0, sizeof(BanContext));
b.client = client;
b.banstr = e->value;
b.ban_check_types = BANCHK_TKL;
if (extban->is_banned(&b))
return 1;
}
return 0;
}
int test_extended_list(Extban *extban, ConfigEntry *cep, int *errors)
{
BanContext b;
if (cep->value)
{
memset(&b, 0, sizeof(BanContext));
b.banstr = cep->value;
b.ban_check_types = BANCHK_TKL;
b.what = MODE_ADD;
if (!extban->conv_param(&b, extban))
{
config_error("%s:%i: %s has an invalid value",
cep->file->filename, cep->line_number, cep->name);
(*errors)++;
return 0;
}
}
for (cep = cep->items; cep; cep = cep->next)
{
memset(&b, 0, sizeof(BanContext));
b.banstr = cep->name;
b.ban_check_types = BANCHK_TKL;
b.what = MODE_ADD;
if (!extban->conv_param(&b, extban))
{
config_error("%s:%i: %s has an invalid value",
cep->file->filename, cep->line_number, cep->name);
(*errors)++;
return 0;
}
}
return 1;
}
/** Returns 1 if the user is allowed by any of the security groups in the named list.
* This is only used by security-group::security-group and
* security-group::exclude-security-group.
* @param client Client to check
* @param l The NameList
* @returns 1 if any of the security groups match, 0 if none of them matched.
*/
int user_allowed_by_security_group_list(Client *client, NameList *l)
{
for (; l; l = l->next)
if (user_allowed_by_security_group_name(client, l->name))
return 1;
return 0;
}
/** Helper for security-group::rule and mask::rule */
int user_allowed_by_rule(Client *client, CRuleNode *rule)
{
crule_context context;
memset(&context, 0, sizeof(context));
context.client = client;
return crule_eval(&context, rule);
}
/** Returns 1 if the user is OK as far as the security-group is concerned.
* @param client The client to check
* @param s The security-group to check against
* @retval 1 if user is allowed by security-group, 0 if not.
*/
int user_allowed_by_security_group(Client *client, SecurityGroup *s)
{
static int recursion_security_group = 0;
/* Allow NULL securitygroup, makes it easier in the code elsewhere */
if (!s)
return 0;
if (recursion_security_group > 8)
{
unreal_log(ULOG_WARNING, "main", "SECURITY_GROUP_LOOP_DETECTED", client,
"Loop detected while processing security-group '$security_group' -- "
"are you perhaps referencing a security-group from a security-group?",
log_data_string("security_group", s->name));
return 0;
}
recursion_security_group++;
/* DO NOT USE 'return' IN CODE BELOW!!!!!!!!!
* - use 'goto user_not_allowed' to reject
* - use 'goto user_allowed' to accept
*/
/* Process EXCLUSION criteria first... */
if (s->exclude_identified && IsLoggedIn(client))
goto user_not_allowed;
if (s->exclude_webirc && moddata_client_get(client, "webirc"))
goto user_not_allowed;
if (s->exclude_websocket && moddata_client_get(client, "websocket"))
goto user_not_allowed;
if ((s->exclude_reputation_score > 0) && (GetReputation(client) >= s->exclude_reputation_score))
goto user_not_allowed;
if ((s->exclude_reputation_score < 0) && (GetReputation(client) < 0 - s->exclude_reputation_score))
goto user_not_allowed;
if (s->exclude_connect_time != 0)
{
long connect_time = get_connected_time(client);
if ((s->exclude_connect_time > 0) && (connect_time >= s->exclude_connect_time))
goto user_not_allowed;
if ((s->exclude_connect_time < 0) && (connect_time < 0 - s->exclude_connect_time))
goto user_not_allowed;
}
if (s->exclude_tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
goto user_not_allowed;
if (s->exclude_mask && unreal_mask_match(client, s->exclude_mask))
goto user_not_allowed;
if (s->exclude_ip && unreal_match_iplist(client, s->exclude_ip))
goto user_not_allowed;
if (s->exclude_rule && user_allowed_by_rule(client, s->exclude_rule))
goto user_not_allowed;
if (s->exclude_extended && user_matches_extended_list(client, s->exclude_extended))
goto user_not_allowed;
if (s->exclude_security_group && user_allowed_by_security_group_list(client, s->exclude_security_group))
goto user_not_allowed;
/* Then process INCLUSION criteria... */
if (s->identified && IsLoggedIn(client))
goto user_allowed;
if (s->webirc && moddata_client_get(client, "webirc"))
goto user_allowed;
if (s->websocket && moddata_client_get(client, "websocket"))
goto user_allowed;
if ((s->reputation_score > 0) && (GetReputation(client) >= s->reputation_score))
goto user_allowed;
if ((s->reputation_score < 0) && (GetReputation(client) < 0 - s->reputation_score))
goto user_allowed;
if (s->connect_time != 0)
{
long connect_time = get_connected_time(client);
if ((s->connect_time > 0) && (connect_time >= s->connect_time))
goto user_allowed;
if ((s->connect_time < 0) && (connect_time < 0 - s->connect_time))
goto user_allowed;
}
if (s->tls && (IsSecureConnect(client) || (MyConnect(client) && IsSecure(client))))
goto user_allowed;
if (s->mask && unreal_mask_match(client, s->mask))
goto user_allowed;
if (s->ip && unreal_match_iplist(client, s->ip))
goto user_allowed;
if (s->rule && user_allowed_by_rule(client, s->rule))
goto user_allowed;
if (s->extended && user_matches_extended_list(client, s->extended))
goto user_allowed;
if (s->security_group && user_allowed_by_security_group_list(client, s->security_group))
goto user_allowed;
user_not_allowed:
recursion_security_group--;
return 0;
user_allowed:
recursion_security_group--;
return 1;
}
/** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version.
* @param client The client to check
* @param secgroupname The name of the security-group to check against
* @retval 1 if user is allowed by security-group, 0 if not.
*/
int user_allowed_by_security_group_name(Client *client, const char *secgroupname)
{
SecurityGroup *s;
/* Handle the magical 'unknown-users' case. */
if (!strcmp(secgroupname, "unknown-users"))
{
/* This is simply the inverse of 'known-users' */
s = find_security_group("known-users");
if (!s)
return 0; /* that's weird!? pretty impossible. */
return !user_allowed_by_security_group(client, s);
}
/* Find the group and evaluate it */
s = find_security_group(secgroupname);
if (!s)
return 0; /* security group not found: no match */
return user_allowed_by_security_group(client, s);
}
/** Get comma separated list of matching security groups for 'client'.
* This is usually only used for displaying purposes.
* @returns string like "unknown-users,tls-users" from a static buffer.
*/
const char *get_security_groups(Client *client)
{
SecurityGroup *s;
static char buf[512];
*buf = '\0';
/* We put known-users or unknown-users at the beginning.
* The latter is special and doesn't actually exist
* in the linked list, hence the special code here,
* and again later in the for loop to skip it.
*/
if (user_allowed_by_security_group_name(client, "known-users"))
strlcat(buf, "known-users,", sizeof(buf));
else
strlcat(buf, "unknown-users,", sizeof(buf));
for (s = securitygroups; s; s = s->next)
{
if (strcmp(s->name, "known-users") &&
user_allowed_by_security_group(client, s))
{
strlcat(buf, s->name, sizeof(buf));
strlcat(buf, ",", sizeof(buf));
}
}
if (*buf)
buf[strlen(buf)-1] = '\0';
return buf;
}