bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct { /* for TCP/UDP over IPv4, len = 12 */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct { /* for TCP/UDP over IPv6, len = 36 */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct { /* for AF_UNIX sockets, len = 216 */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct master_service_haproxy_conn *prev, *next;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_conn_free(struct master_service_haproxy_conn *hpconn)
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct master_service *service = hpconn->service;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen DLLIST_REMOVE(&service->haproxy_conns, hpconn);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_conn_failure(struct master_service_haproxy_conn *hpconn)
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct master_service *service = hpconn->service;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct master_service_connection conn = hpconn->conn;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen master_service_client_connection_handled(service, &conn);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_conn_success(struct master_service_haproxy_conn *hpconn)
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct master_service *service = hpconn->service;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct master_service_connection conn = hpconn->conn;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen master_service_client_connection_callback(service, &conn);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_timeout(struct master_service_haproxy_conn *hpconn)
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomimaster_service_haproxy_recv(int fd, void *buf, size_t len, int flags)
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi if (ret <= 0) {
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomistatic int get_ssl_tlv(const unsigned char *kvdata, size_t dlen,
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* spec does not specify the endianess of this field */
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomistatic int get_tlv(const unsigned char *kvdata, size_t dlen,
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* spec says
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi uint8_t type
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi uint8_t len_hi
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi uint8_t len_lo
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi so we combine the hi and lo here. */
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomimaster_service_haproxy_parse_ssl_tlv(struct master_service_haproxy_conn *hpconn,
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi const char **error_r)
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi hpconn->conn.proxy.ssl = (ssl_kv->client & (PP2_CLIENT_SSL)) != 0;
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* try parse some more */
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi if (get_tlv(ssl_kv->data + i, ssl_kv->len - i, &kv) < 0) {
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi *error_r = t_strdup_printf("get_tlv(%"PRIuSIZE_T") failed:"
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi "Truncated data", i);
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* we don't care about these */
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomimaster_service_haproxy_parse_tlv(struct master_service_haproxy_conn *hpconn,
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi const char **error_r)
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi *error_r = t_strdup_printf("get_tlv(%"PRIuSIZE_T") failed:"
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi "Truncated data", i);
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* skip unsupported values */
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* store hostname somewhere */
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi *error_r = t_strdup_printf("get_ssl_tlv(%"PRIuSIZE_T") failed:"
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi "Truncated data", i);
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi if (master_service_haproxy_parse_ssl_tlv(hpconn, &ssl_kv, error_r)<0)
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_read(struct master_service_haproxy_conn *hpconn)
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi /* reasonable max size for haproxy data */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen static union {
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen unsigned char v1_data[HAPROXY_V1_MAX_HEADER_SIZE];
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen struct ip_addr *real_remote_ip = &hpconn->conn.remote_ip;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* the protocol specification explicitly states that the protocol header
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen must be sent as one TCP frame, meaning that we will get it in full
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen with the first recv() call.
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi /* see if there is a HAPROXY protocol command waiting */
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi if ((ret = master_service_haproxy_recv(fd, &buf, sizeof(buf), MSG_PEEK))<=0) {
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi i_info("haproxy: Client disconnected (rip=%s): %m",
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi /* see if there is a haproxy command, 8 is used later on as well */
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi } else if (ret >= 8 && memcmp(buf.v1_data, "PROXY", 5) == 0) {
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi memcmp(buf.v2.hdr.sig, haproxy_v2sig, sizeof(haproxy_v2sig)) == 0) {
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi want = ntohs(buf.v2.hdr.len) + sizeof(buf.v2.hdr);
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi i_error("haproxy: Client disconnected: Too long header (rip=%s)",
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi if ((ret = master_service_haproxy_recv(fd, rbuf, want, MSG_WAITALL))<=0) {
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi i_info("haproxy: Client disconnected (rip=%s): %m",
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi i_info("haproxy: Client disconnected: Failed to read full header (rip=%s)",
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi /* it wasn't haproxy data */
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi "Failed to read valid HAproxy data (rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* don't update true connection data until we succeed */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* protocol version 2 */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen const struct haproxy_header_v2 *hdr = &buf.v2.hdr;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen const struct haproxy_data_v2 *data = &buf.v2.data;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Unsupported protocol version (version=%02x, rip=%s)",
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi /* keep tab of how much address data there really is because
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi because TLVs begin after that. */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Protocol payload length does not match header "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(got=%"PRIuSIZE_T", expect=%"PRIuSIZE_T", rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen (size_t)ret, size, net_ip2addr(real_remote_ip));
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi i += sizeof(*hdr);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* keep local connection address for LOCAL */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /*i_debug("haproxy(v2): Local connection (rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen net_ip2addr(real_remote_ip));*/
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen if ((hdr->fam & 0x0f) != HAPROXY_SOCK_STREAM) {
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* UDP makes no sense currently */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Not using TCP (type=%02x, rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen (hdr->fam & 0x0f), net_ip2addr(real_remote_ip));
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "IPv4 data is incomplete (rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen local_ip.u.ip4.s_addr = data->addr.ip4.dst_addr;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen remote_ip.u.ip4.s_addr = data->addr.ip4.src_addr;
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "IPv6 data is incomplete (rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen memcpy(&local_ip.u.ip6.s6_addr, data->addr.ip6.dst_addr, 16);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen memcpy(&remote_ip.u.ip6.s6_addr, data->addr.ip6.src_addr, 16);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* unsupported; ignored */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen i_error("haproxy(v2): Unsupported address family "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(family=%02x, rip=%s)", (hdr->fam & 0xf0) >> 4,
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* unsupported; error */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Unknown address family "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(family=%02x, rip=%s)", (hdr->fam & 0xf0) >> 4,
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Invalid command (cmd=%02x, rip=%s)",
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi if (master_service_haproxy_parse_tlv(hpconn, rbuf+i, size-i, &error) < 0) {
b68b98e1545bad8af9cb58ef89e8d7f6e16577beAki Tuomi "Invalid TLV: %s (cmd=%02x, rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* protocol version 1 (soon obsolete) */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* find end of header line */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* protocol */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Field for proxied protocol is missing "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Unknown proxied protocol "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(protocol=`%s', rip=%s)", str_sanitize(*fields, 64),
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* remote address */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Field for proxied remote address is missing "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Proxied remote address is invalid "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(address=`%s', rip=%s)", str_sanitize(*fields, 64),
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* local address */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Field for proxied local address is missing "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Proxied local address is invalid "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(address=`%s', rip=%s)", str_sanitize(*fields, 64),
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* remote port */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Field for proxied local port is missing "
009217abb57a24a4076092e8e4e165545747839eStephan Bosch if (net_str2port(*fields, &remote_port) < 0) {
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Proxied remote port is invalid "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(port=`%s', rip=%s)", str_sanitize(*fields, 64),
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* local port */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Field for proxied local port is missing "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Proxied local port is invalid "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(port=`%s', rip=%s)", str_sanitize(*fields, 64),
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "Header line has spurius extra field "
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen "(field=`%s', rip=%s)", str_sanitize(*fields, 64),
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi if ((ret = master_service_haproxy_recv(fd, &buf, size, 0))<=0) {
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi i_info("haproxy: Client disconnected (rip=%s): %m",
f43567aab398e7ecc0cede120b442affc8f29807Aki Tuomi "Failed to read full header (rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* invalid protocol */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen /* assign data from proxy */
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_input(struct master_service_haproxy_conn *hpconn)
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen if ((ret = master_service_haproxy_read(hpconn)) <= 0) {
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenmaster_service_haproxy_conn_is_trusted(struct master_service *service,
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen if (service->set->haproxy_trusted_networks == NULL)
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen net = t_strsplit_spaces(service->set->haproxy_trusted_networks, ", ");
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen if (net_parse_range(*net, &net_ip, &bits) < 0) {
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen if (net_is_in_network(&conn->real_remote_ip, &net_ip, bits))
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenvoid master_service_haproxy_new(struct master_service *service,
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen if (!master_service_haproxy_conn_is_trusted(service, conn)) {
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen i_warning("haproxy: Client not trusted (rip=%s)",
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen master_service_client_connection_handled(service, conn);
894610212596c35aade07a4d0af9d5e7fd6245c7Aki Tuomi pool = pool_alloconly_create("haproxy connection", 128);
894610212596c35aade07a4d0af9d5e7fd6245c7Aki Tuomi hpconn = p_new(pool, struct master_service_haproxy_conn, 1);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen DLLIST_PREPEND(&service->haproxy_conns, hpconn);
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainen hpconn->to = timeout_add(service->set->haproxy_timeout*1000,
8761992b5aa05862e7ec3a460cdc17af41a4a0f5Timo Sirainenvoid master_service_haproxy_abort(struct master_service *service)