[MINOR] generic auth support with groups and encrypted passwords

Add generic authentication & authorization support.

Groups are implemented as bitmaps so the count is limited to
sizeof(int)*8 == 32.

Encrypted passwords are supported with libcrypt and crypt(3), so it is
possible to use any method supported by your system. For example modern
Linux/glibc instalations support MD5/SHA-256/SHA-512 and of course classic,
DES-based encryption.
diff --git a/Makefile b/Makefile
index 0c9df3f..022731d 100644
--- a/Makefile
+++ b/Makefile
@@ -108,7 +108,8 @@
 #### Debug settings
 # You can enable debugging on specific code parts by setting DEBUG=-DDEBUG_xxx.
 # Currently defined DEBUG macros include DEBUG_FULL, DEBUG_MEMORY, DEBUG_FSM,
-# and DEBUG_HASH. Please check sources for exact meaning or do not use at all.
+# DEBUG_HASH and DEBUG_AUTH. Please check sources for exact meaning or do not
+# use at all.
 DEBUG =
 
 #### Additional include and library dirs
@@ -170,6 +171,7 @@
   USE_GETSOCKNAME = implicit
   USE_POLL        = implicit
   USE_TPROXY      = implicit
+  USE_LIBCRYPT    = implicit
 else
 ifeq ($(TARGET),linux24)
   # This is for standard Linux 2.4 with netfilter but without epoll()
@@ -177,6 +179,7 @@
   USE_NETFILTER   = implicit
   USE_POLL        = implicit
   USE_TPROXY      = implicit
+  USE_LIBCRYPT    = implicit
 else
 ifeq ($(TARGET),linux24e)
   # This is for enhanced Linux 2.4 with netfilter and epoll() patch > 0.21
@@ -187,6 +190,7 @@
   USE_SEPOLL      = implicit
   USE_MY_EPOLL    = implicit
   USE_TPROXY      = implicit
+  USE_LIBCRYPT    = implicit
 else
 ifeq ($(TARGET),linux26)
   # This is for standard Linux 2.6 with netfilter and standard epoll()
@@ -196,6 +200,7 @@
   USE_EPOLL       = implicit
   USE_SEPOLL      = implicit
   USE_TPROXY      = implicit
+  USE_LIBCRYPT    = implicit
 else
 ifeq ($(TARGET),solaris)
   # This is for Solaris 8
@@ -209,6 +214,7 @@
   USE_POLL       = implicit
   USE_KQUEUE     = implicit
   USE_TPROXY     = implicit
+  USE_LIBCRYPT   = implicit
 else
 ifeq ($(TARGET),openbsd)
   # This is for OpenBSD >= 3.0
@@ -324,6 +330,12 @@
 BUILD_OPTIONS  += $(call ignore_implicit,USE_LINUX_TPROXY)
 endif
 
+ifneq ($(USE_LIBCRYPT),)
+OPTIONS_CFLAGS  += -DCONFIG_HAP_CRYPT
+BUILD_OPTIONS   += $(call ignore_implicit,USE_LIBCRYPT)
+OPTIONS_LDFLAGS += -lcrypt
+endif
+
 ifneq ($(USE_POLL),)
 OPTIONS_CFLAGS += -DENABLE_POLL
 OPTIONS_OBJS   += src/ev_poll.o
@@ -464,7 +476,7 @@
        src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o src/lb_map.o \
        src/stream_interface.o src/dumpstats.o src/proto_tcp.o \
        src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \
-       src/acl.o src/pattern.o src/memory.o src/freq_ctr.o
+       src/acl.o src/pattern.o src/memory.o src/freq_ctr.o src/auth.o
 
 EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \
               $(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \
diff --git a/include/common/cfgparse.h b/include/common/cfgparse.h
index 3b376a0..a67f0d1 100644
--- a/include/common/cfgparse.h
+++ b/include/common/cfgparse.h
@@ -32,6 +32,7 @@
 #define CFG_NONE	0
 #define CFG_GLOBAL	1
 #define CFG_LISTEN	2
+#define CFG_USERLIST	3
 
 struct cfg_keyword {
 	int section;                            /* section type for this keyword */
diff --git a/include/common/uri_auth.h b/include/common/uri_auth.h
index 64f818b..b4c297c 100644
--- a/include/common/uri_auth.h
+++ b/include/common/uri_auth.h
@@ -15,6 +15,8 @@
 
 #include <common/config.h>
 
+#include <types/auth.h>
+
 /* here we find a very basic list of base64-encoded 'user:passwd' strings */
 struct user_auth {
 	struct user_auth *next;		/* next entry, NULL if none */
@@ -46,6 +48,7 @@
 	int flags;			/* some flags describing the statistics page */
 	struct user_auth *users;	/* linked list of valid user:passwd couples */
 	struct stat_scope *scope;	/* linked list of authorized proxies */
+	struct list req_acl; 		/* */
 	struct uri_auth *next;		/* Used at deinit() to build a list of unique elements */
 };
 
diff --git a/include/proto/auth.h b/include/proto/auth.h
new file mode 100644
index 0000000..9808621
--- /dev/null
+++ b/include/proto/auth.h
@@ -0,0 +1,36 @@
+/*
+ * User authentication & authorization.
+ *
+ * Copyright 2010 Krzysztof Piotr Oledzki <ole@ans.pl>
+ *
+ * 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.
+ *
+ */
+
+#ifndef _PROTO_AUTH_H
+#define _PROTO_AUTH_H
+
+#include <common/config.h>
+#include <types/auth.h>
+
+extern struct userlist *userlist;
+
+struct userlist *auth_find_userlist(char *name);
+unsigned int auth_resolve_groups(struct userlist *l, char *groups);
+struct req_acl_rule *parse_auth_cond(const char **args, const char *file, int linenum, struct list *known_acl, int *acl_requires);
+void userlist_free(struct userlist *ul);
+void req_acl_free(struct list *r);
+int acl_match_auth(struct acl_test *test, struct acl_pattern *pattern);
+
+#endif /* _PROTO_AUTH_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
+
diff --git a/include/types/auth.h b/include/types/auth.h
new file mode 100644
index 0000000..d278de6
--- /dev/null
+++ b/include/types/auth.h
@@ -0,0 +1,73 @@
+/*
+ * User authentication & authorization.
+ *
+ * Copyright 2010 Krzysztof Piotr Oledzki <ole@ans.pl>
+ *
+ * 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.
+ *
+ */
+
+#ifndef _TYPES_AUTH_H
+#define _TYPES_AUTH_H
+
+#include <common/config.h>
+#include <common/mini-clist.h>
+
+#include <types/auth.h>
+
+#define MAX_AUTH_GROUPS (unsigned int)(sizeof(int)*8)
+
+#define AU_O_INSECURE	0x00000001		/* insecure, unencrypted password */
+
+enum {
+	PR_REQ_ACL_ACT_UNKNOWN = 0,
+	PR_REQ_ACL_ACT_ALLOW,
+	PR_REQ_ACL_ACT_DENY,
+	PR_REQ_ACL_ACT_HTTP_AUTH,
+
+	PR_REQ_ACL_ACT_MAX
+};
+
+
+struct req_acl_rule {
+	struct list list;
+	struct acl_cond *cond;			/* acl condition to meet */
+	unsigned int action;
+		union {
+			struct {
+			char *realm;
+		} http_auth;
+	};
+};
+
+struct auth_users {
+	struct auth_users *next;
+	unsigned int flags;
+	char *user, *pass;
+	union {
+		char *groups;
+		unsigned int group_mask;
+	};
+};
+
+struct userlist {
+	struct userlist *next;
+	char *name;
+	struct auth_users *users;
+	int grpcnt;
+	char *groups[MAX_AUTH_GROUPS];
+	char **groupusers;
+};
+
+#endif /* _TYPES_AUTH_H */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ */
+
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 0000000..58fc5be
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,241 @@
+/*
+ * User authentication & authorization
+ *
+ * Copyright 2010 Krzysztof Piotr Oledzki <ole@ans.pl>
+ *
+ * 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.
+ *
+ */
+
+#define _XOPEN_SOURCE 500
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <common/config.h>
+
+#include <proto/acl.h>
+#include <proto/log.h>
+
+#include <types/auth.h>
+
+struct userlist *userlist = NULL;    /* list of all existing userlists */
+
+/* find targets for selected gropus. The function returns pointer to
+ * the userlist struct ot NULL if name is NULL/empty or unresolvable.
+ */
+
+struct userlist *
+auth_find_userlist(char *name)
+{
+	struct userlist *l;
+
+	if (!name || !*name)
+		return NULL;
+
+	for (l = userlist; l; l = l->next)
+		if (!strcmp(l->name, name))
+			return l;
+
+	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)
+{
+
+	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;
+}
+
+struct req_acl_rule *
+parse_auth_cond(const char **args, const char *file, int linenum, struct list *known_acl, int *acl_requires)
+{
+	struct req_acl_rule *req_acl;
+	int cur_arg;
+
+	req_acl = (struct req_acl_rule*)calloc(1, sizeof(struct req_acl_rule));
+	if (!req_acl) {
+		Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+		return NULL;
+	}
+
+	if (!*args[0]) {
+		goto req_error_parsing;
+	} else if (!strcmp(args[0], "allow")) {
+		req_acl->action = PR_REQ_ACL_ACT_ALLOW;
+		cur_arg = 1;
+	} else if (!strcmp(args[0], "deny")) {
+		req_acl->action = PR_REQ_ACL_ACT_DENY;
+		cur_arg = 1;
+	} else if (!strcmp(args[0], "auth")) {
+		req_acl->action = PR_REQ_ACL_ACT_HTTP_AUTH;
+		cur_arg = 1;
+
+		while(*args[cur_arg]) {
+			if (!strcmp(args[cur_arg], "realm")) {
+				req_acl->http_auth.realm = strdup(args[cur_arg + 1]);
+				cur_arg+=2;
+				continue;
+			} else
+				break;
+		}
+	} else {
+req_error_parsing:
+		Alert("parsing [%s:%d]: %s '%s', expects 'allow', 'deny', 'auth'.\n",
+			file, linenum, *args[1]?"unknown parameter":"missing keyword in", args[*args[1]?1:0]);
+		return NULL;
+	}
+
+	if (*args[cur_arg]) {
+		int pol = ACL_COND_NONE;
+		struct acl_cond *cond;
+
+		if (!strcmp(args[cur_arg], "if"))
+			pol = ACL_COND_IF;
+		else if (!strcmp(args[cur_arg], "unless"))
+			pol = ACL_COND_UNLESS;
+		else {
+			Alert("parsing [%s:%d]: '%s' expects 'realm' for 'auth' or"
+			      " either 'if' or 'unless' followed by a condition but found '%s'.\n",
+			      file, linenum, args[0], args[cur_arg]);
+			return NULL;
+		}
+
+		if ((cond = parse_acl_cond((const char **)args + cur_arg + 1, known_acl, pol)) == NULL) {
+			Alert("parsing [%s:%d]: error detected while parsing 'req' condition.\n",
+			      file, linenum);
+			return NULL;
+		}
+
+		cond->file = file;
+		cond->line = linenum;
+		*acl_requires |= cond->requires;
+		req_acl->cond = cond;
+	}
+
+	return req_acl;
+}
+
+void
+userlist_free(struct userlist *ul)
+{
+	struct userlist *tul;
+	struct auth_users *au, *tau;
+	int i;
+
+	while (ul) {
+		au = ul->users;
+		while (au) {
+			tau = au;
+			au = au->next;
+			free(tau->user);
+			free(tau->pass);
+			free(tau);
+		}
+
+		tul = ul;
+		ul = ul->next;
+
+		for (i = 0; i < tul->grpcnt; i++)
+			free(tul->groups[i]);
+
+		free(tul->name);
+		free(tul);
+	};
+}
+
+void
+req_acl_free(struct list *r) {
+	struct req_acl_rule *tr, *pr;
+
+	list_for_each_entry_safe(pr, tr, r, list) {
+		LIST_DEL(&pr->list);
+		if (pr->action == PR_REQ_ACL_ACT_HTTP_AUTH)
+			free(pr->http_auth.realm);
+
+		free(pr);
+	}
+}
+
+/*
+ * 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)
+{
+
+	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);
+#endif
+
+	for (u = ul->users; u; u = u->next)
+		if (!strcmp(user, u->user))
+			break;
+
+	if (!u)
+		return 0;
+
+#ifdef DEBUG_AUTH
+	fprintf(stderr, "cfg: user=%s, pass=%s, group_mask=%u, flags=%X",
+		u->user, u->pass, u->group_mask, u->flags);
+#endif
+
+	/*
+	 * if user matches but group does not,
+	 * it makes no sens to check passwords
+	 */
+	if (group_mask && !(group_mask & u->group_mask))
+		return 0;
+
+	if (!(u->flags & AU_O_INSECURE)) {
+#ifdef CONFIG_HAP_CRYPT
+		ep = crypt(pass, u->pass);
+#else
+		return 0;
+#endif
+	} else
+		ep = pass;
+
+#ifdef DEBUG_AUTH
+	fprintf(stderr, ", crypt=%s\n", ep);
+#endif
+
+	if (!strcmp(ep, u->pass))
+		return 1;
+	else
+		return 0;
+}
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 77e61d8..5c181f1 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -37,6 +37,7 @@
 #include <types/global.h>
 
 #include <proto/acl.h>
+#include <proto/auth.h>
 #include <proto/backend.h>
 #include <proto/buffers.h>
 #include <proto/checks.h>
@@ -4008,6 +4009,175 @@
 	return err_code;
 }
 
+int
+cfg_parse_users(const char *file, int linenum, char **args, int kwm)
+{
+
+	int err_code = 0;
+	const char *err;
+
+	if (!strcmp(args[0], "userlist")) {		/* new userlist */
+		struct userlist *newul;
+
+		if (!*args[1]) {
+			Alert("parsing [%s:%d]: '%s' expects <name> as arguments.\n",
+			      file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		err = invalid_char(args[1]);
+		if (err) {
+			Alert("parsing [%s:%d]: character '%c' is not permitted in '%s' name '%s'.\n",
+			      file, linenum, *err, args[0], args[1]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		for (newul = userlist; newul; newul = newul->next)
+			if (!strcmp(newul->name, args[1])) {
+				Warning("parsing [%s:%d]: ignoring duplicated userlist '%s'.\n",
+					file, linenum, args[1]);
+				err_code |= ERR_WARN;
+				goto out;
+			}
+
+		newul = (struct userlist *)calloc(1, sizeof(struct userlist));
+		if (!newul) {
+			Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+			err_code |= ERR_ALERT | ERR_ABORT;
+			goto out;
+		}
+
+		newul->groupusers = calloc(MAX_AUTH_GROUPS, sizeof(char *));
+		newul->name = strdup(args[1]);
+
+		if (!newul->groupusers | !newul->name) {
+			Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+			err_code |= ERR_ALERT | ERR_ABORT;
+			goto out;
+		}
+
+		newul->next = userlist;
+		userlist = newul;
+
+	} else if (!strcmp(args[0], "group")) {  	/* new group */
+		int cur_arg, i;
+		const char *err;
+
+		if (!*args[1]) {
+			Alert("parsing [%s:%d]: '%s' expects <name> as arguments.\n",
+			      file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		err = invalid_char(args[1]);
+		if (err) {
+			Alert("parsing [%s:%d]: character '%c' is not permitted in '%s' name '%s'.\n",
+			      file, linenum, *err, args[0], args[1]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		for(i = 0; i < userlist->grpcnt; i++)
+			if (!strcmp(userlist->groups[i], 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;
+			goto out;
+		}
+
+		cur_arg = 2;
+
+		while (*args[cur_arg]) {
+			if (!strcmp(args[cur_arg], "users")) {
+				userlist->groupusers[userlist->grpcnt] = strdup(args[cur_arg + 1]);
+				cur_arg += 2;
+				continue;
+			} else {
+				Alert("parsing [%s:%d]: '%s' only supports 'users' option.\n",
+				      file, linenum, args[0]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+		}
+
+		userlist->groups[userlist->grpcnt++] = strdup(args[1]);
+	} else if (!strcmp(args[0], "user")) {		/* new user */
+		struct auth_users *newuser;
+		int cur_arg;
+
+		if (!*args[1]) {
+			Alert("parsing [%s:%d]: '%s' expects <name> as arguments.\n",
+			      file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		for (newuser = userlist->users; newuser; newuser = newuser->next)
+			if (!strcmp(newuser->user, args[1])) {
+				Warning("parsing [%s:%d]: ignoring duplicated user '%s' in userlist '%s'.\n",
+				      file, linenum, args[1], userlist->name);
+				err_code |= ERR_ALERT;
+				goto out;
+			}
+
+		newuser = (struct auth_users *)calloc(1, sizeof(struct auth_users));
+		if (!newuser) {
+			Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+			err_code |= ERR_ALERT | ERR_ABORT;
+			goto out;
+		}
+
+		newuser->user = strdup(args[1]);
+
+		newuser->next = userlist->users;
+		userlist->users = newuser;
+
+		cur_arg = 2;
+
+		while (*args[cur_arg]) {
+			if (!strcmp(args[cur_arg], "password")) {
+#ifndef CONFIG_HAP_CRYPT
+				Warning("parsing [%s:%d]: no crypt(3) support compiled, encrypted passwords will not work.\n",
+					file, linenum);
+				err_code |= ERR_ALERT;
+#endif
+				newuser->pass = strdup(args[cur_arg + 1]);
+				cur_arg += 2;
+				continue;
+			} else if (!strcmp(args[cur_arg], "insecure-password")) {
+				newuser->pass = strdup(args[cur_arg + 1]);
+				newuser->flags |= AU_O_INSECURE;
+				cur_arg += 2;
+				continue;
+			} else if (!strcmp(args[cur_arg], "groups")) {
+				newuser->groups = strdup(args[cur_arg + 1]);
+				cur_arg += 2;
+				continue;
+			} else {
+				Alert("parsing [%s:%d]: '%s' only supports 'password', 'insecure-password' and 'groups' options.\n",
+				      file, linenum, args[0]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+		}
+	} else {
+		Alert("parsing [%s:%d]: unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "users");
+		err_code |= ERR_ALERT | ERR_FATAL;
+	}
+
+out:
+	return err_code;
+}
 
 /*
  * This function reads and parses the configuration file given in the argument.
@@ -4172,6 +4342,10 @@
 			confsect = CFG_GLOBAL;
 			free(cursection);
 			cursection = strdup(args[0]);
+		} else if (!strcmp(args[0], "userlist")) {
+			confsect = CFG_USERLIST;
+			free(cursection);
+			cursection = strdup(args[0]);
 		}
 		/* else it's a section keyword */
 
@@ -4182,8 +4356,11 @@
 		case CFG_GLOBAL:
 			err_code |= cfg_parse_global(file, linenum, args, kwm);
 			break;
+		case CFG_USERLIST:
+			err_code |= cfg_parse_users(file, linenum, args, kwm);
+			break;
 		default:
-			Alert("parsing [%s:%d] : unknown keyword '%s' out of section.\n", file, linenum, args[0]);
+			Alert("parsing [%s:%d]: unknown keyword '%s' out of section.\n", file, linenum, args[0]);
 			err_code |= ERR_ALERT | ERR_FATAL;
 		}
 
@@ -4210,6 +4387,7 @@
 	int cfgerr = 0;
 	struct proxy *curproxy = NULL;
 	struct server *newsrv = NULL;
+	struct userlist *curuserlist = NULL;
 	int err_code = 0;
 	unsigned int next_pxid = 1;
 
@@ -4817,6 +4995,78 @@
 		curproxy = curproxy->next;
 	}
 
+	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->groups)
+				continue;
+
+			while ((group = strtok(group?NULL:curuser->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->groups);
+			curuser->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->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->group_mask & (1 << g))
+					fprintf(stderr, " %s", curuser->user);
+			}
+
+			fprintf(stderr, "\n");
+		}
+#endif
+
+	}
+
 	/*
 	 * Recount currently required checks.
 	 */
diff --git a/src/haproxy.c b/src/haproxy.c
index 2fdde61..0393ebf 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -67,6 +67,7 @@
 #include <types/capture.h>
 #include <types/global.h>
 
+#include <proto/auth.h>
 #include <proto/acl.h>
 #include <proto/backend.h>
 #include <proto/buffers.h>
@@ -177,6 +178,16 @@
 	       "\n\n",
 	       DEFAULT_MAXCONN, BUFSIZE, MAXREWRITE, MAX_POLL_EVENTS);
 
+	printf("Encrypted password support via crypt(3): "
+#ifdef CONFIG_HAP_CRYPT
+		"yes"
+#else
+		"no"
+#endif
+		"\n");
+
+	putchar('\n');
+
 	list_pollers(stdout);
 	putchar('\n');
 }
@@ -851,6 +862,7 @@
 		pool_destroy2(p->req_cap_pool);
 		pool_destroy2(p->rsp_cap_pool);
 		pool_destroy2(p->hdr_idx_pool);
+
 		p0 = p;
 		p = p->next;
 		free(p0);
@@ -874,6 +886,8 @@
 		free(uap);
 	}
 
+	userlist_free(userlist);
+
 	protocol_unbind_all();
 
 	free(global.chroot);  global.chroot = NULL;