From e286121a40eec8d7ac435f0a8550ca3375b86fc8 Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 18 Jan 2015 14:01:17 +0000 Subject: [PATCH] First stab at implementing basic auth. 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 | 14 ++++- usr.sbin/httpd/httpd.h | 9 +++- usr.sbin/httpd/parse.y | 40 +++++++++++++-- usr.sbin/httpd/server_fcgi.c | 10 +++- usr.sbin/httpd/server_http.c | 99 +++++++++++++++++++++++++++++++++++- 5 files changed, 162 insertions(+), 10 deletions(-) diff --git a/usr.sbin/httpd/httpd.conf.5 b/usr.sbin/httpd/httpd.conf.5 index 08efe74f7a7..f1cf3cc4e4a 100644 --- a/usr.sbin/httpd/httpd.conf.5 +++ b/usr.sbin/httpd/httpd.conf.5 @@ -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 .\" @@ -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 diff --git a/usr.sbin/httpd/httpd.h b/usr.sbin/httpd/httpd.h index a6f470658b7..0822a5b3014 100644 --- a/usr.sbin/httpd/httpd.h +++ b/usr.sbin/httpd/httpd.h @@ -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 @@ -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; diff --git a/usr.sbin/httpd/parse.y b/usr.sbin/httpd/parse.y index f06927095a2..b47173a0f69 100644 --- a/usr.sbin/httpd/parse.y +++ b/usr.sbin/httpd/parse.y @@ -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 @@ -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 STRING %token NUMBER %type 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; diff --git a/usr.sbin/httpd/server_fcgi.c b/usr.sbin/httpd/server_fcgi.c index d4135aa3925..7305eb00847 100644 --- a/usr.sbin/httpd/server_fcgi.c +++ b/usr.sbin/httpd/server_fcgi.c @@ -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 @@ -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(¶m, "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, ¶m) == -1) { errstr = "failed to encode param"; diff --git a/usr.sbin/httpd/server_http.c b/usr.sbin/httpd/server_http.c index c5dd4ab5650..5bf6bd517fe 100644 --- a/usr.sbin/httpd/server_http.c +++ b/usr.sbin/httpd/server_http.c @@ -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 @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -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); -- 2.20.1