]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-master: Support HAproxy TLVs
authorAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 18 Sep 2017 12:25:21 +0000 (15:25 +0300)
committerTimo Sirainen <tss@dovecot.fi>
Mon, 16 Oct 2017 08:04:36 +0000 (11:04 +0300)
This allows passing in information about SSL and client
certificate(s) and other information from haproxy.

src/lib-master/master-service-haproxy.c
src/lib-master/master-service.h

index 384b33772f7035c2aa4d12540caa6a22d7b24cf2..a9598a3023d61902aaf979930bae55298ad3e617 100644 (file)
@@ -9,6 +9,22 @@
 
 #define HAPROXY_V1_MAX_HEADER_SIZE (108)
 
+#define PP2_TYPE_ALPN           0x01
+#define PP2_TYPE_AUTHORITY      0x02
+#define PP2_TYPE_CRC32C         0x03
+#define PP2_TYPE_NOOP           0x04
+#define PP2_TYPE_SSL            0x20
+#define PP2_SUBTYPE_SSL_VERSION 0x21
+#define PP2_SUBTYPE_SSL_CN      0x22
+#define PP2_SUBTYPE_SSL_CIPHER  0x23
+#define PP2_SUBTYPE_SSL_SIG_ALG 0x24
+#define PP2_SUBTYPE_SSL_KEY_ALG 0x25
+#define PP2_TYPE_NETNS          0x30
+
+#define PP2_CLIENT_SSL         0x01
+#define PP2_CLIENT_CERT_CONN   0x02
+#define PP2_CLIENT_CERT_SESS   0x04
+
 enum haproxy_version_t {
        HAPROXY_VERSION_1,
        HAPROXY_VERSION_2,
@@ -63,6 +79,22 @@ struct haproxy_data_v2 {
        } addr;
 };
 
+#define SIZEOF_PP2_TLV (1U+2U)
+struct haproxy_pp2_tlv {
+       uint8_t type;
+       uint16_t len;
+       const unsigned char *data;
+};
+
+#define SIZEOF_PP2_TLV_SSL (1U+4U)
+struct haproxy_pp2_tlv_ssl {
+       uint8_t client;
+       uint32_t verify;
+
+       size_t len;
+       const unsigned char *data;
+};
+
 struct master_service_haproxy_conn {
        struct master_service_connection conn;
 
@@ -116,7 +148,7 @@ master_service_haproxy_timeout(struct master_service_haproxy_conn *hpconn)
        master_service_haproxy_conn_failure(hpconn);
 }
 
-static ssize_t
+static int
 master_service_haproxy_recv(int fd, void *buf, size_t len, int flags)
 {
        ssize_t ret;
@@ -136,11 +168,119 @@ master_service_haproxy_recv(int fd, void *buf, size_t len, int flags)
        return ret;
 }
 
+static int get_ssl_tlv(const unsigned char *kvdata, size_t dlen,
+                      struct haproxy_pp2_tlv_ssl *kv)
+{
+       if (dlen < SIZEOF_PP2_TLV_SSL)
+               return -1;
+       kv->client = kvdata[0];
+       /* spec does not specify the endianess of this field */
+       kv->verify = cpu32_to_cpu_unaligned(kvdata+1);
+       kv->data = kvdata+SIZEOF_PP2_TLV_SSL;
+       kv->len = dlen - SIZEOF_PP2_TLV_SSL;
+       return 0;
+}
+
+static int get_tlv(const unsigned char *kvdata, size_t dlen,
+                  struct haproxy_pp2_tlv *kv)
+{
+       if (dlen < SIZEOF_PP2_TLV)
+               return -1;
+
+       /* spec says
+               uint8_t type
+               uint8_t len_hi
+               uint8_t len_lo
+         so we combine the hi and lo here. */
+       kv->type = kvdata[0];
+       kv->len = (kvdata[1]<<8)+kvdata[2];
+       kv->data = kvdata + SIZEOF_PP2_TLV;
+
+       if (kv->len + SIZEOF_PP2_TLV > dlen)
+               return -1;
+
+       return 0;
+}
+
+static int
+master_service_haproxy_parse_ssl_tlv(struct master_service_haproxy_conn *hpconn,
+                                    const struct haproxy_pp2_tlv_ssl *ssl_kv,
+                                    const char **error_r)
+{
+       hpconn->conn.proxy.ssl = (ssl_kv->client & (PP2_CLIENT_SSL)) != 0;
+
+       /* try parse some more */
+       for(size_t i = 0; i < ssl_kv->len;) {
+               struct haproxy_pp2_tlv kv;
+               if (get_tlv(ssl_kv->data + i, ssl_kv->len - i, &kv) < 0) {
+                       *error_r = t_strdup_printf("get_tlv(%"PRIuSIZE_T") failed:"
+                                                  "Truncated data", i);
+                       return -1;
+               }
+               i += SIZEOF_PP2_TLV + kv.len;
+               switch(kv.type) {
+               /* we don't care about these */
+               case PP2_SUBTYPE_SSL_CIPHER:
+               case PP2_SUBTYPE_SSL_SIG_ALG:
+               case PP2_SUBTYPE_SSL_KEY_ALG:
+                       break;
+               case PP2_SUBTYPE_SSL_CN:
+                       hpconn->conn.proxy.cert_common_name =
+                               p_strndup(hpconn->pool, kv.data, kv.len);
+                       break;
+               }
+       }
+       return 0;
+}
+
+static int
+master_service_haproxy_parse_tlv(struct master_service_haproxy_conn *hpconn,
+                                const unsigned char *buf, size_t blen,
+                                const char **error_r)
+{
+       for(size_t i = 0; i < blen;) {
+               struct haproxy_pp2_tlv kv;
+                struct haproxy_pp2_tlv_ssl ssl_kv;
+
+               if (get_tlv(buf + i, blen - i, &kv) < 0) {
+                       *error_r = t_strdup_printf("get_tlv(%"PRIuSIZE_T") failed:"
+                                                  "Truncated data", i);
+                       return -1;
+               }
+
+                /* skip unsupported values */
+                switch(kv.type) {
+               case PP2_TYPE_ALPN:
+                       hpconn->conn.proxy.alpn_size = kv.len;
+                       hpconn->conn.proxy.alpn =
+                               p_memdup(hpconn->pool, kv.data, kv.len);
+                       break;
+                case PP2_TYPE_AUTHORITY:
+                        /* store hostname somewhere */
+                        hpconn->conn.proxy.hostname =
+                               p_strndup(hpconn->pool, kv.data, kv.len);
+                        break;
+                case PP2_TYPE_SSL:
+                       if (get_ssl_tlv(kv.data, kv.len, &ssl_kv) < 0) {
+                               *error_r = t_strdup_printf("get_ssl_tlv(%"PRIuSIZE_T") failed:"
+                                                          "Truncated data", i);
+                               return -1;
+                       }
+                        if (master_service_haproxy_parse_ssl_tlv(hpconn, &ssl_kv, error_r)<0)
+                               return -1;
+                        break;
+               }
+               i += SIZEOF_PP2_TLV + kv.len;
+        }
+       return 0;
+}
+
 static int
 master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
 {
        /* reasonable max size for haproxy data */
        unsigned char rbuf[1500];
+       const char *error;
        static union {
                unsigned char v1_data[HAPROXY_V1_MAX_HEADER_SIZE];
                struct {
@@ -152,7 +292,7 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
        int fd = hpconn->conn.fd;
        struct ip_addr local_ip, remote_ip;
        in_port_t local_port, remote_port;
-       size_t size,want;
+       size_t size,i,want;
        ssize_t ret;
        enum haproxy_version_t version;
 
@@ -228,6 +368,9 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
 
                hdr_len = ntohs(hdr->len);
                size = sizeof(*hdr) + hdr_len;
+               /* keep tab of how much address data there really is because
+                  because TLVs begin after that. */
+               i = 0;
 
                if (ret < (ssize_t)size) {
                        i_error("haproxy(v2): Client disconnected: "
@@ -237,6 +380,8 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
                        return -1;
                }
 
+               i += sizeof(*hdr);
+
                switch (hdr->ver_cmd & 0x0f) {
                case HAPROXY_CMD_LOCAL:
                        /* keep local connection address for LOCAL */
@@ -266,6 +411,7 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
                                remote_ip.family = AF_INET;
                                remote_ip.u.ip4.s_addr = data->addr.ip4.src_addr;
                                remote_port = ntohs(data->addr.ip4.src_port);
+                               i += sizeof(data->addr.ip4);
                                break;
                        case HAPROXY_AF_INET6:
                                /* IPv6 */
@@ -281,6 +427,7 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
                                remote_ip.family = AF_INET6;
                                memcpy(&remote_ip.u.ip6.s6_addr, data->addr.ip6.src_addr, 16);
                                remote_port = ntohs(data->addr.ip6.src_port);
+                               i += sizeof(data->addr.ip6);
                                break;
                        case HAPROXY_AF_UNSPEC:
                        case HAPROXY_AF_UNIX:
@@ -306,9 +453,14 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
                        return -1; /* not a supported command */
                }
 
-               // FIXME: TLV vectors are ignored
-               //         (useful to see whether proxied client is using SSL)
-
+               if (master_service_haproxy_parse_tlv(hpconn, rbuf+i, size-i, &error) < 0) {
+                       i_error("haproxy(v2): Client disconnected: "
+                               "Invalid TLV: %s (cmd=%02x, rip=%s)",
+                               error,
+                               (hdr->ver_cmd & 0x0f),
+                               net_ip2addr(real_remote_ip));
+                       return -1;
+               }
        /* protocol version 1 (soon obsolete) */
        } else if (version == HAPROXY_VERSION_1) {
                unsigned char *data = buf.v1_data, *end;
@@ -449,6 +601,8 @@ master_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
        hpconn->conn.remote_ip = remote_ip;
        hpconn->conn.local_port = local_port;
        hpconn->conn.remote_port = remote_port;
+       hpconn->conn.proxied = TRUE;
+
        return 1;
 }
 
index ac5eaa84bd8a8e6c07188606d9f3f6f90e65121e..022ad85532e0f5f011de1406ff56906c1651b660 100644 (file)
@@ -39,6 +39,17 @@ enum master_service_flags {
        MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME = 0x800
 };
 
+struct master_service_connection_proxy {
+        /* only set if ssl is TRUE */
+        const char *hostname;
+        const char *cert_common_name;
+        const unsigned char *alpn;
+        unsigned int alpn_size;
+
+       bool ssl:1;
+       bool ssl_client_cert:1;
+};
+
 struct master_service_connection {
        /* fd of the new connection. */
        int fd;
@@ -56,6 +67,12 @@ struct master_service_connection {
        struct ip_addr real_remote_ip, real_local_ip;
        in_port_t real_remote_port, real_local_port;
 
+       /* filled if connection is proxied */
+       struct master_service_connection_proxy proxy;
+
+       /* This is a connection proxied wit HAproxy (or similar) */
+       bool proxied:1;
+
        /* This is a FIFO fd. Only a single "connection" is ever received from
           a FIFO after the first writer sends something to it. */
        bool fifo:1;