>Synopsis: httpd(/slowcgi) is not compliant with CGI RFC w.r.t. SERVER_NAME
>Category: user
>Environment:
System : OpenBSD 5.9
Details : OpenBSD 5.9-stable (GENERIC.MP) #4: Fri Jun 3 20:56:54
AEST 2016
[email protected]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
Architecture: OpenBSD.amd64
Machine : amd64
>Description:
[AFAIK, this problem still exists in -current.]
OpenBSD's httpd(8) sets SERVER_NAME to the name specified in httpd.conf:
if (fcgi_add_param(¶m, "SERVER_NAME", srv_conf->name,
and the first example in httpd.conf(5) has
server "default" {
Using that config in httpd.conf, CGI programs (via slowcgi) see
SERVER_NAME as "default".
However, RFC 3875 "The Common Gateway Interface (CGI) Version 1.1", has
========
4.1.14. SERVER_NAME
The SERVER_NAME variable MUST be set to the name of the server host
to which the client request is directed. It is a case-insensitive
hostname or network address. It forms the host part of the
Script-URI.
SERVER_NAME = server-name
server-name = hostname | ipv4-address | ( "[" ipv6-address "]" )
[...]
A deployed server can have more than one possible value for this
variable, where several HTTP virtual hosts share the same IP
address. In that case, the server would use the contents of the
request's Host header field to select the correct virtual host.
========
"default" is neither a hostname nor a network address, but is seen
if no "Host:" header is sent, or if the "Host:" header is set and
no error is detected:
# IPv4: "Host: " ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: example.my.domain" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: example.my.domain:" ==>
HTTP/1.0 400 Bad Request
# IPv4: "Host: example.my.domain:80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: example.my.domain:666" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: example.my.domain:!bogus" ==>
HTTP/1.0 400 Bad Request
# IPv4: "Host: unknown-host" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: :80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: !bogus" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: I am a duck" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: I am:a duck" ==>
HTTP/1.0 400 Bad Request
# IPv4: "Host: 192.168.1.123" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv4: "Host: 192.168.1.666" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv6: "Host: [fe80::okok:okok:okok:okok]" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv6: "Host: [fe80::okok:okok:okok:okok]:80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
# IPv6: "Host: [fe80::bad:bad:bad:bad]:80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "default"
Also, note that valid numbers which are _not_ the correct port
number, are *not rejected as errors*.
>How-To-Repeat:
Run a CGI script via slowcgi(8).
>Fix:
chrisz@ provided a partial patch in early May:
========
Index: server_fcgi.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v
retrieving revision 1.68
diff -u -p -r1.68 server_fcgi.c
--- server_fcgi.c 24 Apr 2016 20:09:45 -0000 1.68
+++ server_fcgi.c 4 May 2016 15:41:15 -0000
@@ -340,7 +340,7 @@ server_fcgi(struct httpd *env, struct cl
goto fail;
}
- if (fcgi_add_param(¶m, "SERVER_NAME", srv_conf->name,
+ if (fcgi_add_param(¶m, "SERVER_NAME", desc->http_host,
clt) == -1) {
errstr = "failed to encode param";
goto fail;
Index: server_http.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v
retrieving revision 1.106
diff -u -p -r1.106 server_http.c
--- server_http.c 8 Mar 2016 09:33:15 -0000 1.106
+++ server_http.c 4 May 2016 15:41:18 -0000
@@ -1016,13 +1016,13 @@ server_expand_http(struct client *clt, c
}
if (strstr(val, "$SERVER_PORT") != NULL) {
snprintf(ibuf, sizeof(ibuf), "%u",
- ntohs(srv_conf->port));
+ ntohs(server_socket_getport(&clt->clt_srv_ss)));
if (expand_string(buf, len,
"$SERVER_PORT", ibuf) != 0)
return (NULL);
}
if (strstr(val, "$SERVER_NAME") != NULL) {
- if ((str = url_encode(srv_conf->name))
+ if ((str = url_encode(desc->http_host))
== NULL)
return (NULL);
ret = expand_string(buf, len, "$SERVER_NAME", str);
@@ -1047,7 +1047,6 @@ server_response(struct httpd *httpd, str
struct kv *kv, key, *host;
struct str_find sm;
int portval = -1, ret;
- char *hostval;
const char *errstr = NULL;
/* Canonicalize the request path */
@@ -1094,7 +1093,7 @@ server_response(struct httpd *httpd, str
* XXX the Host can also appear in the URL path.
*/
if (host != NULL) {
- if ((hostval = server_http_parsehost(host->kv_value,
+ if ((server_http_parsehost(host->kv_value,
hostname, sizeof(hostname), &portval)) == NULL)
goto fail;
@@ -1123,24 +1122,17 @@ server_response(struct httpd *httpd, str
(portval != -1 && portval == srv_conf->port))) {
/* Replace host configuration */
clt->clt_srv_conf = srv_conf;
- srv_conf = NULL;
+ srv_conf = clt->clt_srv_conf;
break;
}
}
}
- if (srv_conf != NULL) {
+ if (srv_conf != NULL)
/* Use the actual server IP address */
if (server_http_host(&clt->clt_srv_ss, hostname,
sizeof(hostname)) == NULL)
goto fail;
- } else {
- /* Host header was valid and found */
- if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >=
- sizeof(hostname))
- goto fail;
- srv_conf = clt->clt_srv_conf;
- }
if ((desc->http_host = strdup(hostname)) == NULL)
goto fail;
========
In limited testing, this patch appears to fix things when the client does
_not_ supply a "Host:" header, but when such a header is supplied, the
behaviour remains (highly) questionable:
# IPv4: "Host: " ==>
HTTP/1.0 200 OK
SERVER_NAME = ""
# IPv4: "Host: example.my.domain" ==>
HTTP/1.0 200 OK
SERVER_NAME = "example.my.domain"
# IPv4: "Host: example.my.domain:" ==>
HTTP/1.0 400 Bad Request
# IPv4: "Host: example.my.domain:80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "example.my.domain"
# IPv4: "Host: example.my.domain:666" ==>
HTTP/1.0 200 OK
SERVER_NAME = "example.my.domain"
# IPv4: "Host: example.my.domain:!bogus" ==>
HTTP/1.0 400 Bad Request
# IPv4: "Host: unknown-host" ==>
HTTP/1.0 200 OK
SERVER_NAME = "unknown-host"
# IPv4: "Host: :80" ==>
HTTP/1.0 200 OK
SERVER_NAME = ""
# IPv4: "Host: !bogus" ==>
HTTP/1.0 200 OK
SERVER_NAME = "!bogus"
# IPv4: "Host: I am a duck" ==>
HTTP/1.0 200 OK
SERVER_NAME = "I am a duck"
# IPv4: "Host: I am:a duck" ==>
HTTP/1.0 400 Bad Request
# IPv4: "Host: 192.168.1.123" ==>
HTTP/1.0 200 OK
SERVER_NAME = "192.168.1.123"
# IPv4: "Host: 192.168.1.666" ==>
HTTP/1.0 200 OK
SERVER_NAME = "192.168.1.666"
# IPv6: "Host: [fe80::okok:okok:okok:okok]" ==>
HTTP/1.0 200 OK
SERVER_NAME = "fe80::okok:okok:okok:okok"
# IPv6: "Host: [fe80::okok:okok:okok:okok]:80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "fe80::okok:okok:okok:okok"
# IPv6: "Host: [fe80::bad:bad:bad:bad]:80" ==>
HTTP/1.0 200 OK
SERVER_NAME = "fe80::bad:bad:bad:bad"