Add support for "virtual hosts" aka. server blocks aka. multiple
authorreyk <reyk@openbsd.org>
Fri, 25 Jul 2014 16:23:19 +0000 (16:23 +0000)
committerreyk <reyk@openbsd.org>
Fri, 25 Jul 2014 16:23:19 +0000 (16:23 +0000)
servers with the same or "overlapping" IP address but a different name.

ok beck@

usr.sbin/httpd/config.c
usr.sbin/httpd/httpd.c
usr.sbin/httpd/httpd.h
usr.sbin/httpd/parse.y
usr.sbin/httpd/server.c
usr.sbin/httpd/server_http.c

index 8dd395d..eb9e003 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: config.c,v 1.3 2014/07/23 13:26:39 reyk Exp $ */
+/*     $OpenBSD: config.c,v 1.4 2014/07/25 16:23:19 reyk Exp $ */
 
 /*
  * Copyright (c) 2011 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -46,6 +46,9 @@
 
 #include "httpd.h"
 
+int     config_getserver_config(struct httpd *, struct server *,
+           struct imsg *);
+
 int
 config_init(struct httpd *env)
 {
@@ -204,6 +207,31 @@ config_setserver(struct httpd *env, struct server *srv)
        return (0);
 }
 
+int
+config_getserver_config(struct httpd *env, struct server *srv,
+    struct imsg *imsg)
+{
+#ifdef DEBUG
+       struct privsep          *ps = env->sc_ps;
+#endif
+       struct server_config    *srv_conf;
+       u_int8_t                *p = imsg->data;
+
+       if ((srv_conf = calloc(1, sizeof(*srv_conf))) == NULL)
+               return (-1);
+
+       IMSG_SIZE_CHECK(imsg, srv_conf);
+       memcpy(srv_conf, p, sizeof(*srv_conf));
+
+       TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry);
+
+       DPRINTF("%s: %s %d received configuration \"%s\", parent \"%s\"",
+           __func__, ps->ps_title[privsep_process], ps->ps_instance,
+           srv_conf->name, srv->srv_conf.name);
+
+       return (0);
+}
+
 int
 config_getserver(struct httpd *env, struct imsg *imsg)
 {
@@ -211,21 +239,36 @@ config_getserver(struct httpd *env, struct imsg *imsg)
        struct privsep          *ps = env->sc_ps;
 #endif
        struct server           *srv;
+       struct server_config     srv_conf;
        u_int8_t                *p = imsg->data;
        size_t                   s;
 
+       IMSG_SIZE_CHECK(imsg, &srv_conf);
+       memcpy(&srv_conf, p, sizeof(srv_conf));
+       s = sizeof(srv_conf);
+
+       /* Check if server with matching listening socket already exists */
+       if ((srv = server_byaddr((struct sockaddr *)
+           &srv_conf.ss)) != NULL) {
+               /* Add "host" to existing listening server */
+               close(imsg->fd);
+               return (config_getserver_config(env,
+                   srv, imsg));
+       }
+
+       /* Otherwise create a new server */
        if ((srv = calloc(1, sizeof(*srv))) == NULL) {
                close(imsg->fd);
                return (-1);
        }
 
-       IMSG_SIZE_CHECK(imsg, &srv->srv_conf);
-       memcpy(&srv->srv_conf, p, sizeof(srv->srv_conf));
-       s = sizeof(srv->srv_conf);
-
+       memcpy(&srv->srv_conf, &srv_conf, sizeof(srv->srv_conf));
        srv->srv_s = imsg->fd;
 
        SPLAY_INIT(&srv->srv_clients);
+       TAILQ_INIT(&srv->srv_hosts);
+
+       TAILQ_INSERT_TAIL(&srv->srv_hosts, &srv->srv_conf, entry);
        TAILQ_INSERT_TAIL(env->sc_servers, srv, srv_entry);
 
        DPRINTF("%s: %s %d received configuration \"%s\"", __func__,
index d38b4d9..22ae5f9 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: httpd.c,v 1.7 2014/07/24 08:32:36 reyk Exp $  */
+/*     $OpenBSD: httpd.c,v 1.8 2014/07/25 16:23:19 reyk Exp $  */
 
 /*
  * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
@@ -579,6 +579,102 @@ get_data(u_int8_t *ptr, size_t len)
        return (data);
 }
 
+int
+sockaddr_cmp(struct sockaddr *a, struct sockaddr *b, int prefixlen)
+{
+       struct sockaddr_in      *a4, *b4;
+       struct sockaddr_in6     *a6, *b6;
+       u_int32_t                av[4], bv[4], mv[4];
+
+       if (a->sa_family == AF_UNSPEC || b->sa_family == AF_UNSPEC)
+               return (0);
+       else if (a->sa_family > b->sa_family)
+               return (1);
+       else if (a->sa_family < b->sa_family)
+               return (-1);
+
+       if (prefixlen == -1)
+               memset(&mv, 0xff, sizeof(mv));
+
+       switch (a->sa_family) {
+       case AF_INET:
+               a4 = (struct sockaddr_in *)a;
+               b4 = (struct sockaddr_in *)b;
+
+               av[0] = a4->sin_addr.s_addr;
+               bv[0] = b4->sin_addr.s_addr;
+               if (prefixlen != -1)
+                       mv[0] = prefixlen2mask(prefixlen);
+
+               if ((av[0] & mv[0]) > (bv[0] & mv[0]))
+                       return (1);
+               if ((av[0] & mv[0]) < (bv[0] & mv[0]))
+                       return (-1);
+               break;
+       case AF_INET6:
+               a6 = (struct sockaddr_in6 *)a;
+               b6 = (struct sockaddr_in6 *)b;
+
+               memcpy(&av, &a6->sin6_addr.s6_addr, 16);
+               memcpy(&bv, &b6->sin6_addr.s6_addr, 16);
+               if (prefixlen != -1)
+                       prefixlen2mask6(prefixlen, mv);
+
+               if ((av[3] & mv[3]) > (bv[3] & mv[3]))
+                       return (1);
+               if ((av[3] & mv[3]) < (bv[3] & mv[3]))
+                       return (-1);
+               if ((av[2] & mv[2]) > (bv[2] & mv[2]))
+                       return (1);
+               if ((av[2] & mv[2]) < (bv[2] & mv[2]))
+                       return (-1);
+               if ((av[1] & mv[1]) > (bv[1] & mv[1]))
+                       return (1);
+               if ((av[1] & mv[1]) < (bv[1] & mv[1]))
+                       return (-1);
+               if ((av[0] & mv[0]) > (bv[0] & mv[0]))
+                       return (1);
+               if ((av[0] & mv[0]) < (bv[0] & mv[0]))
+                       return (-1);
+               break;
+       }
+
+       return (0);
+}
+
+u_int32_t
+prefixlen2mask(u_int8_t prefixlen)
+{
+       if (prefixlen == 0)
+               return (0);
+
+       if (prefixlen > 32)
+               prefixlen = 32;
+
+       return (htonl(0xffffffff << (32 - prefixlen)));
+}
+
+struct in6_addr *
+prefixlen2mask6(u_int8_t prefixlen, u_int32_t *mask)
+{
+       static struct in6_addr  s6;
+       int                     i;
+
+       if (prefixlen > 128)
+               prefixlen = 128;
+
+       bzero(&s6, sizeof(s6));
+       for (i = 0; i < prefixlen / 8; i++)
+               s6.s6_addr[i] = 0xff;
+       i = prefixlen % 8;
+       if (i)
+               s6.s6_addr[prefixlen / 8] = 0xff00 >> i;
+
+       memcpy(mask, &s6, sizeof(s6));
+
+       return (&s6);
+}
+
 int
 accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
     int reserve, volatile int *counter)
index 301d4b2..cc10bfb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: httpd.h,v 1.11 2014/07/25 13:10:18 reyk Exp $ */
+/*     $OpenBSD: httpd.h,v 1.12 2014/07/25 16:23:19 reyk Exp $ */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -123,6 +123,7 @@ struct portrange {
 struct address {
        struct sockaddr_storage  ss;
        int                      ipproto;
+       int                      prefixlen;
        struct portrange         port;
        char                     ifname[IFNAMSIZ];
        TAILQ_ENTRY(address)     entry;
@@ -291,12 +292,17 @@ struct server_config {
        char                     docroot[MAXPATHLEN];
        in_port_t                port;
        struct sockaddr_storage  ss;
+       int                      prefixlen;
        struct timeval           timeout;
+
+       TAILQ_ENTRY(server_config) entry;
 };
+TAILQ_HEAD(serverhosts, server_config);
 
 struct server {
        TAILQ_ENTRY(server)      srv_entry;
        struct server_config     srv_conf;
+       struct serverhosts       srv_hosts;
 
        u_int8_t                 srv_tcpflags;
        int                      srv_tcpbufsiz;
@@ -380,6 +386,8 @@ int  server_bufferevent_write_chunk(struct client *,
 int     server_bufferevent_add(struct event *, int);
 int     server_bufferevent_write(struct client *, void *, size_t);
 void    server_inflight_dec(struct client *, const char *);
+struct server *
+        server_byaddr(struct sockaddr *);
 
 SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp);
 
@@ -420,6 +428,9 @@ int          imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t,
 void            socket_rlimit(int);
 char           *get_string(u_int8_t *, size_t);
 void           *get_data(u_int8_t *, size_t);
+int             sockaddr_cmp(struct sockaddr *, struct sockaddr *, int);
+struct in6_addr *prefixlen2mask6(u_int8_t, u_int32_t *);
+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 *);
index bd172b4..334ea48 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.5 2014/07/25 15:47:11 reyk Exp $  */
+/*     $OpenBSD: parse.y,v 1.6 2014/07/25 16:23:19 reyk Exp $  */
 
 /*
  * Copyright (c) 2007 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -937,7 +937,10 @@ host_v4(const char *s)
        sain->sin_len = sizeof(struct sockaddr_in);
        sain->sin_family = AF_INET;
        sain->sin_addr.s_addr = ina.s_addr;
-
+       if (sain->sin_addr.s_addr == INADDR_ANY)
+               h->prefixlen = 0; /* 0.0.0.0 address */
+       else
+               h->prefixlen = -1; /* host address */
        return (h);
 }
 
@@ -963,7 +966,11 @@ host_v6(const char *s)
                    sizeof(sa_in6->sin6_addr));
                sa_in6->sin6_scope_id =
                    ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
-
+               if (memcmp(&sa_in6->sin6_addr, &in6addr_any,
+                   sizeof(sa_in6->sin6_addr)) == 0)
+                       h->prefixlen = 0; /* any address */
+               else
+                       h->prefixlen = -1; /* host address */
                freeaddrinfo(res);
        }
 
@@ -1016,6 +1023,7 @@ host_dns(const char *s, struct addresslist *al, int max,
                if (ipproto != -1)
                        h->ipproto = ipproto;
                h->ss.ss_family = res->ai_family;
+               h->prefixlen = -1; /* host address */
 
                if (res->ai_family == AF_INET) {
                        sain = (struct sockaddr_in *)&h->ss;
@@ -1078,6 +1086,7 @@ host_if(const char *s, struct addresslist *al, int max,
                if (ipproto != -1)
                        h->ipproto = ipproto;
                h->ss.ss_family = af;
+               h->prefixlen = -1; /* host address */
 
                if (af == AF_INET) {
                        sain = (struct sockaddr_in *)&h->ss;
index 4380cb6..21ccff2 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server.c,v 1.10 2014/07/25 13:10:18 reyk Exp $        */
+/*     $OpenBSD: server.c,v 1.11 2014/07/25 16:23:19 reyk Exp $        */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -151,7 +151,8 @@ server_launch(void)
 void
 server_purge(struct server *srv)
 {
-       struct client   *clt;
+       struct client           *clt;
+       struct server_config    *srv_conf;
 
        /* shutdown and remove server */
        if (event_initialized(&srv->srv_ev))
@@ -167,9 +168,33 @@ server_purge(struct server *srv)
            SPLAY_ROOT(&srv->srv_clients)) != NULL)
                server_close(clt, NULL);
 
+       /* cleanup hosts */
+       while ((srv_conf =
+           TAILQ_FIRST(&srv->srv_hosts)) != NULL) {
+               TAILQ_REMOVE(&srv->srv_hosts, srv_conf, entry);
+
+               /* It might point to our own "default" entry */
+               if (srv_conf != &srv->srv_conf)
+                       free(srv_conf);
+       }
+
        free(srv);
 }
 
+struct server *
+server_byaddr(struct sockaddr *addr)
+{
+       struct server   *srv;
+
+       TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+               if (sockaddr_cmp((struct sockaddr *)&srv->srv_conf.ss,
+                   addr, srv->srv_conf.prefixlen) == 0)
+                       return (srv);
+       }
+
+       return (NULL);
+}
+
 int
 server_socket_af(struct sockaddr_storage *ss, in_port_t port)
 {
index 3fe0926..98edb96 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server_http.c,v 1.13 2014/07/25 13:10:18 reyk Exp $   */
+/*     $OpenBSD: server_http.c,v 1.14 2014/07/25 16:23:19 reyk Exp $   */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -628,6 +628,8 @@ int
 server_response(struct httpd *httpd, struct client *clt)
 {
        struct http_descriptor  *desc   = clt->clt_desc;
+       struct server           *srv = clt->clt_srv;
+       struct server_config    *srv_conf;
        struct kv               *kv, key;
        int                      ret;
 
@@ -657,6 +659,23 @@ server_response(struct httpd *httpd, struct client *clt)
                        clt->clt_persist = 0;
        }
 
+       /*
+        * Do we have a Host header and matching configuration?
+        * XXX the Host can also appear in the URL path.
+        */
+       key.kv_key = "Host";
+       if ((kv = kv_find(&desc->http_headers, &key)) != NULL) {
+               /* XXX maybe better to turn srv_hosts into a tree */
+               TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
+                       if (fnmatch(srv_conf->name, kv->kv_value,
+                           FNM_CASEFOLD) == 0) {
+                               /* Replace host configuration */
+                               clt->clt_srv_conf = srv_conf;
+                               break;
+                       }
+               }
+       }
+
        if ((ret = server_file(httpd, clt)) == -1)
                return (-1);