From 708c3b0177532044b74fc3581fb25c6734831e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:31 +0100 Subject: [PATCH 01/12] Make probes work even in the face of arbitrary data --- probe.c | 60 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/probe.c b/probe.c index fa34f96..d2b3477 100644 --- a/probe.c +++ b/probe.c @@ -125,7 +125,7 @@ void hexdump(const char *mem, unsigned int len) /* Is the buffer the beginning of an SSH connection? */ static int is_ssh_protocol(const char *p, int len, struct proto *proto) { - if (!strncmp(p, "SSH-", 4)) { + if (len >= 4 && !strncmp(p, "SSH-", 4)) { return 1; } return 0; @@ -143,8 +143,12 @@ static int is_ssh_protocol(const char *p, int len, struct proto *proto) */ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) { - int packet_len = ntohs(*(uint16_t*)p); + int packet_len; + if (len < 2) + return 0; + + packet_len = ntohs(*(uint16_t*)p); return packet_len == len - 2; } @@ -153,6 +157,9 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) * */ static int is_tinc_protocol( const char *p, int len, struct proto *proto) { + if (len < 2) + return 0; + return !strncmp(p, "0 ", 2); } @@ -162,37 +169,47 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto) * */ static int is_xmpp_protocol( const char *p, int len, struct proto *proto) { - return strstr(p, "jabber") ? 1 : 0; + return memmem(p, len, "jabber", 6) ? 1 : 0; } -static int probe_http_method(const char *p, const char *opt) +static int probe_http_method(const char *p, int len, const char *opt) { - return !strcmp(p, opt); + if (len < strlen(opt)) + return 0; + + return !strncmp(p, opt, len); } /* Is the buffer the beginning of an HTTP connection? */ static int is_http_protocol(const char *p, int len, struct proto *proto) { /* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */ - if (strstr(p, "HTTP")) + if (memmem(p, len, "HTTP", 4)) return 1; +#define PROBE_HTTP_METHOD(opt) if (probe_http_method(p, len, opt)) return 1 + /* Otherwise it could be HTTP/1.0 without version: check if it's got an * HTTP method (RFC2616 5.1.1) */ - probe_http_method(p, "OPTIONS"); - probe_http_method(p, "GET"); - probe_http_method(p, "HEAD"); - probe_http_method(p, "POST"); - probe_http_method(p, "PUT"); - probe_http_method(p, "DELETE"); - probe_http_method(p, "TRACE"); - probe_http_method(p, "CONNECT"); + PROBE_HTTP_METHOD("OPTIONS"); + PROBE_HTTP_METHOD("GET"); + PROBE_HTTP_METHOD("HEAD"); + PROBE_HTTP_METHOD("POST"); + PROBE_HTTP_METHOD("PUT"); + PROBE_HTTP_METHOD("DELETE"); + PROBE_HTTP_METHOD("TRACE"); + PROBE_HTTP_METHOD("CONNECT"); + +#undef PROBE_HTTP_METHOD return 0; } static int is_tls_protocol(const char *p, int len, struct proto *proto) { + if (len < 3) + return 0; + /* TLS packet starts with a record "Hello" (0x16), followed by version * (0x03 0x00-0x03) (RFC6101 A.1) * This means we reject SSLv2 and lower, which is actually a good thing (RFC6176) @@ -202,16 +219,13 @@ static int is_tls_protocol(const char *p, int len, struct proto *proto) static int regex_probe(const char *p, int len, struct proto *proto) { - regex_t** probe_list = (regex_t**)(proto->data); - int i=0; + regex_t **probe = proto->data; + regmatch_t pos = { 0, len }; - while (probe_list[i]) { - if (!regexec(probe_list[i], p, 0, NULL, 0)) { - return 1; - } - i++; - } - return 0; + for (; *probe && regexec(*probe, p, 0, &pos, REG_STARTEND); probe++) + /* try them all */; + + return (probe != NULL); } /* From c5cd91d92c26184055312cd6b77c95f69e759c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:32 +0100 Subject: [PATCH 02/12] Let defer_write accumulate data --- common.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/common.c b/common.c index 2f008d5..9f7edaa 100644 --- a/common.c +++ b/common.c @@ -161,12 +161,20 @@ int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name) /* Store some data to write to the queue later */ int defer_write(struct queue *q, void* data, int data_size) { + char *p; if (verbose) fprintf(stderr, "**** writing defered on fd %d\n", q->fd); - q->defered_data = malloc(data_size); - q->begin_defered_data = q->defered_data; - q->defered_data_size = data_size; - memcpy(q->defered_data, data, data_size); + + p = realloc(q->defered_data, q->defered_data_size + data_size); + if (!p) { + perror("realloc"); + exit(1); + } + + q->defered_data = q->begin_defered_data = p; + p += q->defered_data_size; + q->defered_data_size += data_size; + memcpy(p, data, data_size); return 0; } From c84a6af847c9c1357dc3a3566b3198e1e3f23aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:33 +0100 Subject: [PATCH 03/12] Introduce the probe return codes. --- probe.c | 26 +++++++++++++++----------- probe.h | 6 ++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/probe.c b/probe.c index d2b3477..39b5f8c 100644 --- a/probe.c +++ b/probe.c @@ -125,10 +125,10 @@ void hexdump(const char *mem, unsigned int len) /* Is the buffer the beginning of an SSH connection? */ static int is_ssh_protocol(const char *p, int len, struct proto *proto) { - if (len >= 4 && !strncmp(p, "SSH-", 4)) { - return 1; - } - return 0; + if (len < 4) + return PROBE_NEXT; + + return !strncmp(p, "SSH-", 4); } /* Is the buffer the beginning of an OpenVPN connection? @@ -146,7 +146,7 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) int packet_len; if (len < 2) - return 0; + return PROBE_NEXT; packet_len = ntohs(*(uint16_t*)p); return packet_len == len - 2; @@ -158,7 +158,7 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) static int is_tinc_protocol( const char *p, int len, struct proto *proto) { if (len < 2) - return 0; + return PROBE_NEXT; return !strncmp(p, "0 ", 2); } @@ -169,13 +169,16 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto) * */ static int is_xmpp_protocol( const char *p, int len, struct proto *proto) { + if (len < 6) + return PROBE_NEXT; + return memmem(p, len, "jabber", 6) ? 1 : 0; } static int probe_http_method(const char *p, int len, const char *opt) { if (len < strlen(opt)) - return 0; + return PROBE_NEXT; return !strncmp(p, opt, len); } @@ -183,11 +186,12 @@ static int probe_http_method(const char *p, int len, const char *opt) /* Is the buffer the beginning of an HTTP connection? */ static int is_http_protocol(const char *p, int len, struct proto *proto) { + int res; /* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */ if (memmem(p, len, "HTTP", 4)) - return 1; + return PROBE_MATCH; -#define PROBE_HTTP_METHOD(opt) if (probe_http_method(p, len, opt)) return 1 +#define PROBE_HTTP_METHOD(opt) if ((res = probe_http_method(p, len, opt)) != PROBE_NEXT) return res /* Otherwise it could be HTTP/1.0 without version: check if it's got an * HTTP method (RFC2616 5.1.1) */ @@ -202,13 +206,13 @@ static int is_http_protocol(const char *p, int len, struct proto *proto) #undef PROBE_HTTP_METHOD - return 0; + return PROBE_NEXT; } static int is_tls_protocol(const char *p, int len, struct proto *proto) { if (len < 3) - return 0; + return PROBE_NEXT; /* TLS packet starts with a record "Hello" (0x16), followed by version * (0x03 0x00-0x03) (RFC6101 A.1) diff --git a/probe.h b/probe.h index 9cb3cb0..55ba322 100644 --- a/probe.h +++ b/probe.h @@ -5,6 +5,12 @@ #include "common.h" +typedef enum { + PROBE_NEXT, + PROBE_MATCH, + PROBE_AGAIN, +} probe_result; + struct proto; typedef int T_PROBE(const char*, int, struct proto*); From dbafd6510db30d8f5e43098132dc923497e92cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:34 +0100 Subject: [PATCH 04/12] Allow probes to say they cannot decide yet --- common.c | 2 ++ common.h | 2 +- probe.c | 18 +++++++++++------- probe.h | 2 +- sslh-fork.c | 36 ++++++++++++++++++++---------------- sslh-select.c | 20 +++++++++++--------- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/common.c b/common.c index 9f7edaa..e507d1b 100644 --- a/common.c +++ b/common.c @@ -8,6 +8,7 @@ #include #include "common.h" +#include "probe.h" /* Added to make the code compilable under CYGWIN * */ @@ -215,6 +216,7 @@ void init_cnx(struct connection *cnx) memset(cnx, 0, sizeof(*cnx)); cnx->q[0].fd = -1; cnx->q[1].fd = -1; + cnx->proto = get_first_protocol(); } void dump_connection(struct connection *cnx) diff --git a/common.h b/common.h index 7d49b0f..c77e51a 100644 --- a/common.h +++ b/common.h @@ -69,6 +69,7 @@ struct queue { struct connection { enum connection_state state; time_t probe_timeout; + struct proto *proto; /* q[0]: queue for external connection (client); * q[1]: queue for internal connection (httpd or sshd); @@ -87,7 +88,6 @@ int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name); int fd2fd(struct queue *target, struct queue *from); char* sprintaddr(char* buf, size_t size, struct addrinfo *a); void resolve_name(struct addrinfo **out, char* fullname); -struct proto* probe_client_protocol(struct connection *cnx); void log_connection(struct connection *cnx); int check_access_rights(int in_socket, const char* service); void setup_signals(void); diff --git a/probe.c b/probe.c index 39b5f8c..9790c38 100644 --- a/probe.c +++ b/probe.c @@ -238,7 +238,7 @@ static int regex_probe(const char *p, int len, struct proto *proto) * write buffer of the connection and returns a pointer to the protocol * structure */ -struct proto* probe_client_protocol(struct connection *cnx) +int probe_client_protocol(struct connection *cnx) { char buffer[BUFSIZ]; struct proto *p; @@ -251,16 +251,19 @@ struct proto* probe_client_protocol(struct connection *cnx) * function does not have to deal with a specific failure condition (the * connection will just fail later normally). */ if (n > 0) { + int res = PROBE_NEXT; + defer_write(&cnx->q[1], buffer, n); - for (p = protocols; p; p = p->next) { + for (p = cnx->proto; p && res == PROBE_NEXT; p = p->next) { if (! p->probe) continue; if (verbose) fprintf(stderr, "probing for %s\n", p->description); - if (p->probe(buffer, n, p)) { - if (verbose) fprintf(stderr, "probe %s successful\n", p->description); - return p; - } + + cnx->proto = p; + res = p->probe(cnx->q[1].defered_data, cnx->q[1].defered_data_size, p); } + if (res != PROBE_NEXT) + return res; } if (verbose) @@ -270,7 +273,8 @@ struct proto* probe_client_protocol(struct connection *cnx) /* If none worked, return the first one affected (that's completely * arbitrary) */ - return protocols; + cnx->proto = protocols; + return PROBE_MATCH; } /* Returns the structure for specified protocol or NULL if not found */ diff --git a/probe.h b/probe.h index 55ba322..edc3aac 100644 --- a/probe.h +++ b/probe.h @@ -49,7 +49,7 @@ void set_protocol_list(struct proto*); * write buffer of the connection and returns a pointer to the protocol * structure */ -struct proto* probe_client_protocol(struct connection *cnx); +int probe_client_protocol(struct connection *cnx); /* set the protocol to connect to in case of timeout */ void set_ontimeout(const char* name); diff --git a/sslh-fork.c b/sslh-fork.c index c3afc84..05c0c42 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -70,39 +70,43 @@ void start_shoveler(int in_socket) fd_set fds; struct timeval tv; struct addrinfo *saddr; - int res; + int res = PROBE_AGAIN; int out_socket; struct connection cnx; - struct proto *prot; init_cnx(&cnx); + cnx.q[0].fd = in_socket; FD_ZERO(&fds); FD_SET(in_socket, &fds); memset(&tv, 0, sizeof(tv)); tv.tv_sec = probing_timeout; - res = select(in_socket + 1, &fds, NULL, NULL, &tv); - if (res == -1) - perror("select"); - cnx.q[0].fd = in_socket; + while (res == PROBE_AGAIN) { + /* POSIX does not guarantee that tv will be updated, but the client can + * only postpone the inevitable for so long */ + res = select(in_socket + 1, &fds, NULL, NULL, &tv); + if (res == -1) + perror("select"); - if (FD_ISSET(in_socket, &fds)) { - /* Received data: figure out what protocol it is */ - prot = probe_client_protocol(&cnx); - } else { - /* Timed out: it's necessarily SSH */ - prot = timeout_protocol(); + if (FD_ISSET(in_socket, &fds)) { + /* Received data: figure out what protocol it is */ + res = probe_client_protocol(&cnx); + } else { + /* Timed out: it's necessarily SSH */ + cnx.proto = timeout_protocol(); + break; + } } - saddr = prot->saddr; - if (prot->service && - check_access_rights(in_socket, prot->service)) { + saddr = cnx.proto->saddr; + if (cnx.proto->service && + check_access_rights(in_socket, cnx.proto->service)) { exit(0); } /* Connect the target socket */ - out_socket = connect_addr(saddr, in_socket, prot->description); + out_socket = connect_addr(saddr, in_socket, cnx.proto->description); CHECK_RES_DIE(out_socket, "connect"); cnx.q[1].fd = out_socket; diff --git a/sslh-select.c b/sslh-select.c index 14b3716..02f7cad 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -210,7 +210,6 @@ void main_loop(int listen_sockets[], int num_addr_listen) struct timeval tv; int max_fd, in_socket, i, j, res; struct connection *cnx; - struct proto *prot; int num_cnx; /* Number of connections in *cnx */ int num_probing = 0; /* Number of connections currently probing * We use this to know if we need to time out of @@ -303,26 +302,29 @@ void main_loop(int listen_sockets[], int num_addr_listen) dump_connection(&cnx[i]); exit(1); } - num_probing--; - cnx[i].state = ST_SHOVELING; /* If timed out it's SSH, otherwise the client sent * data so probe the protocol */ if ((cnx[i].probe_timeout < time(NULL))) { - prot = timeout_protocol(); + cnx[i].proto = timeout_protocol(); } else { - prot = probe_client_protocol(&cnx[i]); + res = probe_client_protocol(&cnx[i]); + if (res == PROBE_AGAIN) + continue; } + num_probing--; + cnx[i].state = ST_SHOVELING; + /* libwrap check if required for this protocol */ - if (prot->service && - check_access_rights(in_socket, prot->service)) { + if (cnx[i].proto->service && + check_access_rights(in_socket, cnx[i].proto->service)) { tidy_connection(&cnx[i], &fds_r, &fds_w); res = -1; } else { res = connect_queue(&cnx[i], - prot->saddr, - prot->description, + cnx[i].proto->saddr, + cnx[i].proto->description, &fds_r, &fds_w); } From bcad6fbadeb84118c8975492876941bfd7d54202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:35 +0100 Subject: [PATCH 05/12] Enable the PROBE_AGAIN return code --- probe.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/probe.c b/probe.c index 9790c38..55d26ee 100644 --- a/probe.c +++ b/probe.c @@ -126,7 +126,7 @@ void hexdump(const char *mem, unsigned int len) static int is_ssh_protocol(const char *p, int len, struct proto *proto) { if (len < 4) - return PROBE_NEXT; + return PROBE_AGAIN; return !strncmp(p, "SSH-", 4); } @@ -146,7 +146,7 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) int packet_len; if (len < 2) - return PROBE_NEXT; + return PROBE_AGAIN; packet_len = ntohs(*(uint16_t*)p); return packet_len == len - 2; @@ -158,7 +158,7 @@ static int is_openvpn_protocol (const char*p,int len, struct proto *proto) static int is_tinc_protocol( const char *p, int len, struct proto *proto) { if (len < 2) - return PROBE_NEXT; + return PROBE_AGAIN; return !strncmp(p, "0 ", 2); } @@ -170,7 +170,7 @@ static int is_tinc_protocol( const char *p, int len, struct proto *proto) static int is_xmpp_protocol( const char *p, int len, struct proto *proto) { if (len < 6) - return PROBE_NEXT; + return PROBE_AGAIN; return memmem(p, len, "jabber", 6) ? 1 : 0; } @@ -178,7 +178,7 @@ static int is_xmpp_protocol( const char *p, int len, struct proto *proto) static int probe_http_method(const char *p, int len, const char *opt) { if (len < strlen(opt)) - return PROBE_NEXT; + return PROBE_AGAIN; return !strncmp(p, opt, len); } @@ -212,7 +212,7 @@ static int is_http_protocol(const char *p, int len, struct proto *proto) static int is_tls_protocol(const char *p, int len, struct proto *proto) { if (len < 3) - return PROBE_NEXT; + return PROBE_AGAIN; /* TLS packet starts with a record "Hello" (0x16), followed by version * (0x03 0x00-0x03) (RFC6101 A.1) From d7bbec0dc7b62a39bdd606f9f67e3181ffdd609f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:36 +0100 Subject: [PATCH 06/12] Simplify function signatures --- common.c | 12 ++++++------ common.h | 2 +- sslh-fork.c | 4 +--- sslh-select.c | 11 +++-------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/common.c b/common.c index e507d1b..7e9cf67 100644 --- a/common.c +++ b/common.c @@ -126,22 +126,22 @@ int bind_peer(int fd, int fd_from) /* Connect to first address that works and returns a file descriptor, or -1 if * none work. * If transparent proxying is on, use fd_from peer address on external address - * of new file descriptor. - * cnx_name points to the name of the service (for logging) */ -int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name) + * of new file descriptor. */ +int connect_addr(struct connection *cnx, int fd_from) { struct addrinfo *a; char buf[NI_MAXHOST]; int fd, res; - for (a = addr; a; a = a->ai_next) { + for (a = cnx->proto->saddr; a; a = a->ai_next) { if (verbose) fprintf(stderr, "connecting to %s family %d len %d\n", sprintaddr(buf, sizeof(buf), a), a->ai_addr->sa_family, a->ai_addrlen); fd = socket(a->ai_family, SOCK_STREAM, 0); if (fd == -1) { - log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx_name, strerror(errno)); + log_message(LOG_ERR, "forward to %s failed:socket: %s\n", + cnx->proto->description, strerror(errno)); } else { if (transparent) { res = bind_peer(fd, fd_from); @@ -150,7 +150,7 @@ int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name) res = connect(fd, a->ai_addr, a->ai_addrlen); if (res == -1) { log_message(LOG_ERR, "forward to %s failed:connect: %s\n", - cnx_name, strerror(errno)); + cnx->proto->description, strerror(errno)); } else { return fd; } diff --git a/common.h b/common.h index c77e51a..1b8e8a6 100644 --- a/common.h +++ b/common.h @@ -84,7 +84,7 @@ struct connection { /* common.c */ void init_cnx(struct connection *cnx); -int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name); +int connect_addr(struct connection *cnx, int fd_from); int fd2fd(struct queue *target, struct queue *from); char* sprintaddr(char* buf, size_t size, struct addrinfo *a); void resolve_name(struct addrinfo **out, char* fullname); diff --git a/sslh-fork.c b/sslh-fork.c index 05c0c42..02785b5 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -69,7 +69,6 @@ void start_shoveler(int in_socket) { fd_set fds; struct timeval tv; - struct addrinfo *saddr; int res = PROBE_AGAIN; int out_socket; struct connection cnx; @@ -99,14 +98,13 @@ void start_shoveler(int in_socket) } } - saddr = cnx.proto->saddr; if (cnx.proto->service && check_access_rights(in_socket, cnx.proto->service)) { exit(0); } /* Connect the target socket */ - out_socket = connect_addr(saddr, in_socket, cnx.proto->description); + out_socket = connect_addr(&cnx, in_socket); CHECK_RES_DIE(out_socket, "connect"); cnx.q[1].fd = out_socket; diff --git a/sslh-select.c b/sslh-select.c index 02f7cad..5b4b2f0 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -129,13 +129,11 @@ int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_ /* Connect queue 1 of connection to SSL; returns new file descriptor */ -int connect_queue(struct connection *cnx, struct addrinfo *addr, - const char* cnx_name, - fd_set *fds_r, fd_set *fds_w) +int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w) { struct queue *q = &cnx->q[1]; - q->fd = connect_addr(addr, cnx->q[0].fd, cnx_name); + q->fd = connect_addr(cnx, cnx->q[0].fd); if ((q->fd != -1) && fd_is_in_range(q->fd)) { log_connection(cnx); set_nonblock(q->fd); @@ -322,10 +320,7 @@ void main_loop(int listen_sockets[], int num_addr_listen) tidy_connection(&cnx[i], &fds_r, &fds_w); res = -1; } else { - res = connect_queue(&cnx[i], - cnx[i].proto->saddr, - cnx[i].proto->description, - &fds_r, &fds_w); + res = connect_queue(&cnx[i], &fds_r, &fds_w); } if (res >= max_fd) From e4fb8b8496534c90c7523f8aba7fc6b3591898e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:38 +0100 Subject: [PATCH 07/12] defered -> deferred --- ChangeLog | 2 +- common.c | 34 +++++++++++++++++----------------- common.h | 10 +++++----- probe.c | 4 ++-- probe.h | 2 +- sslh-fork.c | 2 +- sslh-select.c | 18 +++++++++--------- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9bc9536..5d4dbcb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,7 +14,7 @@ v1.15: 27JUL2013 would happen. Fixed bug in sslh-select: if socket dropped while - defered_data was present, sslh-select would crash. + deferred_data was present, sslh-select would crash. Increased FD_SETSIZE for Cygwin, as the default 64 is too low for even moderate load. diff --git a/common.c b/common.c index 7e9cf67..8e4d0be 100644 --- a/common.c +++ b/common.c @@ -164,17 +164,17 @@ int defer_write(struct queue *q, void* data, int data_size) { char *p; if (verbose) - fprintf(stderr, "**** writing defered on fd %d\n", q->fd); + fprintf(stderr, "**** writing deferred on fd %d\n", q->fd); - p = realloc(q->defered_data, q->defered_data_size + data_size); + p = realloc(q->deferred_data, q->deferred_data_size + data_size); if (!p) { perror("realloc"); exit(1); } - q->defered_data = q->begin_defered_data = p; - p += q->defered_data_size; - q->defered_data_size += data_size; + q->deferred_data = q->begin_deferred_data = p; + p += q->deferred_data_size; + q->deferred_data_size += data_size; memcpy(p, data, data_size); return 0; @@ -184,27 +184,27 @@ int defer_write(struct queue *q, void* data, int data_size) * Upon success, the number of bytes written is returned. * Upon failure, -1 returned (e.g. connexion closed) * */ -int flush_defered(struct queue *q) +int flush_deferred(struct queue *q) { int n; if (verbose) - fprintf(stderr, "flushing defered data to fd %d\n", q->fd); + fprintf(stderr, "flushing deferred data to fd %d\n", q->fd); - n = write(q->fd, q->defered_data, q->defered_data_size); + n = write(q->fd, q->deferred_data, q->deferred_data_size); if (n == -1) return n; - if (n == q->defered_data_size) { + if (n == q->deferred_data_size) { /* All has been written -- release the memory */ - free(q->begin_defered_data); - q->begin_defered_data = NULL; - q->defered_data = NULL; - q->defered_data_size = 0; + free(q->begin_deferred_data); + q->begin_deferred_data = NULL; + q->deferred_data = NULL; + q->deferred_data_size = 0; } else { /* There is data left */ - q->defered_data += n; - q->defered_data_size -= n; + q->deferred_data += n; + q->deferred_data_size -= n; } return n; @@ -222,8 +222,8 @@ void init_cnx(struct connection *cnx) void dump_connection(struct connection *cnx) { printf("state: %d\n", cnx->state); - printf("fd %d, %d defered\n", cnx->q[0].fd, cnx->q[0].defered_data_size); - printf("fd %d, %d defered\n", cnx->q[1].fd, cnx->q[1].defered_data_size); + printf("fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size); + printf("fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size); } diff --git a/common.h b/common.h index 1b8e8a6..a88440a 100644 --- a/common.h +++ b/common.h @@ -58,12 +58,12 @@ enum connection_state { #define PROT_SHIFT 1000 /* protocol options will be 1000, 1001, etc */ /* A 'queue' is composed of a file descriptor (which can be read from or - * written to), and a queue for defered write data */ + * written to), and a queue for deferred write data */ struct queue { int fd; - void *begin_defered_data; - void *defered_data; - int defered_data_size; + void *begin_deferred_data; + void *deferred_data; + int deferred_data_size; }; struct connection { @@ -101,7 +101,7 @@ int resolve_split_name(struct addrinfo **out, const char* hostname, const char* int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list); int defer_write(struct queue *q, void* data, int data_size); -int flush_defered(struct queue *q); +int flush_deferred(struct queue *q); extern int probing_timeout, verbose, inetd, foreground, background, transparent, numeric; diff --git a/probe.c b/probe.c index 55d26ee..69f51a2 100644 --- a/probe.c +++ b/probe.c @@ -234,7 +234,7 @@ static int regex_probe(const char *p, int len, struct proto *proto) /* * Read the beginning of data coming from the client connection and check if - * it's a known protocol. Then leave the data on the defered + * it's a known protocol. Then leave the data on the deferred * write buffer of the connection and returns a pointer to the protocol * structure */ @@ -260,7 +260,7 @@ int probe_client_protocol(struct connection *cnx) if (verbose) fprintf(stderr, "probing for %s\n", p->description); cnx->proto = p; - res = p->probe(cnx->q[1].defered_data, cnx->q[1].defered_data_size, p); + res = p->probe(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size, p); } if (res != PROBE_NEXT) return res; diff --git a/probe.h b/probe.h index edc3aac..eaf3fec 100644 --- a/probe.h +++ b/probe.h @@ -45,7 +45,7 @@ void set_protocol_list(struct proto*); /* probe_client_protocol * * Read the beginning of data coming from the client connection and check if - * it's a known protocol. Then leave the data on the defered + * it's a known protocol. Then leave the data on the deferred * write buffer of the connection and returns a pointer to the protocol * structure */ diff --git a/sslh-fork.c b/sslh-fork.c index 02785b5..f42fec6 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -111,7 +111,7 @@ void start_shoveler(int in_socket) log_connection(&cnx); - flush_defered(&cnx.q[1]); + flush_deferred(&cnx.q[1]); shovel(&cnx); diff --git a/sslh-select.c b/sslh-select.c index 5b4b2f0..6c696eb 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -64,8 +64,8 @@ int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2) close(cnx->q[i].fd); FD_CLR(cnx->q[i].fd, fds); FD_CLR(cnx->q[i].fd, fds2); - if (cnx->q[i].defered_data) - free(cnx->q[i].defered_data); + if (cnx->q[i].deferred_data) + free(cnx->q[i].deferred_data); } } init_cnx(cnx); @@ -137,8 +137,8 @@ int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w) if ((q->fd != -1) && fd_is_in_range(q->fd)) { log_connection(cnx); set_nonblock(q->fd); - flush_defered(q); - if (q->defered_data) { + flush_deferred(q); + if (q->deferred_data) { FD_SET(q->fd, fds_w); } else { FD_SET(q->fd, fds_r); @@ -192,9 +192,9 @@ int is_fd_active(int fd, fd_set* set) * - When a file descriptor goes off, process it: read from it, write the data * to its corresponding pair. * - When a file descriptor blocks when writing, remove the read fd from fds_r, - * move the data to a defered buffer, and add the write fd to fds_w. Defered + * move the data to a deferred buffer, and add the write fd to fds_w. Defered * buffer is allocated dynamically. - * - When we can write to a file descriptor that has defered data, we try to + * - When we can write to a file descriptor that has deferred data, we try to * write as much as we can. Once all data is written, remove the fd from fds_w * and add its corresponding pair to fds_r, free the buffer. * @@ -265,16 +265,16 @@ void main_loop(int listen_sockets[], int num_addr_listen) if (cnx[i].q[0].fd != -1) { for (j = 0; j < 2; j++) { if (is_fd_active(cnx[i].q[j].fd, &writefds)) { - res = flush_defered(&cnx[i].q[j]); + res = flush_deferred(&cnx[i].q[j]); if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) { if (cnx[i].state == ST_PROBING) num_probing--; tidy_connection(&cnx[i], &fds_r, &fds_w); if (verbose) fprintf(stderr, "closed slot %d\n", i); } else { - /* If no defered data is left, stop monitoring the fd + /* If no deferred data is left, stop monitoring the fd * for write, and restart monitoring the other one for reads*/ - if (!cnx[i].q[j].defered_data_size) { + if (!cnx[i].q[j].deferred_data_size) { FD_CLR(cnx[i].q[j].fd, &fds_w); FD_SET(cnx[i].q[1-j].fd, &fds_r); } From 66c7d674a0a3d92cfcbb2d6e02831d5faf4e29d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:39 +0100 Subject: [PATCH 08/12] is a bashism --- genver.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genver.sh b/genver.sh index ee1efe6..5f1d4dc 100755 --- a/genver.sh +++ b/genver.sh @@ -1,6 +1,6 @@ #! /bin/bash -if [ ${#} -eq 1 ] && [ "x$1" == "x-r" ]; then +if [ ${#} -eq 1 ] && [ "x$1" = "x-r" ]; then # release text only QUIET=1 else From d14dcdee5c45f5cb2511d62c674050d69b31faa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 23 Sep 2013 23:30:40 +0100 Subject: [PATCH 09/12] Fix build issues when version.h doesn't exist yet --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6525717..e06db53 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ distclean: clean rm -f tags cscope.* clean: - rm -f sslh-fork sslh-select echosrv $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info + rm -f sslh-fork sslh-select echosrv version.h $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info tags: ctags --globals -T *.[ch] From 025545aee3dce3517e83e2c74684e3fe0d2408c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Kuzn=EDk?= Date: Sat, 28 Sep 2013 20:49:46 +0200 Subject: [PATCH 10/12] Fix typos and type warnings --- common.c | 8 ++++---- sslh-main.c | 2 +- sslh-select.c | 2 +- t | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common.c b/common.c index 8e4d0be..ab67cd7 100644 --- a/common.c +++ b/common.c @@ -230,7 +230,7 @@ void dump_connection(struct connection *cnx) /* * moves data from one fd to other * - * retuns number of bytes copied if success + * returns number of bytes copied if success * returns 0 (FD_CNXCLOSED) if incoming socket closed * returns FD_NODATA if no data was available * returns FD_STALLED if data was read, could not be written, and has been @@ -265,7 +265,7 @@ int fd2fd(struct queue *target_q, struct queue *from_q) size_w = write(target, buffer, size_r); /* process -1 when we know how to deal with it */ - if ((size_w == -1)) { + if (size_w == -1) { switch (errno) { case EAGAIN: /* write blocked: Defer data */ @@ -479,7 +479,7 @@ void setup_signals(void) res = sigaction(SIGCHLD, &action, NULL); CHECK_RES_DIE(res, "sigaction"); - /* Set SIGTERM to exit. For some reason if it's not set explicitely, + /* Set SIGTERM to exit. For some reason if it's not set explicitly, * coverage information is lost when killing the process */ memset(&action, 0, sizeof(action)); action.sa_handler = exit; @@ -509,7 +509,7 @@ void setup_syslog(const char* bin_name) { log_message(LOG_INFO, "%s %s started\n", server_type, VERSION); } -/* We don't want to run as root -- drop priviledges if required */ +/* We don't want to run as root -- drop privileges if required */ void drop_privileges(const char* user_name) { int res; diff --git a/sslh-main.c b/sslh-main.c index a522b1a..d483f00 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -275,7 +275,7 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto * config_lookup_bool(&config, "numeric", &numeric); config_lookup_bool(&config, "transparent", &transparent); - if (config_lookup_int(&config, "timeout", &timeout) == CONFIG_TRUE) { + if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) { probing_timeout = timeout; } diff --git a/sslh-select.c b/sslh-select.c index 6c696eb..db05645 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -188,7 +188,7 @@ int is_fd_active(int fd, fd_set* set) } /* Main loop: the idea is as follow: - * - fds_r and fds_w contain the file descritors to monitor in read and write + * - fds_r and fds_w contain the file descriptors to monitor in read and write * - When a file descriptor goes off, process it: read from it, write the data * to its corresponding pair. * - When a file descriptor blocks when writing, remove the read fd from fds_r, diff --git a/t b/t index ed2e26d..7bef47d 100755 --- a/t +++ b/t @@ -58,7 +58,7 @@ for my $binary (@binaries) { my $cmd = "./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile"; warn "$cmd\n"; #exec $cmd; - exec "valgrind --leak-check=full ./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile"; + exec "valgrind --leak-check=full ./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile"; exit 0; } warn "spawned $sslh_pid\n"; From 96f5d6387e813157cded854e0662357b54d30652 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sat, 28 Sep 2013 21:33:25 +0200 Subject: [PATCH 11/12] new test for PROBE_AGAIN; changed deferred_data to begin_deferred_data where appropriate --- common.c | 2 +- probe.c | 8 ++++---- probe.h | 6 +++--- t | 19 ++++++++++++++++++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/common.c b/common.c index ab67cd7..2214d7e 100644 --- a/common.c +++ b/common.c @@ -166,7 +166,7 @@ int defer_write(struct queue *q, void* data, int data_size) if (verbose) fprintf(stderr, "**** writing deferred on fd %d\n", q->fd); - p = realloc(q->deferred_data, q->deferred_data_size + data_size); + p = realloc(q->begin_deferred_data, q->deferred_data_size + data_size); if (!p) { perror("realloc"); exit(1); diff --git a/probe.c b/probe.c index 69f51a2..79659e9 100644 --- a/probe.c +++ b/probe.c @@ -234,9 +234,9 @@ static int regex_probe(const char *p, int len, struct proto *proto) /* * Read the beginning of data coming from the client connection and check if - * it's a known protocol. Then leave the data on the deferred - * write buffer of the connection and returns a pointer to the protocol - * structure + * it's a known protocol. + * Return PROBE_AGAIN if not enough data, or PROBE_MATCH if it succeeded in + * which case cnx->proto is set to the appropriate protocol. */ int probe_client_protocol(struct connection *cnx) { @@ -260,7 +260,7 @@ int probe_client_protocol(struct connection *cnx) if (verbose) fprintf(stderr, "probing for %s\n", p->description); cnx->proto = p; - res = p->probe(cnx->q[1].deferred_data, cnx->q[1].deferred_data_size, p); + res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p); } if (res != PROBE_NEXT) return res; diff --git a/probe.h b/probe.h index eaf3fec..d79b795 100644 --- a/probe.h +++ b/probe.h @@ -6,9 +6,9 @@ #include "common.h" typedef enum { - PROBE_NEXT, - PROBE_MATCH, - PROBE_AGAIN, + PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */ + PROBE_MATCH, /* Enough data, probe successful -- it's the current protocol */ + PROBE_AGAIN, /* Not enough data for this probe, try again with more data */ } probe_result; struct proto; diff --git a/t b/t index 7bef47d..1aecbbf 100755 --- a/t +++ b/t @@ -18,6 +18,7 @@ my $pidfile = "/tmp/sslh_test.pid"; my $SSL_CNX = 1; my $SSH_SHY_CNX = 1; my $SSH_BOLD_CNX = 1; +my $SSH_PROBE_AGAIN = 1; my $SSL_MIX_SSH = 1; my $SSH_MIX_SSL = 1; my $BIG_MSG = 0; # This test is unreliable @@ -62,7 +63,7 @@ for my $binary (@binaries) { exit 0; } warn "spawned $sslh_pid\n"; - sleep 1; # valgrind can be heavy -- wait 5 seconds + sleep 5; # valgrind can be heavy -- wait 5 seconds my $test_data = "hello world\n"; @@ -108,6 +109,22 @@ for my $binary (@binaries) { } } +# Test: PROBE_AGAIN, incomplete first frame + if ($SSH_PROBE_AGAIN) { + print "***Test: incomplete SSH first frame\n"; + my $cnx_h = new IO::Socket::INET(PeerHost => "localhost:$sslh_port"); + warn "$!\n" unless $cnx_h; + if (defined $cnx_h) { + my $td = "SSH-2.0 testsuite\t$test_data"; + print $cnx_h substr $td, 0, 2; + sleep 1; + print $cnx_h substr $td, 2; + my $data = <$cnx_h>; + is($data, "ssh: $td", "Incomplete first SSH frame"); + } + } + + # Test: One SSL half-started then one SSH if ($SSL_MIX_SSH) { print "***Test: One SSL half-started then one SSH\n"; From f2ca4c13a65ea9252c814e4679f7b7448cb4d97d Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sat, 28 Sep 2013 21:38:33 +0200 Subject: [PATCH 12/12] ChangeLog entry for the branch --- ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index 5d4dbcb..3c1ff68 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,10 @@ vNEXT: + Probes made more resilient, to incoming data + containing NULLs. Also made them behave properly + when receiving too short packets to probe on the + first incoming packet. + (Ondrej Kuzník) + Fixed bugs related to getpeername that would cause sslh to quit erroneously (getpeername can return actual errors if connections are dropped before