[MEDIUM] added the new 'stats' keyword with user authentication subsystem.

Right now it only validates the user/passwd according to a specified list,
and lets the user pass through the proxy if the authentication is OK, and
it refuses any invalid access with a 401 Unauthorized response.
diff --git a/Makefile b/Makefile
index 63e2316..6607ce0 100644
--- a/Makefile
+++ b/Makefile
@@ -109,7 +109,7 @@
 
 all: haproxy
 
-haproxy: src/list.o src/chtbl.o src/hashpjw.o haproxy.o
+haproxy: src/list.o src/chtbl.o src/hashpjw.o haproxy.o src/base64.o src/uri_auth.o
 	$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
 
 %.o:	%.c
diff --git a/haproxy.c b/haproxy.c
index b09ae3c..0832cda 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -85,6 +85,8 @@
 #include <assert.h>
 #endif
 
+#include <include/base64.h>
+#include <include/uri_auth.h>
 #include "include/appsession.h"
 #include "include/mini-clist.h"
 
@@ -493,6 +495,12 @@
 
 /*********************************************************************/
 
+/* describes a chunk of string */
+struct chunk {
+    char *str;	/* beginning of the string itself. Might not be 0-terminated */
+    int len;	/* size of the string from first to last char. <0 = uninit. */
+};
+
 struct cap_hdr {
     struct cap_hdr *next;
     char *name;				/* header name, case insensitive */
@@ -584,6 +592,8 @@
     struct pendconn *pend_pos;		/* if not NULL, points to the position in the pending queue */
     char **req_cap;			/* array of captured request headers (may be NULL) */
     char **rsp_cap;			/* array of captured response headers (may be NULL) */
+    struct chunk req_line;		/* points to first line */
+    struct chunk auth_hdr;		/* points to 'Authorization:' header */
     struct {
 	int logwait;			/* log fields waiting to be collected : LW_* */
 	struct timeval tv_accept;	/* date of the accept() (beginning of the session) */
@@ -630,6 +640,7 @@
     char *capture_name;			/* beginning of the name of the cookie to capture */
     int  capture_namelen;		/* length of the cookie name to match */
     int  capture_len;			/* length of the string to be captured */
+    struct uri_auth *uri_auth;		/* if non-NULL, the (list of) per-URI authentications */
     int clitimeout;			/* client I/O timeout (in milliseconds) */
     int srvtimeout;			/* server I/O timeout (in milliseconds) */
     int contimeout;			/* connect timeout (in milliseconds) */
@@ -839,6 +850,15 @@
 	"\r\n"
 	"<html><body><h1>400 Bad request</h1>\nYour browser sent an invalid request.\n</body></html>\n";
 
+/* Warning: this one is an sprintf() fmt string, with <realm> as its only argument */
+const char *HTTP_401_fmt =
+	"HTTP/1.0 401 Unauthorized\r\n"
+	"Cache-Control: no-cache\r\n"
+	"Connection: close\r\n"
+	"WWW-Authenticate: Basic realm=\"%s\"\r\n"
+	"\r\n"
+	"<html><body><h1>401 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n";
+
 const char *HTTP_403 =
 	"HTTP/1.0 403 Forbidden\r\n"
 	"Cache-Control: no-cache\r\n"
@@ -3139,6 +3159,8 @@
 	s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT;
 	s->cli_fd = cfd;
 	s->srv_fd = -1;
+	s->req_line.len = -1;
+	s->auth_hdr.len = -1;
 	s->srv = NULL;
 	s->pend_pos = NULL;
 	s->conn_retries = p->conn_retries;
@@ -3683,6 +3705,71 @@
 		    return 1;
 		}
 
+		/* Right now, we know that we have processed the entire headers
+		 * and that unwanted requests have been filtered out. We can do
+		 * whatever we want.
+		 */
+
+		/* FIXME debugging code !!! */
+		if (t->req_line.len >= 0) {
+		    write(2, t->req_line.str, t->req_line.len);
+		}
+
+		if (t->proxy->uri_auth != NULL
+		    && t->req_line.len >= t->proxy->uri_auth->uri_len + 4) {   /* +4 for "GET /" */
+		    if (!memcmp(t->req_line.str + 4,
+				t->proxy->uri_auth->uri_prefix, t->proxy->uri_auth->uri_len)
+			&& !memcmp(t->req_line.str, "GET ", 4)) {
+			struct user_auth *user;
+			int authenticated;
+
+			/* we are in front of a interceptable URI. Let's check
+			 * if there's an authentication and if it's valid.
+			 */
+			user = t->proxy->uri_auth->users;
+			if (!user) {
+			    /* no user auth required, it's OK */
+			    authenticated = 1;
+			} else {
+			    authenticated = 0;
+
+			    /* a user list is defined, we have to check.
+			     * skip 21 chars for "Authorization: Basic ".
+			     */
+			    if (t->auth_hdr.len < 21 || memcmp(t->auth_hdr.str + 14, " Basic ", 7))
+				user = NULL;
+
+			    while (user) {
+				if ((t->auth_hdr.len == user->user_len + 21)
+				    && !memcmp(t->auth_hdr.str+21, user->user_pwd, user->user_len)) {
+				    authenticated = 1;
+				    break;
+				}
+				user = user->next;
+				write(2, t->auth_hdr.str, t->auth_hdr.len);
+			    }
+			}
+
+			if (!authenticated) {
+			    int msglen;
+
+			    /* no need to go further */
+
+			    msglen = sprintf(trash, HTTP_401_fmt, t->proxy->uri_auth->auth_realm);
+			    t->logs.status = 401;
+			    client_retnclose(t, msglen, trash);
+			    if (!(t->flags & SN_ERR_MASK))
+				t->flags |= SN_ERR_PRXCOND;
+			    if (!(t->flags & SN_FINST_MASK))
+				t->flags |= SN_FINST_R;
+			    return 1;
+			}
+
+			/* Hey, we have passed the authentication ! */
+		    }
+		}
+
+
 		for (line = 0; line < t->proxy->nb_reqadd; line++) {
 		    len = sprintf(trash, "%s\r\n", t->proxy->req_add[line]);
 		    buffer_replace2(req, req->h, req->h, trash, len);
@@ -4228,8 +4315,8 @@
 			/* WARNING! <ptr> becomes invalid for now. If some code
 			 * below needs to rely on it before the end of the global
 			 * header loop, we need to correct it with this code :
-			 * ptr = del_colon;
 			 */
+			ptr = del_colon;
 		    }
 		    else
 			delete_header = 1;
@@ -4239,8 +4326,24 @@
 	    /* let's look if we have to delete this header */
 	    if (delete_header && !(t->flags & SN_CLDENY)) {
 		buffer_replace2(req, req->h, req->lr, NULL, 0);
+		/* WARNING: ptr is not valid anymore, since the header may have
+		 * been deleted or truncated ! */
+	    } else {
+		/* try to catch the first line as the request */
+		if (t->req_line.len < 0) {
+		    t->req_line.str = req->h;
+		    t->req_line.len = ptr - req->h;
+		}
+
+		/* We might also need the 'Authorization: ' header */
+		if (t->auth_hdr.len < 0 &&
+		    t->proxy->uri_auth != NULL &&
+		    ptr > req->h + 15 &&
+		    !strncasecmp("Authorization: ", req->h, 15)) {
+		    t->auth_hdr.str = req->h;
+		    t->auth_hdr.len = ptr - req->h;
+		}
 	    }
-	    /* WARNING: ptr is not valid anymore, since the header may have been deleted or truncated ! */
 
 	    req->h = req->lr;
 	} /* while (req->lr < req->r) */
@@ -7545,6 +7648,45 @@
 	}
 	curproxy->conn_retries = atol(args[1]);
     }
+    else if (!strcmp(args[0], "stats")) {
+	if (*(args[1]) == 0) {
+	    Alert("parsing [%s:%d] : '%s' expects 'uri', 'realm', 'auth' or 'enable'.\n", file, linenum, args[0]);
+	    return -1;
+	} else if (!strcmp(args[1], "uri")) {
+	    if (*(args[2]) == 0) {
+		Alert("parsing [%s:%d] : 'uri' needs an URI prefix.\n", file, linenum);
+		return -1;
+	    } else if (!stats_set_uri(&curproxy->uri_auth, args[2])) {
+		Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+		return -1;
+	    }
+	} else if (!strcmp(args[1], "realm")) {
+	    if (*(args[2]) == 0) {
+		Alert("parsing [%s:%d] : 'realm' needs an realm name.\n", file, linenum);
+		return -1;
+	    } else if (!stats_set_realm(&curproxy->uri_auth, args[2])) {
+		Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+		return -1;
+	    }
+	} else if (!strcmp(args[1], "auth")) {
+	    if (*(args[2]) == 0) {
+		Alert("parsing [%s:%d] : 'auth' needs a user:password account.\n", file, linenum);
+		return -1;
+	    } else if (!stats_add_auth(&curproxy->uri_auth, args[2])) {
+		Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+		return -1;
+	    }
+	} else if (!strcmp(args[1], "enable")) {
+	    if (!stats_check_init_uri_auth(&curproxy->uri_auth)) {
+		Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+		return -1;
+	    }
+	} else {
+	    Alert("parsing [%s:%d] : unknown stats parameter '%s' (expects 'uri', 'realm', 'auth' or 'enable').\n",
+		  file, linenum, args[0]);
+	    return -1;
+	}
+    }
     else if (!strcmp(args[0], "option")) {
 	if (*(args[1]) == 0) {
 	    Alert("parsing [%s:%d] : '%s' expects an option name.\n", file, linenum, args[0]);
diff --git a/include/base64.h b/include/base64.h
new file mode 100644
index 0000000..6408acf
--- /dev/null
+++ b/include/base64.h
@@ -0,0 +1,17 @@
+/*
+ * Ascii to Base64 conversion as described in RFC1421.
+ * Copyright 2006 Willy Tarreau <willy@w.ods.org>
+ *
+ * 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 BASE64_H
+#define BASE64_H
+
+int a2base64(char *in, int ilen, char *out, int olen);
+
+#endif /* BASE64_H */
diff --git a/include/uri_auth.h b/include/uri_auth.h
new file mode 100644
index 0000000..f7ff6c8
--- /dev/null
+++ b/include/uri_auth.h
@@ -0,0 +1,57 @@
+/*
+ * URI-based user authentication using the HTTP basic method.
+ *
+ * Copyright 2006 Willy Tarreau <willy@w.ods.org>
+ *
+ * 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 URI_AUTH_H
+#define URI_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 */
+	int user_len;			/* user:passwd length */
+	char *user_pwd;			/* auth as base64("user":"passwd") (see RFC2617) */
+};
+
+/* later we may link them to support multiple URI matching */
+struct uri_auth {
+	int uri_len;			/* the prefix length */
+	char *uri_prefix;		/* the prefix we want to match */
+	char *auth_realm;		/* the realm reported to the client */
+	struct user_auth *users;	/* linked list of valid user:passwd couples */
+};
+
+/* This is the default statistics URI */
+#ifdef CONFIG_STATS_DEFAULT_URI
+#define STATS_DEFAULT_URI CONFIG_STATS_DEFAULT_URI
+#else
+#define STATS_DEFAULT_URI "/haproxy?stats"
+#endif
+
+/* This is the default statistics realm */
+#ifdef CONFIG_STATS_DEFAULT_REALM
+#define STATS_DEFAULT_REALM CONFIG_STATS_DEFAULT_REALM
+#else
+#define STATS_DEFAULT_REALM "HAProxy Statistics"
+#endif
+
+
+/* Various functions used to set the fields during the configuration parsing.
+ * Please that all those function can initialize the root entry in order not to
+ * force the user to respect a certain order in the configuration file.
+ *
+ * Default values are used during initialization. Check STATS_DEFAULT_* for
+ * more information.
+ */
+struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root);
+struct uri_auth *stats_set_uri(struct uri_auth **root, char *uri);
+struct uri_auth *stats_set_realm(struct uri_auth **root, char *realm);
+struct uri_auth *stats_add_auth(struct uri_auth **root, char *user);
+
+#endif /* URI_AUTH_H */
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 0000000..79e86c2
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,57 @@
+/*
+ * Ascii to Base64 conversion as described in RFC1421.
+ * Copyright 2006 Willy Tarreau <willy@w.ods.org>
+ *
+ * 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.
+ *
+ */
+
+#include <include/base64.h>
+
+
+/* Encodes <ilen> bytes from <in> to <out> for at most <olen> chars (including
+ * the trailing zero). Returns the number of bytes written. No check is made
+ * for <in> or <out> to be NULL. Returns negative value if <olen> is too short
+ * to accept <ilen>. 4 output bytes are produced for 1 to 3 input bytes.
+ */
+int a2base64(char *in, int ilen, char *out, int olen)
+{
+        char base64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	int convlen;
+
+	convlen = ((ilen + 2) / 3) * 4;
+
+	if (convlen >= olen)
+		return -1;
+
+	/* we don't need to check olen anymore */
+	while (ilen >= 3) {
+		out[0] = base64[(((unsigned char)in[0]) >> 2)];
+		out[1] = base64[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)];
+		out[2] = base64[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)];
+		out[3] = base64[(((unsigned char)in[2] & 0x3F))];
+		out += 4;
+		in += 3; ilen -= 3;
+	}
+	
+	if (!ilen) {
+		out[0] = '\0';
+	} else {
+		out[0] = base64[((unsigned char)in[0]) >> 2];
+		if (ilen == 1) {
+			out[1] = base64[((unsigned char)in[0] & 0x03) << 4];
+			out[2] = '=';
+		} else {
+			out[1] = base64[(((unsigned char)in[0] & 0x03) << 4) |
+					(((unsigned char)in[1]) >> 4)];
+			out[2] = base64[((unsigned char)in[1] & 0x0F) << 2];
+		}
+		out[3] = '=';
+		out[4] = '\0';
+	}
+
+	return convlen;
+}
diff --git a/src/uri_auth.c b/src/uri_auth.c
new file mode 100644
index 0000000..3c4965e
--- /dev/null
+++ b/src/uri_auth.c
@@ -0,0 +1,162 @@
+/*
+ * URI-based user authentication using the HTTP basic method.
+ *
+ * Copyright 2006 Willy Tarreau <willy@w.ods.org>
+ *
+ * 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.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <include/uri_auth.h>
+#include <include/base64.h>
+
+
+/*
+ * Initializes a basic uri_auth structure header and returns a pointer to it.
+ * Uses the pointer provided if not NULL and not initialized.
+ */
+struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root)
+{
+	struct uri_auth *u;
+
+	if (!root || !*root) {
+		if ((u = (struct uri_auth *)calloc(1, sizeof (*u))) == NULL)
+			goto out_u;
+	} else
+		u = *root;
+
+	if (!u->uri_prefix) {
+		u->uri_len = strlen(STATS_DEFAULT_URI);
+		if ((u->uri_prefix = strdup(STATS_DEFAULT_URI)) == NULL)
+			goto out_uri;
+	}
+
+	if (!u->auth_realm)
+		if ((u->auth_realm = strdup(STATS_DEFAULT_REALM)) == NULL)
+			goto out_realm;
+
+	if (root && !*root)
+		*root = u;
+
+	return u;
+
+ out_realm:
+	free(u->uri_prefix);
+ out_uri:
+	if (!root || !*root)
+		free(u);
+ out_u:
+	return NULL;
+}
+
+/*
+ * Returns a default uri_auth with <uri> set as the uri_prefix.
+ * Uses the pointer provided if not NULL and not initialized.
+ */
+struct uri_auth *stats_set_uri(struct uri_auth **root, char *uri)
+{
+	struct uri_auth *u;
+	char *uri_copy;
+	int uri_len;
+
+	uri_len  = strlen(uri);
+	if ((uri_copy = strdup(uri)) == NULL)
+		goto out_uri;
+	
+	if ((u = stats_check_init_uri_auth(root)) == NULL)
+		goto out_u;
+	
+	if (u->uri_prefix)
+		free(u->uri_prefix);
+
+	u->uri_len = uri_len;
+	u->uri_prefix = uri_copy;
+	return u;
+
+ out_u:
+	free(uri_copy);
+ out_uri:
+	return NULL;
+}
+
+/*
+ * Returns a default uri_auth with <realm> set as the realm.
+ * Uses the pointer provided if not NULL and not initialized.
+ */
+struct uri_auth *stats_set_realm(struct uri_auth **root, char *realm)
+{
+	struct uri_auth *u;
+	char *realm_copy;
+
+	if ((realm_copy = strdup(realm)) == NULL)
+		goto out_realm;
+	
+	if ((u = stats_check_init_uri_auth(root)) == NULL)
+		goto out_u;
+	
+	if (u->auth_realm)
+		free(u->auth_realm);
+
+	u->auth_realm = realm_copy;
+	return u;
+
+ out_u:
+	free(realm_copy);
+ out_realm:
+	return NULL;
+}
+
+/*
+ * Returns a default uri_auth with a <user:passwd> entry added to the list of
+ * authorized users. If a matching entry is found, no update will be performed.
+ * Uses the pointer provided if not NULL and not initialized.
+ */
+struct uri_auth *stats_add_auth(struct uri_auth **root, char *auth)
+{
+	struct uri_auth *u;
+	char *auth_base64;
+	int alen, blen;
+	struct user_auth *users, **ulist;
+
+	alen = strlen(auth);
+	blen = ((alen + 2) / 3) * 4;
+
+	if ((auth_base64 = (char *)calloc(1, blen + 1)) == NULL)
+		goto out_ubase64;
+
+	/* convert user:passwd to base64. It should return exactly blen */
+	if (a2base64(auth, alen, auth_base64, blen + 1) != blen)
+		goto out_base64;
+
+	if ((u = stats_check_init_uri_auth(root)) == NULL)
+		goto out_base64;
+
+	ulist = &u->users;
+	while ((users = *ulist)) {
+		if (!strcmp(users->user_pwd, auth_base64))
+			break;
+		ulist = &users->next;
+	}
+
+	if (!users) {
+		if ((users = (struct user_auth *)calloc(1, sizeof(*users))) == NULL)
+			goto out_u;
+		*ulist = users;
+		users->user_pwd = auth_base64;
+		users->user_len = blen;
+	}
+	return u;
+
+ out_u:
+	free(u);
+ out_base64:
+	free(auth_base64);
+ out_ubase64:
+	return NULL;
+}
+