Skip to content

Commit

Permalink
Add httpd custom error page facility. Adapted by me from
Browse files Browse the repository at this point in the history
https://github.com/mpfr/httpd-plus.
Improvements from & (earlier version) reads fine to tracey@;
improvements & OK this version benno@, florian@. Thanks.
  • Loading branch information
IanDarwin committed Oct 24, 2021
1 parent ba90635 commit cbced0b
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 10 deletions.
9 changes: 8 additions & 1 deletion usr.sbin/httpd/config.c
@@ -1,4 +1,4 @@
/* $OpenBSD: config.c,v 1.61 2020/09/21 09:42:07 tobhe Exp $ */
/* $OpenBSD: config.c,v 1.62 2021/10/24 16:01:04 ian Exp $ */

/*
* Copyright (c) 2011 - 2015 Reyk Floeter <reyk@openbsd.org>
Expand Down Expand Up @@ -51,6 +51,9 @@ config_init(struct httpd *env)
CONFIG_SERVERS|CONFIG_MEDIA|CONFIG_AUTH;
ps->ps_what[PROC_LOGGER] = CONFIG_SERVERS;

(void)strlcpy(env->sc_errdocroot, "",
sizeof(env->sc_errdocroot));

/* Other configuration */
what = ps->ps_what[privsep_process];

Expand Down Expand Up @@ -585,6 +588,10 @@ config_getserver_config(struct httpd *env, struct server *srv,
srv_conf->maxrequests = parent->maxrequests;
srv_conf->maxrequestbody = parent->maxrequestbody;

srv_conf->flags |= parent->flags & SRVFLAG_ERRDOCS;
(void)strlcpy(srv_conf->errdocroot, parent->errdocroot,
sizeof(srv_conf->errdocroot));

DPRINTF("%s: %s %d location \"%s\", "
"parent \"%s[%u]\", flags: %s",
__func__, ps->ps_title[privsep_process], ps->ps_instance,
Expand Down
49 changes: 47 additions & 2 deletions usr.sbin/httpd/httpd.conf.5
@@ -1,4 +1,4 @@
.\" $OpenBSD: httpd.conf.5,v 1.118 2021/06/07 10:53:59 tb Exp $
.\" $OpenBSD: httpd.conf.5,v 1.119 2021/10/24 16:01:04 ian Exp $
.\"
.\" Copyright (c) 2014, 2015 Reyk Floeter <reyk@openbsd.org>
.\"
Expand All @@ -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: June 7 2021 $
.Dd $Mdocdate: October 24 2021 $
.Dt HTTPD.CONF 5
.Os
.Sh NAME
Expand Down Expand Up @@ -121,6 +121,44 @@ see the
section below.
If not specified, the default type is set to
.Ar application/octet-stream .
.It Ic errdocs Ar directory
Let
.Xr httpd 8
return custom error documents instead of the built-in ones.
.Pp
.Ar directory
is relative to the
.Ic chroot .
.Pp
Custom error documents are standalone
.Dq .html
files provided in one of the following ways:
.Bl -bullet -offset indent -compact
.It
As HTML files named after the 3-digit HTTP response code they are used
for, e.g.,
.Pa 404.html .
.It
As a generic template file named
.Pa err.html
which is used for response codes no dedicated file is provided for.
.El
.Pp
In case the latter does not exist and there is no dedicated file available for
a certain response code, the built-in error document will be used as fallback.
.Pp
A custom error document may contain the following macros that will be expanded
at runtime:
.Pp
.Bl -tag -width $RESPONSE_CODE -offset indent -compact
.It Ic $HTTP_ERROR
The error message text.
.It Ic $RESPONSE_CODE
The 3-digit HTTP response code sent to the client.
.It Ic $SERVER_SOFTWARE
The server software name of
.Xr httpd 8 .
.El
.It Ic logdir Ar directory
Specifies the full path of the directory in which log files will be written.
If not specified, it defaults to
Expand Down Expand Up @@ -279,6 +317,12 @@ Disable the directory index.
.Xr httpd 8
will neither display nor generate a directory index.
.El
.It Oo Ic no Oc Ic errdocs Ar directory
Overrides or, if the
.Ic no
keyword is given, disables globally defined custom error documents for the
current
.Ic server .
.It Oo Ic no Oc Ic fastcgi Oo Ar option Oc
Enable FastCGI instead of serving files.
Multiple options may be specified within curly braces.
Expand Down Expand Up @@ -418,6 +462,7 @@ A location section may include most of the server configuration rules
except
.Ic alias ,
.Ic connection ,
.Ic errdocs ,
.Ic hsts ,
.Ic listen on ,
.Ic location ,
Expand Down
11 changes: 9 additions & 2 deletions usr.sbin/httpd/httpd.h
@@ -1,4 +1,4 @@
/* $OpenBSD: httpd.h,v 1.157 2021/05/17 09:26:52 florian Exp $ */
/* $OpenBSD: httpd.h,v 1.158 2021/10/24 16:01:04 ian Exp $ */

/*
* Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
Expand Down Expand Up @@ -48,6 +48,8 @@
#define HTTPD_USER "www"
#define HTTPD_SERVERNAME "OpenBSD httpd"
#define HTTPD_DOCROOT "/htdocs"
#define HTTPD_ERRDOCTEMPLATE "err" /* 3-char name */
#define HTTPD_ERRDOCROOT_MAX (PATH_MAX - sizeof("000.html"))
#define HTTPD_INDEX "index.html"
#define HTTPD_FCGI_SOCKET "/run/slowcgi.sock"
#define HTTPD_LOGROOT "/logs"
Expand Down Expand Up @@ -378,6 +380,7 @@ SPLAY_HEAD(client_tree, client);
#define SRVFLAG_NO_FCGI 0x00000080
#define SRVFLAG_LOG 0x00000100
#define SRVFLAG_NO_LOG 0x00000200
#define SRVFLAG_ERRDOCS 0x00000400
#define SRVFLAG_SYSLOG 0x00000800
#define SRVFLAG_NO_SYSLOG 0x00001000
#define SRVFLAG_TLS 0x00002000
Expand All @@ -398,7 +401,7 @@ SPLAY_HEAD(client_tree, client);

#define SRVFLAG_BITS \
"\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \
"\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG" \
"\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13ERRDOCS" \
"\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" \
Expand Down Expand Up @@ -542,6 +545,7 @@ struct server_config {

struct server_fcgiparams fcgiparams;
int fcgistrip;
char errdocroot[HTTPD_ERRDOCROOT_MAX];

TAILQ_ENTRY(server_config) entry;
};
Expand Down Expand Up @@ -600,6 +604,9 @@ struct httpd {

struct privsep *sc_ps;
int sc_reload;

int sc_custom_errdocs;
char sc_errdocroot[HTTPD_ERRDOCROOT_MAX];
};

#define HTTPD_OPT_VERBOSE 0x01
Expand Down
43 changes: 42 additions & 1 deletion usr.sbin/httpd/parse.y
@@ -1,4 +1,4 @@
/* $OpenBSD: parse.y,v 1.126 2021/10/15 15:01:28 naddy Exp $ */
/* $OpenBSD: parse.y,v 1.127 2021/10/24 16:01:04 ian Exp $ */

/*
* Copyright (c) 2020 Matthias Pressfreund <mpfr@fn.de>
Expand Down Expand Up @@ -141,6 +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
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.port> port
Expand Down Expand Up @@ -212,6 +213,17 @@ main : PREFORK NUMBER {
| CHROOT STRING {
conf->sc_chroot = $2;
}
| ERRDOCS STRING {
if ($2 != NULL && strlcpy(conf->sc_errdocroot, $2,
sizeof(conf->sc_errdocroot)) >=
sizeof(conf->sc_errdocroot)) {
yyerror("errdoc root path too long");
free($2);
YYERROR;
}
free($2);
conf->sc_custom_errdocs = 1;
}
| LOGDIR STRING {
conf->sc_logdir = $2;
}
Expand Down Expand Up @@ -288,6 +300,12 @@ server : SERVER optmatch STRING {

s->srv_conf.hsts_max_age = SERVER_HSTS_DEFAULT_AGE;

(void)strlcpy(s->srv_conf.errdocroot,
conf->sc_errdocroot,
sizeof(s->srv_conf.errdocroot));
if (conf->sc_custom_errdocs)
s->srv_conf.flags |= SRVFLAG_ERRDOCS;

if (last_server_id == INT_MAX) {
yyerror("too many servers defined");
free(s);
Expand Down Expand Up @@ -477,6 +495,28 @@ serveroptsl : LISTEN ON STRING opttls port {

TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry);
}
| ERRDOCS STRING {
if (parentsrv != NULL) {
yyerror("errdocs inside location");
YYERROR;
}
if ($2 != NULL && strlcpy(srv->srv_conf.errdocroot, $2,
sizeof(srv->srv_conf.errdocroot)) >=
sizeof(srv->srv_conf.errdocroot)) {
yyerror("errdoc root path too long");
free($2);
YYERROR;
}
free($2);
srv->srv_conf.flags |= SRVFLAG_ERRDOCS;
}
| NO ERRDOCS {
if (parentsrv != NULL) {
yyerror("errdocs inside location");
YYERROR;
}
srv->srv_conf.flags &= ~SRVFLAG_ERRDOCS;
}
| tcpip {
if (parentsrv != NULL) {
yyerror("tcp flags inside location");
Expand Down Expand Up @@ -1396,6 +1436,7 @@ lookup(char *s)
{ "directory", DIRECTORY },
{ "drop", DROP },
{ "ecdhe", ECDHE },
{ "errdocs", ERRDOCS },
{ "error", ERR },
{ "fastcgi", FCGI },
{ "forwarded", FORWARDED },
Expand Down
87 changes: 83 additions & 4 deletions usr.sbin/httpd/server_http.c
@@ -1,4 +1,4 @@
/* $OpenBSD: server_http.c,v 1.146 2021/10/23 15:30:28 benno Exp $ */
/* $OpenBSD: server_http.c,v 1.147 2021/10/24 16:01:04 ian Exp $ */

/*
* Copyright (c) 2020 Matthias Pressfreund <mpfr@fn.de>
Expand Down Expand Up @@ -49,9 +49,11 @@ 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 *);
int http_version_num(char *);
char *server_expand_http(struct client *, const char *,
char *, size_t);
int http_version_num(char *);
char *replace_var(char *, const char *, const char *);
char *read_errdoc(const char *, const char *);

static struct http_method http_methods[] = HTTP_METHODS;
static struct http_error http_errors[] = HTTP_ERRORS;
Expand Down Expand Up @@ -885,7 +887,8 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg)
char *clenheader = NULL;
char buf[IBUF_READ_SIZE];
char *escapedmsg = NULL;
int bodylen;
char cstr[5];
ssize_t bodylen;

if (code == 0) {
server_close(clt, "dropped");
Expand Down Expand Up @@ -961,6 +964,23 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg)

free(escapedmsg);

if ((srv_conf->flags & SRVFLAG_ERRDOCS) == 0)
goto builtin; /* errdocs not enabled */
if ((size_t)snprintf(cstr, sizeof(cstr), "%03u", code) >= sizeof(cstr))
goto builtin;

if ((body = read_errdoc(srv_conf->errdocroot, cstr)) == NULL &&
(body = read_errdoc(srv_conf->errdocroot, HTTPD_ERRDOCTEMPLATE))
== NULL)
goto builtin;

body = replace_var(body, "$HTTP_ERROR", httperr);
body = replace_var(body, "$RESPONSE_CODE", cstr);
body = replace_var(body, "$SERVER_SOFTWARE", HTTPD_SERVERNAME);
bodylen = strlen(body);
goto send;

builtin:
/* A CSS stylesheet allows minimal customization by the user */
style = "body { background-color: white; color: black; font-family: "
"'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
Expand Down Expand Up @@ -988,6 +1008,7 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg)
goto done;
}

send:
if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
srv_conf->flags & SRVFLAG_TLS) {
if (asprintf(&hstsheader, "Strict-Transport-Security: "
Expand All @@ -1005,7 +1026,7 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg)
clenheader = NULL;
else {
if (asprintf(&clenheader,
"Content-Length: %d\r\n", bodylen) == -1) {
"Content-Length: %zd\r\n", bodylen) == -1) {
clenheader = NULL;
goto done;
}
Expand Down Expand Up @@ -1729,6 +1750,64 @@ server_httperror_cmp(const void *a, const void *b)
return (ea->error_code - eb->error_code);
}

/*
* return -1 on failure, strlen() of read file otherwise.
* body is NULL on failure, contents of file with trailing \0 otherwise.
*/
char *
read_errdoc(const char *root, const char *file)
{
struct stat sb;
char *path;
int fd;
char *ret = NULL;

if (asprintf(&path, "%s/%s.html", root, file) == -1)
fatal("asprintf");
if ((fd = open(path, O_RDONLY)) == -1) {
free(path);
log_warn("%s: open", __func__);
return (NULL);
}
free(path);
if (fstat(fd, &sb) < 0) {
log_warn("%s: stat", __func__);
return (NULL);
}

if ((ret = calloc(1, sb.st_size + 1)) == NULL)
fatal("calloc");
if (sb.st_size == 0)
return (ret);
if (read(fd, ret, sb.st_size) != sb.st_size) {
log_warn("%s: read", __func__);
close(fd);
free(ret);
ret = NULL;
return (ret);
}
close(fd);

return (ret);
}

char *
replace_var(char *str, const char *var, const char *repl)
{
char *iv, *r;
size_t vlen;

vlen = strlen(var);
while ((iv = strstr(str, var)) != NULL) {
*iv = '\0';
if (asprintf(&r, "%s%s%s", str, repl, &iv[vlen]) == -1)
fatal("asprintf");
free(str);
str = r;
}
return (str);
}

int
server_log_http(struct client *clt, unsigned int code, size_t len)
{
Expand Down

0 comments on commit cbced0b

Please sign in to comment.