diff --git a/doc/configuration.txt b/doc/configuration.txt index 81a50994e..7e9a65036 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -10750,15 +10750,19 @@ http_auth() : boolean fetch function is not really useful outside of ACLs. Currently only http basic auth is supported. -http_auth_group() : group - Returns a boolean indicating whether the authentication data received from - the client match a username & password stored in the specified userlist, and - whether that username belongs to one of the groups supplied in ACL patterns. +http_auth_group() : string + Returns a string corresponding to the user name found in the authentication + data received from the client if both the user name and password are valid + according to the specified userlist. The main purpose is to use it in ACLs + where it is then checked whether the user belongs to any group within a list. This fetch function is not really useful outside of ACLs. Currently only http basic auth is supported. ACL derivatives : - http_auth_group() : user group match + http_auth_group() : group ... + Returns true when the user extracted from the request and whose password is + valid according to the specified userlist belongs to at least one of the + groups. http_first_req : boolean Returns true when the request being processed is the first one of the diff --git a/include/proto/auth.h b/include/proto/auth.h index 711b2f89f..a9606134b 100644 --- a/include/proto/auth.h +++ b/include/proto/auth.h @@ -20,9 +20,11 @@ extern struct userlist *userlist; struct userlist *auth_find_userlist(char *name); unsigned int auth_resolve_groups(struct userlist *l, char *groups); +int userlist_postinit(); void userlist_free(struct userlist *ul); enum pat_match_res pat_match_auth(struct sample *smp, struct pattern *pattern); -int check_user(struct userlist *ul, unsigned int group_mask, const char *user, const char *pass); +int check_user(struct userlist *ul, const char *user, const char *pass); +int check_group(struct userlist *ul, char *name); #endif /* _PROTO_AUTH_H */ diff --git a/include/types/auth.h b/include/types/auth.h index e60d363ec..964f29b6a 100644 --- a/include/types/auth.h +++ b/include/types/auth.h @@ -18,17 +18,26 @@ #include -#define MAX_AUTH_GROUPS (unsigned int)(sizeof(int)*8) - #define AU_O_INSECURE 0x00000001 /* insecure, unencrypted password */ +struct auth_groups { + struct auth_groups *next; + char *name; + char *groupusers; /* Just used during the configuration parsing. */ +}; + +struct auth_groups_list { + struct auth_groups_list *next; + struct auth_groups *group; +}; + struct auth_users { struct auth_users *next; unsigned int flags; char *user, *pass; union { - char *groups; - unsigned int group_mask; + char *groups_names; /* Just used during the configuration parsing. */ + struct auth_groups_list *groups; } u; }; @@ -36,9 +45,7 @@ struct userlist { struct userlist *next; char *name; struct auth_users *users; - int grpcnt; - char *groups[MAX_AUTH_GROUPS]; - char **groupusers; + struct auth_groups *groups; }; #endif /* _TYPES_AUTH_H */ diff --git a/include/types/pattern.h b/include/types/pattern.h index 4e893724f..93ee29700 100644 --- a/include/types/pattern.h +++ b/include/types/pattern.h @@ -138,7 +138,6 @@ struct pattern { unsigned char mask; /* number of bits */ } ipv6; /* IPv6 address/mask */ struct pat_time time; /* valid hours and days */ - unsigned int group_mask; struct eb_root *tree; /* tree storing all values if any */ } val; /* direct value */ union { diff --git a/src/acl.c b/src/acl.c index abfd04cc2..8ce49a91b 100644 --- a/src/acl.c +++ b/src/acl.c @@ -1053,18 +1053,13 @@ int acl_find_targets(struct proxy *p) continue; } + /* For each pattern, check if the group exists. */ list_for_each_entry(pattern, &expr->pat.patterns, list) { - /* this keyword only has one argument */ - pattern->val.group_mask = auth_resolve_groups(expr->smp->arg_p->data.usr, pattern->ptr.str); - - if (!pattern->val.group_mask) { + if (!check_group(expr->smp->arg_p->data.usr, pattern->ptr.str)) { Alert("proxy %s: acl %s %s(): invalid group '%s'.\n", p->id, acl->name, expr->kw, pattern->ptr.str); cfgerr++; } - free(pattern->ptr.str); - pattern->ptr.str = NULL; - pattern->len = 0; } } } diff --git a/src/auth.c b/src/auth.c index 203b5aaf7..961c28866 100644 --- a/src/auth.c +++ b/src/auth.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -54,36 +55,14 @@ auth_find_userlist(char *name) return NULL; } -/* find group_mask for selected gropus. The function returns 1 if OK or nothing to do, - * 0 if case of unresolved groupname. - * WARING: the function destroys the list (strtok), so it can only be used once. - */ - -unsigned int -auth_resolve_groups(struct userlist *l, char *groups) +int check_group(struct userlist *ul, char *name) { + struct auth_groups *ag; - char *group = NULL; - unsigned int g, group_mask = 0; - - if (!groups || !*groups) - return 0; - - while ((group = strtok(group?NULL:groups," "))) { - for (g = 0; g < l->grpcnt; g++) - if (!strcmp(l->groups[g], group)) - break; - - if (g == l->grpcnt) { - Alert("No such group '%s' in userlist '%s'.\n", - group, l->name); - return 0; - } - - group_mask |= (1 << g); - } - - return group_mask; + for (ag = ul->groups; ag; ag = ag->next) + if (strcmp(name, ag->name) == 0) + return 1; + return 0; } void @@ -91,11 +70,21 @@ userlist_free(struct userlist *ul) { struct userlist *tul; struct auth_users *au, *tau; - int i; + struct auth_groups_list *agl, *tagl; + struct auth_groups *ag, *tag; while (ul) { + /* Free users. */ au = ul->users; while (au) { + /* Free groups that own current user. */ + agl = au->u.groups; + while (agl) { + tagl = agl; + agl = agl->next; + free(tagl); + } + tau = au; au = au->next; free(tau->user); @@ -103,30 +92,135 @@ userlist_free(struct userlist *ul) free(tau); } + /* Free grouplist. */ + ag = ul->groups; + while (ag) { + tag = ag; + ag = ag->next; + free(tag->name); + free(tag); + } + tul = ul; ul = ul->next; - - for (i = 0; i < tul->grpcnt; i++) - free(tul->groups[i]); - free(tul->name); free(tul); }; } +int userlist_postinit() +{ + struct userlist *curuserlist = NULL; + + /* Resolve usernames and groupnames. */ + for (curuserlist = userlist; curuserlist; curuserlist = curuserlist->next) { + struct auth_groups *ag; + struct auth_users *curuser; + struct auth_groups_list *grl; + + for (curuser = curuserlist->users; curuser; curuser = curuser->next) { + char *group = NULL; + struct auth_groups_list *groups = NULL; + + if (!curuser->u.groups_names) + continue; + + while ((group = strtok(group?NULL:curuser->u.groups_names, ","))) { + for (ag = curuserlist->groups; ag; ag = ag->next) { + if (!strcmp(ag->name, group)) + break; + } + + if (!ag) { + Alert("userlist '%s': no such group '%s' specified in user '%s'\n", + curuserlist->name, group, curuser->user); + return ERR_ALERT | ERR_FATAL; + } + + /* Add this group at the group userlist. */ + grl = calloc(1, sizeof(*grl)); + if (!grl) { + Alert("userlist '%s': no more memory when trying to allocate the user groups.\n", + curuserlist->name); + return ERR_ALERT | ERR_FATAL; + } + + grl->group = ag; + grl->next = groups; + groups = grl; + } + + free(curuser->u.groups); + curuser->u.groups = groups; + } + + for (ag = curuserlist->groups; ag; ag = ag->next) { + char *user = NULL; + + if (!ag->groupusers) + continue; + + while ((user = strtok(user?NULL:ag->groupusers, ","))) { + for (curuser = curuserlist->users; curuser; curuser = curuser->next) { + if (!strcmp(curuser->user, user)) + break; + } + + if (!curuser) { + Alert("userlist '%s': no such user '%s' specified in group '%s'\n", + curuserlist->name, user, ag->name); + return ERR_ALERT | ERR_FATAL; + } + + /* Add this group at the group userlist. */ + grl = calloc(1, sizeof(*grl)); + if (!grl) { + Alert("userlist '%s': no more memory when trying to allocate the user groups.\n", + curuserlist->name); + return ERR_ALERT | ERR_FATAL; + } + + grl->group = ag; + grl->next = curuser->u.groups; + curuser->u.groups = grl; + } + + free(ag->groupusers); + ag->groupusers = NULL; + } + +#ifdef DEBUG_AUTH + for (ag = curuserlist->groups; ag; ag = ag->next) { + struct auth_groups_list *agl; + + fprintf(stderr, "group %s, id %p, users:", ag->name, ag); + for (curuser = curuserlist->users; curuser; curuser = curuser->next) { + for (agl = curuser->u.groups; agl; agl = agl->next) { + if (agl->group == ag) + fprintf(stderr, " %s", curuser->user); + } + } + fprintf(stderr, "\n"); + } +#endif + } + + return ERR_NONE; +} + /* * Authenticate and authorize user; return 1 if OK, 0 if case of error. */ int -check_user(struct userlist *ul, unsigned int group_mask, const char *user, const char *pass) +check_user(struct userlist *ul, const char *user, const char *pass) { struct auth_users *u; const char *ep; #ifdef DEBUG_AUTH - fprintf(stderr, "req: userlist=%s, user=%s, pass=%s, group_mask=%u\n", - ul->name, user, pass, group_mask); + fprintf(stderr, "req: userlist=%s, user=%s, pass=%s, group=%s\n", + ul->name, user, pass, group); #endif for (u = ul->users; u; u = u->next) @@ -137,17 +231,12 @@ check_user(struct userlist *ul, unsigned int group_mask, const char *user, const return 0; #ifdef DEBUG_AUTH - fprintf(stderr, "cfg: user=%s, pass=%s, group_mask=%u, flags=%X", - u->user, u->pass, u->u.group_mask, u->flags); + fprintf(stderr, "cfg: user=%s, pass=%s, flags=%X, groups=", + u->user, u->pass, u->flags); + for (agl = u->u.groups; agl; agl = agl->next) + fprintf(stderr, " %s", agl->group->name); #endif - /* - * if user matches but group does not, - * it makes no sens to check passwords - */ - if (group_mask && !(group_mask & u->u.group_mask)) - return 0; - if (!(u->flags & AU_O_INSECURE)) { #ifdef CONFIG_HAP_CRYPT ep = crypt(pass, u->pass); @@ -170,14 +259,28 @@ check_user(struct userlist *ul, unsigned int group_mask, const char *user, const enum pat_match_res pat_match_auth(struct sample *smp, struct pattern *pattern) { - struct userlist *ul = smp->ctx.a[0]; - char *user = smp->ctx.a[1]; - char *pass = smp->ctx.a[2]; - unsigned int group_mask = pattern->val.group_mask; + struct auth_users *u; + struct auth_groups_list *agl; - if (check_user(ul, group_mask, user, pass)) - return PAT_MATCH; - else + /* Check if the userlist is present in the context data. */ + if (!ul) return PAT_NOMATCH; + + /* Browse the userlist for searching user. */ + for (u = ul->users; u; u = u->next) { + if (strcmp(smp->data.str.str, u->user) == 0) + break; + } + if (!u) + return 0; + + /* Browse each group for searching group name that match the pattern. */ + for (agl = u->u.groups; agl; agl = agl->next) { + if (strcmp(agl->group->name, pattern->ptr.str) == 0) + break; + } + if (!agl) + return PAT_NOMATCH; + return PAT_MATCH; } diff --git a/src/cfgparse.c b/src/cfgparse.c index 5cbb9e2a6..10dfa895e 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -6248,10 +6248,8 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) goto out; } - newul->groupusers = calloc(MAX_AUTH_GROUPS, sizeof(char *)); newul->name = strdup(args[1]); - - if (!newul->groupusers | !newul->name) { + if (!newul->name) { Alert("parsing [%s:%d]: out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; @@ -6261,8 +6259,9 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) userlist = newul; } else if (!strcmp(args[0], "group")) { /* new group */ - int cur_arg, i; + int cur_arg; const char *err; + struct auth_groups *ag; if (!*args[1]) { Alert("parsing [%s:%d]: '%s' expects as arguments.\n", @@ -6279,18 +6278,25 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) goto out; } - for(i = 0; i < userlist->grpcnt; i++) - if (!strcmp(userlist->groups[i], args[1])) { + for (ag = userlist->groups; ag; ag = ag->next) + if (!strcmp(ag->name, args[1])) { Warning("parsing [%s:%d]: ignoring duplicated group '%s' in userlist '%s'.\n", file, linenum, args[1], userlist->name); err_code |= ERR_ALERT; goto out; } - if (userlist->grpcnt >= MAX_AUTH_GROUPS) { - Alert("parsing [%s:%d]: too many groups (%u) in in userlist '%s' while adding group '%s'.\n", - file, linenum, MAX_AUTH_GROUPS, userlist->name, args[1]); - err_code |= ERR_ALERT | ERR_FATAL; + ag = calloc(1, sizeof(*ag)); + if (!ag) { + Alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + + ag->name = strdup(args[1]); + if (!ag) { + Alert("parsing [%s:%d]: out of memory.\n", file, linenum); + err_code |= ERR_ALERT | ERR_ABORT; goto out; } @@ -6298,7 +6304,7 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) while (*args[cur_arg]) { if (!strcmp(args[cur_arg], "users")) { - userlist->groupusers[userlist->grpcnt] = strdup(args[cur_arg + 1]); + ag->groupusers = strdup(args[cur_arg + 1]); cur_arg += 2; continue; } else { @@ -6309,7 +6315,9 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) } } - userlist->groups[userlist->grpcnt++] = strdup(args[1]); + ag->next = userlist->groups; + userlist->groups = ag; + } else if (!strcmp(args[0], "user")) { /* new user */ struct auth_users *newuser; int cur_arg; @@ -6359,7 +6367,7 @@ cfg_parse_users(const char *file, int linenum, char **args, int kwm) cur_arg += 2; continue; } else if (!strcmp(args[cur_arg], "groups")) { - newuser->u.groups = strdup(args[cur_arg + 1]); + newuser->u.groups_names = strdup(args[cur_arg + 1]); cur_arg += 2; continue; } else { @@ -6602,7 +6610,6 @@ int check_config_validity() int cfgerr = 0; struct proxy *curproxy = NULL; struct server *newsrv = NULL; - struct userlist *curuserlist = NULL; int err_code = 0; unsigned int next_pxid = 1; struct bind_conf *bind_conf; @@ -6623,6 +6630,11 @@ int check_config_validity() pool2_capture = create_pool("capture", global.tune.cookie_len, MEM_F_SHARED); + /* Post initialisation of the users and groups lists. */ + err_code = userlist_postinit(); + if (err_code != ERR_NONE) + goto out; + /* first, we will invert the proxy list order */ curproxy = NULL; while (proxy) { @@ -7091,7 +7103,9 @@ out_uri_auth_compat: curproxy->conf.args.line = 0; } - /* only now we can check if some args remain unresolved */ + /* only now we can check if some args remain unresolved. + * This must be done after the users and groups resolution. + */ cfgerr += smp_resolve_args(curproxy); if (!cfgerr) cfgerr += acl_find_targets(curproxy); @@ -7680,78 +7694,6 @@ out_uri_auth_compat: } } - for (curuserlist = userlist; curuserlist; curuserlist = curuserlist->next) { - struct auth_users *curuser; - int g; - - for (curuser = curuserlist->users; curuser; curuser = curuser->next) { - unsigned int group_mask = 0; - char *group = NULL; - - if (!curuser->u.groups) - continue; - - while ((group = strtok(group?NULL:curuser->u.groups, ","))) { - - for (g = 0; g < curuserlist->grpcnt; g++) - if (!strcmp(curuserlist->groups[g], group)) - break; - - if (g == curuserlist->grpcnt) { - Alert("userlist '%s': no such group '%s' specified in user '%s'\n", - curuserlist->name, group, curuser->user); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - - group_mask |= (1 << g); - } - - free(curuser->u.groups); - curuser->u.group_mask = group_mask; - } - - for (g = 0; g < curuserlist->grpcnt; g++) { - char *user = NULL; - - if (!curuserlist->groupusers[g]) - continue; - - while ((user = strtok(user?NULL:curuserlist->groupusers[g], ","))) { - for (curuser = curuserlist->users; curuser; curuser = curuser->next) - if (!strcmp(curuser->user, user)) - break; - - if (!curuser) { - Alert("userlist '%s': no such user '%s' specified in group '%s'\n", - curuserlist->name, user, curuserlist->groups[g]); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - - curuser->u.group_mask |= (1 << g); - } - - free(curuserlist->groupusers[g]); - } - - free(curuserlist->groupusers); - -#ifdef DEBUG_AUTH - for (g = 0; g < curuserlist->grpcnt; g++) { - fprintf(stderr, "group %s, id %d, mask %08X, users:", curuserlist->groups[g], g , 1 << g); - - for (curuser = curuserlist->users; curuser; curuser = curuser->next) { - if (curuser->u.group_mask & (1 << g)) - fprintf(stderr, " %s", curuser->user); - } - - fprintf(stderr, "\n"); - } -#endif - - } - /* automatically compute fullconn if not set. We must not do it in the * loop above because cross-references are not yet fully resolved. */ diff --git a/src/pattern.c b/src/pattern.c index 306b514b2..31df937b5 100644 --- a/src/pattern.c +++ b/src/pattern.c @@ -242,45 +242,6 @@ int pat_parse_bin(const char **text, struct pattern *pattern, enum pat_usage usa return !!parse_binary(*text, &pattern->ptr.str, &pattern->len, err); } -/* Parse and concatenate all further strings into one. */ -int -pat_parse_strcat(const char **text, struct pattern *pattern, enum pat_usage usage, int *opaque, char **err) -{ - int len = 0, i; - char *s; - struct chunk *trash; - - for (i = 0; *text[i]; i++) - len += strlen(text[i])+1; - - pattern->type = SMP_T_CSTR; - if (usage == PAT_U_COMPILE) { - pattern->ptr.str = calloc(1, len); - if (!pattern->ptr.str) { - memprintf(err, "out of memory while loading pattern"); - return 0; - } - } - else { - trash = get_trash_chunk(); - if (trash->size < len) { - memprintf(err, "no space avalaible in the buffer. expect %d, provides %d", - len, trash->size); - return 0; - } - pattern->ptr.str = trash->str; - } - - s = pattern->ptr.str; - - for (i = 0; *text[i]; i++) - s += sprintf(s, i?" %s":"%s", text[i]); - - pattern->len = len; - - return i; -} - /* Parse a regex. It is allocated. */ int pat_parse_reg(const char **text, struct pattern *pattern, enum pat_usage usage, int *opaque, char **err) { diff --git a/src/proto_http.c b/src/proto_http.c index 0c6a6233f..702ef22fb 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -1361,6 +1361,9 @@ const char *http_parse_reqline(struct http_msg *msg, * have the credentials overwritten by another session in parallel. */ +/* This bufffer is initialized in the file 'src/haproxy.c'. This length is + * set according to global.tune.bufsize. + */ char *get_http_auth_buff; int @@ -9630,7 +9633,7 @@ smp_fetch_http_auth(struct proxy *px, struct session *l4, void *l7, unsigned int return 0; smp->type = SMP_T_BOOL; - smp->data.uint = check_user(args->data.usr, 0, l4->txn.auth.user, l4->txn.auth.pass); + smp->data.uint = check_user(args->data.usr, l4->txn.auth.user, l4->txn.auth.pass); return 1; } @@ -9648,20 +9651,19 @@ smp_fetch_http_auth_grp(struct proxy *px, struct session *l4, void *l7, unsigned if (!get_http_auth(l4)) return 0; - /* pat_match_auth() will need several information at once */ - smp->ctx.a[0] = args->data.usr; /* user list */ - smp->ctx.a[1] = l4->txn.auth.user; /* user name */ - smp->ctx.a[2] = l4->txn.auth.pass; /* password */ - /* if the user does not belong to the userlist or has a wrong password, * report that it unconditionally does not match. Otherwise we return - * a non-zero integer which will be ignored anyway since all the params - * that pat_match_auth() will use are in test->ctx.a[0,1,2]. + * a string containing the username. */ - smp->type = SMP_T_BOOL; - smp->data.uint = check_user(args->data.usr, 0, l4->txn.auth.user, l4->txn.auth.pass); - if (smp->data.uint) - smp->type = SMP_T_UINT; + if (!check_user(args->data.usr, l4->txn.auth.user, l4->txn.auth.pass)) + return 0; + + /* pat_match_auth() will need the user list */ + smp->ctx.a[0] = args->data.usr; + + smp->type = SMP_T_CSTR; + smp->data.str.str = l4->txn.auth.user; + smp->data.str.len = strlen(l4->txn.auth.user); return 1; } @@ -10371,7 +10373,7 @@ static struct acl_kw_list acl_kws = {ILH, { { "hdr_reg", "req.hdr", pat_parse_reg, pat_match_reg }, { "hdr_sub", "req.hdr", pat_parse_str, pat_match_sub }, - { "http_auth_group", NULL, pat_parse_strcat, pat_match_auth }, + { "http_auth_group", NULL, pat_parse_str, pat_match_auth }, { "method", NULL, pat_parse_meth, pat_match_meth }, @@ -10461,7 +10463,7 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { { "hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_UINT, SMP_USE_HRQHV }, { "http_auth", smp_fetch_http_auth, ARG1(1,USR), NULL, SMP_T_BOOL, SMP_USE_HRQHV }, - { "http_auth_group", smp_fetch_http_auth_grp, ARG1(1,USR), NULL, SMP_T_BOOL, SMP_USE_HRQHV }, + { "http_auth_group", smp_fetch_http_auth_grp, ARG1(1,USR), NULL, SMP_T_CSTR, SMP_USE_HRQHV }, { "http_first_req", smp_fetch_http_first_req, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHP }, { "method", smp_fetch_meth, 0, NULL, SMP_T_UINT, SMP_USE_HRQHP }, { "path", smp_fetch_path, 0, NULL, SMP_T_CSTR, SMP_USE_HRQHV },