summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReyk Floeter <reyk@cvs.openbsd.org>2007-11-20 15:54:56 +0000
committerReyk Floeter <reyk@cvs.openbsd.org>2007-11-20 15:54:56 +0000
commite6c7ec852fff7782025440ba3dc423ef88e77468 (patch)
treeded00acf8d5fff247ba0242a56098a6f229155c4
parent06cda98f2ac65d76367d70562e5e25990af9200b (diff)
it may be desirable to send a HTTP error page with error code and a
meaningful message if a HTTP/HTTPS relay closes the connection for some reason. for example, a "403 Forbidden" if the request was rejected by a filter. this will be enabled with the "return error" option and is disabled by default, the standard behaviour is to silently drop the connection; the browser may display an empty page in this case. the look+feel of the HTTP error page can be customized with a CSS style sheet, but we do not intend to allow customization of the error page contents (hoststated is not a webserver!). ok pyr@
-rw-r--r--usr.sbin/hoststated/hoststated.c4
-rw-r--r--usr.sbin/hoststated/hoststated.conf.517
-rw-r--r--usr.sbin/hoststated/hoststated.h6
-rw-r--r--usr.sbin/hoststated/log.c58
-rw-r--r--usr.sbin/hoststated/parse.y25
-rw-r--r--usr.sbin/hoststated/relay.c127
-rw-r--r--usr.sbin/relayd/log.c58
-rw-r--r--usr.sbin/relayd/parse.y25
-rw-r--r--usr.sbin/relayd/relay.c127
-rw-r--r--usr.sbin/relayd/relayd.c4
-rw-r--r--usr.sbin/relayd/relayd.conf.517
-rw-r--r--usr.sbin/relayd/relayd.h6
12 files changed, 434 insertions, 40 deletions
diff --git a/usr.sbin/hoststated/hoststated.c b/usr.sbin/hoststated/hoststated.c
index 7d800f39a36..0d44c297bc2 100644
--- a/usr.sbin/hoststated/hoststated.c
+++ b/usr.sbin/hoststated/hoststated.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: hoststated.c,v 1.54 2007/11/19 15:31:36 reyk Exp $ */
+/* $OpenBSD: hoststated.c,v 1.55 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -500,6 +500,8 @@ purge_config(struct hoststated *env, u_int8_t what)
TAILQ_REMOVE(env->protos, proto, entry);
purge_tree(&proto->request_tree);
purge_tree(&proto->response_tree);
+ if (proto->style != NULL)
+ free(proto->style);
free(proto);
}
free(env->protos);
diff --git a/usr.sbin/hoststated/hoststated.conf.5 b/usr.sbin/hoststated/hoststated.conf.5
index ae5bb09413d..08b83354506 100644
--- a/usr.sbin/hoststated/hoststated.conf.5
+++ b/usr.sbin/hoststated/hoststated.conf.5
@@ -1,4 +1,4 @@
-.\" $OpenBSD: hoststated.conf.5,v 1.55 2007/11/20 15:44:21 pyr Exp $
+.\" $OpenBSD: hoststated.conf.5,v 1.56 2007/11/20 15:54:55 reyk Exp $
.\"
.\" Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
.\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -594,6 +594,21 @@ section above.
.It Ic log Ar key
Log the name and the value of the entity.
.El
+.It Ic return error Op Ar option
+Return an error reponse to the client if an internal operation or the
+forward connection to the client failed.
+By default, the connection will be silently dropped.
+The effect of this option depends on the protocol, HTTP will send a
+error header and page to the client before closing the connection.
+Additional valid options are:
+.Bl -tag -width Ds
+.It Ic style Ar string
+Specify a Cascading Style Sheet (CSS) to be used for the returned
+HTTP error pages, for example:
+.Bd -literal -offset indent
+body { background: #a00000; color: white; }
+.Ed
+.El
.It Ic tcp Ar option
Enable or disable the specified TCP/IP options; see
.Xr tcp 4
diff --git a/usr.sbin/hoststated/hoststated.h b/usr.sbin/hoststated/hoststated.h
index ffd86a0652e..f895a401175 100644
--- a/usr.sbin/hoststated/hoststated.h
+++ b/usr.sbin/hoststated/hoststated.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: hoststated.h,v 1.75 2007/11/20 15:44:21 pyr Exp $ */
+/* $OpenBSD: hoststated.h,v 1.76 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -25,6 +25,7 @@
#define PF_SOCKET "/dev/pf"
#define HOSTSTATED_USER "_hoststated"
#define HOSTSTATED_ANCHOR "hoststated"
+#define HOSTSTATED_SERVERNAME "OpenBSD hoststated"
#define CHECK_TIMEOUT 200
#define CHECK_INTERVAL 10
#define EMPTY_TABLE UINT_MAX
@@ -305,6 +306,7 @@ TAILQ_HEAD(addresslist, address);
#define F_LOOKUP_PATH 0x00004000
#define F_DEMOTED 0x00008000
#define F_UDP 0x00010000
+#define F_RETURN 0x00020000
struct host_config {
objid_t id;
@@ -499,6 +501,7 @@ struct protocol {
int cache;
enum prototype type;
int lateconnect;
+ char *style;
int request_nodes;
struct proto_tree request_tree;
@@ -664,6 +667,7 @@ const char *table_check(enum table_check);
const char *print_availability(u_long, u_long);
const char *print_host(struct sockaddr_storage *, char *, size_t);
const char *print_time(struct timeval *, struct timeval *, char *, size_t);
+const char *print_httperror(u_int);
/* buffer.c */
struct buf *buf_open(size_t);
diff --git a/usr.sbin/hoststated/log.c b/usr.sbin/hoststated/log.c
index 44724696450..2f2e37ce781 100644
--- a/usr.sbin/hoststated/log.c
+++ b/usr.sbin/hoststated/log.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: log.c,v 1.8 2007/11/04 22:09:02 reyk Exp $ */
+/* $OpenBSD: log.c,v 1.9 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -246,3 +246,59 @@ print_time(struct timeval *a, struct timeval *b, char *buf, size_t len)
snprintf(buf, len, "%.2lu:%.2lu:%.2lu", h, min, sec);
return (buf);
}
+
+const char *
+print_httperror(u_int code)
+{
+ u_int i;
+ struct {
+ u_int ht_code;
+ const char *ht_err;
+ } httperr[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 200, "OK" },
+ { 201, "Created" },
+ { 202, "Accepted" },
+ { 203, "Non-Authorative Information" },
+ { 204, "No Content" },
+ { 205, "Reset Content" },
+ { 206, "Partial Content" },
+ { 300, "Multiple Choices" },
+ { 301, "Moved Permanently" },
+ { 302, "Moved Temporarily" },
+ { 303, "See Other" },
+ { 304, "Not Modified" },
+ { 307, "Temporary Redirect" },
+ { 400, "Bad Request" },
+ { 401, "Unauthorized" },
+ { 402, "Payment Required" },
+ { 403, "Forbidden" },
+ { 404, "Not Found" },
+ { 405, "Method Not Allowed" },
+ { 406, "Not Acceptable" },
+ { 407, "Proxy Authentication Required" },
+ { 408, "Request Timeout" },
+ { 409, "Conflict" },
+ { 410, "Gone" },
+ { 411, "Length Required" },
+ { 412, "Precondition Failed" },
+ { 413, "Request Entity Too Large" },
+ { 414, "Request-URL Too Long" },
+ { 415, "Unsupported Media Type" },
+ { 416, "Requested Range Not Satisfiable" },
+ { 417, "Expectation Failed" },
+ { 500, "Internal Server Error" },
+ { 501, "Not Implemented" },
+ { 502, "Bad Gateway" },
+ { 503, "Service Unavailable" },
+ { 504, "Gateway Timeout" },
+ { 505, "HTTP Version Not Supported" },
+ { 0 }
+ };
+
+ for (i = 0; httperr[i].ht_code != 0; i++)
+ if (httperr[i].ht_code == code)
+ return (httperr[i].ht_err);
+ return ("Unknown Error");
+}
diff --git a/usr.sbin/hoststated/parse.y b/usr.sbin/hoststated/parse.y
index f8db0d6c26e..4be6e44b47d 100644
--- a/usr.sbin/hoststated/parse.y
+++ b/usr.sbin/hoststated/parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: parse.y,v 1.85 2007/11/20 15:44:21 pyr Exp $ */
+/* $OpenBSD: parse.y,v 1.86 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -116,7 +116,7 @@ typedef struct {
%token SERVICE TABLE BACKUP HOST REAL INCLUDE
%token CHECK TCP ICMP EXTERNAL REQUEST RESPONSE
-%token TIMEOUT CODE DIGEST PORT TAG INTERFACE
+%token TIMEOUT CODE DIGEST PORT TAG INTERFACE STYLE RETURN
%token VIRTUAL INTERVAL DISABLE STICKYADDR BACKLOG PATH SCRIPT
%token SEND EXPECT NOTHING SSL LOADBALANCE ROUNDROBIN CIPHERS COOKIE
%token RELAY LISTEN ON FORWARD TO NAT LOOKUP PREFORK NO MARK MARKED
@@ -203,6 +203,22 @@ proto_type : TCP { $$ = RELAY_PROTO_TCP; }
}
;
+eflags_l : eflags comma eflags_l
+ | eflags
+ ;
+
+opteflags : /* nothing */
+ | eflags
+ ;
+
+eflags : STYLE STRING
+ {
+ if ((proto->style = strdup($2)) == NULL)
+ fatal("out of memory");
+ free($2);
+ }
+ ;
+
port : PORT STRING {
struct servent *servent;
@@ -649,6 +665,8 @@ protoptsl : SSL sslflags
| TCP tcpflags
| TCP '{' tcpflags_l '}'
| PROTO proto_type { proto->type = $2; }
+ | RETURN ERROR opteflags { proto->flags |= F_RETURN; }
+ | RETURN ERROR '{' eflags_l '}' { proto->flags |= F_RETURN; }
| direction protonode log {
struct protonode *pn, *proot, pk;
struct proto_tree *tree;
@@ -1236,6 +1254,7 @@ lookup(char *s)
{ "demote", DEMOTE },
{ "digest", DIGEST },
{ "disable", DISABLE },
+ { "error", ERROR },
{ "expect", EXPECT },
{ "external", EXTERNAL },
{ "filter", FILTER },
@@ -1270,6 +1289,7 @@ lookup(char *s)
{ "request", REQUEST },
{ "response", RESPONSE },
{ "retry", RETRY },
+ { "return", RETURN },
{ "roundrobin", ROUNDROBIN },
{ "sack", SACK },
{ "script", SCRIPT },
@@ -1279,6 +1299,7 @@ lookup(char *s)
{ "socket", SOCKET },
{ "ssl", SSL },
{ "sticky-address", STICKYADDR },
+ { "style", STYLE },
{ "table", TABLE },
{ "tag", TAG },
{ "tcp", TCP },
diff --git a/usr.sbin/hoststated/relay.c b/usr.sbin/hoststated/relay.c
index 80b1a03a7e5..28b90f48edb 100644
--- a/usr.sbin/hoststated/relay.c
+++ b/usr.sbin/hoststated/relay.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: relay.c,v 1.58 2007/11/20 15:10:46 reyk Exp $ */
+/* $OpenBSD: relay.c,v 1.59 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
@@ -83,6 +83,7 @@ int relay_from_table(struct session *);
void relay_write(struct bufferevent *, void *);
void relay_read(struct bufferevent *, void *);
void relay_error(struct bufferevent *, short, void *);
+void relay_dump(struct ctl_relay_event *, const void *, size_t);
int relay_resolve(struct ctl_relay_event *,
struct protonode *, struct protonode *);
@@ -94,6 +95,7 @@ void relay_read_httpcontent(struct bufferevent *, void *);
void relay_read_httpchunks(struct bufferevent *, void *);
char *relay_expand_http(struct ctl_relay_event *, char *,
char *, size_t);
+void relay_close_http(struct session *, u_int, const char *);
SSL_CTX *relay_ssl_ctx_create(struct relay *);
void relay_ssl_transaction(struct session *);
@@ -692,7 +694,7 @@ relay_connected(int fd, short sig, void *arg)
struct bufferevent *bev;
if (sig == EV_TIMEOUT) {
- relay_close(con, "connect timeout");
+ relay_close_http(con, 504, "connect timeout");
return;
}
@@ -706,7 +708,8 @@ relay_connected(int fd, short sig, void *arg)
outrd = relay_read_http;
if ((con->out.nodes = calloc(proto->response_nodes,
sizeof(u_int8_t))) == NULL) {
- relay_close(con, "failed to allocate nodes");
+ relay_close_http(con, 500,
+ "failed to allocate nodes");
return;
}
}
@@ -723,7 +726,8 @@ relay_connected(int fd, short sig, void *arg)
*/
bev = bufferevent_new(fd, outrd, outwr, relay_error, &con->out);
if (bev == NULL) {
- relay_close(con, "failed to allocate output buffer event");
+ relay_close_http(con, 500,
+ "failed to allocate output buffer event");
return;
}
evbuffer_free(bev->output);
@@ -796,6 +800,21 @@ relay_write(struct bufferevent *bev, void *arg)
}
void
+relay_dump(struct ctl_relay_event *cre, const void *buf, size_t len)
+{
+ /*
+ * This function will dump the specified message directly
+ * to the underlying session, without waiting for success
+ * of non-blocking events etc. This is useful to print an
+ * error message before gracefully closing the session.
+ */
+ if (cre->ssl != NULL)
+ (void)SSL_write(cre->ssl, buf, len);
+ else
+ (void)write(cre->s, buf, len);
+}
+
+void
relay_read(struct bufferevent *bev, void *arg)
{
struct ctl_relay_event *cre = (struct ctl_relay_event *)arg;
@@ -862,7 +881,7 @@ relay_resolve(struct ctl_relay_event *cre,
relay_bufferevent_print(cre->dst, ": ") == -1 ||
relay_bufferevent_print(cre->dst, ptr) == -1 ||
relay_bufferevent_print(cre->dst, "\r\n") == -1) {
- relay_close(con, "failed to modify header");
+ relay_close_http(con, 500, "failed to modify header");
return (-1);
}
DPRINTF("relay_resolve: add '%s: %s'",
@@ -873,14 +892,14 @@ relay_resolve(struct ctl_relay_event *cre,
break;
DPRINTF("relay_resolve: missing '%s: %s'",
pn->key, pn->value);
- relay_close(con, "incomplete header");
+ relay_close_http(con, 403, "incomplete request");
return (-1);
case NODE_ACTION_FILTER:
if (pn->flags & PNFLAG_MARK)
break;
DPRINTF("relay_resolve: filtered '%s: %s'",
pn->key, pn->value);
- relay_close(con, "rejecting header");
+ relay_close_http(con, 403, "rejecting request");
return (-1);
default:
break;
@@ -979,7 +998,7 @@ relay_handle_http(struct ctl_relay_event *cre, struct protonode *proot,
* trying to circumvent the filter.
*/
if (cre->nodes[proot->id] > 1) {
- relay_close(con, "repeated header line");
+ relay_close_http(con, 400, "repeated header line");
return (PN_FAIL);
}
ret = PN_PASS;
@@ -1024,7 +1043,7 @@ relay_handle_http(struct ctl_relay_event *cre, struct protonode *proot,
return (ret);
fail:
- relay_close(con, strerror(errno));
+ relay_close_http(con, 500, strerror(errno));
return (PN_FAIL);
}
@@ -1206,7 +1225,8 @@ relay_read_http(struct bufferevent *bev, void *arg)
if (pk.value == NULL || strlen(pk.value) < 3) {
if (cre->line == 1) {
free(line);
- goto fail;
+ relay_close_http(con, 400, "malformed");
+ return;
}
DPRINTF("relay_read_http: request '%s'", line);
@@ -1315,9 +1335,8 @@ relay_read_http(struct bufferevent *bev, void *arg)
* include the line length in the content-length.
*/
cre->toread = strtonum(pk.value, 1, INT_MAX, &errstr);
-
if (errstr) {
- relay_close(con, errstr);
+ relay_close_http(con, 500, errstr);
free(line);
return;
}
@@ -1483,7 +1502,7 @@ relay_read_http(struct bufferevent *bev, void *arg)
if (cre->dir == RELAY_DIR_REQUEST &&
proto->lateconnect && cre->dst->bev == NULL &&
relay_connect(con) == -1) {
- relay_close(con, "session failed");
+ relay_close_http(con, 502, "session failed");
return;
}
}
@@ -1497,7 +1516,87 @@ relay_read_http(struct bufferevent *bev, void *arg)
relay_close(con, "last http read (done)");
return;
fail:
- relay_close(con, strerror(errno));
+ relay_close_http(con, 500, strerror(errno));
+}
+
+void
+relay_close_http(struct session *con, u_int code, const char *msg)
+{
+ struct relay *rlay = (struct relay *)con->relay;
+ struct bufferevent *bev = con->in.bev;
+ const char *httperr = print_httperror(code), *text = "";
+ char *httpmsg;
+ time_t t;
+ struct tm *lt;
+ char tmbuf[32], hbuf[128];
+ const char *style;
+
+ /* In some cases this function may be called from generic places */
+ if (rlay->proto->type != RELAY_PROTO_HTTP ||
+ (rlay->proto->flags & F_RETURN) == 0) {
+ relay_close(con, msg);
+ return;
+ }
+
+ if (bev == NULL)
+ goto done;
+
+ /* Some system information */
+ if (print_host(&rlay->conf.ss, hbuf, sizeof(hbuf)) == NULL)
+ goto done;
+
+ /* RFC 2616 "tolerates" asctime() */
+ time(&t);
+ lt = localtime(&t);
+ tmbuf[0] = '\0';
+ if (asctime_r(lt, tmbuf) != NULL)
+ tmbuf[strlen(tmbuf) - 1] = '\0'; /* skip final '\n' */
+
+ /* Do not send details of the Internal Server Error */
+ if (code != 500)
+ text = msg;
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ if ((style = rlay->proto->style) == NULL)
+ style = "body { background-color: #a00000; color: white; }";
+
+ /* Generate simple HTTP+HTML error document */
+ if (asprintf(&httpmsg,
+ "HTTP/1.x %03d %s\r\n"
+ "Date: %s\r\n"
+ "Server: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "<!DOCTYPE HTML PUBLIC "
+ "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%03d %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>%s</h1>\n"
+ "<p>%s</p>\n"
+ "<hr><address>%s at %s port %d</address>\n"
+ "</body>\n"
+ "</html>\n",
+ code, httperr, tmbuf, HOSTSTATED_SERVERNAME,
+ code, httperr, style, httperr, text,
+ HOSTSTATED_SERVERNAME, hbuf, ntohs(rlay->conf.port)) == -1)
+ goto done;
+
+ /* Dump the message without checking for success */
+ relay_dump(&con->in, httpmsg, strlen(httpmsg));
+ free(httpmsg);
+
+ done:
+ if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
+ relay_close(con, msg);
+ else {
+ relay_close(con, httpmsg);
+ free(httpmsg);
+ }
}
void
diff --git a/usr.sbin/relayd/log.c b/usr.sbin/relayd/log.c
index 44724696450..2f2e37ce781 100644
--- a/usr.sbin/relayd/log.c
+++ b/usr.sbin/relayd/log.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: log.c,v 1.8 2007/11/04 22:09:02 reyk Exp $ */
+/* $OpenBSD: log.c,v 1.9 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -246,3 +246,59 @@ print_time(struct timeval *a, struct timeval *b, char *buf, size_t len)
snprintf(buf, len, "%.2lu:%.2lu:%.2lu", h, min, sec);
return (buf);
}
+
+const char *
+print_httperror(u_int code)
+{
+ u_int i;
+ struct {
+ u_int ht_code;
+ const char *ht_err;
+ } httperr[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 200, "OK" },
+ { 201, "Created" },
+ { 202, "Accepted" },
+ { 203, "Non-Authorative Information" },
+ { 204, "No Content" },
+ { 205, "Reset Content" },
+ { 206, "Partial Content" },
+ { 300, "Multiple Choices" },
+ { 301, "Moved Permanently" },
+ { 302, "Moved Temporarily" },
+ { 303, "See Other" },
+ { 304, "Not Modified" },
+ { 307, "Temporary Redirect" },
+ { 400, "Bad Request" },
+ { 401, "Unauthorized" },
+ { 402, "Payment Required" },
+ { 403, "Forbidden" },
+ { 404, "Not Found" },
+ { 405, "Method Not Allowed" },
+ { 406, "Not Acceptable" },
+ { 407, "Proxy Authentication Required" },
+ { 408, "Request Timeout" },
+ { 409, "Conflict" },
+ { 410, "Gone" },
+ { 411, "Length Required" },
+ { 412, "Precondition Failed" },
+ { 413, "Request Entity Too Large" },
+ { 414, "Request-URL Too Long" },
+ { 415, "Unsupported Media Type" },
+ { 416, "Requested Range Not Satisfiable" },
+ { 417, "Expectation Failed" },
+ { 500, "Internal Server Error" },
+ { 501, "Not Implemented" },
+ { 502, "Bad Gateway" },
+ { 503, "Service Unavailable" },
+ { 504, "Gateway Timeout" },
+ { 505, "HTTP Version Not Supported" },
+ { 0 }
+ };
+
+ for (i = 0; httperr[i].ht_code != 0; i++)
+ if (httperr[i].ht_code == code)
+ return (httperr[i].ht_err);
+ return ("Unknown Error");
+}
diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y
index f8db0d6c26e..4be6e44b47d 100644
--- a/usr.sbin/relayd/parse.y
+++ b/usr.sbin/relayd/parse.y
@@ -1,4 +1,4 @@
-/* $OpenBSD: parse.y,v 1.85 2007/11/20 15:44:21 pyr Exp $ */
+/* $OpenBSD: parse.y,v 1.86 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -116,7 +116,7 @@ typedef struct {
%token SERVICE TABLE BACKUP HOST REAL INCLUDE
%token CHECK TCP ICMP EXTERNAL REQUEST RESPONSE
-%token TIMEOUT CODE DIGEST PORT TAG INTERFACE
+%token TIMEOUT CODE DIGEST PORT TAG INTERFACE STYLE RETURN
%token VIRTUAL INTERVAL DISABLE STICKYADDR BACKLOG PATH SCRIPT
%token SEND EXPECT NOTHING SSL LOADBALANCE ROUNDROBIN CIPHERS COOKIE
%token RELAY LISTEN ON FORWARD TO NAT LOOKUP PREFORK NO MARK MARKED
@@ -203,6 +203,22 @@ proto_type : TCP { $$ = RELAY_PROTO_TCP; }
}
;
+eflags_l : eflags comma eflags_l
+ | eflags
+ ;
+
+opteflags : /* nothing */
+ | eflags
+ ;
+
+eflags : STYLE STRING
+ {
+ if ((proto->style = strdup($2)) == NULL)
+ fatal("out of memory");
+ free($2);
+ }
+ ;
+
port : PORT STRING {
struct servent *servent;
@@ -649,6 +665,8 @@ protoptsl : SSL sslflags
| TCP tcpflags
| TCP '{' tcpflags_l '}'
| PROTO proto_type { proto->type = $2; }
+ | RETURN ERROR opteflags { proto->flags |= F_RETURN; }
+ | RETURN ERROR '{' eflags_l '}' { proto->flags |= F_RETURN; }
| direction protonode log {
struct protonode *pn, *proot, pk;
struct proto_tree *tree;
@@ -1236,6 +1254,7 @@ lookup(char *s)
{ "demote", DEMOTE },
{ "digest", DIGEST },
{ "disable", DISABLE },
+ { "error", ERROR },
{ "expect", EXPECT },
{ "external", EXTERNAL },
{ "filter", FILTER },
@@ -1270,6 +1289,7 @@ lookup(char *s)
{ "request", REQUEST },
{ "response", RESPONSE },
{ "retry", RETRY },
+ { "return", RETURN },
{ "roundrobin", ROUNDROBIN },
{ "sack", SACK },
{ "script", SCRIPT },
@@ -1279,6 +1299,7 @@ lookup(char *s)
{ "socket", SOCKET },
{ "ssl", SSL },
{ "sticky-address", STICKYADDR },
+ { "style", STYLE },
{ "table", TABLE },
{ "tag", TAG },
{ "tcp", TCP },
diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c
index 80b1a03a7e5..28b90f48edb 100644
--- a/usr.sbin/relayd/relay.c
+++ b/usr.sbin/relayd/relay.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: relay.c,v 1.58 2007/11/20 15:10:46 reyk Exp $ */
+/* $OpenBSD: relay.c,v 1.59 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
@@ -83,6 +83,7 @@ int relay_from_table(struct session *);
void relay_write(struct bufferevent *, void *);
void relay_read(struct bufferevent *, void *);
void relay_error(struct bufferevent *, short, void *);
+void relay_dump(struct ctl_relay_event *, const void *, size_t);
int relay_resolve(struct ctl_relay_event *,
struct protonode *, struct protonode *);
@@ -94,6 +95,7 @@ void relay_read_httpcontent(struct bufferevent *, void *);
void relay_read_httpchunks(struct bufferevent *, void *);
char *relay_expand_http(struct ctl_relay_event *, char *,
char *, size_t);
+void relay_close_http(struct session *, u_int, const char *);
SSL_CTX *relay_ssl_ctx_create(struct relay *);
void relay_ssl_transaction(struct session *);
@@ -692,7 +694,7 @@ relay_connected(int fd, short sig, void *arg)
struct bufferevent *bev;
if (sig == EV_TIMEOUT) {
- relay_close(con, "connect timeout");
+ relay_close_http(con, 504, "connect timeout");
return;
}
@@ -706,7 +708,8 @@ relay_connected(int fd, short sig, void *arg)
outrd = relay_read_http;
if ((con->out.nodes = calloc(proto->response_nodes,
sizeof(u_int8_t))) == NULL) {
- relay_close(con, "failed to allocate nodes");
+ relay_close_http(con, 500,
+ "failed to allocate nodes");
return;
}
}
@@ -723,7 +726,8 @@ relay_connected(int fd, short sig, void *arg)
*/
bev = bufferevent_new(fd, outrd, outwr, relay_error, &con->out);
if (bev == NULL) {
- relay_close(con, "failed to allocate output buffer event");
+ relay_close_http(con, 500,
+ "failed to allocate output buffer event");
return;
}
evbuffer_free(bev->output);
@@ -796,6 +800,21 @@ relay_write(struct bufferevent *bev, void *arg)
}
void
+relay_dump(struct ctl_relay_event *cre, const void *buf, size_t len)
+{
+ /*
+ * This function will dump the specified message directly
+ * to the underlying session, without waiting for success
+ * of non-blocking events etc. This is useful to print an
+ * error message before gracefully closing the session.
+ */
+ if (cre->ssl != NULL)
+ (void)SSL_write(cre->ssl, buf, len);
+ else
+ (void)write(cre->s, buf, len);
+}
+
+void
relay_read(struct bufferevent *bev, void *arg)
{
struct ctl_relay_event *cre = (struct ctl_relay_event *)arg;
@@ -862,7 +881,7 @@ relay_resolve(struct ctl_relay_event *cre,
relay_bufferevent_print(cre->dst, ": ") == -1 ||
relay_bufferevent_print(cre->dst, ptr) == -1 ||
relay_bufferevent_print(cre->dst, "\r\n") == -1) {
- relay_close(con, "failed to modify header");
+ relay_close_http(con, 500, "failed to modify header");
return (-1);
}
DPRINTF("relay_resolve: add '%s: %s'",
@@ -873,14 +892,14 @@ relay_resolve(struct ctl_relay_event *cre,
break;
DPRINTF("relay_resolve: missing '%s: %s'",
pn->key, pn->value);
- relay_close(con, "incomplete header");
+ relay_close_http(con, 403, "incomplete request");
return (-1);
case NODE_ACTION_FILTER:
if (pn->flags & PNFLAG_MARK)
break;
DPRINTF("relay_resolve: filtered '%s: %s'",
pn->key, pn->value);
- relay_close(con, "rejecting header");
+ relay_close_http(con, 403, "rejecting request");
return (-1);
default:
break;
@@ -979,7 +998,7 @@ relay_handle_http(struct ctl_relay_event *cre, struct protonode *proot,
* trying to circumvent the filter.
*/
if (cre->nodes[proot->id] > 1) {
- relay_close(con, "repeated header line");
+ relay_close_http(con, 400, "repeated header line");
return (PN_FAIL);
}
ret = PN_PASS;
@@ -1024,7 +1043,7 @@ relay_handle_http(struct ctl_relay_event *cre, struct protonode *proot,
return (ret);
fail:
- relay_close(con, strerror(errno));
+ relay_close_http(con, 500, strerror(errno));
return (PN_FAIL);
}
@@ -1206,7 +1225,8 @@ relay_read_http(struct bufferevent *bev, void *arg)
if (pk.value == NULL || strlen(pk.value) < 3) {
if (cre->line == 1) {
free(line);
- goto fail;
+ relay_close_http(con, 400, "malformed");
+ return;
}
DPRINTF("relay_read_http: request '%s'", line);
@@ -1315,9 +1335,8 @@ relay_read_http(struct bufferevent *bev, void *arg)
* include the line length in the content-length.
*/
cre->toread = strtonum(pk.value, 1, INT_MAX, &errstr);
-
if (errstr) {
- relay_close(con, errstr);
+ relay_close_http(con, 500, errstr);
free(line);
return;
}
@@ -1483,7 +1502,7 @@ relay_read_http(struct bufferevent *bev, void *arg)
if (cre->dir == RELAY_DIR_REQUEST &&
proto->lateconnect && cre->dst->bev == NULL &&
relay_connect(con) == -1) {
- relay_close(con, "session failed");
+ relay_close_http(con, 502, "session failed");
return;
}
}
@@ -1497,7 +1516,87 @@ relay_read_http(struct bufferevent *bev, void *arg)
relay_close(con, "last http read (done)");
return;
fail:
- relay_close(con, strerror(errno));
+ relay_close_http(con, 500, strerror(errno));
+}
+
+void
+relay_close_http(struct session *con, u_int code, const char *msg)
+{
+ struct relay *rlay = (struct relay *)con->relay;
+ struct bufferevent *bev = con->in.bev;
+ const char *httperr = print_httperror(code), *text = "";
+ char *httpmsg;
+ time_t t;
+ struct tm *lt;
+ char tmbuf[32], hbuf[128];
+ const char *style;
+
+ /* In some cases this function may be called from generic places */
+ if (rlay->proto->type != RELAY_PROTO_HTTP ||
+ (rlay->proto->flags & F_RETURN) == 0) {
+ relay_close(con, msg);
+ return;
+ }
+
+ if (bev == NULL)
+ goto done;
+
+ /* Some system information */
+ if (print_host(&rlay->conf.ss, hbuf, sizeof(hbuf)) == NULL)
+ goto done;
+
+ /* RFC 2616 "tolerates" asctime() */
+ time(&t);
+ lt = localtime(&t);
+ tmbuf[0] = '\0';
+ if (asctime_r(lt, tmbuf) != NULL)
+ tmbuf[strlen(tmbuf) - 1] = '\0'; /* skip final '\n' */
+
+ /* Do not send details of the Internal Server Error */
+ if (code != 500)
+ text = msg;
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ if ((style = rlay->proto->style) == NULL)
+ style = "body { background-color: #a00000; color: white; }";
+
+ /* Generate simple HTTP+HTML error document */
+ if (asprintf(&httpmsg,
+ "HTTP/1.x %03d %s\r\n"
+ "Date: %s\r\n"
+ "Server: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "<!DOCTYPE HTML PUBLIC "
+ "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%03d %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>%s</h1>\n"
+ "<p>%s</p>\n"
+ "<hr><address>%s at %s port %d</address>\n"
+ "</body>\n"
+ "</html>\n",
+ code, httperr, tmbuf, HOSTSTATED_SERVERNAME,
+ code, httperr, style, httperr, text,
+ HOSTSTATED_SERVERNAME, hbuf, ntohs(rlay->conf.port)) == -1)
+ goto done;
+
+ /* Dump the message without checking for success */
+ relay_dump(&con->in, httpmsg, strlen(httpmsg));
+ free(httpmsg);
+
+ done:
+ if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
+ relay_close(con, msg);
+ else {
+ relay_close(con, httpmsg);
+ free(httpmsg);
+ }
}
void
diff --git a/usr.sbin/relayd/relayd.c b/usr.sbin/relayd/relayd.c
index 0f08383bfd5..ae6380116ed 100644
--- a/usr.sbin/relayd/relayd.c
+++ b/usr.sbin/relayd/relayd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: relayd.c,v 1.54 2007/11/19 15:31:36 reyk Exp $ */
+/* $OpenBSD: relayd.c,v 1.55 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -500,6 +500,8 @@ purge_config(struct hoststated *env, u_int8_t what)
TAILQ_REMOVE(env->protos, proto, entry);
purge_tree(&proto->request_tree);
purge_tree(&proto->response_tree);
+ if (proto->style != NULL)
+ free(proto->style);
free(proto);
}
free(env->protos);
diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5
index d863f6972e9..bae09455edf 100644
--- a/usr.sbin/relayd/relayd.conf.5
+++ b/usr.sbin/relayd/relayd.conf.5
@@ -1,4 +1,4 @@
-.\" $OpenBSD: relayd.conf.5,v 1.55 2007/11/20 15:44:21 pyr Exp $
+.\" $OpenBSD: relayd.conf.5,v 1.56 2007/11/20 15:54:55 reyk Exp $
.\"
.\" Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
.\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -594,6 +594,21 @@ section above.
.It Ic log Ar key
Log the name and the value of the entity.
.El
+.It Ic return error Op Ar option
+Return an error reponse to the client if an internal operation or the
+forward connection to the client failed.
+By default, the connection will be silently dropped.
+The effect of this option depends on the protocol, HTTP will send a
+error header and page to the client before closing the connection.
+Additional valid options are:
+.Bl -tag -width Ds
+.It Ic style Ar string
+Specify a Cascading Style Sheet (CSS) to be used for the returned
+HTTP error pages, for example:
+.Bd -literal -offset indent
+body { background: #a00000; color: white; }
+.Ed
+.El
.It Ic tcp Ar option
Enable or disable the specified TCP/IP options; see
.Xr tcp 4
diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h
index 5091bbb785b..eddc059a11b 100644
--- a/usr.sbin/relayd/relayd.h
+++ b/usr.sbin/relayd/relayd.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: relayd.h,v 1.75 2007/11/20 15:44:21 pyr Exp $ */
+/* $OpenBSD: relayd.h,v 1.76 2007/11/20 15:54:55 reyk Exp $ */
/*
* Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
@@ -25,6 +25,7 @@
#define PF_SOCKET "/dev/pf"
#define HOSTSTATED_USER "_hoststated"
#define HOSTSTATED_ANCHOR "hoststated"
+#define HOSTSTATED_SERVERNAME "OpenBSD hoststated"
#define CHECK_TIMEOUT 200
#define CHECK_INTERVAL 10
#define EMPTY_TABLE UINT_MAX
@@ -305,6 +306,7 @@ TAILQ_HEAD(addresslist, address);
#define F_LOOKUP_PATH 0x00004000
#define F_DEMOTED 0x00008000
#define F_UDP 0x00010000
+#define F_RETURN 0x00020000
struct host_config {
objid_t id;
@@ -499,6 +501,7 @@ struct protocol {
int cache;
enum prototype type;
int lateconnect;
+ char *style;
int request_nodes;
struct proto_tree request_tree;
@@ -664,6 +667,7 @@ const char *table_check(enum table_check);
const char *print_availability(u_long, u_long);
const char *print_host(struct sockaddr_storage *, char *, size_t);
const char *print_time(struct timeval *, struct timeval *, char *, size_t);
+const char *print_httperror(u_int);
/* buffer.c */
struct buf *buf_open(size_t);