From c2fe175d1b98ae3cc728d9254568201d45e2aeca Mon Sep 17 00:00:00 2001 From: Vladislav Grishenko Date: Tue, 27 Feb 2018 16:48:59 +0500 Subject: sstp: implement proxy-protocol 1 & 2 support --- accel-pppd/accel-ppp.conf | 2 +- accel-pppd/accel-ppp.conf.5 | 5 +- accel-pppd/ctrl/sstp/proxy_prot.h | 74 ++++++++++++ accel-pppd/ctrl/sstp/sstp.c | 247 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 313 insertions(+), 15 deletions(-) create mode 100644 accel-pppd/ctrl/sstp/proxy_prot.h (limited to 'accel-pppd') diff --git a/accel-pppd/accel-ppp.conf b/accel-pppd/accel-ppp.conf index d2e7a26d..607609ac 100644 --- a/accel-pppd/accel-ppp.conf +++ b/accel-pppd/accel-ppp.conf @@ -113,7 +113,7 @@ verbose=1 #cert-hash-proto=sha1,sha256 #cert-hash-sha1= #cert-hash-sha256= -#accept=ssl +#accept=ssl,proxy #ssl-ciphers=DEFAULT #ssl-prefer-server-ciphers=0 #ssl-ca-file=/etc/ssl/sstp-ca.crt diff --git a/accel-pppd/accel-ppp.conf.5 b/accel-pppd/accel-ppp.conf.5 index fb38fea1..97e7ed5b 100644 --- a/accel-pppd/accel-ppp.conf.5 +++ b/accel-pppd/accel-ppp.conf.5 @@ -662,11 +662,14 @@ If this option is given and greater then zero then sstp will send echo-request e seconds and drop connection without a reply. Default is 60. .TP -.BI "accept=" ssl +.BI "accept=" ssl,proxy Specifies incoming connection acceptance mode. .br .B ssl - enable SSL/TLS support. +.br +.B proxy +- enable PROXY protocol 1 & 2 support. .TP .BI "ssl-ciphers=" string Specifies the enabled ciphers. The ciphers are specified in the format understood by the OpenSSL library. diff --git a/accel-pppd/ctrl/sstp/proxy_prot.h b/accel-pppd/ctrl/sstp/proxy_prot.h new file mode 100644 index 00000000..bada8aa3 --- /dev/null +++ b/accel-pppd/ctrl/sstp/proxy_prot.h @@ -0,0 +1,74 @@ +#ifndef PROXY_PROT_H +#define PROXY_PROT_H + +#include +#include +#include + +#define PROXY_SIG { 'P', 'R', 'O', 'X', 'Y' } +#define PROXY_MINLEN 8 +#define PROXY_TCP4 "TCP4" +#define PROXY_TCP6 "TCP6" +#define PROXY_UNKNOWN "UNKNOWN" + +#define PROXY2_SIG { 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a } +#define PROXY2_MINLEN 16 +#define PROXY2_LOCAL 0 +#define PROXY2_PROXY 1 +#define PROXY2_AF_UNSPEC 0 +#define PROXY2_AF_INET 1 +#define PROXY2_AF_INET6 2 +#define PROXY2_AF_UNIX 3 +#define PROXY2_UNSPEC 0 +#define PROXY2_STREAM 1 +#define PROXY2_DGRAM 2 + +struct proxy_hdr { + char line[108]; +} __attribute__((packed)); + +struct proxy2_ipv4 { + struct in_addr src_addr; + struct in_addr dst_addr; + uint16_t src_port; + uint16_t dst_port; +} __attribute__((packed)); + +struct proxy2_ipv6 { + struct in6_addr src_addr; + struct in6_addr dst_addr; + uint16_t src_port; + uint16_t dst_port; +} __attribute__((packed)); + +struct proxy2_unix { + char src_addr[108]; + char dst_addr[108]; +} __attribute__((packed)); + +union proxy2_addr { + struct proxy2_ipv4 ipv4_addr; + struct proxy2_ipv6 ipv6_addr; + struct proxy2_unix unix_addr; +}; + +struct proxy2_hdr { + uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ + uint8_t ver_cmd; /* protocol version and command */ + uint8_t fam; /* protocol family and address */ + uint16_t len; /* number of following bytes part of the header */ + union proxy2_addr __addr[0]; +#define ipv4_addr __addr[0].ipv4_addr +#define ipv6_addr __addr[0].ipv6_addr +#define unix_addr __addr[0].unix_addr +} __attribute__((packed)); + +struct BUG_bad_sizeof_proxy_hdr { + uint8_t proxy_hdr[sizeof(struct proxy_hdr) < PROXY_MINLEN ? -1 : 0 ]; +}; + +struct BUG_bad_sizeof_proxy2_hdr { + uint8_t proxy2_hdr[sizeof(struct proxy2_hdr) != PROXY2_MINLEN ? -1 : 0 ]; +}; + +#endif diff --git a/accel-pppd/ctrl/sstp/sstp.c b/accel-pppd/ctrl/sstp/sstp.c index 11d57bae..5eabbfc1 100644 --- a/accel-pppd/ctrl/sstp/sstp.c +++ b/accel-pppd/ctrl/sstp/sstp.c @@ -36,11 +36,15 @@ #include "memdebug.h" +#include "proxy_prot.h" #include "sstp_prot.h" #ifndef min #define min(x,y) ((x) < (y) ? (x) : (y)) #endif +#ifndef max +#define max(x,y) ((x) > (y) ? (x) : (y)) +#endif #define PPP_SYNC 0 /* buggy yet */ #define PPP_BUF_SIZE 8192 @@ -132,6 +136,7 @@ struct sstp_conn_t { struct buffer_t *ppp_in; struct list_head ppp_queue; + struct sockaddr_t addr; struct ppp_t ppp; struct ap_ctrl ctrl; }; @@ -153,6 +158,7 @@ static int conf_verbose = 0; static int conf_ppp_max_mtu = 1452; static const char *conf_ip_pool; static const char *conf_ifname; +static int conf_proxyproto = 0; static int conf_hash_protocol = CERT_HASH_PROTOCOL_SHA1 | CERT_HASH_PROTOCOL_SHA256; static struct hash_t conf_hash_sha1 = { .len = 0 }; @@ -168,6 +174,7 @@ static int sstp_send(struct sstp_conn_t *conn, struct buffer_t *buf); static int sstp_abort(struct sstp_conn_t *conn, int disconnect); static void sstp_disconnect(struct sstp_conn_t *conn); static int sstp_handler(struct sstp_conn_t *conn, struct buffer_t *buf); +static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf); /* * FCS lookup table as calculated by genfcstab. @@ -240,6 +247,27 @@ static int hex2bin(const char *src, uint8_t *dst, size_t size) return n; } +#define vstrsep(buf, sep, args...) _vstrsep(buf, sep, args, (void*)-1) +static int _vstrsep(char *buf, const char *sep, ...) +{ + va_list ap; + char **arg, *val, *ptr; + int n = 0; + + va_start(ap, sep); + while ((arg = va_arg(ap, char **)) != (void *)-1) { + val = strtok_r(buf, sep, &ptr); + buf = NULL; + if (!val) + break; + if (arg) + *arg = val; + n++; + } + va_end(ap); + return n; +} + static in_addr_t sockaddr_ipv4(struct sockaddr_t *addr) { switch (addr->u.sa.sa_family) { @@ -526,6 +554,194 @@ error: } #endif +/* proxy */ + +static int proxy_parse(struct buffer_t *buf, struct sockaddr_t *peer, struct sockaddr_t *addr) +{ + static const uint8_t proxy_sig[] = PROXY_SIG; + struct proxy_hdr *hdr; + char *ptr, *src_addr, *dst_addr, *src_port, *dst_port; + int n, count; + + if (buf->len < PROXY_MINLEN || memcmp(buf->head, proxy_sig, sizeof(proxy_sig)) != 0) + return 0; + + ptr = memmem(buf->head, buf->len, "\r\n", 2); + if (!ptr) { + if (buf_tailroom(buf) > 0) + return 0; + log_error("sstp: proxy: %s\n", "too long header"); + return -1; + } else + *ptr = '\0'; + + hdr = (void *)buf->head; + n = ptr + 2 - hdr->line; + + if (conf_verbose) + log_ppp_info2("recv [PROXY <%s>]\n", hdr->line); + + count = vstrsep(hdr->line, " ", NULL, &ptr, &src_addr, &dst_addr, &src_port, &dst_port); + if (count < 2) + goto error; + + if (strcasecmp(ptr, PROXY_TCP4) == 0) { + if (count < 6 || + inet_pton(AF_INET, src_addr, &peer->u.sin.sin_addr) <= 0 || + inet_pton(AF_INET, dst_addr, &addr->u.sin.sin_addr) <= 0) { + goto error; + } + peer->len = addr->len = sizeof(addr->u.sin); + peer->u.sin.sin_family = addr->u.sin.sin_family = AF_INET; + peer->u.sin.sin_port = htons(atoi(src_port)); + addr->u.sin.sin_port = htons(atoi(dst_port)); + } else if (strcasecmp(ptr, PROXY_TCP6) == 0) { + if (count < 6 || + inet_pton(AF_INET6, src_addr, &peer->u.sin6.sin6_addr) <= 0 || + inet_pton(AF_INET6, dst_addr, &addr->u.sin6.sin6_addr) <= 0) { + goto error; + } + peer->len = addr->len = sizeof(addr->u.sin6); + peer->u.sin6.sin6_family = addr->u.sin6.sin6_family = AF_INET; + peer->u.sin6.sin6_port = htons(atoi(src_port)); + addr->u.sin6.sin6_port = htons(atoi(dst_port)); + } else if (strcasecmp(ptr, PROXY_UNKNOWN) != 0) + goto error; + + return n; + +error: + log_error("sstp: proxy: %s\n", "invalid header"); + return -1; +} + +static int proxy_parse_v2(struct buffer_t *buf, struct sockaddr_t *peer, struct sockaddr_t *addr) +{ + static const uint8_t proxy2_sig[] = PROXY2_SIG; + struct proxy2_hdr *hdr; + int n; + + if (buf->len < PROXY2_MINLEN || memcmp(buf->head, proxy2_sig, sizeof(proxy2_sig)) != 0) + return 0; + + hdr = (void *)buf->head; + + if (conf_verbose) { + log_ppp_info2("recv [PROXY ver/cmd=0x%02x fam/addr=0x%02x len=%d]\n", + hdr->ver_cmd, hdr->fam, ntohs(hdr->len)); + } + + if ((hdr->ver_cmd & 0xf0) != 0x20) + goto error; + + n = sizeof(*hdr) + ntohs(hdr->len); + if (n > buf->len) { + if (buf_tailroom(buf) > 0) + return 0; + log_error("sstp: proxy2: %s\n", "too long header"); + return -1; + } + + switch (hdr->ver_cmd & 0x0f) { + case PROXY2_PROXY: + switch (hdr->fam >> 4) { + case PROXY2_AF_INET: + if (n < sizeof(hdr) + sizeof(hdr->ipv4_addr)) + goto error; + peer->len = addr->len = sizeof(addr->u.sin); + peer->u.sin.sin_family = addr->u.sin.sin_family = AF_INET; + peer->u.sin.sin_addr.s_addr = hdr->ipv4_addr.src_addr.s_addr; + peer->u.sin.sin_port = hdr->ipv4_addr.src_port; + addr->u.sin.sin_addr.s_addr = hdr->ipv4_addr.dst_addr.s_addr; + addr->u.sin.sin_port = hdr->ipv4_addr.dst_port; + break; + case PROXY2_AF_INET6: + if (n < sizeof(hdr) + sizeof(hdr->ipv6_addr)) + goto error; + peer->len = addr->len = sizeof(addr->u.sin6); + peer->u.sin6.sin6_family = addr->u.sin6.sin6_family = AF_INET6; + memcpy(&peer->u.sin6.sin6_addr, &hdr->ipv6_addr.src_addr, sizeof(peer->u.sin6.sin6_addr)); + peer->u.sin6.sin6_port = hdr->ipv6_addr.src_port; + memcpy(&addr->u.sin6.sin6_addr, &hdr->ipv6_addr.dst_addr, sizeof(addr->u.sin6.sin6_addr)); + addr->u.sin6.sin6_port = hdr->ipv6_addr.dst_port; + break; + case PROXY2_AF_UNIX: + if (n < sizeof(hdr) + sizeof(hdr->unix_addr)) + goto error; + peer->len = addr->len = sizeof(addr->u.sun); + peer->u.sun.sun_family = addr->u.sun.sun_family = AF_UNIX; + memcpy(peer->u.sun.sun_path, hdr->unix_addr.src_addr, sizeof(peer->u.sun.sun_path)); + memcpy(addr->u.sun.sun_path, hdr->unix_addr.dst_addr, sizeof(addr->u.sun.sun_path)); + break; + case PROXY2_AF_UNSPEC: + break; + default: + goto error; + } + /* fall through */ + case PROXY2_LOCAL: + break; + default: + goto error; + } + + return n; + +error: + log_error("sstp: proxy2: %s\n", "invalid header"); + return -1; +} + +static int proxy_handler(struct sstp_conn_t *conn, struct buffer_t *buf) +{ + struct sockaddr_t addr; + char addr_buf[ADDRSTR_MAXLEN]; + in_addr_t ip; + int n; + + if (conn->sstp_state != STATE_SERVER_CALL_DISCONNECTED) + return -1; + + memset(&addr, 0, sizeof(addr)); + + n = proxy_parse_v2(buf, &conn->addr, &addr); + if (n == 0) + n = proxy_parse(buf, &conn->addr, &addr); + + if (n == 0 && buf->len >= max(PROXY2_MINLEN, PROXY_MINLEN)) { + log_error("sstp: proxy: %s\n", "no header found"); + return -1; + } else if (n <= 0) + return n; + + ip = sockaddr_ipv4(&conn->addr); + if (ip && triton_module_loaded("connlimit") && connlimit_check(cl_key_from_ipv4(ip))) + return -1; + + sockaddr_ntop(&conn->addr, addr_buf, sizeof(addr_buf)); + log_info2("sstp: proxy: connection from %s\n", addr_buf); + + if (ip && iprange_client_check(ip)) { + log_warn("sstp: proxy: IP is out of client-ip-range, droping connection...\n"); + return -1; + } + + if (addr.u.sa.sa_family != AF_UNSPEC) { + _free(conn->ctrl.calling_station_id); + conn->ctrl.calling_station_id = _strdup(addr_buf); + conn->ppp.ses.chan_name = conn->ctrl.calling_station_id; + + sockaddr_ntop(&addr, addr_buf, sizeof(addr_buf)); + _free(conn->ctrl.called_station_id); + conn->ctrl.called_station_id = _strdup(addr_buf); + } + + buf_pull(buf, n); + + conn->handler = http_handler; + return buf->len; +} + /* http */ static char *http_getline(struct buffer_t *buf, char *line, size_t size) @@ -619,12 +835,9 @@ static int http_recv_request(struct sstp_conn_t *conn, uint8_t *data, int len) if (conf_verbose) log_ppp_info2("recv [HTTP <%s>]\n", line); - method = strsep(&line, " "); - request = strsep(&line, " "); - proto = strsep(&line, " "); host = NULL; - if (!method || !request || !proto) { + if (vstrsep(line, " ", &method, &request, &proto) < 3) { http_send_response(conn, "HTTP/1.1", "400 Bad Request", NULL); goto error; } @@ -703,7 +916,7 @@ static int http_handler(struct sstp_conn_t *conn, struct buffer_t *buf) conn->sstp_state = STATE_SERVER_CONNECT_REQUEST_PENDING; conn->handler = sstp_handler; - return 0; + return buf->len; } /* ppp */ @@ -1574,9 +1787,11 @@ static int sstp_read(struct triton_md_handler_t *h) } buf_put(buf, n); - n = conn->handler(conn, buf); - if (n < 0) - goto drop; + do { + n = conn->handler(conn, buf); + if (n < 0) + goto drop; + } while (n > 0); buf_expand_tail(buf, SSTP_MAX_PACKET_SIZE); } @@ -1823,7 +2038,7 @@ static int sstp_connect(struct triton_md_handler_t *h) continue; } - ip = sockaddr_ipv4(&addr); + ip = conf_proxyproto ? INADDR_ANY : sockaddr_ipv4(&addr); if (ip && triton_module_loaded("connlimit") && connlimit_check(cl_key_from_ipv4(ip))) { close(sock); return 0; @@ -1877,7 +2092,7 @@ static int sstp_connect(struct triton_md_handler_t *h) conn->sstp_state = STATE_SERVER_CALL_DISCONNECTED; conn->ppp_state = STATE_INIT; - conn->handler = http_handler; + conn->handler = conf_proxyproto ? proxy_handler : http_handler; //conn->bypass_auth = conf_bypass_auth; //conn->http_cookie = NULL: @@ -1887,6 +2102,7 @@ static int sstp_connect(struct triton_md_handler_t *h) conn->in = alloc_buf(SSTP_MAX_PACKET_SIZE*2); INIT_LIST_HEAD(&conn->out_queue); INIT_LIST_HEAD(&conn->ppp_queue); + memcpy(&conn->addr, &addr, sizeof(conn->addr)); conn->ctrl.ctx = &conn->ctx; conn->ctrl.started = ppp_started; @@ -2090,14 +2306,19 @@ static void load_config(void) conf_hash_protocol |= CERT_HASH_PROTOCOL_SHA256; } + opt = conf_get_opt("sstp", "accept"); + conf_proxyproto = strhas(opt, "proxy", ','); + #ifdef CRYPTO_OPENSSL ssl_load_config(&serv, conf_hostname); opt = serv.ssl_ctx ? "enabled" : "disabled"; #else opt = "not available"; #endif - if (conf_verbose) - log_info2("sstp: SSL support %s\n", opt); + if (conf_verbose) { + log_info2("sstp: SSL/TLS support %s, PROXY support %s\n", + opt, conf_proxyproto ? "enabled" : "disabled"); + } opt = conf_get_opt("sstp", "cert-hash-sha1"); if (opt) { @@ -2126,7 +2347,7 @@ static void load_config(void) conf_ip_pool = conf_get_opt("sstp", "ip-pool"); conf_ifname = conf_get_opt("sstp", "ifname"); - ipmode = (serv.addr.u.sa.sa_family == AF_INET) ? + ipmode = (serv.addr.u.sa.sa_family == AF_INET && !conf_proxyproto) ? iprange_check_activation() : -1; switch (ipmode) { case IPRANGE_DISABLED: -- cgit v1.2.3