>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(&param, "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(&param, "SERVER_NAME", srv_conf->name,
+       if (fcgi_add_param(&param, "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"

Reply via email to