untrusted comment: signature from openbsd 6.0 base secret key
RWSho3oKSqgLQz5yMCgijPP8NK5lgFOuTCaBS/1ls877CCT5dqYFarm6EcmFP8PBQLYFN6D0BjlMrwArvGZOokKnQ9M4fMrYcQA=

OpenBSD 6.0 errata 3, Aug 06, 2016:

Improve parsing of the Host-header by following RFC 7230 Section 5.4 more
strictly.

Apply by doing:
   signify -Vep /etc/signify/openbsd-60-base.pub -x 003_relayd.patch.sig \
       -m - | (cd /usr/src && patch -p0)

And then rebuild and install relayd:
       cd /usr/src/usr.sbin/relayd
       make obj
       make depend
       make
       make install

Index: usr.sbin/relayd/relay_http.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relay_http.c,v
retrieving revision 1.56
diff -u -p -r1.56 relay_http.c
--- usr.sbin/relayd/relay_http.c        22 Jul 2016 09:30:36 -0000      1.56
+++ usr.sbin/relayd/relay_http.c        2 Aug 2016 19:46:28 -0000
@@ -150,7 +150,8 @@ relay_read_http(struct bufferevent *bev,
       struct protocol         *proto = rlay->rl_proto;
       struct evbuffer         *src = EVBUFFER_INPUT(bev);
       char                    *line = NULL, *key, *value;
-       int                      action;
+       char                    *urlproto, *host, *path;
+       int                      action, unique, ret;
       const char              *errstr;
       size_t                   size, linelen;
       struct kv               *hdr = NULL;
@@ -331,11 +332,35 @@ relay_read_http(struct bufferevent *bev,
                   strcasecmp("chunked", value) == 0)
                       desc->http_chunked = 1;

+               /* The following header should only occur once */
+               if (strcasecmp("Host", key) == 0) {
+                       unique = 1;
+
+                       /*
+                        * The path may contain a URL.  The host in the
+                        * URL has to match the Host: value.
+                        */
+                       if (parse_url(desc->http_path,
+                           &urlproto, &host, &path) == 0) {
+                               ret = strcasecmp(host, value);
+                               free(urlproto);
+                               free(host);
+                               free(path);
+                               if (ret != 0) {
+                                       relay_abort_http(con, 400,
+                                           "malformed host", 0);
+                                       goto abort;
+                               }
+                       }
+               } else
+                       unique = 0;
+
               if (cre->line != 1) {
                       if ((hdr = kv_add(&desc->http_headers, key,
-                           value)) == NULL) {
-                               free(line);
-                               goto fail;
+                           value, unique)) == NULL) {
+                               relay_abort_http(con, 400,
+                                   "malformed header", 0);
+                               goto abort;
                       }
                       desc->http_lastheader = hdr;
               }
@@ -1537,7 +1562,7 @@ relay_apply_actions(struct ctl_relay_eve
               if (addkv && kv->kv_matchtree != NULL) {
                       /* Add new entry to the list (eg. new HTTP header) */
                       if ((match = kv_add(kv->kv_matchtree, kp->kv_key,
-                           kp->kv_value)) == NULL)
+                           kp->kv_value, 0)) == NULL)
                               goto fail;
                       match->kv_option = kp->kv_option;
                       match->kv_type = kp->kv_type;
Index: usr.sbin/relayd/relayd.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.c,v
retrieving revision 1.153
diff -u -p -r1.153 relayd.c
--- usr.sbin/relayd/relayd.c    2 Feb 2016 17:51:11 -0000       1.153
+++ usr.sbin/relayd/relayd.c    2 Aug 2016 19:46:28 -0000
@@ -645,9 +645,8 @@ purge_relay(struct relayd *env, struct r
       free(rlay);
}

-
struct kv *
-kv_add(struct kvtree *keys, char *key, char *value)
+kv_add(struct kvtree *keys, char *key, char *value, int unique)
{
       struct kv       *kv, *oldkv;

@@ -655,24 +654,30 @@ kv_add(struct kvtree *keys, char *key, c
               return (NULL);
       if ((kv = calloc(1, sizeof(*kv))) == NULL)
               return (NULL);
-       if ((kv->kv_key = strdup(key)) == NULL) {
-               free(kv);
-               return (NULL);
-       }
+       if ((kv->kv_key = strdup(key)) == NULL)
+               goto fail;
       if (value != NULL &&
-           (kv->kv_value = strdup(value)) == NULL) {
-               free(kv->kv_key);
-               free(kv);
-               return (NULL);
-       }
+           (kv->kv_value = strdup(value)) == NULL)
+               goto fail;
       TAILQ_INIT(&kv->kv_children);

       if ((oldkv = RB_INSERT(kvtree, keys, kv)) != NULL) {
+               /*
+                * return error if the key should occur only once,
+                * or add it to a list attached to the key's node.
+                */
+               if (unique)
+                       goto fail;
               TAILQ_INSERT_TAIL(&oldkv->kv_children, kv, kv_entry);
               kv->kv_parent = oldkv;
       }

       return (kv);
+ fail:
+       free(kv->kv_key);
+       free(kv->kv_value);
+       free(kv);
+       return (NULL);
}

int
@@ -1379,6 +1384,52 @@ canonicalize_host(const char *host, char
 fail:
       errno = EINVAL;
       return (NULL);
+}
+
+int
+parse_url(const char *url, char **protoptr, char **hostptr, char **pathptr)
+{
+       char    *p, *proto = NULL, *host = NULL, *path = NULL;
+
+       /* return error if it is not a URL */
+       if ((p = strstr(url, ":/")) == NULL ||
+           (strcspn(url, ":/") != (size_t)(p - url)))
+               return (-1);
+
+       /* get protocol */
+       if ((proto = strdup(url)) == NULL)
+               goto fail;
+       p = proto + (p - url);
+
+       /* get host */
+       p += strspn(p, ":/");
+       if (*p == '\0' || (host = strdup(p)) == NULL)
+               goto fail;
+       *p = '\0';
+
+       /* find and copy path or default to "/" */
+       if ((p = strchr(host, '/')) == NULL)
+               p = "/";
+       if ((path = strdup(p)) == NULL)
+               goto fail;
+
+       /* strip path after host */
+       host[strcspn(host, "/")] = '\0';
+
+       DPRINTF("%s: %s proto %s, host %s, path %s", __func__,
+           url, proto, host, path);
+
+       *protoptr = proto;
+       *hostptr = host;
+       *pathptr = path;
+
+       return (0);
+
+ fail:
+       free(proto);
+       free(host);
+       free(path);
+       return (-1);
}

int
Index: usr.sbin/relayd/relayd.h
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.h,v
retrieving revision 1.223
diff -u -p -r1.223 relayd.h
--- usr.sbin/relayd/relayd.h    22 Jul 2016 09:30:36 -0000      1.223
+++ usr.sbin/relayd/relayd.h    2 Aug 2016 19:46:28 -0000
@@ -1268,6 +1268,7 @@ void               purge_table(struct relayd *, stru
void            purge_relay(struct relayd *, struct relay *);
char           *digeststr(enum digest_type, const u_int8_t *, size_t, char *);
const char     *canonicalize_host(const char *, char *, size_t);
+int             parse_url(const char *, char **, char **, char **);
int             map6to4(struct sockaddr_storage *);
int             map4to6(struct sockaddr_storage *, struct sockaddr_storage *);
void            imsg_event_add(struct imsgev *);
@@ -1281,7 +1282,7 @@ struct in6_addr *prefixlen2mask6(u_int8_
u_int32_t       prefixlen2mask(u_int8_t);
int             accept_reserve(int, struct sockaddr *, socklen_t *, int,
                    volatile int *);
-struct kv      *kv_add(struct kvtree *, char *, char *);
+struct kv      *kv_add(struct kvtree *, char *, char *, int);
int             kv_set(struct kv *, char *, ...);
int             kv_setkey(struct kv *, char *, ...);
void            kv_delete(struct kvtree *, struct kv *);