Add support for wildcard ALPN/SNI values

This commit is contained in:
Yves Rutschlé 2017-06-12 21:05:12 +00:00
commit 21f524f711
9 changed files with 159 additions and 154 deletions

View File

@ -67,6 +67,8 @@ version.h:
sslh: sslh-fork sslh-select
$(OBJS): version.h
sslh-fork: version.h $(OBJS) sslh-fork.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-fork sslh-fork.o $(OBJS) $(LIBS)
#strip sslh-fork

View File

@ -37,6 +37,7 @@ int probing_timeout = 2;
int inetd = 0;
int foreground = 0;
int background = 0;
int transparent = 0;
int numeric = 0;
const char *user_name, *pid_file;
@ -47,8 +48,13 @@ struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */
int allow_severity =0, deny_severity = 0;
#endif
typedef enum {
CR_DIE,
CR_WARN
} CR_ACTION;
/* check result and die, printing the offending address and error */
void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall)
{
char buf[NI_MAXHOST];
@ -57,7 +63,9 @@ void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
sprintaddr(buf, sizeof(buf), addr),
syscall,
strerror(errno));
exit(1);
if (act == CR_DIE)
exit(1);
}
}
@ -118,28 +126,28 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
saddr = (struct sockaddr_storage*)addr->ai_addr;
(*sockfd)[i] = socket(saddr->ss_family, SOCK_STREAM, 0);
check_res_dumpdie((*sockfd)[i], addr, "socket");
check_res_dump(CR_DIE, (*sockfd)[i], addr, "socket");
one = 1;
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one));
check_res_dumpdie(res, addr, "setsockopt(SO_REUSEADDR)");
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)");
if (addr->ai_flags & SO_KEEPALIVE) {
res = setsockopt((*sockfd)[i], SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one));
check_res_dumpdie(res, addr, "setsockopt(SO_KEEPALIVE)");
check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)");
printf("set up keepalive\n");
}
if (IP_FREEBIND) {
res = setsockopt((*sockfd)[i], IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one));
check_res_dumpdie(res, addr, "setsockopt(IP_FREEBIND)");
}
check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)");
}
res = bind((*sockfd)[i], addr->ai_addr, addr->ai_addrlen);
check_res_dumpdie(res, addr, "bind");
check_res_dump(CR_DIE, res, addr, "bind");
res = listen ((*sockfd)[i], 50);
check_res_dumpdie(res, addr, "listen");
check_res_dump(CR_DIE, res, addr, "listen");
}
@ -236,7 +244,7 @@ int connect_addr(struct connection *cnx, int fd_from)
for (a = cnx->proto->saddr; a; a = a->ai_next) {
/* When transparent, make sure both connections use the same address family */
if (cnx->proto->transparent && a->ai_family != from.ai_addr->sa_family)
if (transparent && a->ai_family != from.ai_addr->sa_family)
continue;
if (verbose)
fprintf(stderr, "connecting to %s family %d len %d\n",
@ -249,7 +257,7 @@ int connect_addr(struct connection *cnx, int fd_from)
log_message(LOG_ERR, "forward to %s failed:socket: %s\n",
cnx->proto->description, strerror(errno));
} else {
if (cnx->proto->transparent) {
if (transparent) {
res = bind_peer(fd, fd_from);
CHECK_RES_RETURN(res, "bind_peer");
}
@ -434,16 +442,31 @@ char* sprintaddr(char* buf, size_t size, struct addrinfo *a)
/* Turns a hostname and port (or service) into a list of struct addrinfo
* returns 0 on success, -1 otherwise and logs error
*
* *host gets modified
**/
int resolve_split_name(struct addrinfo **out, const char* host, const char* serv)
int resolve_split_name(struct addrinfo **out, char* host, const char* serv)
{
struct addrinfo hint;
char *end;
int res;
memset(&hint, 0, sizeof(hint));
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = SOCK_STREAM;
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
* around IP address */
if (host[0] == '[') {
end = strrchr(host, ']');
if (!end) {
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
}
host++; /* skip first bracket */
*end = 0; /* remove last bracket */
}
res = getaddrinfo(host, serv, &hint, out);
if (res)
log_message(LOG_ERR, "%s `%s:%s'\n", gai_strerror(res), host, serv);
@ -456,7 +479,7 @@ fullname: input string -- it gets clobbered
*/
void resolve_name(struct addrinfo **out, char* fullname)
{
char *serv, *host, *end;
char *serv, *host;
int res;
/* Find port */
@ -470,17 +493,6 @@ void resolve_name(struct addrinfo **out, char* fullname)
host = fullname;
/* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets
* around IP address */
if (host[0] == '[') {
end = strrchr(host, ']');
if (!end) {
fprintf(stderr, "%s: no closing bracket in IPv6 address?\n", host);
}
host++; /* skip first bracket */
*end = 0; /* remove last bracket */
}
res = resolve_split_name(out, host, serv);
if (res) {
fprintf(stderr, "%s `%s'\n", gai_strerror(res), fullname);

View File

@ -106,14 +106,15 @@ void drop_privileges(const char* user_name);
void write_pid_file(const char* pidfile);
void log_message(int type, char* msg, ...);
void dump_connection(struct connection *cnx);
int resolve_split_name(struct addrinfo **out, const char* hostname, const char* port);
int resolve_split_name(struct addrinfo **out, char* hostname, const char* port);
int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list);
int defer_write(struct queue *q, void* data, int data_size);
int flush_deferred(struct queue *q);
extern int probing_timeout, verbose, inetd, foreground, background, numeric;
extern int probing_timeout, verbose, inetd, foreground,
background, transparent, numeric;
extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn;
extern struct addrinfo *addr_listen;
extern const char* USAGE_STRING;

View File

@ -74,6 +74,9 @@ protocols:
{ 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" ]; },
# Let's Encrypt (tls-sni-* challenges)
{ name: "tls"; host: "localhost"; port: "letsencrypt-client"; sni_hostnames: [ "*.*.acme.invalid" ]; log_level: 0;},
# Catch-all
{ name: "regex"; host: "localhost"; port: "443"; regex_patterns: [ "" ]; },

20
probe.c
View File

@ -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 transparent probe */
{ "ssh", "sshd", NULL, 1, 0, 0, is_ssh_protocol},
{ "openvpn", NULL, NULL, 1, 0, 0, is_openvpn_protocol },
{ "tinc", NULL, NULL, 1, 0, 0, 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 }
/* 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 }
};
static struct proto *protocols;

View File

@ -24,7 +24,6 @@ struct proto {
* 1: Log incoming connection
*/
int keepalive; /* 0: No keepalive ; 1: Set Keepalive for this connection */
int transparent; /* 0: opaque proxy ; 1: transparent proxy */
/* function to probe that protocol; parameters are buffer and length
* containing the data to probe, and a pointer to the protocol structure */

View File

@ -61,14 +61,11 @@ const char* USAGE_STRING =
/* Constants for options that have no one-character shorthand */
#define OPT_ONTIMEOUT 257
/* Global setting for transparent proxying */
int g_transparent = 0;
static struct option const_options[] = {
{ "inetd", no_argument, &inetd, 1 },
{ "foreground", no_argument, &foreground, 1 },
{ "background", no_argument, &background, 1 },
{ "transparent", no_argument, &g_transparent, 1 },
{ "transparent", no_argument, &transparent, 1 },
{ "numeric", no_argument, &numeric, 1 },
{ "verbose", no_argument, &verbose, 1 },
{ "user", required_argument, 0, 'u' },
@ -126,16 +123,14 @@ 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%s]\n",
"%s addr: %s. libwrap service: %s log_level: %d family %d %d [%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->transparent ? "transparent" : ""
);
p->keepalive ? "keepalive" : "");
}
fprintf(stderr, "listening on:\n");
for (a = addr_listen; a; a = a->ai_next) {
@ -312,7 +307,6 @@ 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, "transparent", &p->transparent);
if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) {
p->log_level = 1;
@ -382,7 +376,7 @@ static int config_parse(char *filename, struct addrinfo **listen, struct proto *
config_lookup_bool(&config, "inetd", &inetd);
config_lookup_bool(&config, "foreground", &foreground);
config_lookup_bool(&config, "numeric", &numeric);
config_lookup_bool(&config, "transparent", &g_transparent);
config_lookup_bool(&config, "transparent", &transparent);
if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) {
probing_timeout = timeout;

View File

@ -5,9 +5,8 @@
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 */
/* 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);
strcpy(conn, hostname);
strcat(conn, ":");
@ -18,136 +17,130 @@ static char* resolve_listen(const char *hostname, const char *port) {
}
static int get_listen_from_conf(const char *filename, char **listen) {
static int get_listen_from_conf(const char *filename, char **listen[]) {
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
return -1;
}
} else {
setting = config_lookup(&config, "listen");
if (setting) {
len = config_setting_length(setting);
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
return -1;
} else {
listen[i] = malloc(strlen(resolve_listen(hostname, port)));
strcpy(listen[i], resolve_listen(hostname, port));
}
}
} else {
setting = config_lookup(&config, "listen");
if (setting) {
len = config_setting_length(setting);
*listen = malloc(len * sizeof(**listen));
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
(*listen)[i] = malloc(strlen(resolve_listen(hostname, port)));
strcpy((*listen)[i], resolve_listen(hostname, port));
}
}
}
}
}
return len;
return len;
}
static int write_socket_unit(FILE *socket, char **listen, int num_addr, const char *source) {
static int write_socket_unit(FILE *socket, char *listen[], int num_addr, const char *source) {
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"SourcePath=%s\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"SourcePath=%s\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
return 0;
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
}
}
}
fclose(config);
fclose(config);
num_addr = get_listen_from_conf(sslh_conf, &listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir != "") {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
}
num_addr = get_listen_from_conf(sslh_conf, listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir != "") {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
}
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}

3
tls.c
View File

@ -30,6 +30,7 @@
*/
#include <stdio.h>
#include <stdlib.h> /* malloc() */
#include <fnmatch.h> /* fnmatch() */
#include "tls.h"
#define TLS_HEADER_LEN 5
@ -290,7 +291,7 @@ has_match(char** list, const char* name, size_t name_len) {
for (item = list; *item; item++) {
if (verbose) fprintf(stderr, "matching [%.*s] with [%s]\n", (int)name_len, name, *item);
if(!strncmp(*item, name, name_len)) {
if(!fnmatch(*item, name, 0)) {
return 1;
}
}