Download raw body.
httpd(8): patch to disable/modify the server banner
On Sun, 26 Jan 2025 21:20:35 +0100,
Lloyd <ng2d68@proton.me> wrote:
>
> I have created and tested the following patch for httpd(8) which adds
> two new options to httpd.conf(5):
>
> banner <string> which allows you to customize the server name string.
>
> [no] disablebanner which allows you to suppress the Server header and
> output in errdocs entirely e.g.:
>
> banner "Microsoft-IIS/5.0"
>
> server "foo" {
> listen on * port 80
> disablebanner
> }
>
> server "bar" {
> listen on * port 80
> banner "OpenBSD httpd"
> }
>
> These directives are available both in global and server contexts and
> you can mix and match (e.g. suppress globally but allow publication
> on certain virtual servers only).
>
> Allowing configuration of the Server header is suggested in RFC 2616.
> Suppression of the header entirely is preferred by some operators but
> often difficult to do in most server software without recompiling.
>
> This patch also fixes a bug introduced when gzip-static was merged,
> but the bug only affected debugging output so went unnoticed.
>
> Tested on amd64.
>
> Regards
> Lloyd
>
> Index: config.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/config.c,v
> retrieving revision 1.65
> diff -u -p -u -p -r1.65 config.c
> --- config.c 17 Jan 2024 08:22:40 -0000 1.65
> +++ config.c 26 Jan 2025 19:55:19 -0000
> @@ -54,6 +54,9 @@ config_init(struct httpd *env)
> (void)strlcpy(env->sc_errdocroot, "",
> sizeof(env->sc_errdocroot));
>
> + /* Load the default server banner into the config if unconfigured */
> + strlcpy(env->sc_banner, HTTPD_SERVERNAME, sizeof(env->sc_banner));
> +
Shouldn't you use (void)strlcpy like a line above?
> /* Other configuration */
> what = ps->ps_what[privsep_process];
>
> @@ -591,6 +594,10 @@ config_getserver_config(struct httpd *en
> srv_conf->flags |= parent->flags & SRVFLAG_ERRDOCS;
> (void)strlcpy(srv_conf->errdocroot, parent->errdocroot,
> sizeof(srv_conf->errdocroot));
> +
> + srv_conf->flags |= parent->flags & SRVFLAG_NO_BANNER;
> + strlcpy(srv_conf->banner, parent->banner,
> + sizeof(srv_conf->banner));
and here?
>
> DPRINTF("%s: %s %d location \"%s\", "
> "parent \"%s[%u]\", flags: %s",
> Index: httpd.conf.5
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v
> retrieving revision 1.125
> diff -u -p -u -p -r1.125 httpd.conf.5
> --- httpd.conf.5 3 Nov 2023 13:03:02 -0000 1.125
> +++ httpd.conf.5 26 Jan 2025 19:55:19 -0000
> @@ -105,6 +105,16 @@ server "example.com" {
> .Sh GLOBAL CONFIGURATION
> Here are the settings that can be set globally:
> .Bl -tag -width Ds
> +.It Ic banner Ar string
> +Set a custom server software name for
> +.Xr httpd 8 .
> +This is the value sent to clients in the
> +.Ic Server
> +HTTP response header, the
> +.Ic SERVER_SOFTWARE
> +environment variable for CGI scripts, and the
> +.Ic $SERVER_SOFTWARE
> +macro used to create custom error documents.
> .It Ic chroot Ar directory
> Set the
> .Xr chroot 2
> @@ -121,6 +131,14 @@ see the
> section below.
> If not specified, the default type is set to
> .Ar application/octet-stream .
> +.It Ic disablebanner
> +Do not send the
> +.Ic Server
> +HTTP response header, and hide the server software name in error documents.
> +The
> +.Ic SERVER_SOFTWARE
> +CGI environment variable is always set in accordance with
> +.Pa RFC 3875 .
> .It Ic errdocs Ar directory
> Let
> .Xr httpd 8
> @@ -227,6 +245,12 @@ and must be readable by the www user.
> Use the
> .Ic no authenticate
> directive to disable authentication in a location.
> +.It Ic banner Ar string
> +Set a custom
> +.Xr httpd 8
> +software name for the current
> +.Ic server ,
> +overriding the global setting if set.
> .It Ic block drop
> Drop the connection without sending an error page.
> .It Ic block Op Ic return Ar code Op Ar uri
> @@ -323,6 +347,13 @@ Disable the directory index.
> .Xr httpd 8
> will neither display nor generate a directory index.
> .El
> +.It Oo Ic no Oc Ic disablebanner
> +Suppresses the server software name in HTTP headers and error documents
> +for the current
> +.Ic server.
> +The
> +.Ic no
> +keyword can be used to override the global setting.
> .It Oo Ic no Oc Ic errdocs Ar directory
> Overrides or, if the
> .Ic no
> Index: httpd.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v
> retrieving revision 1.165
> diff -u -p -u -p -r1.165 httpd.h
> --- httpd.h 8 Oct 2024 05:28:11 -0000 1.165
> +++ httpd.h 26 Jan 2025 19:55:19 -0000
> @@ -46,6 +46,7 @@
> #define CONF_FILE "/etc/httpd.conf"
> #define HTTPD_USER "www"
> #define HTTPD_SERVERNAME "OpenBSD httpd"
> +#define HTTPD_SERVERNAME_MAX 64
> #define HTTPD_DOCROOT "/htdocs"
> #define HTTPD_ERRDOCTEMPLATE "err" /* 3-char name */
> #define HTTPD_ERRDOCROOT_MAX (PATH_MAX - sizeof("000.html"))
> @@ -389,6 +390,7 @@ SPLAY_HEAD(client_tree, client);
> #define SRVFLAG_PATH_REWRITE 0x01000000
> #define SRVFLAG_NO_PATH_REWRITE 0x02000000
> #define SRVFLAG_GZIP_STATIC 0x04000000
> +#define SRVFLAG_NO_BANNER 0x08000000
> #define SRVFLAG_LOCATION_FOUND 0x40000000
> #define SRVFLAG_LOCATION_NOT_FOUND 0x80000000
>
> @@ -398,7 +400,7 @@ SPLAY_HEAD(client_tree, client);
> "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \
> "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \
> "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31PATH\32NO_PATH" \
> - "\37LOCATION_FOUND\40LOCATION_NOT_FOUND"
> + "\33GZIP_STATIC\34NO_BANNER\37LOCATION_FOUND\40LOCATION_NOT_FOUND"
>
> #define TCPFLAG_NODELAY 0x01
> #define TCPFLAG_NNODELAY 0x02
> @@ -539,6 +541,7 @@ struct server_config {
> struct server_fcgiparams fcgiparams;
> int fcgistrip;
> char errdocroot[HTTPD_ERRDOCROOT_MAX];
> + char banner[HTTPD_SERVERNAME_MAX];
>
> TAILQ_ENTRY(server_config) entry;
> };
> @@ -600,6 +603,7 @@ struct httpd {
>
> int sc_custom_errdocs;
> char sc_errdocroot[HTTPD_ERRDOCROOT_MAX];
> + char sc_banner[HTTPD_SERVERNAME_MAX];
> };
>
> #define HTTPD_OPT_VERBOSE 0x01
> Index: parse.y
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/parse.y,v
> retrieving revision 1.128
> diff -u -p -u -p -r1.128 parse.y
> --- parse.y 27 Feb 2022 20:30:30 -0000 1.128
> +++ parse.y 26 Jan 2025 19:55:19 -0000
> @@ -141,7 +141,7 @@ typedef struct {
> %token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST
> %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE
> %token CA CLIENT CRL OPTIONAL PARAM FORWARDED FOUND NOT
> -%token ERRDOCS GZIPSTATIC
> +%token ERRDOCS GZIPSTATIC BANNER DISABLEBANNER
> %token <v.string> STRING
> %token <v.number> NUMBER
> %type <v.port> port
> @@ -210,9 +210,16 @@ main : PREFORK NUMBER {
> }
> conf->sc_prefork_server = $2;
> }
> + | BANNER STRING {
> + strlcpy(conf->sc_banner, $2, sizeof(conf->sc_banner));
> + free($2);
> + }
> | CHROOT STRING {
> conf->sc_chroot = $2;
> }
> + | DISABLEBANNER {
> + conf->sc_flags |= SRVFLAG_NO_BANNER;
> + }
> | ERRDOCS STRING {
> if ($2 != NULL && strlcpy(conf->sc_errdocroot, $2,
> sizeof(conf->sc_errdocroot)) >=
> @@ -300,6 +307,12 @@ server : SERVER optmatch STRING {
>
> s->srv_conf.hsts_max_age = SERVER_HSTS_DEFAULT_AGE;
>
> + strlcpy(s->srv_conf.banner, conf->sc_banner,
> + sizeof(s->srv_conf.banner));
and here?
> +
> + if (conf->sc_flags & SRVFLAG_NO_BANNER)
> + s->srv_conf.flags |= SRVFLAG_NO_BANNER;
> +
> (void)strlcpy(s->srv_conf.errdocroot,
> conf->sc_errdocroot,
> sizeof(s->srv_conf.errdocroot));
> @@ -495,6 +508,11 @@ serveroptsl : LISTEN ON STRING opttls po
>
> TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry);
> }
> + | BANNER STRING {
> + strlcpy(srv->srv_conf.banner, $2,
> + sizeof(srv->srv_conf.banner));
and here?
> + free($2);
> + }
> | ERRDOCS STRING {
> if (parentsrv != NULL) {
> yyerror("errdocs inside location");
> @@ -550,6 +568,7 @@ serveroptsl : LISTEN ON STRING opttls po
> | request
> | root
> | directory
> + | disablebanner
> | logformat
> | fastcgi
> | authenticate
> @@ -687,6 +706,22 @@ serveroptsl : LISTEN ON STRING opttls po
> }
> ;
>
> +disablebanner : NO DISABLEBANNER {
> + if (parentsrv != NULL) {
> + yyerror("disablebanner inside location");
> + YYERROR;
> + }
> + srv->srv_conf.flags &= ~SRVFLAG_NO_BANNER;
> + }
> + | DISABLEBANNER {
> + if (parentsrv != NULL) {
> + yyerror("disablebanner inside location");
> + YYERROR;
> + }
> + srv->srv_conf.flags |= SRVFLAG_NO_BANNER;
> + }
> + ;
> +
> optfound : /* empty */ { $$ = 0; }
> | FOUND { $$ = 1; }
> | NOT FOUND { $$ = -1; }
> @@ -1428,6 +1463,7 @@ lookup(char *s)
> { "authenticate", AUTHENTICATE},
> { "auto", AUTO },
> { "backlog", BACKLOG },
> + { "banner", BANNER },
> { "block", BLOCK },
> { "body", BODY },
> { "buffer", BUFFER },
> @@ -1443,6 +1479,7 @@ lookup(char *s)
> { "default", DEFAULT },
> { "dhe", DHE },
> { "directory", DIRECTORY },
> + { "disablebanner", DISABLEBANNER },
> { "drop", DROP },
> { "ecdhe", ECDHE },
> { "errdocs", ERRDOCS },
> Index: server_fcgi.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v
> retrieving revision 1.97
> diff -u -p -u -p -r1.97 server_fcgi.c
> --- server_fcgi.c 8 Nov 2023 19:19:10 -0000 1.97
> +++ server_fcgi.c 26 Jan 2025 19:55:19 -0000
> @@ -345,7 +345,8 @@ server_fcgi(struct httpd *env, struct cl
> goto fail;
> }
>
> - if (fcgi_add_param(¶m, "SERVER_SOFTWARE", HTTPD_SERVERNAME,
> + /* RFC 3875 requires this variable always be present */
> + if (fcgi_add_param(¶m, "SERVER_SOFTWARE", srv_conf->banner,
Not sure that sending a custom banner is safe, because it can be an empty
string. Am I wrong?
> clt) == -1) {
> errstr = "failed to encode param";
> goto fail;
> @@ -659,9 +660,12 @@ server_fcgi_header(struct client *clt, u
> kv_set(&resp->http_pathquery, "%s", error) == -1)
> return (-1);
>
> - /* Add headers */
> - if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
> - return (-1);
> + /* Add server banner header to response unless suppressed */
> + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) {
> + if (kv_add(&resp->http_headers, "Server",
> + srv_conf->banner) == NULL)
> + return (-1);
> + }
>
> if (clt->clt_fcgi.type == FCGI_END_REQUEST ||
> EVBUFFER_LENGTH(clt->clt_srvevb) == 0) {
> Index: server_http.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v
> retrieving revision 1.155
> diff -u -p -u -p -r1.155 server_http.c
> --- server_http.c 22 Dec 2024 13:51:42 -0000 1.155
> +++ server_http.c 26 Jan 2025 19:55:19 -0000
> @@ -890,6 +890,7 @@ server_abort_http(struct client *clt, un
> char *httpmsg, *body = NULL, *extraheader = NULL;
> char tmbuf[32], hbuf[128], *hstsheader = NULL;
> char *clenheader = NULL;
> + char *bannerheader = NULL, *bannertoken = NULL;
> char buf[IBUF_READ_SIZE];
> char *escapedmsg = NULL;
> char cstr[5];
> @@ -981,7 +982,11 @@ server_abort_http(struct client *clt, un
>
> body = replace_var(body, "$HTTP_ERROR", httperr);
> body = replace_var(body, "$RESPONSE_CODE", cstr);
> - body = replace_var(body, "$SERVER_SOFTWARE", HTTPD_SERVERNAME);
> + /* Check if server banner is suppressed */
> + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0)
> + body = replace_var(body, "$SERVER_SOFTWARE", srv_conf->banner);
> + else
> + body = replace_var(body, "$SERVER_SOFTWARE", "");
> bodylen = strlen(body);
> goto send;
>
> @@ -994,6 +999,11 @@ server_abort_http(struct client *clt, un
> "body { background-color: #1E1F21; color: #EEEFF1; }\n"
> "a { color: #BAD7FF; }\n}";
>
> + /* If banner is suppressed, don't write it to the error document */
> + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0)
> + asprintf(&bannertoken, "<hr>\n<address>%s</address>\n",
> + srv_conf->banner);
> +
> /* Generate simple HTML error document */
> if ((bodylen = asprintf(&body,
> "<!DOCTYPE html>\n"
> @@ -1005,10 +1015,11 @@ server_abort_http(struct client *clt, un
> "</head>\n"
> "<body>\n"
> "<h1>%03d %s</h1>\n"
> - "<hr>\n<address>%s</address>\n"
> + "%s"
> "</body>\n"
> "</html>\n",
> - code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) {
> + code, httperr, style, code, httperr,
> + bannertoken == NULL ? "" : bannertoken)) == -1) {
> body = NULL;
> goto done;
> }
> @@ -1037,11 +1048,16 @@ server_abort_http(struct client *clt, un
> }
> }
>
> + /* If banner is suppressed, don't write a Server header */
> + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0)
> + asprintf(&bannerheader, "Server: %s\r\n",
> + srv_conf->banner);
> +
> /* Add basic HTTP headers */
> if (asprintf(&httpmsg,
> "HTTP/1.0 %03d %s\r\n"
> "Date: %s\r\n"
> - "Server: %s\r\n"
> + "%s"
> "Connection: close\r\n"
> "Content-Type: text/html\r\n"
> "%s"
> @@ -1049,7 +1065,8 @@ server_abort_http(struct client *clt, un
> "%s"
> "\r\n"
> "%s",
> - code, httperr, tmbuf, HTTPD_SERVERNAME,
> + code, httperr, tmbuf,
> + bannerheader == NULL ? "" : bannerheader,
> clenheader == NULL ? "" : clenheader,
> extraheader == NULL ? "" : extraheader,
> hstsheader == NULL ? "" : hstsheader,
> @@ -1558,10 +1575,12 @@ server_response_http(struct client *clt,
> kv_set(&resp->http_pathquery, "%s", error) == -1)
> return (-1);
>
> - /* Add headers */
I think we should keep this comment.
> - if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
> - return (-1);
> -
> + /* Add server banner header to response unless suppressed */
> + if ((srv_conf->flags & SRVFLAG_NO_BANNER) == 0) {
> + if (kv_add(&resp->http_headers, "Server",
> + srv_conf->banner) == NULL)
> + return (-1);
> + }
> /* Is it a persistent connection? */
> if (clt->clt_persist) {
> if (kv_add(&resp->http_headers,
>
--
wbr, Kirill
httpd(8): patch to disable/modify the server banner