/* # 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 #endif #ifdef LIBPCRE #include #endif #include "common.h" #include "probe.h" const char* USAGE_STRING = "sslh " VERSION "\n" \ "usage:\n" \ "\tsslh [-v] [-i] [-V] [-f] [-n] [--transparent] [-F ]\n" "\t[-t ] [-P ] -u -p [-p ...] \n" \ "%s\n\n" /* Dynamically built list of builtin protocols */ \ "\t[--on-timeout ]\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 ]\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_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; char** sni_hostname_list; if(!config_items || !config_setting_is_array(config_items)) { fprintf(stderr, "%s: no %s specified\n", p->description, name); return; } num_probes = config_setting_length(config_items); if (!num_probes) { fprintf(stderr, "%s: no %s specified\n", p->description, name); return; } max_server_name_len = 0; for (i = 0; i < num_probes; i++) { server_name_len = strlen(config_setting_get_string_elem(config_items, 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); for (i = 0; i < num_probes; i++) { config_item = config_setting_get_string_elem(config_items, i); sni_hostname_list[i] = malloc(max_server_name_len); strcpy (sni_hostname_list[i], config_item); if(verbose) fprintf(stderr, "%s: %s[%d]: %s\n", p->description, name, i, sni_hostname_list[i]); } p->data = (void*)tls_data_set_list(p->data, alpn, sni_hostname_list); } static void setup_sni_alpn(struct proto *p, config_setting_t* sni_hostnames, config_setting_t* alpn_protocols) { p->data = (void*)new_tls_data(); p->probe = get_probe("sni_alpn"); setup_sni_alpn_list(p, sni_hostnames, "sni_hostnames", 0); setup_sni_alpn_list(p, alpn_protocols, "alpn_protocols", 1); } #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, *alpn_protocols; 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 || !strcmp(name, "sni_alpn")) { 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/ALPN */ if (!strcmp(name, "tls")) { sni_hostnames = config_setting_get_member(prot, "sni_hostnames"); alpn_protocols = config_setting_get_member(prot, "alpn_protocols"); if((sni_hostnames && config_setting_is_array(sni_hostnames)) || (alpn_protocols && config_setting_is_array(alpn_protocols))) { setup_sni_alpn(p, sni_hostnames, alpn_protocols); } } } } } 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; }