mirror of
https://github.com/moparisthebest/sslh
synced 2024-11-21 16:45:03 -05:00
Probes made resilient to packets that are too short, or
contain NULLs.
This commit is contained in:
commit
fb0760dd72
@ -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.
|
||||
|
2
Makefile
2
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]
|
||||
|
64
common.c
64
common.c
@ -8,6 +8,7 @@
|
||||
#include <stdarg.h>
|
||||
|
||||
#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;
|
||||
|
14
common.h
14
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;
|
||||
|
@ -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
|
||||
|
98
probe.c
98
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 */
|
||||
|
10
probe.h
10
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);
|
||||
|
38
sslh-fork.c
38
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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
21
t
21
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";
|
||||
|
Loading…
Reference in New Issue
Block a user