diff --git a/ChangeLog b/ChangeLog index 9bc9536..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 @@ -14,7 +20,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/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] diff --git a/common.c b/common.c index 2f008d5..2214d7e 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 * */ @@ -125,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); @@ -149,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; } @@ -161,12 +162,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); + fprintf(stderr, "**** writing deferred on fd %d\n", q->fd); + + p = realloc(q->begin_deferred_data, q->deferred_data_size + data_size); + if (!p) { + perror("realloc"); + exit(1); + } + + 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; } @@ -175,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; @@ -207,20 +216,21 @@ 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) { 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); } /* * 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 @@ -255,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 */ @@ -469,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; @@ -499,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/common.h b/common.h index 7d49b0f..a88440a 100644 --- a/common.h +++ b/common.h @@ -58,17 +58,18 @@ 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 { 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); @@ -83,11 +84,10 @@ 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); -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); @@ -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/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 diff --git a/probe.c b/probe.c index fa34f96..79659e9 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 (!strncmp(p, "SSH-", 4)) { - return 1; - } - return 0; + if (len < 4) + return PROBE_AGAIN; + + return !strncmp(p, "SSH-", 4); } /* Is the buffer the beginning of an OpenVPN connection? @@ -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 PROBE_AGAIN; + + 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 PROBE_AGAIN; + return !strncmp(p, "0 ", 2); } @@ -162,37 +169,51 @@ 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; + if (len < 6) + return PROBE_AGAIN; + + 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 PROBE_AGAIN; + + 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) { + int res; /* If it's got HTTP in the request (HTTP/1.1) then it's HTTP */ - if (strstr(p, "HTTP")) - return 1; + if (memmem(p, len, "HTTP", 4)) + return PROBE_MATCH; + +#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) */ - 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"); - return 0; +#undef PROBE_HTTP_METHOD + + return PROBE_NEXT; } static int is_tls_protocol(const char *p, int len, struct proto *proto) { + if (len < 3) + return PROBE_AGAIN; + /* 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,25 +223,22 @@ 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); } /* * 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 - * 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. */ -struct proto* probe_client_protocol(struct connection *cnx) +int probe_client_protocol(struct connection *cnx) { char buffer[BUFSIZ]; struct proto *p; @@ -233,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].begin_deferred_data, cnx->q[1].deferred_data_size, p); } + if (res != PROBE_NEXT) + return res; } if (verbose) @@ -252,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 9cb3cb0..d79b795 100644 --- a/probe.h +++ b/probe.h @@ -5,6 +5,12 @@ #include "common.h" +typedef enum { + 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; typedef int T_PROBE(const char*, int, struct proto*); @@ -39,11 +45,11 @@ 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 */ -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..f42fec6 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -69,47 +69,49 @@ 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)) { + 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(&cnx, in_socket); CHECK_RES_DIE(out_socket, "connect"); cnx.q[1].fd = out_socket; log_connection(&cnx); - flush_defered(&cnx.q[1]); + flush_deferred(&cnx.q[1]); shovel(&cnx); 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 14b3716..db05645 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); @@ -129,18 +129,16 @@ 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); - 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); @@ -190,13 +188,13 @@ 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, - * 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. * @@ -210,7 +208,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 @@ -268,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); } @@ -303,27 +300,27 @@ 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, - &fds_r, &fds_w); + res = connect_queue(&cnx[i], &fds_r, &fds_w); } if (res >= max_fd) diff --git a/t b/t index ed2e26d..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 @@ -58,11 +59,11 @@ 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"; - 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";