First stab at implementing basic auth.
authorflorian <florian@openbsd.org>
Sun, 18 Jan 2015 14:01:17 +0000 (14:01 +0000)
committerflorian <florian@openbsd.org>
Sun, 18 Jan 2015 14:01:17 +0000 (14:01 +0000)
Currently the htpasswd file needs to be in the chroot; will hopefully
improved soonish.
Based on a diff from Oscar Linderholm many months ago but turned into
a complete rewrite.
input/OK reyk@

usr.sbin/httpd/httpd.conf.5
usr.sbin/httpd/httpd.h
usr.sbin/httpd/parse.y
usr.sbin/httpd/server_fcgi.c
usr.sbin/httpd/server_http.c

index 08efe74..f1cf3cc 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: httpd.conf.5,v 1.44 2015/01/13 09:21:15 reyk Exp $
+.\"    $OpenBSD: httpd.conf.5,v 1.45 2015/01/18 14:01:17 florian Exp $
 .\"
 .\" Copyright (c) 2014, 2015 Reyk Floeter <reyk@openbsd.org>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: January 13 2015 $
+.Dd $Mdocdate: January 18 2015 $
 .Dt HTTPD.CONF 5
 .Os
 .Sh NAME
@@ -139,6 +139,15 @@ and include one or more lines of the following syntax:
 Specify an additional alias
 .Ar name
 for this server.
+.It Ic authenticate Oo Ar realm Oc Ic with Pa htpasswd
+Authenticate a remote user for
+.Ar realm
+by checking the credentials against the user authentication file
+.Pa htpasswd .
+This file needs to be readable by the user
+.Xr httpd 8
+drops to
+.Pq www by default Pc .
 .It Ic connection Ar option
 Set the specified options and limits for HTTP connections.
 Valid options are:
@@ -437,6 +446,7 @@ file directly:
 include "/etc/nginx/mime.types"
 .Ed
 .Sh SEE ALSO
+.Xr htpasswd 1 ,
 .Xr httpd 8
 .Sh AUTHORS
 .An -nosplit
index a6f4706..0822a5b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: httpd.h,v 1.68 2015/01/16 06:40:17 deraadt Exp $      */
+/*     $OpenBSD: httpd.h,v 1.69 2015/01/18 14:01:17 florian Exp $      */
 
 /*
  * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -296,6 +296,7 @@ struct client {
        int                      clt_fcgi_type;
        int                      clt_fcgi_chunked;
        int                      clt_fcgi_end;
+       char                    *clt_fcgi_remote_user;
        struct evbuffer         *clt_srvevb;
 
        struct evbuffer         *clt_log;
@@ -324,11 +325,13 @@ SPLAY_HEAD(client_tree, client);
 #define SRVFLAG_TLS            0x00002000
 #define SRVFLAG_ACCESS_LOG     0x00004000
 #define SRVFLAG_ERROR_LOG      0x00008000
+#define SRVFLAG_AUTH_BASIC     0x00010000
 
 #define SRVFLAG_BITS                                                   \
        "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX"           \
        "\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13SOCKET"   \
-       "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG"
+       "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG"          \
+       "\21AUTH_BASIC"
 
 #define TCPFLAG_NODELAY                0x01
 #define TCPFLAG_NNODELAY       0x02
@@ -367,6 +370,8 @@ struct server_config {
        char                     socket[PATH_MAX];
        char                     accesslog[NAME_MAX];
        char                     errorlog[NAME_MAX];
+       char                     auth_realm[NAME_MAX];
+       char                     auth_htpasswd[PATH_MAX];
 
        in_port_t                port;
        struct sockaddr_storage  ss;
index f069270..b47173a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.54 2015/01/16 06:40:17 deraadt Exp $      */
+/*     $OpenBSD: parse.y,v 1.55 2015/01/18 14:01:17 florian Exp $      */
 
 /*
  * Copyright (c) 2007 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -131,7 +131,7 @@ typedef struct {
 %token COMBINED CONNECTION DIRECTORY ERR FCGI INDEX IP KEY LISTEN LOCATION
 %token LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK REQUEST REQUESTS ROOT
 %token SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT TLS TYPES 
-%token ERROR INCLUDE
+%token ERROR INCLUDE AUTHENTICATE WITH
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.port>        port
@@ -439,6 +439,7 @@ serveroptsl : LISTEN ON STRING opttls port {
                | directory
                | logformat
                | fastcgi
+               | authenticate
                | LOCATION STRING               {
                        struct server   *s;
 
@@ -644,6 +645,37 @@ rootflags  : STRING                {
                }
                ;
 
+authenticate   : AUTHENTICATE STRING WITH STRING               {
+                       if (strlcpy(srv->srv_conf.auth_realm, $2,
+                           sizeof(srv->srv_conf.auth_realm)) >=
+                           sizeof(srv->srv_conf.auth_realm)) {
+                               yyerror("basic auth realm name too long");
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+                       if (strlcpy(srv->srv_conf.auth_htpasswd, $4,
+                           sizeof(srv->srv_conf.auth_htpasswd)) >=
+                           sizeof(srv->srv_conf.auth_htpasswd)) {
+                               yyerror("password file name too long");
+                               free($4);
+                               YYERROR;
+                       }
+                       free($4);
+                       srv->srv_conf.flags |= SRVFLAG_AUTH_BASIC;
+               }
+               | AUTHENTICATE WITH STRING              {
+                       if (strlcpy(srv->srv_conf.auth_htpasswd, $3,
+                           sizeof(srv->srv_conf.auth_htpasswd)) >=
+                           sizeof(srv->srv_conf.auth_htpasswd)) {
+                               yyerror("password file name too long");
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+                       srv->srv_conf.flags |= SRVFLAG_AUTH_BASIC;
+               };
+
 directory      : DIRECTORY dirflags
                | DIRECTORY '{' optnl dirflags_l '}'
                ;
@@ -950,6 +982,7 @@ lookup(char *s)
        static const struct keywords keywords[] = {
                { "access",             ACCESS },
                { "alias",              ALIAS },
+               { "authenticate",       AUTHENTICATE},
                { "auto",               AUTO },
                { "backlog",            BACKLOG },
                { "body",               BODY },
@@ -989,7 +1022,8 @@ lookup(char *s)
                { "tcp",                TCP },
                { "timeout",            TIMEOUT },
                { "tls",                TLS },
-               { "types",              TYPES }
+               { "types",              TYPES },
+               { "with",               WITH }
        };
        const struct keywords   *p;
 
index d4135aa..7305eb0 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server_fcgi.c,v 1.46 2015/01/16 06:40:17 deraadt Exp $        */
+/*     $OpenBSD: server_fcgi.c,v 1.47 2015/01/18 14:01:17 florian Exp $        */
 
 /*
  * Copyright (c) 2014 Florian Obser <florian@openbsd.org>
@@ -259,6 +259,14 @@ server_fcgi(struct httpd *env, struct client *clt)
                goto fail;
        }
 
+       if (srv_conf->flags & SRVFLAG_AUTH_BASIC) {
+               if (fcgi_add_param(&param, "REMOTE_USER",
+                   clt->clt_fcgi_remote_user, clt) == -1) {
+                       errstr = "failed to encode param";
+                       goto fail;
+               }
+       }
+
        /* Add HTTP_* headers */
        if (server_headers(clt, desc, server_fcgi_writeheader, &param) == -1) {
                errstr = "failed to encode param";
index c5dd4ab..5bf6bd5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server_http.c,v 1.64 2015/01/16 06:40:17 deraadt Exp $        */
+/*     $OpenBSD: server_http.c,v 1.65 2015/01/18 14:01:17 florian Exp $        */
 
 /*
  * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -38,6 +38,7 @@
 #include <stdio.h>
 #include <err.h>
 #include <pwd.h>
+#include <resolv.h>
 #include <syslog.h>
 #include <event.h>
 #include <fnmatch.h>
@@ -48,6 +49,8 @@
 static int      server_httpmethod_cmp(const void *, const void *);
 static int      server_httperror_cmp(const void *, const void *);
 void            server_httpdesc_free(struct http_descriptor *);
+int             server_http_authenticate(struct server_config *,
+    struct client *);
 
 static struct httpd    *env = NULL;
 
@@ -128,6 +131,82 @@ server_httpdesc_free(struct http_descriptor *desc)
        desc->http_chunked = 0;
 }
 
+int
+server_http_authenticate(struct server_config *srv_conf, struct client *clt)
+{
+       FILE *fp = NULL;
+       struct http_descriptor *desc = clt->clt_descreq;
+       struct kv *ba, key;
+       size_t linesize = 0;
+       ssize_t linelen;
+       int ret = -1;
+       char *line = NULL, decoded[1024];
+       char *clt_user = NULL, *clt_pass = NULL, *user = NULL, *pass = NULL;
+
+       memset(decoded, 0, sizeof(decoded));
+       key.kv_key = "Authorization";
+
+       if ((ba = kv_find(&desc->http_headers, &key)) == NULL ||
+           ba->kv_value == NULL)
+               goto done;
+
+       if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0)
+               goto done;
+
+       if (b64_pton(strchr(ba->kv_value, ' ') + 1, decoded,
+           sizeof(decoded)) <= 0)
+               goto done;
+
+       if ((clt_pass = strchr(decoded, ':')) == NULL)
+               goto done;
+
+       clt_user = decoded;
+       *clt_pass++ = '\0';
+
+       if (clt_pass == NULL)
+               goto done;
+
+       if ((fp = fopen(srv_conf->auth_htpasswd, "r")) == NULL)
+               goto done;
+
+       while ((linelen = getline(&line, &linesize, fp)) != -1) {
+               if (line[linelen - 1] == '\n')
+                       line[linelen - 1] = '\0';
+               user = line;
+               pass = strchr(line, ':');
+
+               if (pass == NULL) {
+                       explicit_bzero(line, linelen);
+                       continue;
+               }
+
+               *pass++ = '\0';
+
+               if (strcmp(clt_user, user) != 0) {
+                       explicit_bzero(line, linelen);
+                       continue;
+               }
+
+               if (crypt_checkpass(clt_pass, pass) == 0) {
+                       explicit_bzero(line, linelen);
+                       clt->clt_fcgi_remote_user = strdup(clt_user);
+                       if (clt->clt_fcgi_remote_user != NULL)
+                               ret = 0;
+                       break;
+               }
+       }
+done:
+       if (fp != NULL)
+               fclose(fp);
+
+       if (ba != NULL && ba->kv_value != NULL) {
+               explicit_bzero(ba->kv_value, strlen(ba->kv_value));
+               explicit_bzero(decoded, sizeof(decoded));
+       }
+
+       return (ret);
+}
+
 void
 server_read_http(struct bufferevent *bev, void *arg)
 {
@@ -551,6 +630,8 @@ server_reset_http(struct client *clt)
        clt->clt_line = 0;
        clt->clt_done = 0;
        clt->clt_chunk = 0;
+       free(clt->clt_fcgi_remote_user);
+       clt->clt_fcgi_remote_user = NULL;
        clt->clt_bev->readcb = server_read_http;
        clt->clt_srv_conf = &srv->srv_conf;
 
@@ -687,6 +768,13 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
                        extraheader = NULL;
                }
                break;
+       case 401:
+               if (asprintf(&extraheader,
+                   "WWW-Authenticate: Basic realm=\"%s\"\r\n", msg) == -1) {
+                       code = 500;
+                       extraheader = NULL;
+               }
+               break;
        default:
                /*
                 * Do not send details of the error.  Traditionally,
@@ -764,6 +852,8 @@ server_close_http(struct client *clt)
        server_httpdesc_free(desc);
        free(desc);
        clt->clt_descresp = NULL;
+       free(clt->clt_fcgi_remote_user);
+       clt->clt_fcgi_remote_user = NULL;
 }
 
 int
@@ -874,7 +964,12 @@ server_response(struct httpd *httpd, struct client *clt)
        /* Now search for the location */
        srv_conf = server_getlocation(clt, desc->http_path);
 
-       return (server_file(httpd, clt));
+       if (srv_conf->flags & SRVFLAG_AUTH_BASIC &&
+           server_http_authenticate(srv_conf, clt) == -1) {
+               server_abort_http(clt, 401, srv_conf->auth_realm);
+               return (-1);
+       } else
+               return (server_file(httpd, clt));
  fail:
        server_abort_http(clt, 400, "bad request");
        return (-1);