OK? diff --git httpd.conf.5 httpd.conf.5 index b3eaad8..bfca29f 100644 --- httpd.conf.5 +++ httpd.conf.5 @@ -262,6 +262,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 httpd.h httpd.h index 2cb7934..9596000 100644 --- httpd.h +++ httpd.h @@ -68,6 +68,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 +352,14 @@ 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_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" #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 @@ -443,6 +445,9 @@ struct server_config { char *return_uri; off_t return_uri_len; + int64_t hsts_max_age; + int hsts_subdomains; + TAILQ_ENTRY(server_config) entry; }; TAILQ_HEAD(serverhosts, server_config); diff --git parse.y parse.y index 0870819..8dfad1a 100644 --- parse.y +++ parse.y @@ -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 TYPES HSTS MAXAGE SUBDOMAINS %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS %token <v.string> STRING %token <v.number> NUMBER @@ -256,6 +256,8 @@ server : SERVER optmatch STRING { HTTPD_TLS_ECDHE_CURVE, sizeof(s->srv_conf.tls_ecdhe_curve)); + s->srv_conf.hsts_max_age = -1; + if (last_server_id == INT_MAX) { yyerror("too many servers defined"); free(s); @@ -556,6 +558,30 @@ serveroptsl : LISTEN ON STRING opttls port { parentsrv = NULL; } | 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 { + srv_conf->hsts_max_age = $2; + } + | SUBDOMAINS { + srv->srv_conf.hsts_subdomains = 1; + } ; fastcgi : NO FCGI { @@ -1115,6 +1141,7 @@ lookup(char *s) { "ecdhe", ECDHE }, { "error", ERR }, { "fastcgi", FCGI }, + { "hsts", HSTS }, { "include", INCLUDE }, { "index", INDEX }, { "ip", IP }, @@ -1125,6 +1152,7 @@ lookup(char *s) { "logdir", LOGDIR }, { "match", MATCH }, { "max", MAXIMUM }, + { "max-age", MAXAGE }, { "no", NO }, { "nodelay", NODELAY }, { "on", ON }, @@ -1141,6 +1169,7 @@ lookup(char *s) { "socket", SOCKET }, { "strip", STRIP }, { "style", STYLE }, + { "subdomains", SUBDOMAINS }, { "syslog", SYSLOG }, { "tcp", TCP }, { "timeout", TIMEOUT }, diff --git server_http.c server_http.c index 9a6609e..8bdeade 100644 --- server_http.c +++ server_http.c @@ -741,7 +741,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg) 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 +828,16 @@ 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=%lld%s\r\n", + srv_conf->hsts_max_age == -1 ? SERVER_HSTS_DEFAULT_AGE : + 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 +847,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 +863,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,6 +1223,8 @@ int server_response_http(struct client *clt, u_int code, struct media_type *media, off_t size, time_t mtime) { + struct server *srv = clt->clt_srv; + struct server_config *srv_conf = &srv->srv_conf; struct http_descriptor *desc = clt->clt_descreq; struct http_descriptor *resp = clt->clt_descresp; const char *error; @@ -1257,6 +1272,18 @@ 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=%lld%s", srv_conf->hsts_max_age == -1 ? + SERVER_HSTS_DEFAULT_AGE : 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)
-- I'm not entirely sure you are real.