From be1b7213b8e8d0de98b5a6445504a583e8c9a064 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Tue, 24 Feb 2015 08:57:19 +0100 Subject: Allow to specify CGI variables as macros in redirection strings, eg. block return 301 "http://www.example.com/$REQUEST_URI" OK tedu@ florian@ Add return_uri to serverconfig_reset() to avoid using garbage from the imsg buffer. Debugging & OK halex@ Change TLS_PROTOCOLS_DEFAULT to be TLSv1.2 only. Add a TLS_PROTOCOLS_ALL that includes all currently supported protocols (TLSv1.0, TLSv1.1 and TLSv1.2). Change all users of libtls to use TLS_PROTOCOLS_ALL so that they maintain existing behaviour. Discussed with tedu@ and reyk@. --- Makefile | 4 +-- config.c | 27 +++++++++----- http.h | 9 +++-- httpd.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++- httpd.conf.5 | 39 +++++++++++++++++++-- httpd.h | 6 +++- parse.y | 25 +++++++++++-- server.c | 6 +++- server_fcgi.c | 4 +-- server_file.c | 21 ++++++++--- server_http.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 11 files changed, 310 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 441e02c..885ad42 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.26 2014/10/31 13:49:52 jsing Exp $ +# $OpenBSD: Makefile,v 1.27 2015/02/23 10:39:10 reyk Exp $ PROG= httpd SRCS= parse.y @@ -8,7 +8,7 @@ MAN= httpd.8 httpd.conf.5 LDADD= -levent -ltls -lssl -lcrypto -lutil DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} -#DEBUG= -g -DDEBUG=3 +#DEBUG= -g -DDEBUG=3 -O0 CFLAGS+= -Wall -I${.CURDIR} CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations diff --git a/config.c b/config.c index f631214..2dc9159 100644 --- a/config.c +++ b/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.35 2015/02/07 23:56:02 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.36 2015/02/23 11:48:41 reyk Exp $ */ /* * Copyright (c) 2011 - 2015 Reyk Floeter @@ -272,6 +272,17 @@ config_getserver_config(struct httpd *env, struct server *srv, if (config_getserver_auth(env, srv_conf) != 0) goto fail; + /* + * Get variable-length values for the virtual host. The tls_* ones + * aren't needed in the virtual hosts unless we implement SNI. + */ + if (srv_conf->return_uri_len != 0) { + if ((srv_conf->return_uri = get_data(p + s, + srv_conf->return_uri_len)) == NULL) + goto fail; + s += srv_conf->return_uri_len; + } + if (srv_conf->flags & SRVFLAG_LOCATION) { /* Inherit configuration from the parent */ f = SRVFLAG_INDEX|SRVFLAG_NO_INDEX; @@ -344,6 +355,7 @@ config_getserver_config(struct httpd *env, struct server *srv, f = SRVFLAG_BLOCK|SRVFLAG_NO_BLOCK; if ((srv_conf->flags & f) == 0) { + free(srv_conf->return_uri); srv_conf->flags |= parent->flags & f; srv_conf->return_code = parent->return_code; srv_conf->return_uri_len = parent->return_uri_len; @@ -351,13 +363,6 @@ config_getserver_config(struct httpd *env, struct server *srv, (srv_conf->return_uri = strdup(parent->return_uri)) == NULL) goto fail; - } else { - if (srv_conf->return_uri_len != 0) { - if ((srv_conf->return_uri = get_data(p + s, - srv_conf->return_uri_len)) == NULL) - goto fail; - s += srv_conf->return_uri_len; - } } memcpy(&srv_conf->timeout, &parent->timeout, @@ -408,7 +413,8 @@ config_getserver(struct httpd *env, struct imsg *imsg) serverconfig_reset(&srv_conf); if ((IMSG_DATA_SIZE(imsg) - s) < - (srv_conf.tls_cert_len + srv_conf.tls_key_len)) { + (srv_conf.tls_cert_len + srv_conf.tls_key_len + + srv_conf.return_uri_len)) { log_debug("%s: invalid message length", __func__); goto fail; } @@ -450,6 +456,9 @@ config_getserver(struct httpd *env, struct imsg *imsg) srv->srv_conf.name, srv->srv_conf.id, printb_flags(srv->srv_conf.flags, SRVFLAG_BITS)); + /* + * Get all variable-length values for the parent server. + */ if (srv->srv_conf.return_uri_len != 0) { if ((srv->srv_conf.return_uri = get_data(p + s, srv->srv_conf.return_uri_len)) == NULL) diff --git a/http.h b/http.h index 59d1c10..00cf235 100644 --- a/http.h +++ b/http.h @@ -1,4 +1,4 @@ -/* $OpenBSD: http.h,v 1.11 2015/01/13 09:21:15 reyk Exp $ */ +/* $OpenBSD: http.h,v 1.12 2015/02/11 12:52:01 florian Exp $ */ /* * Copyright (c) 2012 - 2015 Reyk Floeter @@ -172,7 +172,8 @@ struct http_error { { 415, "Unsupported Media Type" }, \ { 416, "Range Not Satisfiable" }, \ { 417, "Expectation Failed" }, \ - /* 418-421 unassigned */ \ + { 418, "I'm a teapot" }, \ + /* 419-421 unassigned */ \ { 420, "Enhance Your Calm" }, \ { 422, "Unprocessable Entity" }, \ { 423, "Locked" }, \ @@ -184,7 +185,9 @@ struct http_error { { 429, "Too Many Requests" }, \ /* 430 unassigned */ \ { 431, "Request Header Fields Too Large" }, \ - /* 432-499 unassigned */ \ + /* 432-450 unassigned */ \ + { 451, "Unavailable For Legal Reasons" }, \ + /* 452-499 unassigned */ \ { 500, "Internal Server Error" }, \ { 501, "Not Implemented" }, \ { 502, "Bad Gateway" }, \ diff --git a/httpd.c b/httpd.c index f640dc2..c6f183f 100644 --- a/httpd.c +++ b/httpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.c,v 1.32 2015/02/08 00:00:59 reyk Exp $ */ +/* $OpenBSD: httpd.c,v 1.35 2015/02/23 18:43:18 reyk Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -494,6 +494,39 @@ event_again(struct event *ev, int fd, short event, event_add(ev, &tv); } +int +expand_string(char *label, size_t len, const char *srch, const char *repl) +{ + char *tmp; + char *p, *q; + + if ((tmp = calloc(1, len)) == NULL) { + log_debug("%s: calloc", __func__); + return (-1); + } + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, len) >= len) || + (strlcat(tmp, repl, len) >= len)) { + log_debug("%s: string too long", __func__); + free(tmp); + return (-1); + } + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, len) >= len) { + log_debug("%s: string too long", __func__); + free(tmp); + return (-1); + } + (void)strlcpy(label, tmp, len); /* always fits */ + free(tmp); + + return (0); +} + const char * canonicalize_host(const char *host, char *name, size_t len) { @@ -671,6 +704,62 @@ path_info(char *path) return (p - start); } +char * +url_encode(const char *src) +{ + static char hex[] = "0123456789ABCDEF"; + char *dp, *dst; + unsigned char c; + + /* We need 3 times the memory if every letter is encoded. */ + if ((dst = calloc(3, strlen(src) + 1)) == NULL) + return (NULL); + + for (dp = dst; *src != 0; src++) { + c = (unsigned char) *src; + if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' || + c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) { + *dp++ = '%'; + *dp++ = hex[c >> 4]; + *dp++ = hex[c & 0x0f]; + } else + *dp++ = *src; + } + return (dst); +} + +char* +escape_html(const char* src) +{ + char *dp, *dst; + + /* We need 5 times the memory if every letter is "<" or ">". */ + if ((dst = calloc(5, strlen(src) + 1)) == NULL) + return NULL; + + for (dp = dst; *src != 0; src++) { + if (*src == '<') { + *dp++ = '&'; + *dp++ = 'l'; + *dp++ = 't'; + *dp++ = ';'; + } else if (*src == '>') { + *dp++ = '&'; + *dp++ = 'g'; + *dp++ = 't'; + *dp++ = ';'; + } else if (*src == '&') { + *dp++ = '&'; + *dp++ = 'a'; + *dp++ = 'm'; + *dp++ = 'p'; + *dp++ = ';'; + } else + *dp++ = *src; + } + return (dst); +} + void socket_rlimit(int maxfd) { diff --git a/httpd.conf.5 b/httpd.conf.5 index f93b57e..da51ad3 100644 --- a/httpd.conf.5 +++ b/httpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.conf.5,v 1.50 2015/02/07 08:12:30 jmc Exp $ +.\" $OpenBSD: httpd.conf.5,v 1.53 2015/02/23 18:43:18 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: February 7 2015 $ +.Dd $Mdocdate: February 23 2015 $ .Dt HTTPD.CONF 5 .Os .Sh NAME @@ -157,13 +157,38 @@ Close the connection and send an error page. If the optional return code is not specified, .Xr httpd 8 denies access with a -.Sq 404 Forbidden +.Sq 403 Forbidden response. The optional .Ar uri argument can be used with return codes in the 3xx range to send a .Sq Location: header for redirection to a specified URI. +.Pp +The +.Ar url +may contain predefined macros that will be expanded at runtime: +.Pp +.Bl -tag -width $DOCUMENT_URI -offset indent -compact +.It Ic $DOCUMENT_URI +The request path. +.It Ic $QUERY_STRING +The optional query string of the request. +.It Ic $REMOTE_ADDR +The IP address of the connected client. +.It Ic $REMOTE_PORT +The TCP source port of the connected client. +.It Ic $REMOTE_USER +The remote user for HTTP authentication. +.It Ic $REQUEST_URI +The request path and optional query string. +.It Ic $SERVER_ADDR +The configured IP address of the relay. +.It Ic $SERVER_PORT +The configured TCP server port of the relay. +.It Ic $SERVER_NAME +The name of the server. +.El .It Ic connection Ar option Set the specified options and limits for HTTP connections. Valid options are: @@ -367,6 +392,14 @@ should contain a PEM encoded private key and reside outside of the .Xr chroot 2 root directory of .Nm httpd . +.It Ic protocols Ar string +Specify the TLS protocols to enable for this server. +If not specified, the default value +.Qq all +will be used (all available protocols). +Refer to the +.Xr tls_config_parse_protocols 3 +function for other valid protocol string values. .El .El .Sh TYPES diff --git a/httpd.h b/httpd.h index 98096c9..28e02a5 100644 --- a/httpd.h +++ b/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.77 2015/02/07 23:56:02 reyk Exp $ */ +/* $OpenBSD: httpd.h,v 1.81 2015/02/23 18:43:18 reyk Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -410,6 +410,7 @@ struct server_config { u_int8_t *tls_key; size_t tls_key_len; char *tls_key_file; + u_int32_t tls_protocols; u_int32_t flags; int strip; @@ -583,10 +584,13 @@ int fcgi_add_stdin(struct client *, struct evbuffer *); void event_again(struct event *, int, short, void (*)(int, short, void *), struct timeval *, struct timeval *, void *); +int expand_string(char *, size_t, const char *, const char *); const char *url_decode(char *); +char *url_encode(const char *); const char *canonicalize_host(const char *, char *, size_t); const char *canonicalize_path(const char *, char *, size_t); size_t path_info(char *); +char *escape_html(const char *); void imsg_event_add(struct imsgev *); int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t, pid_t, int, void *, u_int16_t); diff --git a/parse.y b/parse.y index 51f7d98..7e48aba 100644 --- a/parse.y +++ b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.64 2015/02/08 04:50:32 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.65 2015/02/12 04:40:23 jsing Exp $ */ /* * Copyright (c) 2007 - 2015 Reyk Floeter @@ -131,8 +131,9 @@ typedef struct { %token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON %token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN -%token LOCATION LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK REQUEST REQUESTS -%token ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT TLS TYPES +%token LOCATION LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS +%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT +%token TLS TYPES %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS %token STRING %token NUMBER @@ -236,6 +237,7 @@ server : SERVER STRING { s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY; s->srv_conf.flags |= SRVFLAG_LOG; s->srv_conf.logformat = LOG_FORMAT_COMMON; + s->srv_conf.tls_protocols = TLS_PROTOCOLS_ALL; if ((s->srv_conf.tls_cert_file = strdup(HTTPD_TLS_CERT)) == NULL) fatal("out of memory"); @@ -297,6 +299,13 @@ server : SERVER STRING { YYERROR; } + if ((srv->srv_conf.flags & SRVFLAG_TLS) && + srv->srv_conf.tls_protocols == 0) { + yyerror("no TLS protocols"); + free(srv); + YYERROR; + } + if (server_tls_load_keypair(srv) == -1) { yyerror("failed to load public/private keys " "for server %s", srv->srv_conf.name); @@ -643,6 +652,15 @@ tlsopts : CERTIFICATE STRING { } free($2); } + | PROTOCOLS STRING { + if (tls_config_parse_protocols( + &srv_conf->tls_protocols, $2) != 0) { + yyerror("invalid TLS protocols"); + free($2); + YYERROR; + } + free($2); + } ; root : ROOT rootflags @@ -1097,6 +1115,7 @@ lookup(char *s) { "pass", PASS }, { "port", PORT }, { "prefork", PREFORK }, + { "protocols", PROTOCOLS }, { "request", REQUEST }, { "requests", REQUESTS }, { "return", RETURN }, diff --git a/server.c b/server.c index 6e63120..813f945 100644 --- a/server.c +++ b/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.57 2015/02/07 23:56:02 reyk Exp $ */ +/* $OpenBSD: server.c,v 1.60 2015/02/23 09:52:28 reyk Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -175,6 +175,9 @@ server_tls_init(struct server *srv) return (-1); } + tls_config_set_protocols(srv->srv_tls_config, + srv->srv_conf.tls_protocols); + if (tls_config_set_ciphers(srv->srv_tls_config, srv->srv_conf.tls_ciphers) != 0) { log_warn("%s: failed to set tls ciphers", __func__); @@ -320,6 +323,7 @@ serverconfig_reset(struct server_config *srv_conf) { srv_conf->tls_cert_file = srv_conf->tls_key_file = NULL; srv_conf->tls_cert = srv_conf->tls_key = NULL; + srv_conf->return_uri = NULL; srv_conf->auth = NULL; } diff --git a/server_fcgi.c b/server_fcgi.c index 563ffe5..33603a0 100644 --- a/server_fcgi.c +++ b/server_fcgi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_fcgi.c,v 1.51 2015/01/21 22:23:24 reyk Exp $ */ +/* $OpenBSD: server_fcgi.c,v 1.52 2015/02/23 19:22:43 chrisz Exp $ */ /* * Copyright (c) 2014 Florian Obser @@ -242,7 +242,7 @@ server_fcgi(struct httpd *env, struct client *clt) errstr = "failed to encode param"; goto fail; } - if (fcgi_add_param(¶m, "DOCUMENT_URI", desc->http_path, + if (fcgi_add_param(¶m, "DOCUMENT_URI", alias, clt) == -1) { errstr = "failed to encode param"; goto fail; diff --git a/server_file.c b/server_file.c index b24f0c0..f697504 100644 --- a/server_file.c +++ b/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.49 2015/02/08 00:00:59 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.51 2015/02/12 10:05:29 reyk Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -274,6 +274,7 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st) struct evbuffer *evb = NULL; struct media_type *media; const char *stripped, *style; + char *escapeduri, *escapedhtml, *escapedpath; struct tm tm; time_t t, dir_mtime; @@ -304,6 +305,9 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st) /* Indicate failure but continue going through the list */ skip = 0; + if ((escapedpath = escape_html(desc->http_path)) == NULL) + goto fail; + /* A CSS stylesheet allows minimal customization by the user */ style = "body { background-color: white; color: black; font-family: " "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; @@ -318,9 +322,11 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st) "\n" "

Index of %s

\n" "
\n
\n",
-	    desc->http_path, style, desc->http_path) == -1)
+	    escapedpath, style, escapedpath) == -1)
 		skip = 1;
 
+	free(escapedpath);
+
 	for (i = 0; i < namesize; i++) {
 		dp = namelist[i];
 
@@ -335,6 +341,11 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st)
 		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
 		namewidth = 51 - strlen(dp->d_name);
 
+		if ((escapeduri = url_encode(dp->d_name)) == NULL)
+			goto fail;
+		if ((escapedhtml = escape_html(dp->d_name)) == NULL)
+			goto fail;
+
 		if (dp->d_name[0] == '.' &&
 		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
 			/* ignore hidden files starting with a dot */
@@ -342,17 +353,19 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st)
 			namewidth -= 1; /* trailing slash */
 			if (evbuffer_add_printf(evb,
 			    "%s/%*s%s%20s\n",
-			    dp->d_name, dp->d_name,
+			    escapeduri, escapedhtml,
 			    MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1)
 				skip = 1;
 		} else if (S_ISREG(st->st_mode)) {
 			if (evbuffer_add_printf(evb,
 			    "%s%*s%s%20llu\n",
-			    dp->d_name, dp->d_name,
+			    escapeduri, escapedhtml,
 			    MAXIMUM(namewidth, 0), " ",
 			    tmstr, st->st_size) == -1)
 				skip = 1;
 		}
+		free(escapeduri);
+		free(escapedhtml);
 		free(dp);
 	}
 	free(namelist);
diff --git a/server_http.c b/server_http.c
index d4d3c97..b63fc22 100644
--- a/server_http.c
+++ b/server_http.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: server_http.c,v 1.74 2015/02/08 00:00:59 reyk Exp $	*/
+/*	$OpenBSD: server_http.c,v 1.75 2015/02/23 18:43:18 reyk Exp $	*/
 
 /*
  * Copyright (c) 2006 - 2015 Reyk Floeter 
@@ -42,7 +42,9 @@ 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 *);
+		    struct client *);
+char		*server_expand_http(struct client *, const char *,
+		    char *, size_t);
 
 static struct httpd	*env = NULL;
 
@@ -735,6 +737,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
 	const char		*httperr = NULL, *style;
 	char			*httpmsg, *body = NULL, *extraheader = NULL;
 	char			 tmbuf[32], hbuf[128];
+	char			 buf[IBUF_READ_SIZE], *ptr = NULL;
 	int			 bodylen;
 
 	if (code == 0) {
@@ -762,10 +765,20 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
 	switch (code) {
 	case 301:
 	case 302:
-		if (asprintf(&extraheader, "Location: %s\r\n", msg) == -1) {
+	case 303:
+		if (msg == NULL)
+			break;
+		memset(buf, 0, sizeof(buf));
+		if ((ptr = server_expand_http(clt, msg,
+		    buf, sizeof(buf))) == NULL)
+			goto done;
+		if ((ptr = url_encode(ptr)) == NULL)
+			goto done;
+		if (asprintf(&extraheader, "Location: %s\r\n", ptr) == -1) {
 			code = 500;
 			extraheader = NULL;
 		}
+		msg = ptr;
 		break;
 	case 401:
 		if (asprintf(&extraheader,
@@ -829,12 +842,15 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
  done:
 	free(body);
 	free(extraheader);
+	if (msg == NULL)
+		msg = "\"\"";
 	if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) {
 		server_close(clt, msg);
 	} else {
 		server_close(clt, httpmsg);
 		free(httpmsg);
 	}
+	free(ptr);
 }
 
 void
@@ -855,6 +871,94 @@ server_close_http(struct client *clt)
 	clt->clt_remote_user = NULL;
 }
 
+char *
+server_expand_http(struct client *clt, const char *val, char *buf,
+    size_t len)
+{
+	struct http_descriptor	*desc = clt->clt_descreq;
+	struct server_config	*srv_conf = clt->clt_srv_conf;
+	char			 ibuf[128], *str;
+
+	if (strlcpy(buf, val, len) >= len)
+		return (NULL);
+
+	if (strstr(val, "$DOCUMENT_URI") != NULL) {
+		if (expand_string(buf, len, "$DOCUMENT_URI",
+		    desc->http_path) != 0)
+			return (NULL);
+	}
+	if (strstr(val, "$QUERY_STRING") != NULL) {
+		if (expand_string(buf, len, "$QUERY_STRING",
+		    desc->http_query == NULL ? "" :
+		    desc->http_query) != 0)
+			return (NULL);
+	}
+	if (strstr(val, "$REMOTE_") != NULL) {
+		if (strstr(val, "$REMOTE_ADDR") != NULL) {
+			if (print_host(&clt->clt_ss,
+			    ibuf, sizeof(ibuf)) == NULL)
+				return (NULL);
+			if (expand_string(buf, len,
+			    "$REMOTE_ADDR", ibuf) != 0)
+				return (NULL);
+		}
+		if (strstr(val, "$REMOTE_PORT") != NULL) {
+			snprintf(ibuf, sizeof(ibuf),
+			    "%u", ntohs(clt->clt_port));
+			if (expand_string(buf, len,
+			    "$REMOTE_PORT", ibuf) != 0)
+				return (NULL);
+		}
+		if (strstr(val, "$REMOTE_USER") != NULL) {
+			if ((srv_conf->flags & SRVFLAG_AUTH) &&
+			    clt->clt_remote_user != NULL)
+				str = clt->clt_remote_user;
+			else
+				str = "";
+			if (expand_string(buf, len,
+			    "$REMOTE_USER", str) != 0)
+				return (NULL);
+		}
+	}
+	if (strstr(val, "$REQUEST_URI") != NULL) {
+		if (desc->http_query == NULL) {
+			if ((str = strdup(desc->http_path)) == NULL)
+				return (NULL);
+		} else if (asprintf(&str, "%s?%s",
+		    desc->http_path, desc->http_query) == -1)
+			return (NULL);
+		if (expand_string(buf, len, "$REQUEST_URI", str) != 0) {
+			free(str);
+			return (NULL);
+		}
+		free(str);
+	}
+	if (strstr(val, "$SERVER_") != NULL) {
+		if (strstr(val, "$SERVER_ADDR") != NULL) {
+			if (print_host(&srv_conf->ss,
+			    ibuf, sizeof(ibuf)) == NULL)
+				return (NULL);
+			if (expand_string(buf, len,
+			    "$SERVER_ADDR", ibuf) != 0)
+				return (NULL);
+		}
+		if (strstr(val, "$SERVER_PORT") != NULL) {
+			snprintf(ibuf, sizeof(ibuf), "%u",
+			    ntohs(srv_conf->port));
+			if (expand_string(buf, len,
+			    "$SERVER_PORT", ibuf) != 0)
+				return (NULL);
+		}
+		if (strstr(val, "$SERVER_NAME") != NULL) {
+			if (expand_string(buf, len,
+			    "$SERVER_NAME", srv_conf->name) != 0)
+				return (NULL);
+		}
+	}
+
+	return (buf);
+}
+
 int
 server_response(struct httpd *httpd, struct client *clt)
 {
-- 
cgit v1.2.3-54-g00ecf