mirror of
https://github.com/moparisthebest/sslh
synced 2024-12-21 23:08:58 -05:00
603 lines
18 KiB
C
603 lines
18 KiB
C
/*
|
|
# main: processing of config file, command line options and start the main
|
|
# loop.
|
|
#
|
|
# Copyright (C) 2007-2014 Yves Rutschle
|
|
#
|
|
# This program is free software; you can redistribute it
|
|
# and/or modify it under the terms of the GNU General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be
|
|
# useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
# PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# The full text for the General Public License is here:
|
|
# http://www.gnu.org/licenses/gpl.html
|
|
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#ifdef LIBCONFIG
|
|
#include <libconfig.h>
|
|
#endif
|
|
#ifdef LIBPCRE
|
|
#include <regex.h>
|
|
#endif
|
|
|
|
#include "common.h"
|
|
#include "probe.h"
|
|
|
|
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> -p <add> [-p <addr> ...] \n" \
|
|
"%s\n\n" /* Dynamically built list of builtin protocols */ \
|
|
"\t[--on-timeout <addr>]\n" \
|
|
"-v: verbose\n" \
|
|
"-V: version\n" \
|
|
"-f: foreground\n" \
|
|
"-n: numeric output\n" \
|
|
"-u: specify under which user to run\n" \
|
|
"--transparent: behave as a transparent proxy\n" \
|
|
"-F: use configuration file\n" \
|
|
"--on-timeout: connect to specified address upon timeout (default: ssh address)\n" \
|
|
"-t: seconds to wait before connecting to --on-timeout address.\n" \
|
|
"-p: address and port to listen on.\n Can be used several times to bind to several addresses.\n" \
|
|
"--[ssh,ssl,...]: where to connect connections from corresponding protocol.\n" \
|
|
"-P: PID file.\n" \
|
|
"-i: Run as a inetd service.\n" \
|
|
"";
|
|
|
|
/* Constants for options that have no one-character shorthand */
|
|
#define OPT_ONTIMEOUT 257
|
|
|
|
static struct option const_options[] = {
|
|
{ "inetd", no_argument, &inetd, 1 },
|
|
{ "foreground", no_argument, &foreground, 1 },
|
|
{ "background", no_argument, &background, 1 },
|
|
{ "transparent", no_argument, &transparent, 1 },
|
|
{ "numeric", no_argument, &numeric, 1 },
|
|
{ "verbose", no_argument, &verbose, 1 },
|
|
{ "user", required_argument, 0, 'u' },
|
|
{ "config", optional_argument, 0, 'F' },
|
|
{ "pidfile", required_argument, 0, 'P' },
|
|
{ "timeout", required_argument, 0, 't' },
|
|
{ "on-timeout", required_argument, 0, OPT_ONTIMEOUT },
|
|
{ "listen", required_argument, 0, 'p' },
|
|
{}
|
|
};
|
|
static struct option* all_options;
|
|
static struct proto* builtins;
|
|
static const char *optstr = "vt:T:p:VP:F::";
|
|
|
|
|
|
|
|
static void print_usage(void)
|
|
{
|
|
struct proto *p;
|
|
int i;
|
|
int res;
|
|
char *prots = "";
|
|
|
|
p = get_builtins();
|
|
for (i = 0; i < get_num_builtins(); i++) {
|
|
res = asprintf(&prots, "%s\t[--%s <addr>]\n", prots, p[i].description);
|
|
CHECK_RES_DIE(res, "asprintf");
|
|
}
|
|
|
|
fprintf(stderr, USAGE_STRING, prots);
|
|
}
|
|
|
|
static void printcaps(void) {
|
|
#ifdef LIBCAP
|
|
cap_t caps;
|
|
char* desc;
|
|
ssize_t len;
|
|
|
|
caps = cap_get_proc();
|
|
|
|
desc = cap_to_text(caps, &len);
|
|
|
|
fprintf(stderr, "capabilities: %s\n", desc);
|
|
|
|
cap_free(caps);
|
|
cap_free(desc);
|
|
#endif
|
|
}
|
|
|
|
static void printsettings(void)
|
|
{
|
|
char buf[NI_MAXHOST];
|
|
struct addrinfo *a;
|
|
struct proto *p;
|
|
|
|
for (p = get_first_protocol(); p; p = p->next) {
|
|
fprintf(stderr,
|
|
"%s addr: %s. libwrap service: %s log_level: %d family %d %d\n",
|
|
p->description,
|
|
sprintaddr(buf, sizeof(buf), p->saddr),
|
|
p->service,
|
|
p->log_level,
|
|
p->saddr->ai_family,
|
|
p->saddr->ai_addr->sa_family);
|
|
}
|
|
fprintf(stderr, "listening on:\n");
|
|
for (a = addr_listen; a; a = a->ai_next) {
|
|
fprintf(stderr, "\t%s\n", sprintaddr(buf, sizeof(buf), a));
|
|
}
|
|
fprintf(stderr, "timeout: %d\non-timeout: %s\n", probing_timeout,
|
|
timeout_protocol()->description);
|
|
}
|
|
|
|
|
|
/* Extract configuration on addresses and ports on which to listen.
|
|
* out: newly allocated list of addrinfo to listen to
|
|
*/
|
|
#ifdef LIBCONFIG
|
|
static int config_listen(config_t *config, struct addrinfo **listen)
|
|
{
|
|
config_setting_t *setting, *addr;
|
|
int len, i;
|
|
const char *hostname, *port;
|
|
|
|
setting = config_lookup(config, "listen");
|
|
if (setting) {
|
|
len = config_setting_length(setting);
|
|
for (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;
|
|
}
|
|
|
|
resolve_split_name(listen, hostname, port);
|
|
|
|
/* getaddrinfo returned a list of addresses corresponding to the
|
|
* specification; move the pointer to the end of that list before
|
|
* processing the next specification */
|
|
for (; *listen; listen = &((*listen)->ai_next));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef LIBCONFIG
|
|
static void setup_regex_probe(struct proto *p, config_setting_t* probes)
|
|
{
|
|
#ifdef LIBPCRE
|
|
int num_probes, errsize, i, res;
|
|
char *err;
|
|
const char * expr;
|
|
regex_t** probe_list;
|
|
|
|
num_probes = config_setting_length(probes);
|
|
if (!num_probes) {
|
|
fprintf(stderr, "%s: no probes specified\n", p->description);
|
|
exit(1);
|
|
}
|
|
|
|
p->probe = get_probe("regex");
|
|
probe_list = calloc(num_probes + 1, sizeof(*probe_list));
|
|
p->data = (void*)probe_list;
|
|
|
|
for (i = 0; i < num_probes; i++) {
|
|
probe_list[i] = malloc(sizeof(*(probe_list[i])));
|
|
expr = config_setting_get_string_elem(probes, i);
|
|
res = regcomp(probe_list[i], expr, 0);
|
|
if (res) {
|
|
err = malloc(errsize = regerror(res, probe_list[i], NULL, 0));
|
|
regerror(res, probe_list[i], err, errsize);
|
|
fprintf(stderr, "%s:%s\n", expr, err);
|
|
free(err);
|
|
exit(1);
|
|
}
|
|
}
|
|
#else
|
|
fprintf(stderr, "line %d: regex probe specified but not compiled in\n", config_setting_source_line(probes));
|
|
exit(5);
|
|
#endif
|
|
}
|
|
#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, *patterns, *sni_hostnames;
|
|
const char *hostname, *port, *name;
|
|
int i, num_prots;
|
|
struct proto *p, *prev = NULL;
|
|
|
|
setting = config_lookup(config, "protocols");
|
|
if (setting) {
|
|
num_prots = config_setting_length(setting);
|
|
for (i = 0; i < num_prots; i++) {
|
|
p = calloc(1, sizeof(*p));
|
|
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) &&
|
|
config_setting_lookup_string(prot, "host", &hostname) &&
|
|
config_setting_lookup_string(prot, "port", &port)
|
|
)) {
|
|
p->description = name;
|
|
config_setting_lookup_string(prot, "service", &(p->service));
|
|
|
|
if (config_setting_lookup_int(prot, "log_level", &p->log_level) == CONFIG_FALSE) {
|
|
p->log_level = 1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* Parses a config file
|
|
* in: *filename
|
|
* out: *listen, a newly-allocated linked list of listen addrinfo
|
|
* *prots, a newly-allocated linked list of protocols
|
|
* 1 on error, 0 on success
|
|
*/
|
|
#ifdef LIBCONFIG
|
|
static int config_parse(char *filename, struct addrinfo **listen, struct proto **prots)
|
|
{
|
|
config_t config;
|
|
int timeout;
|
|
const char* str;
|
|
|
|
config_init(&config);
|
|
if (config_read_file(&config, filename) == CONFIG_FALSE) {
|
|
/* If it's a parse error then there will be a line number for the failure
|
|
* an I/O error (such as non-existent file) will have the error line as 0
|
|
*/
|
|
if (config_error_line(&config) != 0) {
|
|
fprintf(stderr, "%s:%d:%s\n",
|
|
filename,
|
|
config_error_line(&config),
|
|
config_error_text(&config));
|
|
exit(1);
|
|
}
|
|
fprintf(stderr, "%s:%s\n",
|
|
filename,
|
|
config_error_text(&config));
|
|
return 1;
|
|
}
|
|
|
|
config_lookup_bool(&config, "verbose", &verbose);
|
|
config_lookup_bool(&config, "inetd", &inetd);
|
|
config_lookup_bool(&config, "foreground", &foreground);
|
|
config_lookup_bool(&config, "numeric", &numeric);
|
|
config_lookup_bool(&config, "transparent", &transparent);
|
|
|
|
if (config_lookup_int(&config, "timeout", (int *)&timeout) == CONFIG_TRUE) {
|
|
probing_timeout = timeout;
|
|
}
|
|
|
|
if (config_lookup_string(&config, "on-timeout", &str)) {
|
|
set_ontimeout(str);
|
|
}
|
|
|
|
config_lookup_string(&config, "user", &user_name);
|
|
config_lookup_string(&config, "pidfile", &pid_file);
|
|
|
|
config_listen(&config, listen);
|
|
config_protocols(&config, prots);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* Adds protocols to the list of options, so command-line parsing uses the
|
|
* protocol definition array
|
|
* options: array of options to add to; must be big enough
|
|
* n_opts: number of options in *options before calling (i.e. where to append)
|
|
* prot: array of protocols
|
|
* n_prots: number of protocols in *prot
|
|
* */
|
|
static void append_protocols(struct option *options, int n_opts, struct proto *prot , int n_prots)
|
|
{
|
|
int o, p;
|
|
|
|
for (o = n_opts, p = 0; p < n_prots; o++, p++) {
|
|
options[o].name = prot[p].description;
|
|
options[o].has_arg = required_argument;
|
|
options[o].flag = 0;
|
|
options[o].val = p + PROT_SHIFT;
|
|
}
|
|
}
|
|
|
|
static void make_alloptions(void)
|
|
{
|
|
builtins = get_builtins();
|
|
|
|
/* 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));
|
|
memcpy(all_options, const_options, sizeof(const_options));
|
|
append_protocols(all_options, ARRAY_SIZE(const_options) - 1, builtins, get_num_builtins());
|
|
}
|
|
|
|
/* Performs a first scan of command line options to see if a configuration file
|
|
* is specified. If there is one, parse it now before all other options (so
|
|
* configuration file settings can be overridden from the command line).
|
|
*
|
|
* prots: newly-allocated list of configured protocols, if any.
|
|
*/
|
|
static void cmdline_config(int argc, char* argv[], struct proto** prots)
|
|
{
|
|
#ifdef LIBCONFIG
|
|
int c, res;
|
|
char *config_filename;
|
|
#endif
|
|
|
|
make_alloptions();
|
|
|
|
#ifdef LIBCONFIG
|
|
optind = 1;
|
|
opterr = 0; /* we're missing protocol options at this stage so don't output errors */
|
|
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
|
|
if (c == 'v') {
|
|
verbose++;
|
|
}
|
|
if (c == 'F') {
|
|
config_filename = optarg;
|
|
if (config_filename) {
|
|
res = config_parse(config_filename, &addr_listen, prots);
|
|
} else {
|
|
/* No configuration file specified -- try default file locations */
|
|
res = config_parse("/etc/sslh/sslh.cfg", &addr_listen, prots);
|
|
if (!res && verbose) fprintf(stderr, "Using /etc/sslh/sslh.cfg\n");
|
|
if (res) {
|
|
res = config_parse("/etc/sslh.cfg", &addr_listen, prots);
|
|
if (!res && verbose) fprintf(stderr, "Using /etc/sslh.cfg\n");
|
|
}
|
|
}
|
|
if (res)
|
|
exit(4);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Parse command-line options. prots points to a list of configured protocols,
|
|
* potentially non-allocated */
|
|
static void parse_cmdline(int argc, char* argv[], struct proto* prots)
|
|
{
|
|
int c;
|
|
struct addrinfo **a;
|
|
struct proto *p;
|
|
|
|
optind = 1;
|
|
opterr = 1;
|
|
next_arg:
|
|
while ((c = getopt_long_only(argc, argv, optstr, all_options, NULL)) != -1) {
|
|
if (c == 0) continue;
|
|
|
|
if (c >= PROT_SHIFT) {
|
|
if (prots)
|
|
for (p = prots; p && p->next; p = p->next) {
|
|
/* override if protocol was already defined by config file
|
|
* (note it only overrides address and use builtin probe) */
|
|
if (!strcmp(p->description, builtins[c-PROT_SHIFT].description)) {
|
|
resolve_name(&(p->saddr), optarg);
|
|
p->probe = builtins[c-PROT_SHIFT].probe;
|
|
goto next_arg;
|
|
}
|
|
}
|
|
/* At this stage, it's a new protocol: add it to the end of the
|
|
* list */
|
|
if (!prots) {
|
|
/* No protocols yet -- create the list */
|
|
p = prots = calloc(1, sizeof(*p));
|
|
} else {
|
|
p->next = calloc(1, sizeof(*p));
|
|
p = p->next;
|
|
}
|
|
memcpy(p, &builtins[c-PROT_SHIFT], sizeof(*p));
|
|
resolve_name(&(p->saddr), optarg);
|
|
continue;
|
|
}
|
|
|
|
switch (c) {
|
|
|
|
case 'F':
|
|
/* Legal option, but do nothing, it was already processed in
|
|
* cmdline_config() */
|
|
#ifndef LIBCONFIG
|
|
fprintf(stderr, "Built without libconfig support: configuration file not available.\n");
|
|
exit(1);
|
|
#endif
|
|
break;
|
|
|
|
case 't':
|
|
probing_timeout = atoi(optarg);
|
|
break;
|
|
|
|
case OPT_ONTIMEOUT:
|
|
set_ontimeout(optarg);
|
|
break;
|
|
|
|
case 'p':
|
|
/* find the end of the listen list */
|
|
for (a = &addr_listen; *a; a = &((*a)->ai_next));
|
|
/* append the specified addresses */
|
|
resolve_name(a, optarg);
|
|
|
|
break;
|
|
|
|
case 'V':
|
|
printf("%s %s\n", server_type, VERSION);
|
|
exit(0);
|
|
|
|
case 'u':
|
|
user_name = optarg;
|
|
break;
|
|
|
|
case 'P':
|
|
pid_file = optarg;
|
|
break;
|
|
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
|
|
default:
|
|
print_usage();
|
|
exit(2);
|
|
}
|
|
}
|
|
|
|
if (!prots) {
|
|
fprintf(stderr, "At least one target protocol must be specified.\n");
|
|
exit(2);
|
|
}
|
|
|
|
set_protocol_list(prots);
|
|
|
|
if (!addr_listen && !inetd) {
|
|
fprintf(stderr, "No listening address specified; use at least one -p option\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Did command-line override foreground setting? */
|
|
if (background)
|
|
foreground = 0;
|
|
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
|
|
extern char *optarg;
|
|
extern int optind;
|
|
int res, num_addr_listen;
|
|
struct proto* protocols = NULL;
|
|
|
|
int *listen_sockets;
|
|
|
|
/* Init defaults */
|
|
pid_file = NULL;
|
|
user_name = NULL;
|
|
|
|
cmdline_config(argc, argv, &protocols);
|
|
parse_cmdline(argc, argv, protocols);
|
|
|
|
if (inetd)
|
|
{
|
|
verbose = 0;
|
|
start_shoveler(0);
|
|
exit(0);
|
|
}
|
|
|
|
if (verbose)
|
|
printsettings();
|
|
|
|
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
|
|
|
|
if (!foreground) {
|
|
if (fork() > 0) exit(0); /* Detach */
|
|
|
|
/* New session -- become group leader */
|
|
if (getuid() == 0) {
|
|
res = setsid();
|
|
CHECK_RES_DIE(res, "setsid: already process leader");
|
|
}
|
|
}
|
|
|
|
setup_signals();
|
|
|
|
if (pid_file)
|
|
write_pid_file(pid_file);
|
|
|
|
if (user_name)
|
|
drop_privileges(user_name);
|
|
|
|
|
|
/* Open syslog connection */
|
|
setup_syslog(argv[0]);
|
|
|
|
if (verbose)
|
|
printcaps();
|
|
|
|
main_loop(listen_sockets, num_addr_listen);
|
|
|
|
return 0;
|
|
}
|