Compare commits

...
45 Commits
Author SHA1 Message Date
Yves Rutschle 4ae2e62d25 v1.20 2018-11-20 22:58:41 +01:00
yrutschle 8ec9799ca0 fix ssl tests 2018-11-04 22:59:01 +01:00
yrutschle 80ad31aec0 refactoring: replace magic constants with symbols 2018-11-04 22:25:16 +01:00
yrutschle d6c714166a only try to parse TLS extensions if settings are actually set 2018-09-30 21:35:45 +02:00
yrutschle aa77922ffd turn ssl setting from command line to tls (ssl no longer exists) 2018-09-30 21:34:22 +02:00
yrutschle 2ee0088c5f turn ssl setting from configuration file to tls (ssl no longer exists) 2018-09-30 20:20:06 +02:00
yrutschle f480eb6c7d refactoring: simpler TLS extension parsing algorithm 2018-09-28 13:58:33 +02:00
yrutschle 6431bb7e35 refactoring: changed magic numbers for use_alpn to named bitfields 2018-09-23 22:29:25 +02:00
yrutschle 6d6ea50066 Merge branch 'master' of https://github.com/yrutschle/sslh 2018-09-23 21:57:16 +02:00
yrutschle 71265a8477 Merge pull request #201 from astiob/probe-strlen
Fix incorrect strncmp length in HTTP method probing
2018-09-23 21:56:51 +02:00
yrutschle 0003680137 remove old tls and ssl targets, only use alpn/sni probe also for TLS with no extensions 2018-09-23 12:07:06 +02:00
Oleg Oshmyan e8f0d3ea53 Fix HTTP method probing 2018-09-22 15:50:40 +03:00
yrutschle a5d00568b5 Merge pull request #199 from rom1dep/sni_alpn_errmatching
tls: proposed fix for incorrect SNI/ALPN matching
2018-08-30 23:16:28 +02:00
yrutschle ffe9971624 test suite for SNI/ALPN with multiple targets and all combinations covered 2018-08-30 19:50:53 +02:00
Yves Rutschle 1693436cc3 automatic test for SNI/ALPN (single target) 2018-08-29 19:18:23 +02:00
Romain DEP. e42f670112 tls: proposed fix for incorrect SNI/ALPN matching 2018-08-21 22:36:01 +02:00
Yves Rutschle 60df92c2b2 prevent repeated reads on broken sockets 2018-08-14 23:05:49 +02:00
Yves Rutschle 8ad32816a6 last_p might be used uninitialised if last probe has no probe set (which I don't think might happen, but anyhow...) 2018-08-14 22:52:52 +02:00
Yves Rutschle 677e385fec new probing algorithm 2018-08-13 22:29:09 +02:00
Yves Rutschle b6db83a701 also test probes when no fragmentation occurs 2018-08-12 21:45:42 +02:00
Yves Rutschle 3a17bd6832 removed obsolete tests 2018-08-12 21:35:42 +02:00
Yves Rutschle 94911c1c2a cleaner framework to test all probes methodically (not all tests work as the probe code needs to be reworked) 2018-08-11 23:13:24 +02:00
Yves Rutschle d7889588da Merge branch 'master' of https://www.github.com/yrutschle/sslh 2018-08-04 22:34:20 +02:00
Yves Rutschle ad2b595280 Test suite: some cleanup and use config file instead of command-line parameters 2018-08-04 22:33:02 +02:00
Yves Rutschle 9df1ab8404 fix man page on timeout default protocol 2018-07-31 19:54:46 +00:00
yrutschle 10fb0bce6f Merge pull request #192 from candrews/patch-5
Include common.h in systemd-sslh-generator.c
2018-07-11 13:18:50 +02:00
Craig Andrews eb53c45351 Include common.h in systemd-sslh-generator.c
Fixes https://github.com/yrutschle/sslh/issues/188
2018-07-06 12:30:28 -04:00
Yves Rutschle a1cc399ae5 fix syslog facility name check 2018-07-03 12:22:03 +02:00
yrutschle 108a9780d8 Merge pull request #187 from msantos/segfault
Fix potential segfaults (unverified malloc() returns) and other robustness issues
2018-06-18 16:45:51 +02:00
Michael Santos 9228171eb0 config: exit if list element is invalid 2018-06-18 10:35:28 -04:00
Michael Santos 8ce2b2ea05 Check memory allocations succeed 2018-06-18 10:35:28 -04:00
Michael Santos 4c132e3c8d config: segfault parsing invalid sni/alpn
Check return value of config_setting_get_string_elem() for error
before passing the result to strlen():

~~~ segfault.conf
protocols:
(
 { name: "tls"; host: "localhost"; port: "8443";  sni_hostnames:  [ 0 ];
}
);
~~~
2018-06-18 10:35:28 -04:00
Michael Santos cfd0163a5b main_loop: initialize in_socket
in_socket may be used uninitialized if no addresses are available.

~~~
sslh-select.c:415:8: warning: Function call argument is an uninitialized value
                            check_access_rights(in_socket, cnx[i].proto->service)) {
                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
2018-06-18 10:35:28 -04:00
Michael Santos 5cf591a254 Avoid segfault with malformed IPv6 address
A literal IPv6 address without a trailing bracket will result in a write
past the end of the address buffer:

~~~ segfault.conf
protocols:
(
 { name: "tls"; host: "["; port: "8443"; }
);
~~~

~~~
$ sslh-select -p 127.0.0.1:443 --foreground -F./segfault.conf
[: no closing bracket in IPv6 address?
Segmentation fault (core dumped)
~~~
2018-06-18 10:35:28 -04:00
Michael Santos c179d9a57b start_listen_sockets: exit if no addresses
Do not allocate a 0 byte buffer if no addresses are available:

    common.c:122:14: warning: Call to 'malloc' has an allocation size of 0 bytes
       *sockfd = malloc(num_addr * sizeof(*sockfd[0]));
		 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2018-06-18 10:35:28 -04:00
yrutschle 336e8bb9d1 Merge pull request #186 from Revertron/patch-1
Fixed a typo
2018-06-14 16:36:13 +02:00
Roman 0ada00474b Fixed a typo
Just a typo in usage text.
2018-06-14 14:31:50 +02:00
Yves Rutschle 95e8f5731c document magic constant in socks5 probe 2018-06-13 12:40:02 +00:00
Yves Rutschle dfd9e14866 fix socks5 probe 2018-06-13 09:52:49 +02:00
yrutschle 552723cc5f Merge pull request #180 from sanmai/patch-1
Update README.md
2018-06-01 10:31:34 +02:00
Alexey Kopytko 09aaf39e25 Update README.md
Removed redundant dollar signs since they're not used anywhere else in the README.
2018-06-01 15:13:50 +09:00
Yves Rutschle 7acf9627ee fix memory leak when using transparent proxying 2018-05-29 12:38:57 +02:00
Yves Rutschle b8e63a4d9d fixed systemd config to correct path 2018-05-26 17:02:15 +00:00
yrutschle f451cc8bed Merge pull request #172 from WeirdCarrotMonster/master
Added support for socks5 protocol
2018-04-16 22:38:06 +02:00
Eugene Protozanov a43dd11fc9 Added support for socks5 protocol 2018-04-16 15:27:31 +04:00
16 changed files with 571 additions and 358 deletions

No files matched your search

+30
View File
@@ -1,3 +1,33 @@
v1.20: 20NOV2018
Added support for socks5 protocol (Eugene Protozanov)
New probing method:
Before, probes were tried in order, repeating on the
same probe as long it returned PROBE_AGAIN before
moving to the next one. This means a probe which
requires a lot of data (i.e. returne PROBE_AGAIN for
a long time) could prevent sucessful matches from
subsequent probes. The configuration file needed to
take that into account.
Now, all probes are tried each time new data is
found. If any probe matches, use it. If at least one
probe requires more data, wait for more. If all
probes failed, connect to the last one. So the only
thing to know when writing the configuration file is
that 'anyprot' needs to be last.
Test suite heavily refactored; `t` uses `test.cfg`
to decide which probes to test and all setup is
automatic; probes get tested with 'fast' (entire
first message in one packet) and 'slow' (one byte at
a time); when SNI/ALPN are defined, all combinations
are tested.
Old 'tls' probe removed, 'sni_alpn' probe renamed as 'tls'.
You'll need to change 'sni_alpn' to 'tls' in
your configuration file, if ever you used it.
v1.19: 20JAN2018
Added 'syslog_facility' configuration option to
specify where to log.
+31 -31
View File
@@ -205,11 +205,11 @@ and `CAP_NET_ADMIN` for transparent proxying (see
You can use the `setcap(8)` utility to give these capabilities
to the executable:
# setcap cap_net_bind_service,cap_net_admin+pe sslh-select
sudo setcap cap_net_bind_service,cap_net_admin+pe sslh-select
Then you can run sslh-select as an unpriviledged user, e.g.:
$ sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443
sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443
Caveat: `CAP_NET_ADMIN` does give sslh too many rights, e.g.
configuring the interface. If you're not going to use
@@ -235,46 +235,46 @@ The firewalling tables also need to be adjusted as follows.
I don't think it is possible to have `httpd` and `sslh` both listen to 443 in
this scheme -- let me know if you manage that:
$ # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
$ sysctl -w net.ipv4.conf.default.route_localnet=1
$ sysctl -w net.ipv4.conf.all.route_localnet=1
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
$ # DROP martian packets as they would have been if route_localnet was zero
$ # Note: packets not leaving the server aren't affected by this, thus sslh will still work
$ iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
$ iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP
iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP
$ # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
$ iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
$ # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
$ iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
$ # Configure routing for those marked packets
$ ip rule add fwmark 0x1 lookup 100
$ ip route add local 0.0.0.0/0 dev lo table 100
# Configure routing for those marked packets
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
Tranparent proxying with IPv6 is similarly set up as follows:
$ # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
$ # Not sure if this is needed for ipv6 though
$ sysctl -w net.ipv4.conf.default.route_localnet=1
$ sysctl -w net.ipv4.conf.all.route_localnet=1
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
# Not sure if this is needed for ipv6 though
sysctl -w net.ipv4.conf.default.route_localnet=1
sysctl -w net.ipv4.conf.all.route_localnet=1
$ # DROP martian packets as they would have been if route_localnet was zero
$ # Note: packets not leaving the server aren't affected by this, thus sslh will still work
$ ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
$ ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
ip6tables -t raw -A PREROUTING ! -i lo -d ::1/128 -j DROP
ip6tables -t mangle -A POSTROUTING ! -o lo -s ::1/128 -j DROP
$ # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
$ ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
# Mark all connections made by ssl for special treatment (here sslh is run as user "sslh")
ip6tables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f
$ # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
$ ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
ip6tables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f
$ # Configure routing for those marked packets
$ ip -6 rule add fwmark 0x1 lookup 100
$ ip -6 route add local ::/0 dev lo table 100
# Configure routing for those marked packets
ip -6 rule add fwmark 0x1 lookup 100
ip -6 route add local ::/0 dev lo table 100
Explanation:
To be able to use `localhost` as destination in your sslh config along with transparent proxying
+56 -37
View File
@@ -84,6 +84,7 @@ int get_fd_sockets(int *sockfd[])
if (sd > 0) {
int i;
*sockfd = malloc(sd * sizeof(*sockfd[0]));
CHECK_ALLOC(*sockfd, "malloc");
for (i = 0; i < sd; i++) {
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
}
@@ -116,10 +117,16 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;
if (num_addr == 0) {
fprintf(stderr, "FATAL: No available addresses.\n");
exit(1);
}
if (verbose)
fprintf(stderr, "listening to %d addresses\n", num_addr);
*sockfd = malloc(num_addr * sizeof(*sockfd[0]));
CHECK_ALLOC(*sockfd, "malloc");
for (i = 0, addr = addr_list; i < num_addr && addr; i++, addr = addr->ai_next) {
if (!addr) {
@@ -161,6 +168,48 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
return num_addr;
}
/* returns 1 if given address is on the local machine: iterate through all
* network interfaces and check their addresses */
int is_same_machine(struct addrinfo* from)
{
struct ifaddrs *ifaddrs_p = NULL, *ifa;
int match = 0;
getifaddrs(&ifaddrs_p);
for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next)
{
if (!ifa->ifa_addr)
continue;
if (from->ai_addr->sa_family == ifa->ifa_addr->sa_family)
{
int family = ifa->ifa_addr->sa_family;
if (family == AF_INET)
{
struct sockaddr_in *from_addr = (struct sockaddr_in*)from->ai_addr;
struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr;
if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) {
match = 1;
break;
}
}
else if (family == AF_INET6)
{
struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from->ai_addr;
struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr;
if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) {
match = 1;
break;
}
}
}
}
freeifaddrs(ifaddrs_p);
return match;
}
/* Transparent proxying: bind the peer address of fd to the peer address of
* fd_from */
#define IP_TRANSPARENT 19
@@ -178,39 +227,11 @@ int bind_peer(int fd, int fd_from)
* got here */
res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen);
CHECK_RES_RETURN(res, "getpeername");
/* if the destination is the same machine, there's no need to do bind */
struct ifaddrs *ifaddrs_p = NULL, *ifa;
getifaddrs(&ifaddrs_p);
for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next)
{
if (!ifa->ifa_addr)
continue;
int match = 0;
if (from.ai_addr->sa_family == ifa->ifa_addr->sa_family)
{
int family = ifa->ifa_addr->sa_family;
if (family == AF_INET)
{
struct sockaddr_in *from_addr = (struct sockaddr_in*)from.ai_addr;
struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr;
if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr)
match = 1;
}
else if (family == AF_INET6)
{
struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from.ai_addr;
struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr;
if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16))
match = 1;
}
}
if (match) /* the destination is the same as the source, should not create a transparent bind */
return 0;
}
if (is_same_machine(&from))
return 0;
#ifndef IP_BINDANY /* use IP_TRANSPARENT */
res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans));
CHECK_RES_DIE(res, "setsockopt");
@@ -295,10 +316,7 @@ int defer_write(struct queue *q, void* data, int data_size)
fprintf(stderr, "**** writing deferred on fd %d\n", q->fd);
p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size);
if (!p) {
perror("realloc");
exit(1);
}
CHECK_ALLOC(p, "realloc");
q->begin_deferred_data = p;
q->deferred_data = p + data_offset;
@@ -477,6 +495,7 @@ int resolve_split_name(struct addrinfo **out, const char* ct_host, const char* s
end = strrchr(host, ']');
if (!end) {
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
return -1;
}
host++; /* skip first bracket */
*end = 0; /* remove last bracket */
@@ -664,7 +683,7 @@ void setup_syslog(const char* bin_name) {
for (fn = 0; facilitynames[fn].c_val != -1; fn++)
if (strcmp(facilitynames[fn].c_name, facility) == 0)
break;
if (fn == -1) {
if (facilitynames[fn].c_val == -1) {
fprintf(stderr, "Unknown facility %s\n", facility);
exit(1);
}
+7
View File
@@ -48,6 +48,13 @@
return res; \
}
#define CHECK_ALLOC(a, str) \
if (!a) { \
fprintf(stderr, "%s:%d:", __FILE__, __LINE__); \
perror(str); \
exit(1); \
}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#if 1
+8 -3
View File
@@ -43,20 +43,25 @@ listen:
# (only useful for sslh-select)
#
# Probe-specific options:
# (sslh will try each probe in order they are declared, and
# connect to the first that matches.)
#
# tls:
# sni_hostnames: list of FQDN for that target
# alpn_protocols: list of ALPN protocols for that target, see:
# https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
#
# if both sni_hostnames AND alpn_protocols are specified, both must match
#
# if neither are set, it is just checked whether this is the TLS protocol or not
#
# Obviously set the most specific probes
# first, and if you use TLS with no ALPN/SNI
# set it as the last TLS probe
# regex:
# regex_patterns: list of patterns to match for
# that target.
#
# sslh will try each probe in order they are declared, and
# connect to the first that matches.
#
# You can specify several of 'regex' and 'tls'.
protocols:
+78 -71
View File
@@ -40,6 +40,7 @@ static int is_xmpp_protocol(const char *p, int len, struct proto*);
static int is_http_protocol(const char *p, int len, struct proto*);
static int is_tls_protocol(const char *p, int len, struct proto*);
static int is_adb_protocol(const char *p, int len, struct proto*);
static int is_socks5_protocol(const char *p, int len, struct proto*);
static int is_true(const char *p, int len, struct proto* proto) { return 1; }
/* Table of protocols that have a built-in probe
@@ -51,9 +52,9 @@ static struct proto builtins[] = {
{ "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 },
{ "socks5", NULL, NULL, 1, 0, 0, is_socks5_protocol },
{ "anyprot", NULL, NULL, 1, 0, 0, is_true }
};
@@ -178,13 +179,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 (memmem(p, len, "jabber", 6))
return PROBE_MATCH;
/* sometimes the word 'jabber' shows up late in the initial string,
sometimes after a newline. this makes sure we snarf the entire preamble
and detect it. (fixed for adium/pidgin) */
if (len < 50)
return PROBE_AGAIN;
return memmem(p, len, "jabber", 6) ? 1 : 0;
return PROBE_NEXT;
}
static int probe_http_method(const char *p, int len, const char *opt)
@@ -192,7 +196,7 @@ static int probe_http_method(const char *p, int len, const char *opt)
if (len < strlen(opt))
return PROBE_AGAIN;
return !strncmp(p, opt, len);
return !strncmp(p, opt, strlen(opt));
}
/* Is the buffer the beginning of an HTTP connection? */
@@ -221,45 +225,15 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
return PROBE_NEXT;
}
static int is_sni_alpn_protocol(const char *p, int len, struct proto *proto)
{
int valid_tls;
valid_tls = parse_tls_header(proto->data, p, len);
if(valid_tls < 0)
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
/* There *was* a valid match */
return PROBE_MATCH;
}
/* Says if it's TLS, optionally with SNI and ALPN lists in proto->data */
static int is_tls_protocol(const char *p, int len, struct proto *proto)
{
if (len < 6)
return PROBE_AGAIN;
/* TLS packet starts with a record "Hello" (0x16), followed by the number of
* the highest version of SSL/TLS supported.
*
* A SSLv2 record header contains a two or three byte length code. If the
* most significant bit is set in the first byte of the record length code
* then the record has no padding and the total header length will be 2
* bytes, otherwise the record has padding and the total header length will
* be 3 bytes. Next, a 1 char sized client-hello (0x01) is expected,
* followed by a 2 char sized version that indicates the highest version of
* TLS/SSL supported by the sender. [SSL2] Hickman, Kipp, "The SSL Protocol"
*
* We're checking the highest version of TLS/SSL supported against
* (0x03 0x00-0x03) (RFC6101 A.1). This means we reject the usage of SSLv2
* and lower, which is actually a good thing (RFC6176).
*/
if (p[0] == 0x16) // TLS client-hello
return p[1] == 0x03 && ( p[2] >= 0 && p[2] <= 0x03);
if ((p[0] & 0x80) != 0) // SSLv2 client-hello, no padding
return p[2] == 0x01 && p[3] == 0x03 && ( p[4] >= 0 && p[4] <= 0x03);
else // SSLv2 client-hello, padded
return p[3] == 0x01 && p[4] == 0x03 && ( p[5] >= 0 && p[5] <= 0x03);
switch (parse_tls_header(proto->data, p, len)) {
case TLS_MATCH: return PROBE_MATCH;
case TLS_NOMATCH: return PROBE_NEXT;
case TLS_ELENGTH: return PROBE_AGAIN;
default: return PROBE_NEXT;
}
}
static int probe_adb_cnxn_message(const char *p)
@@ -311,6 +285,39 @@ static int is_adb_protocol(const char *p, int len, struct proto *proto)
return probe_adb_cnxn_message(&p[sizeof(empty_message)]);
}
static int is_socks5_protocol(const char *p_in, int len, struct proto *proto)
{
unsigned char* p = (unsigned char*)p_in;
int i;
if (len < 2)
return PROBE_AGAIN;
/* First byte should be socks protocol version */
if (p[0] != 5)
return PROBE_NEXT;
/* Second byte should be number of supported
* authentication methods, assuming maximum of 10,
* as defined in https://www.iana.org/assignments/socks-methods/socks-methods.xhtml
*/
char m_count = p[1];
if (m_count < 1 || m_count > 10)
return PROBE_NEXT;
if (len < 2 + m_count)
return PROBE_AGAIN;
/* Each authentication method number should be in range 0..9
* (https://www.iana.org/assignments/socks-methods/socks-methods.xhtml)
*/
for (i = 0; i < m_count; i++) {
if (p[2 + i] > 9)
return PROBE_NEXT;
}
return PROBE_MATCH;
}
static int regex_probe(const char *p, int len, struct proto *proto)
{
#ifdef ENABLE_REGEX
@@ -337,8 +344,8 @@ static int regex_probe(const char *p, int len, struct proto *proto)
int probe_client_protocol(struct connection *cnx)
{
char buffer[BUFSIZ];
struct proto *p;
int n;
struct proto *p, *last_p = cnx->proto;
int n, res, again = 0;
n = read(cnx->q[0].fd, buffer, sizeof(buffer));
/* It's possible that read() returns an error, e.g. if the client
@@ -347,36 +354,40 @@ int probe_client_protocol(struct connection *cnx)
* function does not have to deal with a specific failure condition (the
* connection will just fail later normally). */
/* Dump hex values of the packet */
if (verbose>1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buffer, n);
}
if (n > 0) {
int res = PROBE_NEXT;
defer_write(&cnx->q[1], buffer, n);
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);
cnx->proto = p;
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
if (verbose > 1) {
fprintf(stderr, "hexdump of incoming packet:\n");
hexdump(buffer, n);
}
if (res != PROBE_NEXT)
return res;
defer_write(&cnx->q[1], buffer, n);
}
if (verbose)
fprintf(stderr,
"all probes failed, connecting to first protocol: %s\n",
protocols->description);
for (p = cnx->proto; p; p = p->next) {
char* probe_str[3] = {"PROBE_NEXT", "PROBE_MATCH", "PROBE_AGAIN"};
if (! p->probe) continue;
/* If none worked, return the first one affected (that's completely
* arbitrary) */
cnx->proto = protocols;
/* Don't probe last protocol if it is anyprot (and store last protocol) */
if (! p->next) {
last_p = p;
if (!strcmp(p->description, "anyprot"))
break;
}
res = p->probe(cnx->q[1].begin_deferred_data, cnx->q[1].deferred_data_size, p);
if (verbose) fprintf(stderr, "probing for %s: %s\n", p->description, probe_str[res]);
if (res == PROBE_MATCH) {
cnx->proto = p;
return PROBE_MATCH;
}
if (res == PROBE_AGAIN)
again++;
}
if ((again && (n > 0)) || ((n == -1) && (errno == EAGAIN)))
return PROBE_AGAIN;
/* Everything failed: match the last one */
cnx->proto = last_p;
return PROBE_MATCH;
}
@@ -408,10 +419,6 @@ T_PROBE* get_probe(const char* description) {
if (!strcmp(description, "regex"))
return regex_probe;
/* Special case of "sni/alpn" probe for same reason as above*/
if (!strcmp(description, "sni_alpn"))
return is_sni_alpn_protocol;
/* Special case of "timeout" is allowed as a probe name in the
* configuration file even though it's not really a probe */
if (!strcmp(description, "timeout"))
+1 -1
View File
@@ -4,7 +4,7 @@ After=network.target
[Service]
EnvironmentFile=/etc/conf.d/sslh
ExecStart=/usr/bin/sslh --foreground $DAEMON_OPTS
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
KillMode=process
#Hardening
PrivateTmp=true
+1
View File
@@ -145,6 +145,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
listener_pid_number = num_addr_listen;
listener_pid = malloc(listener_pid_number * sizeof(listener_pid[0]));
CHECK_ALLOC(listener_pid, "malloc");
/* Start one process for each listening address */
for (i = 0; i < num_addr_listen; i++) {
+61 -8
View File
@@ -40,7 +40,7 @@ const char* USAGE_STRING =
"sslh " VERSION "\n" \
"usage:\n" \
"\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F<file>]\n"
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <add> [-p <addr> ...] \n" \
"\t[-t <timeout>] [-P <pidfile>] [-u <username>] [-C <chroot>] -p <addr> [-p <addr> ...] \n" \
"%s\n\n" /* Dynamically built list of builtin protocols */ \
"\t[--on-timeout <addr>]\n" \
"-v: verbose\n" \
@@ -147,6 +147,32 @@ static void printsettings(void)
}
/* To removed in v1.21 */
const char* ssl_err_msg = "Usage of 'ssl' setting is deprecated and will be removed in v1.21. Please use 'tls' instead\n";
void ssl_to_tls(char* setting)
{
if (!strcmp(setting, "ssl")) {
strcpy(setting, "tls"); /* legacy configuration */
log_message(LOG_INFO, ssl_err_msg);
}
}
/* Turn 'ssl' command line option to 'tls'. To removed in v1.21 */
void cmd_ssl_to_tls(int argc, char* argv[])
{
int i;
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "--ssl")) {
strcpy(argv[i], "--tls");
/* foreground option not parsed yet, syslog not open, just print on
* stderr and hope for the best */
fprintf(stderr, ssl_err_msg);
}
}
}
/* Extract configuration on addresses and ports on which to listen.
* out: newly allocated list of addrinfo to listen to
*/
@@ -210,14 +236,21 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
p->probe = get_probe("regex");
probe_list = calloc(num_probes + 1, sizeof(*probe_list));
CHECK_ALLOC(probe_list, "calloc");
p->data = (void*)probe_list;
for (i = 0; i < num_probes; i++) {
probe_list[i] = malloc(sizeof(*(probe_list[i])));
CHECK_ALLOC(probe_list[i], "malloc");
expr = config_setting_get_string_elem(probes, i);
if (expr == NULL) {
fprintf(stderr, "%s: invalid probe specified\n", p->description);
exit(1);
}
res = regcomp(probe_list[i], expr, REG_EXTENDED);
if (res) {
err = malloc(errsize = regerror(res, probe_list[i], NULL, 0));
CHECK_ALLOC(err, "malloc");
regerror(res, probe_list[i], err, errsize);
fprintf(stderr, "%s:%s\n", expr, err);
free(err);
@@ -235,7 +268,7 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items, const char* name, int alpn)
{
int num_probes, i, max_server_name_len, server_name_len;
const char * config_item;
const char * config_item, *server_name;
char** sni_hostname_list;
num_probes = config_setting_length(config_items);
@@ -246,16 +279,27 @@ static void setup_sni_alpn_list(struct proto *p, config_setting_t* config_items,
max_server_name_len = 0;
for (i = 0; i < num_probes; i++) {
server_name_len = strlen(config_setting_get_string_elem(config_items, i));
server_name = config_setting_get_string_elem(config_items, i);
if (server_name == NULL) {
fprintf(stderr, "%s: invalid %s specified\n", p->description, name);
exit(1);
}
server_name_len = strlen(server_name);
if(server_name_len > max_server_name_len)
max_server_name_len = server_name_len;
}
sni_hostname_list = calloc(num_probes + 1, ++max_server_name_len);
CHECK_ALLOC(sni_hostname_list, "calloc");
for (i = 0; i < num_probes; i++) {
config_item = config_setting_get_string_elem(config_items, i);
if (config_item == NULL) {
fprintf(stderr, "%s: invalid %s specified\n", p->description, name);
exit(1);
}
sni_hostname_list[i] = malloc(max_server_name_len);
CHECK_ALLOC(sni_hostname_list[i], "malloc");
strcpy (sni_hostname_list[i], config_item);
if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]);
}
@@ -272,11 +316,9 @@ static void setup_sni_alpn(struct proto *p, config_setting_t* prot)
alpn_protocols = config_setting_get_member(prot, "alpn_protocols");
if(sni_hostnames && config_setting_is_array(sni_hostnames)) {
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0);
}
if(alpn_protocols && config_setting_is_array(alpn_protocols)) {
p->probe = get_probe("sni_alpn");
setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1);
}
}
@@ -289,7 +331,8 @@ static void setup_sni_alpn(struct proto *p, config_setting_t* prot)
static int config_protocols(config_t *config, struct proto **prots)
{
config_setting_t *setting, *prot, *patterns;
const char *hostname, *port, *name;
const char *hostname, *port, *cfg_name;
char* name;
int i, num_prots;
struct proto *p, *prev = NULL;
@@ -298,15 +341,20 @@ static int config_protocols(config_t *config, struct proto **prots)
num_prots = config_setting_length(setting);
for (i = 0; i < num_prots; i++) {
p = calloc(1, sizeof(*p));
CHECK_ALLOC(p, "calloc");
if (i == 0) *prots = p;
if (prev) prev->next = p;
prev = p;
prot = config_setting_get_elem(setting, i);
if ((config_setting_lookup_string(prot, "name", &name) &&
if ((config_setting_lookup_string(prot, "name", &cfg_name) &&
config_setting_lookup_string(prot, "host", &hostname) &&
config_setting_lookup_string(prot, "port", &port)
)) {
/* To removed in v1.21 */
name = strdup(cfg_name);
ssl_to_tls(name);
/* /remove */
p->description = name;
config_setting_lookup_string(prot, "service", &(p->service));
config_setting_lookup_bool(prot, "keepalive", &p->keepalive);
@@ -322,7 +370,7 @@ static int config_protocols(config_t *config, struct proto **prots)
}
p->probe = get_probe(name);
if (!p->probe || !strcmp(name, "sni_alpn")) {
if (!p->probe) {
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
exit(1);
}
@@ -438,6 +486,7 @@ static void make_alloptions(void)
/* Create all_options, composed of const_options followed by one option per
* known protocol */
all_options = calloc(ARRAY_SIZE(const_options) + get_num_builtins(), sizeof(struct option));
CHECK_ALLOC(all_options, "calloc");
memcpy(all_options, const_options, sizeof(const_options));
append_protocols(all_options, ARRAY_SIZE(const_options) - 1, builtins, get_num_builtins());
}
@@ -455,6 +504,8 @@ static void cmdline_config(int argc, char* argv[], struct proto** prots)
char *config_filename;
#endif
cmd_ssl_to_tls(argc, argv); /* To remove in v1.21 */
make_alloptions();
#ifdef LIBCONFIG
@@ -516,8 +567,10 @@ next_arg:
if (!prots) {
/* No protocols yet -- create the list */
p = prots = calloc(1, sizeof(*p));
CHECK_ALLOC(p, "calloc");
} else {
p->next = calloc(1, sizeof(*p));
CHECK_ALLOC(p->next, "calloc");
p = p->next;
}
memcpy(p, &builtins[c-PROT_SHIFT], sizeof(*p));
+3 -1
View File
@@ -301,7 +301,8 @@ void main_loop(int listen_sockets[], int num_addr_listen)
fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */
fd_set readfds, writefds; /* working read and write fd sets */
struct timeval tv;
int max_fd, in_socket, i, j, res;
int max_fd, i, j, res;
int in_socket = 0;
struct connection *cnx;
int num_cnx; /* Number of connections in *cnx */
int num_probing = 0; /* Number of connections currently probing
@@ -321,6 +322,7 @@ void main_loop(int listen_sockets[], int num_addr_listen)
num_cnx = cnx_num_alloc; /* Start with a set pool of slots */
cnx = malloc(num_cnx * sizeof(struct connection));
CHECK_ALLOC(cnx, "malloc");
for (i = 0; i < num_cnx; i++)
init_cnx(&cnx[i]);
+5 -1
View File
@@ -95,7 +95,11 @@ protocol (which should usually be SSH). Default is 2s.
=item B<--on-timeout> I<protocol name>
Name of the protocol to connect to after the timeout period
is over. Default is 'ssh'.
is over. Default is to forward to the first specified
protocol. It usually makes sense to specify 'ssh' as the
timeout protocol, as the SSH specification does not tell
who is supposed to speak first and a large number of SSH
clients wait for the server to send its banner.
=item B<--transparent>
+6 -1
View File
@@ -2,12 +2,14 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "common.h"
static char* resolve_listen(const char *hostname, const char *port) {
/* Need room in the strcat for \0 and :
* the format in the socket unit file is hostname:port */
char *conn = (char*)malloc(strlen(hostname)+strlen(port)+2);
char *conn = malloc(strlen(hostname)+strlen(port)+2);
CHECK_ALLOC(conn, "malloc");
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
@@ -41,6 +43,7 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
int i;
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
CHECK_ALLOC(*listen, "malloc");
for (i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
@@ -51,6 +54,7 @@ static int get_listen_from_conf(const char *filename, char **listen[]) {
return -1;
} else {
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
CHECK_ALLOC((*listen)[i], "malloc");
strcpy((*listen)[i], resolve_listen(hostname, port));
}
}
@@ -115,6 +119,7 @@ static int gen_sslh_config(char *runtime_unit_dir) {
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
CHECK_ALLOC(runtime_conf, "malloc");
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
+177 -118
View File
@@ -2,27 +2,30 @@
# Test script for sslh
# Uses Conf::Libconfig to read sslh config file: install
# with:
# cpan Conf::Libconfig
use strict;
use IO::Socket::INET6;
use Test::More qw/no_plan/;
use Conf::Libconfig;
# We use ports 9000, 9001 and 9002 -- hope that won't clash
# with anything...
my $ssh_address = "ip6-localhost:9000";
my $ssl_address = "ip6-localhost:9001";
my $sslh_port = 9002;
my $no_listen = 9003; # Port on which no-one listens
my $pidfile = "/tmp/sslh_test.pid";
my $conf = new Conf::Libconfig;
$conf->read_file("test.cfg");
my $no_listen = 8083; # Port on which no-one listens
my $pidfile = $conf->lookup_value("pidfile");
my $sslh_port = $conf->fetch_array("listen")->[0]->{port};
my $user = (getpwuid $<)[0]; # Run under current username
# Which tests do we run
my $SSL_CNX = 1;
my $SSH_SHY_CNX = 1;
my $SSH_BOLD_CNX = 1;
my $SSH_PROBE_AGAIN = 1;
my $PROBES_NOFRAG = 1;
my $PROBES_AGAIN = 1;
my $SSL_MIX_SSH = 1;
my $SSH_MIX_SSL = 1;
my $BIG_MSG = 0; # This test is unreliable
my $STALL_CNX = 0; # This test needs fixing
# Robustness tests. These are mostly to achieve full test
# coverage, but do not necessarily result in an actual test
@@ -32,22 +35,158 @@ my $RB_CNX_NOSERVER = 1;
my $RB_PARAM_NOHOST = 1;
my $RB_WRONG_USERNAME = 1;
my $RB_OPEN_PID_FILE = 1;
my $RB_BIND_ADDRESS = 1;
my $RB_RESOLVE_ADDRESS = 1;
`lcov --directory . --zerocounters`;
sub verbose_exec
{
my ($cmd) = @_;
my ($ssh_pid, $ssl_pid);
if (!($ssh_pid = fork)) {
exec "./echosrv --listen $ssh_address --prefix 'ssh: '";
warn "$cmd\n";
if (!fork) {
exec $cmd;
}
}
if (!($ssl_pid = fork)) {
exec "./echosrv --listen $ssl_address --prefix 'ssl: '";
# For SNI/ALPN, build a protocol name as such:
# tls:sni1,sni2,...;alpn1,alpn2,...
# input: a protocol entry from Libconfig
sub make_sni_alpn_name {
my ($prot) = @_;
return "tls:" . (join ",", @{$prot->{sni_hostnames} // []})
. ";" . (join ",", @{$prot->{alpn_protocols} // [] });
}
# Tests one probe: given input data, connect, verify we get
# the expected server, verify shoveling works
# Named options:
# data: what to write
# expected: expected protocol prefix
# no_frag: don't print byte-per-byte
sub test_probe {
my (%opts) = @_;
my $cnx = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx;
return unless $cnx;
my $pattern = $opts{data};
if ($opts{no_frag}) {
syswrite $cnx, $pattern;
} else {
while (length $pattern) {
syswrite $cnx, (substr $pattern, 0, 1, '');
select undef, undef, undef, .01;
}
}
my $data;
my $n = sysread $cnx, $data, 1024;
$data =~ /^(.*?): /;
my $prefix = $1;
$data =~ s/$prefix: //g;
print "Received: protocol $prefix data [$data]\n";
close $cnx;
$opts{expected} =~ s/^ssl/tls/; # to remove in 1.21
is($prefix, $opts{expected}, "probe $opts{expected} connected correctly");
is($data, $opts{data}, "data shoveled correctly");
}
# Test all probes, with or without fragmentation
# options:
# no_frag: write test patterns all at once (also
# available per-protocol as some probes don't support
# fragmentation)
sub test_probes {
my (%opts) = @_;
my @probes = @{$conf->fetch_array("protocols")};
foreach my $p (@probes) {
my %protocols = (
'ssh' => { data => "SSH-2.0 tester" },
'socks5' => { data => "\x05\x04\x01\x02\x03\x04" },
'http' => {
data => "GET index.html HTTP/1.1",
no_frag => 1 },
'ssl' => {
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello ssl alone"
},
'tls' => {
# Packet with SNI and ALPN (`openssl s_client -connect localhost:443 -alpn alpn1 -servername sni1`)
data_sni_alpn => "\x16\x03\x01\x00\xc4\x01\x00\x00\xc0\x03\x03\x03\x19\x01\x00\x40\x14\x13\xcc\x1b\x94\xad\x20\x5d\x13\x1a\x8d\xd2\x65\x23\x70\xde\xd1\x3c\x5d\x05\x19\xcb\x27\x0d\x7c\x2c\x89\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x5f\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello sni/alpn",
# Packet with SNI alone
data_sni => "\x16\x03\x01\x00\xb8\x01\x00\x00\xb4\x03\x03\x97\xe4\xe9\xad\x86\xe1\x21\xfd\xc4\x5b\x27\x0e\xad\x4b\x55\xc2\x50\xe4\x1c\x86\x2f\x37\x25\xde\xe8\x9c\x59\xfc\x1b\xa9\x37\x32\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x53\x00\x00\x00\x09\x00\x07\x00\x00\x04\$sni\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello sni",
# packet with ALPN alone
data_alpn => "\x16\x03\x01\x00\xb7\x01\x00\x00\xb3\x03\x03\xe2\x90\xa2\x29\x03\x31\xad\x98\x44\x51\x54\x90\x5b\xd9\x51\x0e\x66\xb5\x3f\xe8\x8b\x09\xc9\xe4\x2b\x97\x24\xef\xad\x56\x06\xc9\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x52\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x10\x00\x08\x00\x06\x05\$alpn\x00\x16\x00\x00\x00\x17\x00\x00hello alpn",
# packet with no SNI, no ALPN
data => "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone"
},
'openvpn' => { data => "\x00\x00" },
'tinc' => { data => "0 hello" },
'xmpp' => {data => "I should get a real jabber connection initialisation here" },
'adb' => { data => "CNXN....................host:..." },
'anyprot' => {data => "hello anyprot this needs to be longer than xmpp and adb which expect about 50 characters, which I all have to write before the timeout!" },
);
my $pattern = $protocols{$p->{name}}->{data};
$opts{no_frag} = 1 if $protocols{$p->{name}}->{no_frag};
if ($p->{sni_hostnames} or $p->{alpn_protocols}) {
my $pname = make_sni_alpn_name($p);
my @sni = @{$p->{sni_hostnames} // [""] };
my @alpn = @{$p->{alpn_protocols} // [""] };
foreach my $sni ( @sni ) {
foreach my $alpn ( @alpn ) {
print "sni: $sni\nalpn: $alpn\n";
$pattern = $protocols{tls}->{
"data". ($sni ? "_sni" : "") .
($alpn ? "_alpn": "")
};
$pattern =~ s/(\$\w+)/$1/eeg;
test_probe(
data => $pattern,
expected => $pname,
%opts
);
}
}
} else {
test_probe(
data => $pattern,
expected => $p->{name},
%opts
);
}
}
}
# Start an echoserver for each service
foreach my $s (@{$conf->fetch_array("protocols")}) {
my $prefix = $s->{name};
$prefix =~ s/^ssl/tls/; # To remove in 1.21
if ($s->{sni_hostnames} or $s->{alpn_protocols}) {
$prefix = make_sni_alpn_name($s);
}
verbose_exec "./echosrv --listen $s->{host}:$s->{port} --prefix '$prefix: '";
}
my @binaries = ('sslh-select', 'sslh-fork');
for my $binary (@binaries) {
warn "Testing $binary\n";
@@ -56,10 +195,10 @@ for my $binary (@binaries) {
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
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 ./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address -ssl $ssl_address -P $pidfile";
#my $cmd = "./$binary -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
my $cmd = "./$binary -v -f -u $user -Ftest.cfg";
verbose_exec $cmd;
#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";
@@ -67,21 +206,7 @@ for my $binary (@binaries) {
my $test_data = "hello world\n";
# my $ssl_test_data = (pack 'n', ((length $test_data) + 2)) . $test_data;
my $ssl_test_data = "\x16\x03\x03$test_data\n";
# Test: SSL connection
if ($SSL_CNX) {
print "***Test: SSL connection\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
if (defined $cnx_l) {
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection");
}
}
my $ssl_test_data = "\x16\x03\x01\x00\xab\x01\x00\x00\xa7\x03\x03\x89\x22\x33\x95\x43\x7a\xc3\x89\x45\x51\x12\x3c\x28\x24\x1b\x6a\x78\xbf\xbe\x95\xd8\x90\x58\xd7\x65\xf7\xbb\x2d\xb2\x8d\xa0\x75\x00\x00\x38\xc0\x2c\xc0\x30\x00\x9f\xcc\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x00\x46\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x19\x00\x18\x00\x23\x00\x00\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x16\x00\x00\x00\x17\x00\x00hello tls alone";
# Test: Shy SSH connection
if ($SSH_SHY_CNX) {
@@ -96,35 +221,6 @@ for my $binary (@binaries) {
}
}
# Test: Bold SSH connection
if ($SSH_BOLD_CNX) {
print "***Test: Bold SSH connection\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 $td;
my $data = <$cnx_h>;
is($data, "ssh: $td", "Bold SSH connection");
}
}
# 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";
@@ -142,7 +238,7 @@ for my $binary (@binaries) {
}
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL connection interrupted by SSH");
is($data, "tls: $ssl_test_data", "SSL connection interrupted by SSH");
}
}
@@ -159,7 +255,7 @@ for my $binary (@binaries) {
print $cnx_l $ssl_test_data;
my $data;
my $n = sysread $cnx_l, $data, 1024;
is($data, "ssl: $ssl_test_data", "SSL during SSH being established");
is($data, "tls: $ssl_test_data", "SSL during SSH being established");
}
print $cnx_h $test_data;
my $data = <$cnx_h>;
@@ -168,53 +264,12 @@ for my $binary (@binaries) {
}
# Test: Big messages (careful: don't go over echosrv's buffer limit (1M))
if ($BIG_MSG) {
print "***Test: big message\n";
my $cnx_l = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless $cnx_l;
my $rept = 1000;
my $test_data2 = $ssl_test_data . ("helloworld"x$rept);
if (defined $cnx_l) {
my $n = syswrite $cnx_l, $test_data2;
my ($data);
$n = sysread $cnx_l, $data, 1 << 20;
is($data, "ssl: ". $test_data2, "Big message");
}
if ($PROBES_NOFRAG) {
test_probes(no_frag => 1);
}
# Test: Stalled connection
# Create two connections, stall one, check the other one
# works, unstall first and check it works fine
# This test needs fixing.
# Now that echosrv no longer works on "lines" (finishing
# with '\n'), it may cut blocks randomly with prefixes.
# The whole thing needs to be re-thought as it'll only
# work by chance.
if ($STALL_CNX) {
print "***Test: Stalled connection\n";
my $cnx_1 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_1;
my $cnx_2 = new IO::Socket::INET(PeerHost => "localhost:$sslh_port");
warn "$!\n" unless defined $cnx_2;
my $test_data2 = "helloworld";
sleep 4;
my $rept = 1000;
if (defined $cnx_1 and defined $cnx_2) {
print $cnx_1 ($test_data2 x $rept);
print $cnx_1 "\n";
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
my $data = <$cnx_2>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (1)");
print $cnx_2 ($test_data2 x $rept);
print $cnx_2 "\n";
$data = <$cnx_2>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (2)");
$data = <$cnx_1>;
is($data, "ssh: " . ($test_data2 x $rept) . "\n", "Stalled connection (3)");
}
if ($PROBES_AGAIN) {
test_probes;
}
my $pid = `cat $pidfile`;
@@ -228,7 +283,6 @@ if ($RB_CNX_NOSERVER) {
print "***Test: Connecting to non-existant server\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh localhost:$no_listen --ssl localhost:$no_listen -P $pidfile";
}
warn "spawned $sslh_pid\n";
@@ -249,12 +303,19 @@ if ($RB_CNX_NOSERVER) {
}
my $ssh_conf = (grep { $_->{name} eq "ssh" } @{$conf->fetch_array("protocols")})[0];
my $ssh_address = $ssh_conf->{host} . ":" . $ssh_conf->{port};
# Use the last TLS echoserv (no SNI/ALPN)
my $ssl_conf = (grep { $_->{name} eq "tls" } @{$conf->fetch_array("protocols")})[-1];
my $ssl_address = $ssl_conf->{host} . ":" . $ssl_conf->{port};
# Robustness: No hostname in address
if ($RB_PARAM_NOHOST) {
print "***Test: No hostname in address\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen $sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
@@ -269,7 +330,6 @@ if ($RB_WRONG_USERNAME) {
print "***Test: Changing to non-existant username\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u ${user}_doesnt_exist --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P $pidfile";
}
warn "spawned $sslh_pid\n";
@@ -284,7 +344,6 @@ if ($RB_OPEN_PID_FILE) {
print "***Test: Can't open PID file\n";
my $sslh_pid;
if (!($sslh_pid = fork)) {
my $user = (getpwuid $<)[0]; # Run under current username
exec "./sslh-select -v -f -u $user --listen localhost:$sslh_port --ssh $ssh_address --ssl $ssl_address -P /dont_exist/$pidfile";
# You don't have a /dont_exist/ directory, do you?!
}
+42
View File
@@ -0,0 +1,42 @@
# Configuration file for testing (use both by sslh under
# test and the test script `t`)
verbose: 2;
foreground: true;
inetd: false;
numeric: false;
transparent: false;
timeout: 10; # Probe test writes slowly
pidfile: "/tmp/sslh_test.pid";
syslog_facility: "auth";
# List of interfaces on which we should listen
# Options:
listen:
(
{ host: "localhost"; port: "8080"; keepalive: true; },
{ host: "localhost"; port: "8081"; keepalive: true; }
);
protocols:
(
{ name: "ssh"; host: "localhost"; port: "9000"; fork: true; },
{ name: "socks5"; host: "localhost"; port: "9001"; },
{ name: "http"; host: "localhost"; port: "9002"; },
{ name: "tinc"; host: "localhost"; port: "9003"; },
{ name: "openvpn"; host: "localhost"; port: "9004"; },
{ name: "xmpp"; host: "localhost"; port: "9009"; },
{ name: "adb"; host: "localhost"; port: "9010"; },
{ name: "tls"; host: "localhost"; port: "9021"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni1" ]; },
{ name: "ssl"; host: "localhost"; port: "9022"; alpn_protocols: [ "alpn1", "alpn2" ]; sni_hostnames: [ "sni2", "sni3" ]; },
{ name: "tls"; host: "localhost"; port: "9023"; alpn_protocols: [ "alpn3" ]; },
{ name: "tls"; host: "localhost"; port: "9024"; sni_hostnames: [ "sni3" ]; },
{ name: "ssl"; host: "localhost"; port: "9025"; },
{ name: "anyprot"; host: "localhost"; port: "9099"; }
);
on-timeout: "ssh";
+55 -86
View File
@@ -41,9 +41,13 @@
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
typedef struct {
int tls_match_sni : 1;
int tls_match_alpn : 1;
} TLS_MATCHMODE;
struct TLSProtocol {
int use_alpn;
TLS_MATCHMODE match_mode;
char** sni_hostname_list;
char** alpn_protocol_list;
};
@@ -57,12 +61,9 @@ static int has_match(char**, const char*, size_t);
* hello handshake, returning a status code
*
* Returns:
* >=0 - length of the hostname and updates *hostname
* caller is responsible for freeing *hostname
* -1 - Incomplete request
* -2 - No Host header included in this request
* -3 - Invalid hostname pointer
* < -4 - Invalid TLS client hello
* 0: no match
* 1: match
* < 0: error code (see tls.h)
*/
int
parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
@@ -74,12 +75,12 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Check that our TCP payload is at least large enough for a TLS header */
if (data_len < TLS_HEADER_LEN)
return -1;
return TLS_ELENGTH;
tls_content_type = data[0];
if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
if (verbose) fprintf(stderr, "Request did not begin with TLS handshake.\n");
return -5;
return TLS_EPROTOCOL;
}
tls_version_major = data[1];
@@ -88,7 +89,7 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which cannot be parsed.\n",
tls_version_major, tls_version_minor);
return -2;
return TLS_EVERSION;
}
/* TLS record length */
@@ -98,18 +99,18 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Check we received entire TLS record length */
if (data_len < len)
return -1;
return TLS_ELENGTH;
/*
* Handshake
*/
if (pos + 1 > data_len) {
return -5;
return TLS_EPROTOCOL;
}
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
if (verbose) fprintf(stderr, "Not a client hello\n");
return -5;
return TLS_EPROTOCOL;
}
/* Skip past fixed length records:
@@ -123,46 +124,54 @@ parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Session ID */
if (pos + 1 > data_len)
return -5;
return TLS_EPROTOCOL;
len = (unsigned char)data[pos];
pos += 1 + len;
/* Cipher Suites */
if (pos + 2 > data_len)
return -5;
return TLS_EPROTOCOL;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2 + len;
/* Compression Methods */
if (pos + 1 > data_len)
return -5;
return TLS_EPROTOCOL;
len = (unsigned char)data[pos];
pos += 1 + len;
if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
if (verbose) fprintf(stderr, "Received SSL 3.0 handshake without extensions\n");
return -2;
return TLS_EVERSION;
}
/* Extensions */
if (pos + 2 > data_len)
return -5;
return TLS_EPROTOCOL;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2;
if (pos + len > data_len)
return -5;
return parse_extensions(tls_data, data + pos, len);
return TLS_EPROTOCOL;
/* By now we know it's TLS. if SNI or ALPN is set, parse extensions to see if
* they match. Otherwise, it's a match already */
if (tls_data &&
(tls_data->match_mode.tls_match_alpn || tls_data->match_mode.tls_match_sni)) {
return parse_extensions(tls_data, data + pos, len);
} else {
return TLS_MATCH;
}
}
static int
parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t data_len) {
size_t pos = 0;
size_t len;
int last_matched = 0;
int sni_match = 0, alpn_match = 0;
if (tls_data == NULL)
return -3;
return TLS_EINVAL;
/* Parse each 4 bytes for the extension header */
while (pos + 4 <= data_len) {
@@ -171,54 +180,17 @@ parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t da
(unsigned char) data[pos + 3];
if (pos + 4 + len > data_len)
return -5;
return TLS_EPROTOCOL;
size_t extension_type = ((unsigned char) data[pos] << 8) +
(unsigned char) data[pos + 1];
/* Check if it's a server name extension */
/* There can be only one extension of each type, so we break
our state and move pos to beginning of the extension here */
if (tls_data->use_alpn == 2) {
/* we want BOTH alpn and sni to match */
if (extension_type == 0x00) { /* Server Name */
if (parse_server_name_extension(tls_data, data + pos + 4, len)) {
/* SNI matched */
if(last_matched) {
/* this is only true if ALPN matched, so return true */
return last_matched;
} else {
/* otherwise store that SNI matched */
last_matched = 1;
}
} else {
/* both can't match */
return -2;
}
} else if (extension_type == 0x10) { /* ALPN */
if (parse_alpn_extension(tls_data, data + pos + 4, len)) {
/* ALPN matched */
if(last_matched) {
/* this is only true if SNI matched, so return true */
return last_matched;
} else {
/* otherwise store that ALPN matched */
last_matched = 1;
}
} else {
/* both can't match */
return -2;
}
}
} else if (extension_type == 0x00 && tls_data->use_alpn == 0) { /* Server Name */
return parse_server_name_extension(tls_data, data + pos + 4, len);
} else if (extension_type == 0x10 && tls_data->use_alpn == 1) { /* ALPN */
if (parse_alpn_extension(tls_data, data + pos + 4, len) > 0) {
return 1;
}
return parse_alpn_extension(tls_data, data + pos + 4, len);
if (extension_type == 0x00 && tls_data->match_mode.tls_match_sni) { /* Server Name */
sni_match = parse_server_name_extension(tls_data, data + pos + 4, len);
if (sni_match < 0) return sni_match;
} else if (extension_type == 0x10 && tls_data->match_mode.tls_match_alpn) { /* ALPN */
alpn_match = parse_alpn_extension(tls_data, data + pos + 4, len);
if (alpn_match < 0) return alpn_match;
}
pos += 4 + len; /* Advance to the next extension header */
@@ -226,9 +198,11 @@ parse_extensions(const struct TLSProtocol *tls_data, const char *data, size_t da
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return (sni_match && alpn_match)
|| (!tls_data->match_mode.tls_match_sni && alpn_match)
|| (!tls_data->match_mode.tls_match_alpn && sni_match);
}
static int
@@ -241,14 +215,14 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
(unsigned char)data[pos + 2];
if (pos + 3 + len > data_len)
return -5;
return TLS_EPROTOCOL;
switch (data[pos]) { /* name type */
case 0x00: /* host_name */
if(has_match(tls_data->sni_hostname_list, data + pos + 3, len)) {
return len;
} else {
return -2;
return TLS_ENOEXT;
}
default:
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
@@ -258,9 +232,9 @@ parse_server_name_extension(const struct TLSProtocol *tls_data, const char *data
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return TLS_ENOEXT;
}
static int
@@ -272,7 +246,7 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
len = (unsigned char)data[pos];
if (pos + 1 + len > data_len)
return -5;
return TLS_EPROTOCOL;
if (len > 0 && has_match(tls_data->alpn_protocol_list, data + pos + 1, len)) {
return len;
@@ -283,15 +257,16 @@ parse_alpn_extension(const struct TLSProtocol *tls_data, const char *data, size_
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return TLS_EPROTOCOL;
return -2;
return TLS_ENOEXT;
}
static int
has_match(char** list, const char* name, size_t name_len) {
char **item;
char *name_nullterminated = malloc(name_len+1);
CHECK_ALLOC(name_nullterminated, "malloc");
memcpy(name_nullterminated, name, name_len);
name_nullterminated[name_len]='\0';
@@ -309,9 +284,9 @@ has_match(char** list, const char* name, size_t name_len) {
struct TLSProtocol *
new_tls_data() {
struct TLSProtocol *tls_data = malloc(sizeof(struct TLSProtocol));
if (tls_data != NULL) {
tls_data->use_alpn = -1;
}
CHECK_ALLOC(tls_data, "malloc");
memset(tls_data, 0, sizeof(*tls_data));
return tls_data;
}
@@ -320,16 +295,10 @@ struct TLSProtocol *
tls_data_set_list(struct TLSProtocol *tls_data, int alpn, char** list) {
if (alpn) {
tls_data->alpn_protocol_list = list;
if(tls_data->use_alpn == 0)
tls_data->use_alpn = 2;
else
tls_data->use_alpn = 1;
tls_data->match_mode.tls_match_alpn = 1;
} else {
tls_data->sni_hostname_list = list;
if(tls_data->use_alpn == 1)
tls_data->use_alpn = 2;
else
tls_data->use_alpn = 0;
tls_data->match_mode.tls_match_sni = 1;
}
return tls_data;
+10
View File
@@ -35,4 +35,14 @@ int parse_tls_header(const struct TLSProtocol *tls_data, const char *data, size_
struct TLSProtocol *new_tls_data();
struct TLSProtocol *tls_data_set_list(struct TLSProtocol *, int, char**);
#define TLS_MATCH 1
#define TLS_NOMATCH 0
#define TLS_EINVAL -1 /* Invalid parameter (NULL data pointer) */
#define TLS_ELENGTH -2 /* Incomplete request */
#define TLS_EVERSION -3 /* TLS version that cannot be parsed */
#define TLS_ENOEXT -4 /* No ALPN or SNI extension found */
#define TLS_EPROTOCOL -5 /* Protocol error */
#endif