diff --git a/Makefile b/Makefile index b1cf9ce..7ebec92 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/common.c b/common.c index 8187b4d..810a610 100644 --- a/common.c +++ b/common.c @@ -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); diff --git a/common.h b/common.h index 1002d23..c83eb91 100644 --- a/common.h +++ b/common.h @@ -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; diff --git a/example.cfg b/example.cfg index 371bcf0..b5cb37a 100644 --- a/example.cfg +++ b/example.cfg @@ -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: [ "" ]; }, diff --git a/probe.c b/probe.c index 22bdc3b..9b4a63e 100644 --- a/probe.c +++ b/probe.c @@ -45,16 +45,16 @@ static int is_true(const char *p, int len, struct proto* proto) { return 1; } /* Table of protocols that have a built-in probe */ static struct proto builtins[] = { - /* description service saddr log_level keepalive 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; diff --git a/probe.h b/probe.h index 492e42f..8c576a2 100644 --- a/probe.h +++ b/probe.h @@ -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 */ diff --git a/sslh-main.c b/sslh-main.c index b72a2c0..37e76d9 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -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; diff --git a/systemd-sslh-generator.c b/systemd-sslh-generator.c index 5909a5b..537d845 100644 --- a/systemd-sslh-generator.c +++ b/systemd-sslh-generator.c @@ -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; } diff --git a/tls.c b/tls.c index 8640dec..fc4ae38 100644 --- a/tls.c +++ b/tls.c @@ -30,6 +30,7 @@ */ #include #include /* malloc() */ +#include /* 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; } }