diff --git a/Makefile b/Makefile index 4788f32..84a3210 100644 --- a/Makefile +++ b/Makefile @@ -32,8 +32,8 @@ CC ?= gcc CFLAGS ?=-Wall -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN) LIBS=-lm -lpcre2-8 -OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o -FORK_OBJS=$(OBJS) sslh-fork.o +OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o +FORK_OBJS=$(OBJS) sslh-fork.o SELECT_OBJS=$(OBJS) processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o EV_OBJS=$(OBJS) processes.o udp-listener.o sslh-ev.o hash.o tcp-listener.o diff --git a/probe.c b/probe.c index ee9f423..84d14bd 100644 --- a/probe.c +++ b/probe.c @@ -340,12 +340,20 @@ static int regex_probe(const char *p, ssize_t len, struct sslhcfg_protocols_item } /* Run all the probes on a buffer + * buf, len: buffer to test on + * proto_in, proto_len: array of protocols to try + * proto_out: protocol that matched + * * Returns * PROBE_AGAIN if not enough data, and set *proto to NULL * PROBE_MATCH if protocol is identified, in which case *proto is set to * point to the appropriate protocol * */ -int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto) +int probe_buffer(char* buf, int len, + struct sslhcfg_protocols_item** proto_in, + int proto_len, + struct sslhcfg_protocols_item** proto_out + ) { struct sslhcfg_protocols_item* p; int i, res, again = 0; @@ -353,17 +361,17 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto) print_message(msg_packets, "hexdump of incoming packet:\n"); hexdump(msg_packets, buf, len); - *proto = NULL; - for (i = 0; i < cfg.protocols_len; i++) { + *proto_out = NULL; + for (i = 0; i < proto_len; i++) { char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"}; - p = &cfg.protocols[i]; + p = proto_in[i]; if (! p->probe) continue; print_message(msg_probe_info, "probing for %s\n", p->name); /* Don't probe last protocol if it is anyprot (and store last protocol) */ - if ((i == cfg.protocols_len - 1) && (!strcmp(p->name, "anyprot"))) + if ((i == proto_len - 1) && (!strcmp(p->name, "anyprot"))) break; if (p->minlength_is_present && (len < p->minlength )) { @@ -377,7 +385,7 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto) print_message(msg_probe_info, "probed for %s: %s\n", p->name, probe_str[res]); if (res == PROBE_MATCH) { - *proto = p; + *proto_out = p; return PROBE_MATCH; } if (res == PROBE_AGAIN) @@ -387,37 +395,7 @@ int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto) return PROBE_AGAIN; /* Everything failed: match the last one */ - *proto = &cfg.protocols[cfg.protocols_len-1]; - return PROBE_MATCH; -} - -/* - * Read the beginning of data coming from the client connection and check if - * 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) -{ - char buffer[BUFSIZ]; - ssize_t n; - - n = read(cnx->q[0].fd, buffer, sizeof(buffer)); - /* It's possible that read() returns an error, e.g. if the client - * disconnected between the previous call to select() and now. If that - * happens, we just connect to the default protocol so the caller of this - * function does not have to deal with a specific failure condition (the - * connection will just fail later normally). */ - - if (n > 0) { - defer_write(&cnx->q[1], buffer, n); - return probe_buffer(cnx->q[1].begin_deferred_data, - cnx->q[1].deferred_data_size, - &cnx->proto); - } - - /* read() returned an error, so just connect to the last protocol to die */ - cnx->proto = &cfg.protocols[cfg.protocols_len-1]; + *proto_out = proto_in[proto_len-1]; return PROBE_MATCH; } diff --git a/probe.h b/probe.h index 01248fc..d0b768b 100644 --- a/probe.h +++ b/probe.h @@ -48,8 +48,12 @@ void set_protocol_list(struct sslhcfg_protocols_item*); */ int probe_client_protocol(struct connection *cnx); -/* Probe, but on a buffer */ -int probe_buffer(char* buf, int len, struct sslhcfg_protocols_item** proto); +/* Probe on a buffer */ +int probe_buffer(char* buf, int len, + struct sslhcfg_protocols_item** proto_in, + int proto_len, + struct sslhcfg_protocols_item** proto_out + ); /* set the protocol to connect to in case of timeout */ void set_ontimeout(const char* name); diff --git a/sslh-ev.c b/sslh-ev.c index 83e6653..cd7dadd 100644 --- a/sslh-ev.c +++ b/sslh-ev.c @@ -143,6 +143,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) ev_info.collection = collection_init(0); ev_info.probing_list = gap_init(0); udp_init(&ev_info); + tcp_init(); watchers_init(&ev_info.watchers, listen_sockets, num_addr_listen); ev_set_userdata(EV_A_ &ev_info); diff --git a/sslh-fork.c b/sslh-fork.c index a08c2b6..9a2c407 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -23,7 +23,7 @@ #include "common.h" #include "probe.h" #include "sslh-conf.h" -#include "udp-listener.h" +#include "tcp-probe.h" #include "log.h" #ifdef LIBBSD @@ -207,6 +207,8 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0])); CHECK_ALLOC(listener_pid, "malloc"); + tcp_init(); + /* Start one process for each listening address */ for (i = 0; i < num_addr_listen; i++) { listener_pid[i] = fork(); diff --git a/sslh-select.c b/sslh-select.c index 0831084..da1544a 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -131,6 +131,7 @@ void main_loop(struct listen_endpoint listen_sockets[], int num_addr_listen) fd_info.num_probing = 0; fd_info.probing_list = gap_init(0); udp_init(&fd_info); + tcp_init(); watchers_init(&fd_info.watchers, listen_sockets, num_addr_listen); diff --git a/t_load b/t_load index 2fc6a5f..34019a3 100755 --- a/t_load +++ b/t_load @@ -65,21 +65,21 @@ my %connect_params = ( test_data => "foo bar", resp_len => 12, }, - # ssh => { - # sleep => 20, # So it times out 50% of connections - # test_data => "SSH-2.0 hello", - # resp_len => 18, # length "ssh: SSH-2.0 hello" => 18 - # }, - # tinc => { - # sleep => 0, - # test_data => "0 ", - # resp_len => 8, # length "tinc: 0 " => 10 - # }, - # openvpn => { - # sleep => 0, - # test_data => "\x00\x00", - # resp_len => 11, # length "openvpn: \x0\x0" => 11 - # }, + ssh => { + sleep => 20, # So it times out 50% of connections + test_data => "SSH-2.0 hello", + resp_len => 18, # length "ssh: SSH-2.0 hello" => 18 + }, + tinc => { + sleep => 0, + test_data => "0 ", + resp_len => 8, # length "tinc: 0 " => 10 + }, + openvpn => { + sleep => 0, + test_data => "\x00\x00", + resp_len => 11, # length "openvpn: \x0\x0" => 11 + }, ); sub connect_service { diff --git a/tcp-listener.h b/tcp-listener.h index b738d73..a4ab845 100644 --- a/tcp-listener.h +++ b/tcp-listener.h @@ -3,6 +3,7 @@ #include "processes.h" #include "collection.h" +#include "tcp-probe.h" void tcp_read_process(struct loop_info* fd_info, int fd); struct connection* accept_new_connection(int listen_socket, struct loop_info* fd_info); diff --git a/tcp-probe.c b/tcp-probe.c new file mode 100644 index 0000000..eebf09b --- /dev/null +++ b/tcp-probe.c @@ -0,0 +1,76 @@ +/* +# tcp-probe.c: TCP code that is common to the sslh-fork and sslh-[ev|select] +# +# Copyright (C) 2022 Yves Rutschle +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# The full text for the General Public License is here: +# http://www.gnu.org/licenses/gpl.html +*/ + + +#include "probe.h" + +static struct sslhcfg_protocols_item** tcp_protocols; +static int tcp_protocols_len = 0; + +/* + * Read the beginning of data coming from the client connection and check if + * 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) +{ + char buffer[BUFSIZ]; + ssize_t n; + + n = read(cnx->q[0].fd, buffer, sizeof(buffer)); + /* It's possible that read() returns an error, e.g. if the client + * disconnected between the previous call to select() and now. If that + * happens, we just connect to the default protocol so the caller of this + * function does not have to deal with a specific failure condition (the + * connection will just fail later normally). */ + + if (n > 0) { + defer_write(&cnx->q[1], buffer, n); + return probe_buffer(cnx->q[1].begin_deferred_data, + cnx->q[1].deferred_data_size, + tcp_protocols, tcp_protocols_len, + &cnx->proto + ); + } + + /* read() returned an error, so just connect to the last protocol to die */ + cnx->proto = &cfg.protocols[cfg.protocols_len-1]; + return PROBE_MATCH; +} + + +static void tcp_protocol_list_init(void) +{ + for (int i = 0; i < cfg.protocols_len; i++) { + struct sslhcfg_protocols_item* p = &cfg.protocols[i]; + if (!p->is_udp) { + tcp_protocols_len++; + tcp_protocols = realloc(tcp_protocols, tcp_protocols_len * sizeof(*tcp_protocols)); + tcp_protocols[tcp_protocols_len-1] = p; + } + } +} + +void tcp_init(void) +{ + tcp_protocol_list_init(); +} diff --git a/tcp-probe.h b/tcp-probe.h new file mode 100644 index 0000000..69d63c2 --- /dev/null +++ b/tcp-probe.h @@ -0,0 +1,6 @@ +#ifndef TCP_PROBE_H +#define TCP_PROBE_H + +void tcp_init(void); + +#endif diff --git a/test.cfg b/test.cfg index c8354c3..ecc4c74 100644 --- a/test.cfg +++ b/test.cfg @@ -35,6 +35,10 @@ listen: { host: "ip4-localhost"; is_udp: true; port: "8086"; } ); + +# Tester beware: when using fork, the forked process loses +# track of buffers of other, concurrent connections. Memory +# leak tools thus complain each time a forked process stops. protocols: ( diff --git a/udp-listener.c b/udp-listener.c index 35e617d..ab5d3f1 100644 --- a/udp-listener.c +++ b/udp-listener.c @@ -76,6 +76,21 @@ static int hash_make_key(hash_item new) return out; } +static struct sslhcfg_protocols_item** udp_protocols; +static int udp_protocols_len = 0; + +static void udp_protocol_list_init(void) +{ + for (int i = 0; i < cfg.protocols_len; i++) { + struct sslhcfg_protocols_item* p = &cfg.protocols[i]; + if (p->is_udp) { + udp_protocols_len++; + udp_protocols = realloc(udp_protocols, udp_protocols_len * sizeof(*udp_protocols)); + udp_protocols[udp_protocols_len-1] = p; + } + } +} + /* Init the UDP subsystem. * - Initialise the hash * - that's all, folks @@ -83,6 +98,8 @@ static int hash_make_key(hash_item new) void udp_init(struct loop_info* fd_info) { fd_info->hash_sources = hash_init(cfg.udp_max_connections, &hash_make_key, &cnx_cmp); + + udp_protocol_list_init(); } @@ -215,7 +232,7 @@ int udp_c2s_forward(int sockfd, struct loop_info* fd_info) len, target, sprintaddr(addr_str, sizeof(addr_str), &addrinfo)); if (target == -1) { - res = probe_buffer(data, len, &proto); + res = probe_buffer(data, len, udp_protocols, udp_protocols_len, &proto); /* First version: if we can't work out the protocol from the first * packet, drop it. Conceivably, we could store several packets to * run probes on packet sets */