1
0
mirror of https://github.com/moparisthebest/sslh synced 2024-11-21 08:35:01 -05:00

Added support for RFC4366 SNI (Server Name Indication). Changed configuration file format accordingly.

This commit is contained in:
Yves Rutschle 2015-07-17 15:05:06 +02:00
commit ca461ea077
10 changed files with 409 additions and 49 deletions

View File

@ -2,6 +2,14 @@ vNEXT:
Added USELIBPCRE to make use of regex engine
optional.
Added support for RFC4366 SNI
(Travis Burtrum)
Changed configuration file format: 'probe' field is
no longer required, 'name' field can now contain
'sni' or 'regex', with corresponding options (see
example.org)
v1.17: 09MAR2015
Support RFC5952-style IPv6 addresses, e.g. [::]:443.

View File

@ -23,7 +23,7 @@ CC ?= gcc
CFLAGS ?=-Wall -g $(CFLAGS_COV)
LIBS=
OBJS=common.o sslh-main.o probe.o
OBJS=common.o sslh-main.o probe.o tls.o
ifneq ($(strip $(USELIBWRAP)),)
LIBS:=$(LIBS) -lwrap
@ -63,7 +63,7 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
#strip sslh-select
echosrv: $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o $(LIBS)
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
$(MAN): sslh.pod Makefile
pod2man --section=8 --release=$(VERSION) --center=" " sslh.pod | gzip -9 - > $(MAN)

View File

@ -19,11 +19,11 @@ listen:
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
{ name: "ssl"; host: "localhost"; port: "443"; probe: "builtin"; },
{ name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "ssl"; host: "localhost"; port: "443"; },
{ name: "anyprot"; host: "localhost"; port: "443"; }
);

View File

@ -23,30 +23,48 @@ listen:
# List of protocols
#
# Each protocol entry consists of:
# name: name of the protocol
# name: name of the probe. These are listed on the command
# line (ssh -?), plus 'regex', 'sni' and 'timeout'.
# service: (optional) libwrap service name (see hosts_access(5))
# host: host name to connect that protocol
# port: port number to connect that protocol
# probe: "builtin" or a list of regular expressions
# (can be left out, e.g. to use with on-timeout)
# host, port: where to connect when this probe succeeds
#
# Probe-specific options:
# sni:
# sni_hotnames: list of FQDN for that target
# 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 'sni'.
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: [ "jabber" ]; },
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
{ name: "ssl"; host: "localhost"; port: "443"; probe: [ "" ]; },
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; },
{ name: "http"; host: "localhost"; port: "80"; },
{ name: "sni"; host: "localhost"; port: "993"; sni_hostnames: [ "mail.rutschle.net", "mail.englishintoulouse.com" ]; },
{ name: "sni"; host: "localhost"; port: "xmpp-client"; sni_hostnames: [ "im.rutschle.net", "im.englishintoulouse.com" ]; },
# OpenVPN
{ name: "regex"; host: "localhost"; port: "1194"; regex_patterns: [ "^\x00[\x0D-\xFF]$", "^\x00[\x0D-\xFF]\x38" ]; },
# Jabber
{ name: "regex"; host: "localhost"; port: "5222"; regex_patterns: [ "jabber" ]; },
# Catch-all
{ name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; },
# Where to connect in case of timeout (defaults to ssh)
{ name: "timeout"; service: "daytime"; host: "localhost"; port: "daytime"; }
);
# Optionally, specify to which protocol to connect in case
# of timeout (defaults to "ssh").
# You can timeout to any arbitrary address by setting a
# protocol with no probe, as is the case with this example.
# You can timeout to any arbitrary address by setting an
# entry in 'protocols' named "timeout".
# This enables you to set a tcpd service name for this
# protocol too.
on-timeout: "timeout";

39
probe.c
View File

@ -1,7 +1,7 @@
/*
# probe.c: Code for probing protocols
#
# Copyright (C) 2007-2012 Yves Rutschle
# Copyright (C) 2007-2015 Yves Rutschle
#
# This program is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
@ -217,6 +217,34 @@ static int is_http_protocol(const char *p, int len, struct proto *proto)
return PROBE_NEXT;
}
static int is_sni_protocol(const char *p, int len, struct proto *proto)
{
int valid_tls;
char *hostname;
char **sni_hostname;
valid_tls = parse_tls_header(p, len, &hostname);
if(valid_tls < 0)
return -1 == valid_tls ? PROBE_AGAIN : PROBE_NEXT;
if (verbose) fprintf(stderr, "sni hostname: %s\n", hostname);
/* Assume does not match */
valid_tls = PROBE_NEXT;
for (sni_hostname = proto->data; *sni_hostname; sni_hostname++) {
fprintf(stderr, "matching [%s] with [%s]\n", hostname, *sni_hostname);
if(!strcmp(hostname, *sni_hostname)) {
valid_tls = PROBE_MATCH;
break;
}
}
free(hostname);
return valid_tls;
}
static int is_tls_protocol(const char *p, int len, struct proto *proto)
{
if (len < 3)
@ -335,6 +363,15 @@ T_PROBE* get_probe(const char* description) {
if (!strcmp(description, "regex"))
return regex_probe;
/* Special case of "sni" probe for same reason as above*/
if (!strcmp(description, "sni"))
return is_sni_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"))
return is_true;
return NULL;
}

View File

@ -4,6 +4,7 @@
#define __PROBE_H_
#include "common.h"
#include "tls.h"
typedef enum {
PROBE_NEXT, /* Enough data, probe failed -- it's some other protocol */
@ -23,7 +24,7 @@ struct proto {
/* function to probe that protocol; parameters are buffer and length
* containing the data to probe, and a pointer to the protocol structure */
T_PROBE* probe;
void* data; /* opaque pointer ; used to pass list of regex to regex probe */
void* data; /* opaque pointer ; used to pass list of regex to regex probe, or sni hostnames to sni probe */
struct proto *next; /* pointer to next protocol in list, NULL if last */
};

View File

@ -211,13 +211,45 @@ static void setup_regex_probe(struct proto *p, config_setting_t* probes)
}
#endif
#ifdef LIBCONFIG
static void setup_sni_hostnames(struct proto *p, config_setting_t* sni_hostnames)
{
int num_probes, i, max_server_name_len, server_name_len;
const char * sni_hostname;
char** sni_hostname_list;
num_probes = config_setting_length(sni_hostnames);
if (!num_probes) {
fprintf(stderr, "%s: no sni_hostnames specified\n", p->description);
exit(1);
}
max_server_name_len = 0;
for (i = 0; i < num_probes; i++) {
server_name_len = strlen(config_setting_get_string_elem(sni_hostnames, i));
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);
p->data = (void*)sni_hostname_list;
for (i = 0; i < num_probes; i++) {
sni_hostname = config_setting_get_string_elem(sni_hostnames, i);
sni_hostname_list[i] = malloc(max_server_name_len);
strcpy (sni_hostname_list[i], sni_hostname);
if(verbose) fprintf(stderr, "sni_hostnames[%d]: %s\n", i, sni_hostname_list[i]);
}
}
#endif
/* Extract configuration for protocols to connect to.
* out: newly-allocated list of protocols
*/
#ifdef LIBCONFIG
static int config_protocols(config_t *config, struct proto **prots)
{
config_setting_t *setting, *prot, *probes;
config_setting_t *setting, *prot, *patterns, *sni_hostnames;
const char *hostname, *port, *name;
int i, num_prots;
struct proto *p, *prev = NULL;
@ -241,28 +273,25 @@ static int config_protocols(config_t *config, struct proto **prots)
resolve_split_name(&(p->saddr), hostname, port);
p->probe = get_probe(name);
if (!p->probe) {
fprintf(stderr, "line %d: %s: probe unknown\n", config_setting_source_line(prot), name);
exit(1);
}
probes = config_setting_get_member(prot, "probe");
if (probes) {
if (config_setting_is_array(probes)) {
/* If 'probe' is an array, setup a regex probe using the
* array of strings as pattern */
/* Probe-specific options: regex patterns */
if (!strcmp(name, "regex")) {
patterns = config_setting_get_member(prot, "regex_patterns");
if (patterns && config_setting_is_array(patterns)) {
setup_regex_probe(p, patterns);
}
}
setup_regex_probe(p, probes);
} else {
/* if 'probe' is 'builtin', set the probe to the
* appropriate builtin protocol */
if (!strcmp(config_setting_get_string(probes), "builtin")) {
p->probe = get_probe(name);
if (!p->probe) {
fprintf(stderr, "%s: no builtin probe for this protocol\n", name);
exit(1);
}
} else {
fprintf(stderr, "%s: illegal probe name\n", name);
exit(1);
}
/* Probe-specific options: SNI hostnames */
if (!strcmp(name, "sni")) {
sni_hostnames = config_setting_get_member(prot, "sni_hostnames");
if (sni_hostnames && config_setting_is_array(sni_hostnames)) {
setup_sni_hostnames(p, sni_hostnames);
}
}
}

View File

@ -51,14 +51,10 @@ and the list of protocols).
The configuration file makes it possible to specify
protocols using regular expressions: a list of regular
expressions is given as the I<probe> parameter, and if the
expressions is given as the I<regex_patterns> parameter, and if the
first packet received from the client matches any of these
expressions, B<sslh> connects to that protocol.
Alternatively, the I<probe> parameter can be set to
"builtin", to use the compiled probes which are much faster
than regular expressions.
=head2 Probing protocols
When receiving an incoming connection, B<sslh> will read the

238
tls.c Normal file
View File

@ -0,0 +1,238 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This is a minimal TLS implementation intended only to parse the server name
* extension. This was created based primarily on Wireshark dissection of a
* TLS handshake and RFC4366.
*/
#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include <string.h> /* strncpy() */
#include <sys/socket.h>
#include "tls.h"
#define TLS_HEADER_LEN 5
#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01
#ifndef MIN
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
static int parse_extensions(const char *, size_t, char **);
static int parse_server_name_extension(const char *, size_t, char **);
const char tls_alert[] = {
0x15, /* TLS Alert */
0x03, 0x01, /* TLS version */
0x00, 0x02, /* Payload length */
0x02, 0x28, /* Fatal, handshake failure */
};
/* Parse a TLS packet for the Server Name Indication extension in the client
* hello handshake, returning the first servername found (pointer to static
* array)
*
* 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 - malloc failure
* < -4 - Invalid TLS client hello
*/
int
parse_tls_header(const char *data, size_t data_len, char **hostname) {
char tls_content_type;
char tls_version_major;
char tls_version_minor;
size_t pos = TLS_HEADER_LEN;
size_t len;
if (hostname == NULL)
return -3;
/* Check that our TCP payload is at least large enough for a TLS header */
if (data_len < TLS_HEADER_LEN)
return -1;
/* SSL 2.0 compatible Client Hello
*
* High bit of first byte (length) and content type is Client Hello
*
* See RFC5246 Appendix E.2
*/
if (data[0] & 0x80 && data[2] == 1) {
if (verbose) fprintf(stderr, "Received SSL 2.0 Client Hello which can not support SNI.\n");
return -2;
}
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;
}
tls_version_major = data[1];
tls_version_minor = data[2];
if (tls_version_major < 3) {
if (verbose) fprintf(stderr, "Received SSL %d.%d handshake which which can not support SNI.\n",
tls_version_major, tls_version_minor);
return -2;
}
/* TLS record length */
len = ((unsigned char)data[3] << 8) +
(unsigned char)data[4] + TLS_HEADER_LEN;
data_len = MIN(data_len, len);
/* Check we received entire TLS record length */
if (data_len < len)
return -1;
/*
* Handshake
*/
if (pos + 1 > data_len) {
return -5;
}
if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
if (verbose) fprintf(stderr, "Not a client hello\n");
return -5;
}
/* Skip past fixed length records:
1 Handshake Type
3 Length
2 Version (again)
32 Random
to Session ID Length
*/
pos += 38;
/* Session ID */
if (pos + 1 > data_len)
return -5;
len = (unsigned char)data[pos];
pos += 1 + len;
/* Cipher Suites */
if (pos + 2 > data_len)
return -5;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2 + len;
/* Compression Methods */
if (pos + 1 > data_len)
return -5;
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;
}
/* Extensions */
if (pos + 2 > data_len)
return -5;
len = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
pos += 2;
if (pos + len > data_len)
return -5;
return parse_extensions(data + pos, len, hostname);
}
int
parse_extensions(const char *data, size_t data_len, char **hostname) {
size_t pos = 0;
size_t len;
/* Parse each 4 bytes for the extension header */
while (pos + 4 <= data_len) {
/* Extension Length */
len = ((unsigned char)data[pos + 2] << 8) +
(unsigned char)data[pos + 3];
/* Check if it's a server name extension */
if (data[pos] == 0x00 && data[pos + 1] == 0x00) {
/* There can be only one extension of each type, so we break
our state and move p to beinnging of the extension here */
if (pos + 4 + len > data_len)
return -5;
return parse_server_name_extension(data + pos + 4, len, hostname);
}
pos += 4 + len; /* Advance to the next extension header */
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return -2;
}
int
parse_server_name_extension(const char *data, size_t data_len,
char **hostname) {
size_t pos = 2; /* skip server name list length */
size_t len;
while (pos + 3 < data_len) {
len = ((unsigned char)data[pos + 1] << 8) +
(unsigned char)data[pos + 2];
if (pos + 3 + len > data_len)
return -5;
switch (data[pos]) { /* name type */
case 0x00: /* host_name */
*hostname = malloc(len + 1);
if (*hostname == NULL) {
if (verbose) fprintf(stderr, "malloc() failure\n");
return -4;
}
strncpy(*hostname, data + pos + 3, len);
(*hostname)[len] = '\0';
return len;
default:
if (verbose) fprintf(stderr, "Unknown server name extension name type: %d\n",
data[pos]);
}
pos += 3 + len;
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;
return -2;
}

33
tls.h Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TLS_H
#define TLS_H
#include "common.h"
int parse_tls_header(const char *data, size_t data_len, char **hostname);
#endif