From 0349dc0e6b211145485e58fd19b83b9c9ce9f2e3 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sat, 18 Jul 2015 08:01:10 +0200 Subject: sync; add HSTS and default type --- httpd/config.c | 14 +++++++++++- httpd/httpd.c | 19 ++++++++++++++-- httpd/httpd.conf.5 | 28 ++++++++++++++++++++++-- httpd/httpd.h | 36 ++++++++++++++++++++---------- httpd/parse.y | 63 ++++++++++++++++++++++++++++++++++++++++++++++++----- httpd/server_file.c | 13 +++++------ httpd/server_http.c | 37 ++++++++++++++++++++++++------- 7 files changed, 172 insertions(+), 38 deletions(-) diff --git a/httpd/config.c b/httpd/config.c index 3c88990..2829bed 100644 --- a/httpd/config.c +++ b/httpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.39 2015/07/15 16:00:39 jsing Exp $ */ +/* $OpenBSD: config.c,v 1.41 2015/07/18 06:00:43 reyk Exp $ */ /* * Copyright (c) 2011 - 2015 Reyk Floeter @@ -436,6 +436,18 @@ config_getserver_config(struct httpd *env, struct server *srv, goto fail; } + f = SRVFLAG_DEFAULT_TYPE; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + memcpy(&srv_conf->default_type, + &parent->default_type, sizeof(struct media_type)); + } + + f = SRVFLAG_SERVER_HSTS; + srv_conf->flags |= parent->flags & f; + srv_conf->hsts_max_age = parent->hsts_max_age; + srv_conf->hsts_subdomains = parent->hsts_subdomains; + memcpy(&srv_conf->timeout, &parent->timeout, sizeof(srv_conf->timeout)); srv_conf->maxrequests = parent->maxrequests; diff --git a/httpd/httpd.c b/httpd/httpd.c index 5f6788c..cc67865 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.c,v 1.37 2015/06/03 02:24:36 millert Exp $ */ +/* $OpenBSD: httpd.c,v 1.38 2015/07/18 06:00:43 reyk Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -1217,7 +1217,7 @@ media_purge(struct mediatypes *types) } struct media_type * -media_find(struct mediatypes *types, char *file) +media_find(struct mediatypes *types, const char *file) { struct media_type *match, media; char *p; @@ -1241,6 +1241,21 @@ media_find(struct mediatypes *types, char *file) return (match); } +struct media_type * +media_find_config(struct httpd *env, struct server_config *srv_conf, + const char *file) +{ + struct media_type *match; + + if ((match = media_find(env->sc_mediatypes, file)) != NULL) + return (match); + else if (srv_conf->flags & SRVFLAG_DEFAULT_TYPE) + return (&srv_conf->default_type); + + /* fallback to the global default type */ + return (&env->sc_default_type); +} + int media_cmp(struct media_type *a, struct media_type *b) { diff --git a/httpd/httpd.conf.5 b/httpd/httpd.conf.5 index b3eaad8..859f68f 100644 --- a/httpd/httpd.conf.5 +++ b/httpd/httpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.conf.5,v 1.64 2015/07/15 17:10:47 jsing Exp $ +.\" $OpenBSD: httpd.conf.5,v 1.66 2015/07/18 06:00:43 reyk 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: July 15 2015 $ +.Dd $Mdocdate: July 18 2015 $ .Dt HTTPD.CONF 5 .Os .Sh NAME @@ -112,6 +112,15 @@ directory. If not specified, it defaults to .Pa /var/www , the home directory of the www user. +.It Ic default type Ar type/subtype +Set the default media type that is used if the media type for a +specified extension is not found in the configured types or for files +without a file extension; +see the +.Sx TYPES +section below. +If not specified, the default type is set to +.Ar application/octet-stream . .It Ic logdir Ar directory Specifies the full path of the directory in which log files will be written. If not specified, it defaults to @@ -236,6 +245,9 @@ Specify the inactivity timeout in seconds for accepted sessions. The default timeout is 600 seconds (10 minutes). The maximum is 2147483647 seconds (68 years). .El +.It Ic default type Ar type/subtype +Set the default media type for the specified location, +overwriting the global setting. .It Ic directory Ar option Set the specified options when serving or accessing directories. Valid options are: @@ -262,6 +274,18 @@ root directory of .Xr httpd 8 and defaults to .Pa /run/slowcgi.sock . +.It Ic hsts Oo Ar option Oc +Enable HTTP Strict Transport Security. +Valid options are: +.Bl -tag -width Ds +.It Ic max-age Ar seconds +Set the maximum time in seconds a receiving user agent should regard +this host as a HSTS host. +The default is one year. +.It Ic subdomains +Signal to the receiving user agent that this host and all sub domains +of the host's domain should be considered HSTS hosts. +.El .It Ic listen on Ar address Oo Ic tls Oc Ic port Ar number Set the listen address and port. This statement can be specified multiple times. diff --git a/httpd/httpd.h b/httpd/httpd.h index 2cb7934..bbf02aa 100644 --- a/httpd/httpd.h +++ b/httpd/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.88 2015/07/16 16:29:25 florian Exp $ */ +/* $OpenBSD: httpd.h,v 1.90 2015/07/18 06:00:43 reyk Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -48,6 +48,7 @@ #define HTTPD_LOGROOT "/logs" #define HTTPD_ACCESS_LOG "access.log" #define HTTPD_ERROR_LOG "error.log" +#define HTTPD_DEFAULT_TYPE { "bin", "application", "octet-stream", NULL } #define HTTPD_LOGVIS VIS_NL|VIS_TAB|VIS_CSTYLE #define HTTPD_TLS_CERT "/etc/ssl/server.crt" #define HTTPD_TLS_KEY "/etc/ssl/private/server.key" @@ -68,6 +69,7 @@ #define SERVER_OUTOF_FD_RETRIES 5 #define SERVER_MAX_PREFETCH 256 #define SERVER_MIN_PREFETCHED 32 +#define SERVER_HSTS_DEFAULT_AGE 31536000 #define MEDIATYPE_NAMEMAX 128 /* file name extension */ #define MEDIATYPE_TYPEMAX 64 /* length of type/subtype */ @@ -351,13 +353,15 @@ SPLAY_HEAD(client_tree, client); #define SRVFLAG_NO_BLOCK 0x00080000 #define SRVFLAG_LOCATION_MATCH 0x00100000 #define SRVFLAG_SERVER_MATCH 0x00200000 +#define SRVFLAG_SERVER_HSTS 0x00400000 +#define SRVFLAG_DEFAULT_TYPE 0x00800000 #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" \ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \ - "\26SERVER_MATCH" + "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE" #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 @@ -387,6 +391,15 @@ struct log_file { }; TAILQ_HEAD(log_files, log_file) log_files; +struct media_type { + char media_name[MEDIATYPE_NAMEMAX]; + char media_type[MEDIATYPE_TYPEMAX]; + char media_subtype[MEDIATYPE_TYPEMAX]; + char *media_encoding; + RB_ENTRY(media_type) media_entry; +}; +RB_HEAD(mediatypes, media_type); + struct auth { char auth_htpasswd[PATH_MAX]; u_int32_t auth_id; @@ -404,6 +417,7 @@ struct server_config { char socket[PATH_MAX]; char accesslog[NAME_MAX]; char errorlog[NAME_MAX]; + struct media_type default_type; in_port_t port; struct sockaddr_storage ss; @@ -443,6 +457,9 @@ struct server_config { char *return_uri; off_t return_uri_len; + int hsts_max_age; + int hsts_subdomains; + TAILQ_ENTRY(server_config) entry; }; TAILQ_HEAD(serverhosts, server_config); @@ -473,15 +490,6 @@ struct server { }; TAILQ_HEAD(serverlist, server); -struct media_type { - char media_name[MEDIATYPE_NAMEMAX]; - char media_type[MEDIATYPE_TYPEMAX]; - char media_subtype[MEDIATYPE_TYPEMAX]; - char *media_encoding; - RB_ENTRY(media_type) media_entry; -}; -RB_HEAD(mediatypes, media_type); - struct httpd { u_int8_t sc_opts; u_int32_t sc_flags; @@ -495,6 +503,7 @@ struct httpd { struct serverlist *sc_servers; struct mediatypes *sc_mediatypes; + struct media_type sc_default_type; struct serverauth *sc_auth; struct privsep *sc_ps; @@ -639,7 +648,10 @@ struct media_type void media_delete(struct mediatypes *, struct media_type *); void media_purge(struct mediatypes *); struct media_type * - media_find(struct mediatypes *, char *); + media_find(struct mediatypes *, const char *); +struct media_type * + media_find_config(struct httpd *, struct server_config *, + const char *); int media_cmp(struct media_type *, struct media_type *); RB_PROTOTYPE(kvtree, kv, kv_node, kv_cmp); RB_PROTOTYPE(mediatypes, media_type, media_entry, media_cmp); diff --git a/httpd/parse.y b/httpd/parse.y index 0870819..7b8da0e 100644 --- a/httpd/parse.y +++ b/httpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.70 2015/07/16 19:05:28 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.72 2015/07/18 06:00:43 reyk Exp $ */ /* * Copyright (c) 2007 - 2015 Reyk Floeter @@ -133,7 +133,7 @@ typedef struct { %token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN %token LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS %token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT -%token TLS TYPES +%token TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS %token STRING %token NUMBER @@ -198,6 +198,10 @@ main : PREFORK NUMBER { | LOGDIR STRING { conf->sc_logdir = $2; } + | DEFAULT TYPE mediastring { + memcpy(&conf->sc_default_type, &media, + sizeof(struct media_type)); + } ; server : SERVER optmatch STRING { @@ -256,6 +260,8 @@ server : SERVER optmatch STRING { HTTPD_TLS_ECDHE_CURVE, sizeof(s->srv_conf.tls_ecdhe_curve)); + s->srv_conf.hsts_max_age = SERVER_HSTS_DEFAULT_AGE; + if (last_server_id == INT_MAX) { yyerror("too many servers defined"); free(s); @@ -555,7 +561,40 @@ serveroptsl : LISTEN ON STRING opttls port { srv_conf = &parentsrv->srv_conf; parentsrv = NULL; } + | DEFAULT TYPE mediastring { + srv_conf->flags |= SRVFLAG_DEFAULT_TYPE; + memcpy(&srv_conf->default_type, &media, + sizeof(struct media_type)); + } | include + | hsts { + if (parentsrv != NULL) { + yyerror("hsts inside location"); + YYERROR; + } + srv->srv_conf.flags |= SRVFLAG_SERVER_HSTS; + } + ; + +hsts : HSTS '{' optnl hstsflags_l '}' + | HSTS hstsflags + | HSTS + ; + +hstsflags_l : hstsflags optcommanl hstsflags_l + | hstsflags optnl + ; + +hstsflags : MAXAGE NUMBER { + if ($2 < 0 || $2 > INT_MAX) { + yyerror("invalid number of seconds: %lld", $2); + YYERROR; + } + srv_conf->hsts_max_age = $2; + } + | SUBDOMAINS { + srv->srv_conf.hsts_subdomains = 1; + } ; fastcgi : NO FCGI { @@ -961,7 +1000,11 @@ mediaopts_l : mediaopts_l mediaoptsl nl | mediaoptsl nl ; -mediaoptsl : STRING '/' STRING { +mediaoptsl : mediastring medianames_l optsemicolon + | include + ; + +mediastring : STRING '/' STRING { if (strlcpy(media.media_type, $1, sizeof(media.media_type)) >= sizeof(media.media_type) || @@ -975,8 +1018,7 @@ mediaoptsl : STRING '/' STRING { } free($1); free($3); - } medianames_l optsemicolon - | include + } ; medianames_l : medianames_l medianamesl @@ -1109,12 +1151,14 @@ lookup(char *s) { "combined", COMBINED }, { "common", COMMON }, { "connection", CONNECTION }, + { "default", DEFAULT }, { "dhe", DHE }, { "directory", DIRECTORY }, { "drop", DROP }, { "ecdhe", ECDHE }, { "error", ERR }, { "fastcgi", FCGI }, + { "hsts", HSTS }, { "include", INCLUDE }, { "index", INDEX }, { "ip", IP }, @@ -1125,6 +1169,7 @@ lookup(char *s) { "logdir", LOGDIR }, { "match", MATCH }, { "max", MAXIMUM }, + { "max-age", MAXAGE }, { "no", NO }, { "nodelay", NODELAY }, { "on", ON }, @@ -1141,10 +1186,12 @@ lookup(char *s) { "socket", SOCKET }, { "strip", STRIP }, { "style", STYLE }, + { "subdomains", SUBDOMAINS }, { "syslog", SYSLOG }, { "tcp", TCP }, { "timeout", TIMEOUT }, { "tls", TLS }, + { "type", TYPE }, { "types", TYPES }, { "with", WITH } }; @@ -1472,7 +1519,8 @@ popfile(void) int parse_config(const char *filename, struct httpd *x_conf) { - struct sym *sym, *next; + struct sym *sym, *next; + struct media_type dflt = HTTPD_DEFAULT_TYPE; conf = x_conf; if (config_init(conf) == -1) { @@ -1480,6 +1528,9 @@ parse_config(const char *filename, struct httpd *x_conf) return (-1); } + /* Set default media type */ + memcpy(&conf->sc_default_type, &dflt, sizeof(struct media_type)); + errors = 0; if ((file = pushfile(filename, 0)) == NULL) diff --git a/httpd/server_file.c b/httpd/server_file.c index 87fe865..ba0554b 100644 --- a/httpd/server_file.c +++ b/httpd/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.56 2015/07/17 21:53:57 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.57 2015/07/18 06:00:43 reyk Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -238,7 +238,7 @@ server_file_request(struct httpd *env, struct client *clt, char *path, if ((fd = open(path, O_RDONLY)) == -1) goto abort; - media = media_find(env->sc_mediatypes, path); + media = media_find_config(env, srv_conf, path); ret = server_response_http(clt, 200, media, st->st_size, MINIMUM(time(NULL), st->st_mtim.tv_sec)); switch (ret) { @@ -290,6 +290,7 @@ int server_partial_file_request(struct httpd *env, struct client *clt, char *path, struct stat *st, char *range_str) { + struct server_config *srv_conf = clt->clt_srv_conf; struct http_descriptor *resp = clt->clt_descresp; struct http_descriptor *desc = clt->clt_descreq; struct media_type *media, multipart_media; @@ -317,7 +318,7 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, if ((fd = open(path, O_RDONLY)) == -1) goto abort; - media = media_find(env->sc_mediatypes, path); + media = media_find_config(env, srv_conf, path); if ((evb = evbuffer_new()) == NULL) { errstr = "failed to allocate file buffer"; goto abort; @@ -347,9 +348,7 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, content_length += i; if ((i = evbuffer_add_printf(evb, "Content-Type: %s/%s\r\n", - media == NULL ? "application" : media->media_type, - media == NULL ? - "octet-stream" : media->media_subtype)) == -1) + media->media_type, media->media_subtype)) == -1) goto abort; content_length += i; @@ -542,7 +541,7 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st) close(fd); fd = -1; - media = media_find(env->sc_mediatypes, "index.html"); + media = media_find_config(env, srv_conf, "index.html"); ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), dir_mtime); switch (ret) { diff --git a/httpd/server_http.c b/httpd/server_http.c index 9a6609e..d51359f 100644 --- a/httpd/server_http.c +++ b/httpd/server_http.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_http.c,v 1.89 2015/07/16 19:05:28 reyk Exp $ */ +/* $OpenBSD: server_http.c,v 1.91 2015/07/18 06:00:43 reyk Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -735,13 +735,12 @@ server_http_parsehost(char *host, char *buf, size_t len, int *portval) void server_abort_http(struct client *clt, u_int code, const char *msg) { - struct server *srv = clt->clt_srv; - struct server_config *srv_conf = &srv->srv_conf; + struct server_config *srv_conf = clt->clt_srv_conf; struct bufferevent *bev = clt->clt_bev; struct http_descriptor *desc = clt->clt_descreq; const char *httperr = NULL, *style; char *httpmsg, *body = NULL, *extraheader = NULL; - char tmbuf[32], hbuf[128]; + char tmbuf[32], hbuf[128], *hstsheader = NULL; char buf[IBUF_READ_SIZE]; int bodylen; @@ -828,6 +827,14 @@ server_abort_http(struct client *clt, u_int code, const char *msg) code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) goto done; + if (srv_conf->flags & SRVFLAG_SERVER_HSTS) { + if (asprintf(&hstsheader, "Strict-Transport-Security: " + "max-age=%d%s\r\n", srv_conf->hsts_max_age, + srv_conf->hsts_subdomains == 0 ? "" : + " ; includeSubDomains") == -1) + goto done; + } + /* Add basic HTTP headers */ if (asprintf(&httpmsg, "HTTP/1.0 %03d %s\r\n" @@ -837,10 +844,12 @@ server_abort_http(struct client *clt, u_int code, const char *msg) "Content-Type: text/html\r\n" "Content-Length: %d\r\n" "%s" + "%s" "\r\n" "%s", code, httperr, tmbuf, HTTPD_SERVERNAME, bodylen, extraheader == NULL ? "" : extraheader, + hstsheader == NULL ? "" : hstsheader, desc->http_method == HTTP_METHOD_HEAD ? "" : body) == -1) goto done; @@ -851,6 +860,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg) done: free(body); free(extraheader); + free(hstsheader); if (msg == NULL) msg = "\"\""; if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) { @@ -1210,13 +1220,15 @@ int server_response_http(struct client *clt, u_int code, struct media_type *media, off_t size, time_t mtime) { + struct server_config *srv_conf = clt->clt_srv_conf; struct http_descriptor *desc = clt->clt_descreq; struct http_descriptor *resp = clt->clt_descresp; const char *error; struct kv *ct, *cl; char tmbuf[32]; - if (desc == NULL || (error = server_httperror_byid(code)) == NULL) + if (desc == NULL || media == NULL || + (error = server_httperror_byid(code)) == NULL) return (-1); if (server_log_http(clt, code, size) == -1) @@ -1241,9 +1253,7 @@ server_response_http(struct client *clt, u_int code, /* Set media type */ if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL || - kv_set(ct, "%s/%s", - media == NULL ? "application" : media->media_type, - media == NULL ? "octet-stream" : media->media_subtype) == -1) + kv_set(ct, "%s/%s", media->media_type, media->media_subtype) == -1) return (-1); /* Set content length, if specified */ @@ -1257,6 +1267,17 @@ server_response_http(struct client *clt, u_int code, kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL) return (-1); + /* HSTS header */ + if (srv_conf->flags & SRVFLAG_SERVER_HSTS) { + if ((cl = + kv_add(&resp->http_headers, "Strict-Transport-Security", + NULL)) == NULL || + kv_set(cl, "max-age=%d%s", srv_conf->hsts_max_age, + srv_conf->hsts_subdomains == 0 ? "" : + " ; includeSubDomains") == -1) + return (-1); + } + /* Date header is mandatory and should be added as late as possible */ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 || kv_add(&resp->http_headers, "Date", tmbuf) == NULL) -- cgit v1.2.3-54-g00ecf