From: willy tarreau Date: Sun, 14 May 2006 21:06:28 +0000 (+0200) Subject: [MEDIUM] added the new 'stats' keyword with user authentication subsystem. X-Git-Tag: v1.2.14~22 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9e1388671af9f72889c1c3824110e1c77d4076a8;p=thirdparty%2Fhaproxy.git [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 63e2316db7..6607ce08c1 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ LDFLAGS = -g 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 b09ae3c622..0832cdaac0 100644 --- a/haproxy.c +++ b/haproxy.c @@ -85,6 +85,8 @@ #include #endif +#include +#include #include "include/appsession.h" #include "include/mini-clist.h" @@ -493,6 +495,12 @@ int strlcpy2(char *dst, const char *src, int size) { /*********************************************************************/ +/* 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 session { 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 @@ struct proxy { 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 @@ const char *HTTP_400 = "\r\n" "

400 Bad request

\nYour browser sent an invalid request.\n\n"; +/* Warning: this one is an sprintf() fmt string, with 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" + "

401 Unauthorized

\nYou need a valid user and password to access this content.\n\n"; + const char *HTTP_403 = "HTTP/1.0 403 Forbidden\r\n" "Cache-Control: no-cache\r\n" @@ -3139,6 +3159,8 @@ int event_accept(int fd) { 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 @@ int process_cli(struct session *t) { 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 @@ int process_cli(struct session *t) { /* WARNING! 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 @@ int process_cli(struct session *t) { /* 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 @@ int cfg_parse_listen(char *file, int linenum, char **args) { } 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 0000000000..6408acfd0b --- /dev/null +++ b/include/base64.h @@ -0,0 +1,17 @@ +/* + * Ascii to Base64 conversion as described in RFC1421. + * Copyright 2006 Willy Tarreau + * + * 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 0000000000..f7ff6c8b8a --- /dev/null +++ b/include/uri_auth.h @@ -0,0 +1,57 @@ +/* + * URI-based user authentication using the HTTP basic method. + * + * Copyright 2006 Willy Tarreau + * + * 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 0000000000..79e86c2000 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,57 @@ +/* + * Ascii to Base64 conversion as described in RFC1421. + * Copyright 2006 Willy Tarreau + * + * 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 + + +/* Encodes bytes from to for at most chars (including + * the trailing zero). Returns the number of bytes written. No check is made + * for or to be NULL. Returns negative value if is too short + * to accept . 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 0000000000..3c4965e03a --- /dev/null +++ b/src/uri_auth.c @@ -0,0 +1,162 @@ +/* + * URI-based user authentication using the HTTP basic method. + * + * Copyright 2006 Willy Tarreau + * + * 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 +#include +#include + + +/* + * 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 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 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 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; +} +