diff --git a/main.c b/main.c index 177bea3..9486abd 100644 --- a/main.c +++ b/main.c @@ -279,6 +279,307 @@ oom_die(const char *argv0) exit(EXIT_FAILURE); } +static void +set_cacrt(opts_t *opts, const char *argv0, char *optarg) +{ + if (opts->cacrt) + X509_free(opts->cacrt); + opts->cacrt = ssl_x509_load(optarg); + if (!opts->cacrt) { + fprintf(stderr, "%s: error loading CA " + "cert from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } + ssl_x509_refcount_inc(opts->cacrt); + sk_X509_insert(opts->chain, opts->cacrt, 0); + if (!opts->cakey) { + opts->cakey = ssl_key_load(optarg); + } +#ifndef OPENSSL_NO_DH + if (!opts->dh) { + opts->dh = ssl_dh_load(optarg); + } +#endif /* !OPENSSL_NO_DH */ + fprintf(stderr, "cacrt: %s\n", optarg); +} + +static void +set_cakey(opts_t *opts, const char *argv0, char *optarg) +{ + if (opts->cakey) + EVP_PKEY_free(opts->cakey); + opts->cakey = ssl_key_load(optarg); + if (!opts->cakey) { + fprintf(stderr, "%s: error loading CA " + "key from '%s':\n", + argv0, optarg); + if (errno) { + fprintf(stderr, "%s\n", + strerror(errno)); + } else { + ERR_print_errors_fp(stderr); + } + exit(EXIT_FAILURE); + } + if (!opts->cacrt) { + opts->cacrt = ssl_x509_load(optarg); + if (opts->cacrt) { + ssl_x509_refcount_inc( + opts->cacrt); + sk_X509_insert(opts->chain, + opts->cacrt, 0); + } + } +#ifndef OPENSSL_NO_DH + if (!opts->dh) { + opts->dh = ssl_dh_load(optarg); + } +#endif /* !OPENSSL_NO_DH */ + fprintf(stderr, "cakey: %s\n", optarg); +} + +static void +set_user(opts_t *opts, const char *argv0, char *optarg) +{ + if (!sys_isuser(optarg)) { + fprintf(stderr, "%s: '%s' is not an " + "existing user\n", + argv0, optarg); + exit(EXIT_FAILURE); + } + if (opts->dropuser) + free(opts->dropuser); + opts->dropuser = strdup(optarg); + if (!opts->dropuser) + oom_die(argv0); + fprintf(stderr, "dropuser: %s\n", opts->dropuser); +} + +static void +set_group(opts_t *opts, const char *argv0, char *optarg) +{ + if (!sys_isgroup(optarg)) { + fprintf(stderr, "%s: '%s' is not an " + "existing group\n", + argv0, optarg); + exit(EXIT_FAILURE); + } + if (opts->dropgroup) + free(opts->dropgroup); + opts->dropgroup = strdup(optarg); + if (!opts->dropgroup) + oom_die(argv0); + fprintf(stderr, "dropgroup: %s\n", opts->dropgroup); +} + +static void +set_pidfile(opts_t *opts, const char *argv0, char *optarg) +{ + if (opts->pidfile) + free(opts->pidfile); + opts->pidfile = strdup(optarg); + if (!opts->pidfile) + oom_die(argv0); + fprintf(stderr, "pidfile: %s\n", opts->pidfile); +} + +static int +load_conffile(opts_t *opts, const char *argv0, const char *natengine) +{ + FILE *f; + int rv, line_num, found; + size_t line_len; + char *n, *value, *v, *value_end; + char *line, *name; + + f = fopen(opts->conffile, "r"); + if (!f) { + fprintf(stderr, "Error opening conf file %s: %s\n", opts->conffile, strerror(errno)); + return -1; + } + + line = NULL; + line_num = 0; + while (!feof(f)) { + rv = getline(&line, &line_len, f); + if (rv == -1) { + break; + } + if (line == NULL) { + fprintf(stderr, "getline() buf=NULL"); + return -1; + } + line_num++; + + // skip white space + for (name = line; *name == ' ' || *name == '\t'; name++); + + // skip comments and empty lines + if ((name[0] == '\0') || (name[0] == '#') || (name[0] == ';') || + (name[0] == '\r') || (name[0] == '\n')) { + continue; + } + + // skip to the end of option name and terminate it with '\0' + for (n = name;; n++) { + if (*n == ' ' || *n == '\t') { + *n = '\0'; + n++; + break; + } + if (*n == '\0') { + n = NULL; + break; + } + } + + // no value + if (n == NULL) { + fprintf(stderr, "Conf error at line %d\n", line_num); + fclose(f); + if (line) { + free(line); + } + return -1; + } + + // skip white space before value + while (*n == ' ' || *n == '\t') { + n++; + } + + value = n; + + // find end of value and terminate it with '\0' + // find first occurrence of trailing white space + value_end = NULL; + for (v = value;; v++) { + if (*v == '\0') { + break; + } + if (*v == '\r' || *v == '\n') { + *v = '\0'; + break; + } + if (*v == ' ' || *v == '\t') { + if (!value_end) { + value_end = v; + } + } else { + value_end = NULL; + } + } + + if (value_end) { + *value_end = '\0'; + } + + found = 0; + if (!strncasecmp(name, "CACert", 6)) { + set_cacrt(opts, argv0, value); + found = 1; + } else if (!strncasecmp(name, "CAKey", 5)) { + set_cakey(opts, argv0, value); + found = 1; + } else if (!strncasecmp(name, "ProxySpec", 9)) { + char **argv = malloc(strlen(value) + 1); + char **save_argv = argv; + int argc = 0; + char *p, *last; + + for ((p = strtok_r(value, " ", &last)); p; (p = strtok_r(NULL, " ", &last))) { + // Limit max # token + if (argc < 10) { + argv[argc++] = p; + } + } + + proxyspec_parse(&argc, &argv, natengine, opts); + free(save_argv); + found = 1; + } else if (!strncasecmp(name, "ConnIdleTimeout", 15)) { + unsigned int rv = atoi(value); + if (rv >= 10 && rv <= 3600) { + opts->conn_idle_timeout = rv; + } else { + fprintf(stderr, "Invalid ConnIdleTimeout %s at line %d, use 10-3600\n", value, line_num); + } + fprintf(stderr, "ConnIdleTimeout: %u\n", opts->conn_idle_timeout); + found = 1; + } else if (!strncasecmp(name, "ExpiredConnCheckPeriod", 22)) { + unsigned int rv = atoi(value); + if (rv >= 10 && rv <= 60) { + opts->expired_conn_check_period = rv; + } else { + fprintf(stderr, "Invalid ExpiredConnCheckPeriod %s at line %d, use 10-60\n", value, line_num); + } + fprintf(stderr, "ExpiredConnCheckPeriod: %u\n", opts->expired_conn_check_period); + found = 1; + } else if (!strncasecmp(name, "SSLShutdownRetryDelay", 21)) { + unsigned int rv = atoi(value); + if (rv >= 100 && rv <= 10000) { + opts->ssl_shutdown_retry_delay = rv; + } else { + fprintf(stderr, "Invalid SSLShutdownRetryDelay %s at line %d, use 100-10000\n", value, line_num); + } + fprintf(stderr, "SSLShutdownRetryDelay: %u\n", opts->ssl_shutdown_retry_delay); + found = 1; + } else if (!strncasecmp(name, "PidFile", 7)) { + set_pidfile(opts, argv0, value); + found = 1; + } else if (!strncasecmp(name, "LogStats", 8)) { + if (!strncasecmp(value, "yes", 3)) { + opts->statslog = 1; + } else if (!strncasecmp(value, "no", 3)) { + opts->statslog = 0; + } else { + fprintf(stderr, "Invalid LogStats %s at line %d, use yes|no\n", value, line_num); + } + fprintf(stderr, "LogStats: %u\n", opts->statslog); + found = 1; + } else if (!strncasecmp(name, "StatsPeriod", 11)) { + unsigned int rv = atoi(value); + if (rv >= 1 && rv <= 10) { + opts->stats_period = rv; + } else { + fprintf(stderr, "Invalid StatsPeriod %s at line %d, use 1-10\n", value, line_num); + } + fprintf(stderr, "StatsPeriod: %u\n", opts->stats_period); + found = 1; + } else if (!strncasecmp(name, "User", 4)) { + set_user(opts, argv0, value); + found = 1; + } else if (!strncasecmp(name, "Group", 5)) { + set_group(opts, argv0, value); + found = 1; + } + + if (found) { + continue; + } + + fprintf(stderr, "Unknown option '%s' at %s line %d\n", name, opts->conffile, line_num); + fclose(f); + if (line) { + free(line); + } + return -1; + } + + fclose(f); + if (line) { + free(line); + } + return 0; +} + /* * Main entry point. */ @@ -302,66 +603,29 @@ main(int argc, char *argv[]) natengine = NULL; } + // Set defaults + opts->conn_idle_timeout = 120; + opts->expired_conn_check_period = 10; + opts->ssl_shutdown_retry_delay = 100; + opts->log_stats = 0; + opts->stats_period = 1; + while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z OPT_i "k:c:C:K:t:" - "OPs:r:R:e:Eu:m:j:p:l:L:S:F:dD::VhW:w:I")) != -1) { + "OPs:r:R:e:Eu:m:j:p:l:L:S:F:dD::VhW:w:If:")) != -1) { switch (ch) { + case 'f': + if (opts->conffile) + free(opts->conffile); + opts->conffile = strdup(optarg); + if (!opts->conffile) + oom_die(argv0); + fprintf(stderr, "Conf file: %s\n", opts->conffile); + break; case 'c': - if (opts->cacrt) - X509_free(opts->cacrt); - opts->cacrt = ssl_x509_load(optarg); - if (!opts->cacrt) { - fprintf(stderr, "%s: error loading CA " - "cert from '%s':\n", - argv0, optarg); - if (errno) { - fprintf(stderr, "%s\n", - strerror(errno)); - } else { - ERR_print_errors_fp(stderr); - } - exit(EXIT_FAILURE); - } - ssl_x509_refcount_inc(opts->cacrt); - sk_X509_insert(opts->chain, opts->cacrt, 0); - if (!opts->cakey) { - opts->cakey = ssl_key_load(optarg); - } -#ifndef OPENSSL_NO_DH - if (!opts->dh) { - opts->dh = ssl_dh_load(optarg); - } -#endif /* !OPENSSL_NO_DH */ + set_cacrt(opts, argv0, optarg); break; case 'k': - if (opts->cakey) - EVP_PKEY_free(opts->cakey); - opts->cakey = ssl_key_load(optarg); - if (!opts->cakey) { - fprintf(stderr, "%s: error loading CA " - "key from '%s':\n", - argv0, optarg); - if (errno) { - fprintf(stderr, "%s\n", - strerror(errno)); - } else { - ERR_print_errors_fp(stderr); - } - exit(EXIT_FAILURE); - } - if (!opts->cacrt) { - opts->cacrt = ssl_x509_load(optarg); - if (opts->cacrt) { - ssl_x509_refcount_inc( - opts->cacrt); - sk_X509_insert(opts->chain, - opts->cacrt, 0); - } - } -#ifndef OPENSSL_NO_DH - if (!opts->dh) { - opts->dh = ssl_dh_load(optarg); - } -#endif /* !OPENSSL_NO_DH */ + set_cakey(opts, argv0, optarg); break; case 'C': if (ssl_x509chain_load(NULL, &opts->chain, @@ -486,37 +750,13 @@ main(int argc, char *argv[]) exit(EXIT_SUCCESS); break; case 'u': - if (!sys_isuser(optarg)) { - fprintf(stderr, "%s: '%s' is not an " - "existing user\n", - argv0, optarg); - exit(EXIT_FAILURE); - } - if (opts->dropuser) - free(opts->dropuser); - opts->dropuser = strdup(optarg); - if (!opts->dropuser) - oom_die(argv0); + set_user(opts, argv0, optarg); break; case 'm': - if (!sys_isgroup(optarg)) { - fprintf(stderr, "%s: '%s' is not an " - "existing group\n", - argv0, optarg); - exit(EXIT_FAILURE); - } - if (opts->dropgroup) - free(opts->dropgroup); - opts->dropgroup = strdup(optarg); - if (!opts->dropgroup) - oom_die(argv0); + set_group(opts, argv0, optarg); break; case 'p': - if (opts->pidfile) - free(opts->pidfile); - opts->pidfile = strdup(optarg); - if (!opts->pidfile) - oom_die(argv0); + set_pidfile(opts, argv0, optarg); break; case 'j': if (!sys_isdir(optarg)) { @@ -675,8 +915,6 @@ main(int argc, char *argv[]) case 'D': opts->debug = 1; - fprintf(stderr, "Debug optarg = %s.\n", optarg); - if (optarg && strncmp(optarg, "2", 1) == 0) { log_dbg_mode(LOG_DBG_MODE_FINE); } else if (optarg && strncmp(optarg, "3", 1) == 0) { @@ -702,7 +940,13 @@ main(int argc, char *argv[]) } argc -= optind; argv += optind; - opts->spec = proxyspec_parse(&argc, &argv, natengine); + proxyspec_parse(&argc, &argv, natengine, opts); + + if (opts->conffile) { + if (load_conffile(opts, argv0, natengine) == -1) { + exit(EXIT_FAILURE); + } + } /* usage checks before defaults */ if (opts->detach && OPTS_DEBUG(opts)) { diff --git a/opts.c b/opts.c index a6ac3a6..5c323eb 100644 --- a/opts.c +++ b/opts.c @@ -278,15 +278,13 @@ opts_proto_dbg_dump(opts_t *opts) ""); } - /* * Parse proxyspecs using a simple state machine. - * Returns NULL if parsing failed. */ -proxyspec_t * -proxyspec_parse(int *argc, char **argv[], const char *natengine) +void +proxyspec_parse(int *argc, char **argv[], const char *natengine, opts_t *opts) { - proxyspec_t *curspec, *spec = NULL; + proxyspec_t *curspec; char *addr = NULL; int af = AF_UNSPEC; int state = 0; @@ -298,35 +296,37 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) /* tcp | ssl | http | https | autossl | mail | mails */ curspec = malloc(sizeof(proxyspec_t)); memset(curspec, 0, sizeof(proxyspec_t)); - curspec->next = spec; - spec = curspec; + + curspec->next = opts->spec; + opts->spec = curspec; + // Defaults - spec->ssl = 0; - spec->http = 0; - spec->upgrade = 0; - spec->mail = 0; + curspec->ssl = 0; + curspec->http = 0; + curspec->upgrade = 0; + curspec->mail = 0; if (!strcmp(**argv, "tcp")) { // use defaults } else if (!strcmp(**argv, "ssl")) { - spec->ssl = 1; + curspec->ssl = 1; } else if (!strcmp(**argv, "http")) { - spec->http = 1; + curspec->http = 1; } else if (!strcmp(**argv, "https")) { - spec->ssl = 1; - spec->http = 1; + curspec->ssl = 1; + curspec->http = 1; } else if (!strcmp(**argv, "autossl")) { - spec->upgrade = 1; + curspec->upgrade = 1; } else if (!strcmp(**argv, "mail")) { - spec->mail = 1; + curspec->mail = 1; } else if (!strcmp(**argv, "mails")) { - spec->ssl = 1; - spec->mail = 1; + curspec->ssl = 1; + curspec->mail = 1; } else { fprintf(stderr, "Unknown connection " "type '%s'\n", **argv); @@ -349,23 +349,23 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) af = AF_INET; else af = AF_UNSPEC; - af = sys_sockaddr_parse(&spec->listen_addr, - &spec->listen_addrlen, + af = sys_sockaddr_parse(&curspec->listen_addr, + &curspec->listen_addrlen, addr, **argv, af, EVUTIL_AI_PASSIVE); if (af == -1) { exit(EXIT_FAILURE); } if (natengine) { - spec->natengine = strdup(natengine); - if (!spec->natengine) { + curspec->natengine = strdup(natengine); + if (!curspec->natengine) { fprintf(stderr, "Out of memory" "\n"); exit(EXIT_FAILURE); } } else { - spec->natengine = NULL; + curspec->natengine = NULL; } state++; break; @@ -375,14 +375,14 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) // @todo Make this a conf file option? // @todo Need IPv6? if (strstr(**argv, "up:")) { - af = sys_sockaddr_parse(&spec->parent_dst_addr, - &spec->parent_dst_addrlen, - "127.0.0.1", **argv+3, AF_INET, EVUTIL_AI_PASSIVE); + af = sys_sockaddr_parse(&curspec->parent_dst_addr, + &curspec->parent_dst_addrlen, + "127.0.0.1", **argv + 3, AF_INET, EVUTIL_AI_PASSIVE); if (af == -1) { exit(EXIT_FAILURE); } - af = sys_sockaddr_parse(&spec->child_src_addr, - &spec->child_src_addrlen, + af = sys_sockaddr_parse(&curspec->child_src_addr, + &curspec->child_src_addrlen, "127.0.0.1", "0", AF_INET, EVUTIL_AI_PASSIVE); if (af == -1) { exit(EXIT_FAILURE); @@ -404,9 +404,9 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) state = 0; } else if (!strcmp(**argv, "sni")) { - free(spec->natengine); - spec->natengine = NULL; - if (!spec->ssl) { + free(curspec->natengine); + curspec->natengine = NULL; + if (!curspec->ssl) { fprintf(stderr, "SNI hostname lookup " "only works for ssl " @@ -418,9 +418,9 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) } else if (nat_exist(**argv)) { /* natengine */ - free(spec->natengine); - spec->natengine = strdup(**argv); - if (!spec->natengine) { + free(curspec->natengine); + curspec->natengine = strdup(**argv); + if (!curspec->natengine) { fprintf(stderr, "Out of memory" "\n"); @@ -429,16 +429,16 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) state = 0; } else { /* explicit target address */ - free(spec->natengine); - spec->natengine = NULL; + free(curspec->natengine); + curspec->natengine = NULL; addr = **argv; state++; } break; case 5: /* dstport */ - af = sys_sockaddr_parse(&spec->connect_addr, - &spec->connect_addrlen, + af = sys_sockaddr_parse(&curspec->connect_addr, + &curspec->connect_addrlen, addr, **argv, af, 0); if (af == -1) { exit(EXIT_FAILURE); @@ -447,13 +447,13 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) break; case 6: /* SNI dstport */ - spec->sni_port = atoi(**argv); - if (!spec->sni_port) { + curspec->sni_port = atoi(**argv); + if (!curspec->sni_port) { fprintf(stderr, "Invalid port '%s'\n", **argv); exit(EXIT_FAILURE); } - spec->dns = 1; + curspec->dns = 1; state = 0; break; } @@ -463,8 +463,6 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine) fprintf(stderr, "Incomplete proxyspec!\n"); exit(EXIT_FAILURE); } - - return spec; } /* diff --git a/opts.h b/opts.h index 94a8d0a..e214d2f 100644 --- a/opts.h +++ b/opts.h @@ -97,6 +97,7 @@ typedef struct opts { char *dropgroup; char *jaildir; char *pidfile; + char *conffile; char *connectlog; int statslog; char *contentlog; @@ -113,6 +114,11 @@ typedef struct opts { char *ecdhcurve; #endif /* !OPENSSL_NO_ECDH */ proxyspec_t *spec; + unsigned int conn_idle_timeout; + unsigned int expired_conn_check_period; + unsigned int ssl_shutdown_retry_delay; + int log_stats; + unsigned int stats_period; } opts_t; opts_t *opts_new(void) MALLOC; @@ -124,7 +130,8 @@ void opts_proto_disable(opts_t *, const char *, const char *) NONNULL(1,2,3); void opts_proto_dbg_dump(opts_t *) NONNULL(1); #define OPTS_DEBUG(opts) unlikely((opts)->debug) -proxyspec_t * proxyspec_parse(int *, char **[], const char *) MALLOC; +void proxyspec_parse(int *, char **[], const char *, opts_t *); + void proxyspec_free(proxyspec_t *) NONNULL(1); char * proxyspec_str(proxyspec_t *) NONNULL(1) MALLOC; diff --git a/pxythrmgr.c b/pxythrmgr.c index d283f46..24bac80 100644 --- a/pxythrmgr.c +++ b/pxythrmgr.c @@ -46,10 +46,6 @@ * The attach and detach functions are thread-safe. */ -#define THR_TIMER_TIMEOUT 10 -#define THR_TIMER_PRINT_INFO_TIMEOUT 1*THR_TIMER_TIMEOUT -#define CONN_EXPIRE_TIME 120 - static void pxy_thrmgr_get_thr_expired_conns(pxy_thr_ctx_t *tctx, pxy_conn_ctx_t **expired_conns) { @@ -61,11 +57,10 @@ pxy_thrmgr_get_thr_expired_conns(pxy_thr_ctx_t *tctx, pxy_conn_ctx_t **expired_c pxy_conn_ctx_t *ctx = tctx->conns; while (ctx) { unsigned long elapsed_time = now - ctx->atime; - if (elapsed_time > CONN_EXPIRE_TIME) { + if (elapsed_time > tctx->thrmgr->opts->conn_idle_timeout) { ctx->next_expired = *expired_conns; *expired_conns = ctx; } - ctx = ctx->next; } @@ -307,10 +302,10 @@ pxy_thrmgr_timer_cb(UNUSED evutil_socket_t fd, UNUSED short what, } } - // @todo Print thread info only if stats logging is enabled, if disabled debug logs are not printed either + // @attention Print thread info only if stats logging is enabled, if disabled debug logs are not printed either if (ctx->thrmgr->opts->statslog) { ctx->timeout_count++; - if (ctx->timeout_count * THR_TIMER_TIMEOUT > THR_TIMER_PRINT_INFO_TIMEOUT) { + if (ctx->timeout_count * ctx->thrmgr->opts->expired_conn_check_period >= ctx->thrmgr->opts->stats_period * ctx->thrmgr->opts->expired_conn_check_period) { ctx->timeout_count = 0; pxy_thrmgr_print_thr_info(ctx); } @@ -325,7 +320,7 @@ static void * pxy_thrmgr_thr(void *arg) { pxy_thr_ctx_t *ctx = arg; - struct timeval timer_delay = {THR_TIMER_TIMEOUT, 0}; + struct timeval timer_delay = {ctx->thrmgr->opts->expired_conn_check_period, 0}; struct event *ev; ev = event_new(ctx->evbase, -1, EV_PERSIST, pxy_thrmgr_timer_cb, ctx);