diff --git a/probe.c b/probe.c index 835b040..2148b0a 100644 --- a/probe.c +++ b/probe.c @@ -45,16 +45,16 @@ static int is_true(const char *p, int len, struct proto* proto) { return 1; } /* Table of protocols that have a built-in probe */ static struct proto builtins[] = { - /* description service saddr log_level keepalive probe */ - { "ssh", "sshd", NULL, 1, 0, is_ssh_protocol}, - { "openvpn", NULL, NULL, 1, 0, is_openvpn_protocol }, - { "tinc", NULL, NULL, 1, 0, is_tinc_protocol }, - { "xmpp", NULL, NULL, 1, 0, is_xmpp_protocol }, - { "http", NULL, NULL, 1, 0, is_http_protocol }, - { "ssl", NULL, NULL, 1, 0, is_tls_protocol }, - { "tls", NULL, NULL, 1, 0, is_tls_protocol }, - { "adb", NULL, NULL, 1, 0, is_adb_protocol }, - { "anyprot", NULL, NULL, 1, 0, is_true } + /* description service saddr log_level keepalive fork probe */ + { "ssh", "sshd", NULL, 1, 0, 1, is_ssh_protocol}, + { "openvpn", NULL, NULL, 1, 0, 1, is_openvpn_protocol }, + { "tinc", NULL, NULL, 1, 0, 1, is_tinc_protocol }, + { "xmpp", NULL, NULL, 1, 0, 0, is_xmpp_protocol }, + { "http", NULL, NULL, 1, 0, 0, is_http_protocol }, + { "ssl", NULL, NULL, 1, 0, 0, is_tls_protocol }, + { "tls", NULL, NULL, 1, 0, 0, is_tls_protocol }, + { "adb", NULL, NULL, 1, 0, 0, is_adb_protocol }, + { "anyprot", NULL, NULL, 1, 0, 0, is_true } }; static struct proto *protocols; diff --git a/probe.h b/probe.h index 8c576a2..2476542 100644 --- a/probe.h +++ b/probe.h @@ -24,6 +24,7 @@ struct proto { * 1: Log incoming connection */ int keepalive; /* 0: No keepalive ; 1: Set Keepalive for this connection */ + int fork; /* 0: Connection can run within shared process ; 1: Separate process required for this connection */ /* function to probe that protocol; parameters are buffer and length * containing the data to probe, and a pointer to the protocol structure */ diff --git a/sslh-main.c b/sslh-main.c index 25e23cd..0920d2f 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -123,14 +123,15 @@ static void printsettings(void) for (p = get_first_protocol(); p; p = p->next) { fprintf(stderr, - "%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s]\n", + "%s addr: %s. libwrap service: %s log_level: %d family %d %d [%s] [%s]\n", p->description, sprintaddr(buf, sizeof(buf), p->saddr), p->service, p->log_level, p->saddr->ai_family, p->saddr->ai_addr->sa_family, - p->keepalive ? "keepalive" : ""); + p->keepalive ? "keepalive" : "", + p->fork ? "fork" : ""); } fprintf(stderr, "listening on:\n"); for (a = addr_listen; a; a = a->ai_next) { @@ -307,6 +308,7 @@ static int config_protocols(config_t *config, struct proto **prots) p->description = name; config_setting_lookup_string(prot, "service", &(p->service)); config_setting_lookup_bool(prot, "keepalive", &p->keepalive); + config_setting_lookup_bool(prot, "fork", &p->fork); if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) { p->log_level = 1; diff --git a/sslh-select.c b/sslh-select.c index f45f8d6..358c8bb 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -27,6 +27,8 @@ const char* server_type = "sslh-select"; +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + /* cnx_num_alloc is the number of connection to allocate at once (at start-up, * and then every time we get too many simultaneous connections: e.g. start * with 100 slots, then if we get more than 100 connections allocate another @@ -186,6 +188,93 @@ void shovel(struct connection *cnx, int active_fd, } } +/* shovels data from one fd to the other and vice-versa + returns after one socket closed + */ +void shovel_single(struct connection *cnx) +{ + fd_set fds_r, fds_w; + int res, i; + int max_fd = MAX(cnx->q[0].fd, cnx->q[1].fd) + 1; + + FD_ZERO(&fds_r); + FD_ZERO(&fds_w); + while (1) { + for (i = 0; i < 2; i++) { + if (cnx->q[i].deferred_data_size) { + FD_SET(cnx->q[i].fd, &fds_w); + FD_CLR(cnx->q[1-i].fd, &fds_r); + } else { + FD_CLR(cnx->q[i].fd, &fds_w); + FD_SET(cnx->q[1-i].fd, &fds_r); + } + } + + res = select( + max_fd, + &fds_r, + &fds_w, + NULL, + NULL + ); + CHECK_RES_DIE(res, "select"); + + for (i = 0; i < 2; i++) { + if (FD_ISSET(cnx->q[i].fd, &fds_w)) { + res = flush_deferred(&cnx->q[i]); + if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) { + if (verbose) + fprintf(stderr, "%s socket closed\n", i ? "server" : "client"); + return; + } + } + if (FD_ISSET(cnx->q[i].fd, &fds_r)) { + res = fd2fd(&cnx->q[1-i], &cnx->q[i]); + if (!res) { + if (verbose) + fprintf(stderr, "socket closed\n"); + return; + } + } + } + } +} + +/* Child process that makes internal connection and proxies + */ +void connect_proxy(struct connection *cnx) +{ + int in_socket; + int out_socket; + + /* Minimize the file descriptor value to help select() */ + in_socket = dup(cnx->q[0].fd); + if (in_socket == -1) { + in_socket = cnx->q[0].fd; + } else { + close(cnx->q[0].fd); + cnx->q[0].fd = in_socket; + } + + /* Connect the target socket */ + out_socket = connect_addr(cnx, in_socket); + CHECK_RES_DIE(out_socket, "connect"); + + cnx->q[1].fd = out_socket; + + log_connection(cnx); + + shovel_single(cnx); + + close(in_socket); + close(out_socket); + + if (verbose) + fprintf(stderr, "connection closed down\n"); + + exit(0); +} + /* returns true if specified fd is initialised and present in fd_set */ int is_fd_active(int fd, fd_set* set) { @@ -326,6 +415,23 @@ void main_loop(int listen_sockets[], int num_addr_listen) check_access_rights(in_socket, cnx[i].proto->service)) { tidy_connection(&cnx[i], &fds_r, &fds_w); res = -1; + } else if (cnx[i].proto->fork) { + if (!fork()) { + struct connection *pcnx_i = &cnx[i]; + struct connection cnx_i = *pcnx_i; + for (i = 0; i < num_addr_listen; i++) + close(listen_sockets[i]); + for (i = 0; i < num_cnx; i++) + if (&cnx[i] != pcnx_i) + for (j = 0; j < 2; j++) + if (cnx[i].q[j].fd != -1) + close(cnx[i].q[j].fd); + free(cnx); + connect_proxy(&cnx_i); + exit(0); + } + tidy_connection(&cnx[i], &fds_r, &fds_w); + res = -1; } else { res = connect_queue(&cnx[i], &fds_r, &fds_w); }