]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] added the new 'stats' keyword with user authentication subsystem.
authorwilly tarreau <willy@wtap.(none)>
Sun, 14 May 2006 21:06:28 +0000 (23:06 +0200)
committerwilly tarreau <willy@wtap.(none)>
Sun, 14 May 2006 21:06:28 +0000 (23:06 +0200)
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.

Makefile
haproxy.c
include/base64.h [new file with mode: 0644]
include/uri_auth.h [new file with mode: 0644]
src/base64.c [new file with mode: 0644]
src/uri_auth.c [new file with mode: 0644]

index 63e2316db7af777a46857d6bb8d62755a629dfe2..6607ce08c1219aea0d06b4d6d213cd74f3861ca6 100644 (file)
--- 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
index b09ae3c622120531dd72a1fdc8151b044ffaf027..0832cdaac008c7453cd24286d4b43c034328ff7e 100644 (file)
--- 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 @@ 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"
        "<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 @@ 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! <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 @@ 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 (file)
index 0000000..6408acf
--- /dev/null
@@ -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 (file)
index 0000000..f7ff6c8
--- /dev/null
@@ -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 (file)
index 0000000..79e86c2
--- /dev/null
@@ -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 (file)
index 0000000..3c4965e
--- /dev/null
@@ -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;
+}
+