/* * stunnel TLS offloading and load-balancing proxy * Copyright (C) 1998-2017 Michal Trojnara * * 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, see . * * Linking stunnel statically or dynamically with other modules is making * a combined work based on stunnel. Thus, the terms and conditions of * the GNU General Public License cover the whole combination. * * In addition, as a special exception, the copyright holder of stunnel * gives you permission to combine stunnel with free software programs or * libraries that are released under the GNU LGPL and with code included * in the standard release of OpenSSL under the OpenSSL License (or * modified versions of such code, with unchanged license). You may copy * and distribute such a system following the terms of the GNU GPL for * stunnel and the licenses of the other code concerned. * * Note that people who make modified versions of stunnel are not obligated * to grant this special exception for their modified versions; it is their * choice whether to do so. The GNU General Public License gives permission * to release a modified version without this exception; this exception * also makes it possible to release a modified version which carries * forward this exception. */ #include "common.h" #include "prototypes.h" #if defined(_WIN32_WCE) && !defined(CONFDIR) #define CONFDIR "\\stunnel" #endif #define CONFLINELEN (16*1024) typedef enum { CMD_BEGIN, /* initialize defaults */ CMD_EXEC, /* process command */ CMD_END, /* end of section */ CMD_FREE, /* TODO: deallocate memory */ CMD_DEFAULT, /* print default value */ CMD_HELP /* print help */ } CMD; NOEXPORT int options_file(char *, CONF_TYPE, SERVICE_OPTIONS **); NOEXPORT int options_include(char *, SERVICE_OPTIONS **); #ifdef USE_WIN32 struct dirent { char d_name[MAX_PATH]; }; int scandir(const char *, struct dirent ***, int (*)(const struct dirent *), int (*)(const struct dirent **, const struct dirent **)); int alphasort(const struct dirent **, const struct dirent **); #endif NOEXPORT char *parse_global_option(CMD, char *, char *); NOEXPORT char *parse_service_option(CMD, SERVICE_OPTIONS *, char *, char *); #ifndef OPENSSL_NO_TLSEXT NOEXPORT char *sni_init(SERVICE_OPTIONS *); #endif /* !defined(OPENSSL_NO_TLSEXT) */ NOEXPORT char *parse_debug_level(char *, SERVICE_OPTIONS *); #ifndef OPENSSL_NO_PSK NOEXPORT PSK_KEYS *psk_read(char *); NOEXPORT void psk_free(PSK_KEYS *); #endif /* !defined(OPENSSL_NO_PSK) */ typedef struct { char *name; long unsigned value; } SSL_OPTION; static const SSL_OPTION ssl_opts[] = { {"MICROSOFT_SESS_ID_BUG", SSL_OP_MICROSOFT_SESS_ID_BUG}, {"NETSCAPE_CHALLENGE_BUG", SSL_OP_NETSCAPE_CHALLENGE_BUG}, #ifdef SSL_OP_LEGACY_SERVER_CONNECT {"LEGACY_SERVER_CONNECT", SSL_OP_LEGACY_SERVER_CONNECT}, #endif {"NETSCAPE_REUSE_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG}, #ifdef SSL_OP_TLSEXT_PADDING {"TLSEXT_PADDING", SSL_OP_TLSEXT_PADDING}, #endif {"MICROSOFT_BIG_SSLV3_BUFFER", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER}, #ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG {"SAFARI_ECDHE_ECDSA_BUG", SSL_OP_SAFARI_ECDHE_ECDSA_BUG}, #endif {"SSLEAY_080_CLIENT_DH_BUG", SSL_OP_SSLEAY_080_CLIENT_DH_BUG}, {"TLS_D5_BUG", SSL_OP_TLS_D5_BUG}, {"TLS_BLOCK_PADDING_BUG", SSL_OP_TLS_BLOCK_PADDING_BUG}, #ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING {"MSIE_SSLV2_RSA_PADDING", SSL_OP_MSIE_SSLV2_RSA_PADDING}, #endif {"SSLREF2_REUSE_CERT_TYPE_BUG", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG}, #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS {"DONT_INSERT_EMPTY_FRAGMENTS", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS}, #endif {"ALL", SSL_OP_ALL}, #ifdef SSL_OP_NO_QUERY_MTU {"NO_QUERY_MTU", SSL_OP_NO_QUERY_MTU}, #endif #ifdef SSL_OP_COOKIE_EXCHANGE {"COOKIE_EXCHANGE", SSL_OP_COOKIE_EXCHANGE}, #endif #ifdef SSL_OP_NO_TICKET {"NO_TICKET", SSL_OP_NO_TICKET}, #endif #ifdef SSL_OP_CISCO_ANYCONNECT {"CISCO_ANYCONNECT", SSL_OP_CISCO_ANYCONNECT}, #endif #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION {"NO_SESSION_RESUMPTION_ON_RENEGOTIATION", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION}, #endif #ifdef SSL_OP_NO_COMPRESSION {"NO_COMPRESSION", SSL_OP_NO_COMPRESSION}, #endif #ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION {"ALLOW_UNSAFE_LEGACY_RENEGOTIATION", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION}, #endif #ifdef SSL_OP_SINGLE_ECDH_USE {"SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE}, #endif {"SINGLE_DH_USE", SSL_OP_SINGLE_DH_USE}, {"EPHEMERAL_RSA", SSL_OP_EPHEMERAL_RSA}, #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE {"CIPHER_SERVER_PREFERENCE", SSL_OP_CIPHER_SERVER_PREFERENCE}, #endif {"TLS_ROLLBACK_BUG", SSL_OP_TLS_ROLLBACK_BUG}, {"NO_SSLv2", SSL_OP_NO_SSLv2}, {"NO_SSLv3", SSL_OP_NO_SSLv3}, {"NO_TLSv1", SSL_OP_NO_TLSv1}, #ifdef SSL_OP_NO_TLSv1_1 {"NO_TLSv1.1", SSL_OP_NO_TLSv1_1}, #endif #ifdef SSL_OP_NO_TLSv1_2 {"NO_TLSv1.2", SSL_OP_NO_TLSv1_2}, #endif {"PKCS1_CHECK_1", SSL_OP_PKCS1_CHECK_1}, {"PKCS1_CHECK_2", SSL_OP_PKCS1_CHECK_2}, {"NETSCAPE_CA_DN_BUG", SSL_OP_NETSCAPE_CA_DN_BUG}, #ifdef SSL_OP_NON_EXPORT_FIRST {"NON_EXPORT_FIRST", SSL_OP_NON_EXPORT_FIRST}, #endif {"NETSCAPE_DEMO_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG}, #ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG {"CRYPTOPRO_TLSEXT_BUG", SSL_OP_CRYPTOPRO_TLSEXT_BUG}, #endif #ifdef SSL_OP_NO_DTLSv1 {"NO_DTLSv1", SSL_OP_NO_DTLSv1}, #endif #ifdef SSL_OP_NO_DTLSv1_2 {"NO_DTLSv1_2", SSL_OP_NO_DTLSv1_2}, #endif #ifdef SSL_OP_NO_SSL_MASK {"NO_SSL_MASK", SSL_OP_NO_SSL_MASK}, #endif #ifdef SSL_OP_NO_DTLS_MASK {"NO_DTLS_MASK", SSL_OP_NO_DTLS_MASK}, #endif #ifdef SSL_OP_NO_ENCRYPT_THEN_MAC {"NO_ENCRYPT_THEN_MAC", SSL_OP_NO_ENCRYPT_THEN_MAC}, #endif #ifdef SSL_OP_NO_TLSv1_3 {"NO_TLSv1_3", SSL_OP_NO_TLSv1_3}, #endif {NULL, 0} }; NOEXPORT long unsigned parse_ssl_option(char *); NOEXPORT void print_ssl_options(void); NOEXPORT void init_socket_options(void); NOEXPORT int print_socket_options(void); NOEXPORT char *print_option(int, OPT_UNION *); NOEXPORT int parse_socket_option(char *); #ifndef OPENSSL_NO_OCSP NOEXPORT unsigned long parse_ocsp_flag(char *); #endif /* !defined(OPENSSL_NO_OCSP) */ #ifndef OPENSSL_NO_ENGINE NOEXPORT void engine_reset_list(void); NOEXPORT char *engine_auto(void); NOEXPORT char *engine_open(const char *); NOEXPORT char *engine_ctrl(const char *, const char *); NOEXPORT char *engine_default(const char *); NOEXPORT char *engine_init(void); NOEXPORT ENGINE *engine_get_by_id(const char *); NOEXPORT ENGINE *engine_get_by_num(const int); #endif /* !defined(OPENSSL_NO_ENGINE) */ NOEXPORT void print_syntax(void); NOEXPORT void name_list_append(NAME_LIST **, char *); #ifndef USE_WIN32 NOEXPORT char **argalloc(char *); #endif char configuration_file[PATH_MAX]; GLOBAL_OPTIONS global_options; SERVICE_OPTIONS service_options; unsigned number_of_sections=0; static GLOBAL_OPTIONS new_global_options; static SERVICE_OPTIONS new_service_options; static char *option_not_found= "Specified option name is not valid here"; static char *stunnel_cipher_list= "HIGH:!DH:!aNULL:!SSLv2"; /**************************************** parse commandline parameters */ /* return values: 0 - configuration accepted 1 - error 2 - information printed */ int options_cmdline(char *arg1, char *arg2) { char *name; CONF_TYPE type; #ifdef USE_WIN32 (void)arg2; /* squash the unused parameter warning */ #endif if(!arg1) { name= #ifdef CONFDIR CONFDIR #ifdef USE_WIN32 "\\" #else "/" #endif #endif "stunnel.conf"; type=CONF_FILE; } else if(!strcasecmp(arg1, "-help")) { parse_global_option(CMD_HELP, NULL, NULL); parse_service_option(CMD_HELP, NULL, NULL, NULL); log_flush(LOG_MODE_INFO); return 2; } else if(!strcasecmp(arg1, "-version")) { parse_global_option(CMD_DEFAULT, NULL, NULL); parse_service_option(CMD_DEFAULT, NULL, NULL, NULL); log_flush(LOG_MODE_INFO); return 2; } else if(!strcasecmp(arg1, "-sockets")) { print_socket_options(); log_flush(LOG_MODE_INFO); return 2; } else if(!strcasecmp(arg1, "-options")) { print_ssl_options(); log_flush(LOG_MODE_INFO); return 2; } else #ifndef USE_WIN32 if(!strcasecmp(arg1, "-fd")) { if(!arg2) { s_log(LOG_ERR, "No file descriptor specified"); print_syntax(); return 1; } name=arg2; type=CONF_FD; } else #endif { name=arg1; type=CONF_FILE; } #ifdef HAVE_REALPATH if(type==CONF_FILE) { if(!realpath(name, configuration_file)) { s_log(LOG_ERR, "Invalid configuration file name \"%s\"", name); ioerror("realpath"); return 1; } return options_parse(type); } #endif strncpy(configuration_file, name, PATH_MAX-1); configuration_file[PATH_MAX-1]='\0'; return options_parse(type); } /**************************************** parse configuration file */ int options_parse(CONF_TYPE type) { SERVICE_OPTIONS *section; char *errstr; options_defaults(); section=&new_service_options; if(options_file(configuration_file, type, §ion)) return 1; if(new_service_options.next) { /* daemon mode: initialize sections */ for(section=new_service_options.next; section; section=section->next) { s_log(LOG_INFO, "Initializing service [%s]", section->servname); errstr=parse_service_option(CMD_END, section, NULL, NULL); if(errstr) break; } } else { /* inetd mode: need to initialize global options */ errstr=parse_global_option(CMD_END, NULL, NULL); if(errstr) { s_log(LOG_ERR, "Global options: %s", errstr); return 1; } s_log(LOG_INFO, "Initializing inetd mode configuration"); section=&new_service_options; errstr=parse_service_option(CMD_END, section, NULL, NULL); } if(errstr) { s_log(LOG_ERR, "Service [%s]: %s", section->servname, errstr); return 1; } s_log(LOG_NOTICE, "Configuration successful"); return 0; } NOEXPORT int options_file(char *path, CONF_TYPE type, SERVICE_OPTIONS **section) { DISK_FILE *df; char line_text[CONFLINELEN], *errstr; char config_line[CONFLINELEN], *config_opt, *config_arg; int i, line_number=0; #ifndef USE_WIN32 int fd; char *tmp_str; #endif s_log(LOG_NOTICE, "Reading configuration from %s %s", type==CONF_FD ? "descriptor" : "file", path); #ifndef USE_WIN32 if(type==CONF_FD) { /* file descriptor */ fd=(int)strtol(path, &tmp_str, 10); if(tmp_str==path || *tmp_str) { /* not a number */ s_log(LOG_ERR, "Invalid file descriptor number"); print_syntax(); return 1; } df=file_fdopen(fd); } else #endif df=file_open(path, FILE_MODE_READ); if(!df) { s_log(LOG_ERR, "Cannot open configuration file"); if(type!=CONF_RELOAD) print_syntax(); return 1; } while(file_getline(df, line_text, CONFLINELEN)>=0) { memcpy(config_line, line_text, CONFLINELEN); ++line_number; config_opt=config_line; if(line_number==1) { if(config_opt[0]==(char)0xef && config_opt[1]==(char)0xbb && config_opt[2]==(char)0xbf) { s_log(LOG_NOTICE, "UTF-8 byte order mark detected"); config_opt+=3; } else { s_log(LOG_NOTICE, "UTF-8 byte order mark not detected"); } } while(isspace((unsigned char)*config_opt)) ++config_opt; /* remove initial whitespaces */ for(i=(int)strlen(config_opt)-1; i>=0 && isspace((unsigned char)config_opt[i]); --i) config_opt[i]='\0'; /* remove trailing whitespaces */ if(config_opt[0]=='\0' || config_opt[0]=='#' || config_opt[0]==';') /* empty or comment */ continue; if(config_opt[0]=='[' && config_opt[strlen(config_opt)-1]==']') { /* new section */ SERVICE_OPTIONS *new_section; if(!new_service_options.next) { /* initialize global options */ errstr=parse_global_option(CMD_END, NULL, NULL); if(errstr) { s_log(LOG_ERR, "%s:%d: \"%s\": %s", path, line_number, line_text, errstr); file_close(df); return 1; } } ++config_opt; config_opt[strlen(config_opt)-1]='\0'; new_section=str_alloc(sizeof(SERVICE_OPTIONS)); memcpy(new_section, &new_service_options, sizeof(SERVICE_OPTIONS)); new_section->servname=str_dup(config_opt); new_section->session=NULL; new_section->next=NULL; (*section)->next=new_section; *section=new_section; continue; } config_arg=strchr(config_line, '='); if(!config_arg) { s_log(LOG_ERR, "%s:%d: \"%s\": No '=' found", path, line_number, line_text); file_close(df); return 1; } *config_arg++='\0'; /* split into option name and argument value */ for(i=(int)strlen(config_opt)-1; i>=0 && isspace((unsigned char)config_opt[i]); --i) config_opt[i]='\0'; /* remove trailing whitespaces */ while(isspace((unsigned char)*config_arg)) ++config_arg; /* remove initial whitespaces */ if(!strcasecmp(config_opt, "include")) { if(options_include(config_arg, section)) { s_log(LOG_ERR, "%s:%d: Failed to include directory \"%s\"", path, line_number, config_arg); file_close(df); return 1; } continue; } errstr=option_not_found; /* try global options first (e.g. for 'debug') */ if(!new_service_options.next) errstr=parse_global_option(CMD_EXEC, config_opt, config_arg); if(errstr==option_not_found) errstr=parse_service_option(CMD_EXEC, *section, config_opt, config_arg); if(errstr) { s_log(LOG_ERR, "%s:%d: \"%s\": %s", path, line_number, line_text, errstr); file_close(df); return 1; } } file_close(df); return 0; } NOEXPORT int options_include(char *directory, SERVICE_OPTIONS **section) { struct dirent **namelist; int i, num, err=0; num=scandir(directory, &namelist, NULL, alphasort); if(num<0) { ioerror("scandir"); return 1; } for(i=0; id_name); stat(name, &sb); if(S_ISREG(sb.st_mode)) err=options_file(name, CONF_FILE, section); else s_log(LOG_DEBUG, "\"%s\" is not a file", name); str_free(name); } free(namelist[i]); } free(namelist); return err; } #ifdef USE_WIN32 int scandir(const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)) { WIN32_FIND_DATA data; HANDLE h; unsigned num=0, allocated=0; LPTSTR path, pattern; char *name; DWORD saved_errno; (void)filter; /* squash the unused parameter warning */ (void)compar; /* squash the unused parameter warning */ path=str2tstr(dirp); pattern=str_tprintf(TEXT("%s\\*"), path); str_free(path); h=FindFirstFile(pattern, &data); saved_errno=GetLastError(); str_free(pattern); SetLastError(saved_errno); if(h==INVALID_HANDLE_VALUE) return -1; *namelist=NULL; do { if(num>=allocated) { allocated+=16; *namelist=realloc(*namelist, allocated*sizeof(**namelist)); } (*namelist)[num]=malloc(sizeof(struct dirent)); if(!(*namelist)[num]) return -1; name=tstr2str(data.cFileName); strncpy((*namelist)[num]->d_name, name, MAX_PATH-1); (*namelist)[num]->d_name[MAX_PATH-1]='\0'; str_free(name); ++num; } while(FindNextFile(h, &data)); FindClose(h); return (int)num; } int alphasort(const struct dirent **a, const struct dirent **b) { (void)a; /* squash the unused parameter warning */ (void)b; /* squash the unused parameter warning */ /* most Windows filesystem return sorted data */ return 0; } #endif void options_defaults() { /* initialize globals *before* opening the config file */ memset(&new_global_options, 0, sizeof(GLOBAL_OPTIONS)); /* reset global options */ memset(&new_service_options, 0, sizeof(SERVICE_OPTIONS)); /* reset local options */ new_service_options.next=NULL; parse_global_option(CMD_BEGIN, NULL, NULL); parse_service_option(CMD_BEGIN, &new_service_options, NULL, NULL); } void options_apply() { /* apply default/validated configuration */ unsigned num=0; SERVICE_OPTIONS *section; for(section=new_service_options.next; section; section=section->next) section->section_number=num++; /* FIXME: this operation may be unsafe, as client() threads use it */ memcpy(&global_options, &new_global_options, sizeof(GLOBAL_OPTIONS)); /* service_options are used for inetd mode and to enumerate services */ memcpy(&service_options, &new_service_options, sizeof(SERVICE_OPTIONS)); number_of_sections=num; } /**************************************** global options */ NOEXPORT char *parse_global_option(CMD cmd, char *opt, char *arg) { if(cmd==CMD_DEFAULT || cmd==CMD_HELP) { s_log(LOG_NOTICE, " "); s_log(LOG_NOTICE, "Global options:"); } /* chroot */ #ifdef HAVE_CHROOT switch(cmd) { case CMD_BEGIN: new_global_options.chroot_dir=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "chroot")) break; new_global_options.chroot_dir=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = directory to chroot stunnel process", "chroot"); break; } #endif /* HAVE_CHROOT */ /* compression */ #ifndef OPENSSL_NO_COMP switch(cmd) { case CMD_BEGIN: new_global_options.compression=COMP_NONE; break; case CMD_EXEC: if(strcasecmp(opt, "compression")) break; if(OpenSSL_version_num()>=0x00908051L && !strcasecmp(arg, "deflate")) new_global_options.compression=COMP_DEFLATE; else if(!strcasecmp(arg, "zlib")) new_global_options.compression=COMP_ZLIB; else return "Specified compression type is not available"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = compression type", "compression"); break; } #endif /* !defined(OPENSSL_NO_COMP) */ /* EGD */ switch(cmd) { case CMD_BEGIN: #ifdef EGD_SOCKET new_global_options.egd_sock=EGD_SOCKET; #else new_global_options.egd_sock=NULL; #endif break; case CMD_EXEC: if(strcasecmp(opt, "EGD")) break; new_global_options.egd_sock=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: #ifdef EGD_SOCKET s_log(LOG_NOTICE, "%-22s = %s", "EGD", EGD_SOCKET); #endif break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = path to Entropy Gathering Daemon socket", "EGD"); break; } #ifndef OPENSSL_NO_ENGINE /* engine */ switch(cmd) { case CMD_BEGIN: engine_reset_list(); break; case CMD_EXEC: if(strcasecmp(opt, "engine")) break; if(!strcasecmp(arg, "auto")) return engine_auto(); else return engine_open(arg); case CMD_END: engine_init(); break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = auto|engine_id", "engine"); break; } /* engineCtrl */ switch(cmd) { case CMD_BEGIN: break; case CMD_EXEC: if(strcasecmp(opt, "engineCtrl")) break; { char *tmp_str=strchr(arg, ':'); if(tmp_str) *tmp_str++='\0'; return engine_ctrl(arg, tmp_str); } case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = cmd[:arg]", "engineCtrl"); break; } /* engineDefault */ switch(cmd) { case CMD_BEGIN: break; case CMD_EXEC: if(strcasecmp(opt, "engineDefault")) break; return engine_default(arg); case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = TASK_LIST", "engineDefault"); break; } #endif /* !defined(OPENSSL_NO_ENGINE) */ /* fips */ switch(cmd) { case CMD_BEGIN: #ifdef USE_FIPS new_global_options.option.fips=0; #endif /* USE_FIPS */ break; case CMD_EXEC: if(strcasecmp(opt, "fips")) break; #ifdef USE_FIPS if(!strcasecmp(arg, "yes")) new_global_options.option.fips=1; else if(!strcasecmp(arg, "no")) new_global_options.option.fips=0; else return "The argument needs to be either 'yes' or 'no'"; #else if(strcasecmp(arg, "no")) return "FIPS support is not available"; #endif /* USE_FIPS */ return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: #ifdef USE_FIPS s_log(LOG_NOTICE, "%-22s = yes|no FIPS 140-2 mode", "fips"); #endif /* USE_FIPS */ break; } /* foreground */ #ifndef USE_WIN32 switch(cmd) { case CMD_BEGIN: new_global_options.option.foreground=0; new_global_options.option.log_stderr=0; break; case CMD_EXEC: if(strcasecmp(opt, "foreground")) break; if(!strcasecmp(arg, "yes")) { new_global_options.option.foreground=1; new_global_options.option.log_stderr=1; } else if(!strcasecmp(arg, "quiet")) { new_global_options.option.foreground=1; new_global_options.option.log_stderr=0; } else if(!strcasecmp(arg, "no")) { new_global_options.option.foreground=0; new_global_options.option.log_stderr=0; } else return "The argument needs to be either 'yes', 'quiet' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|quiet|no foreground mode (don't fork, log to stderr)", "foreground"); break; } #endif #ifdef ICON_IMAGE /* iconActive */ switch(cmd) { case CMD_BEGIN: new_global_options.icon[ICON_ACTIVE]=load_icon_default(ICON_ACTIVE); break; case CMD_EXEC: if(strcasecmp(opt, "iconActive")) break; if(!(new_global_options.icon[ICON_ACTIVE]=load_icon_file(arg))) return "Failed to load the specified icon"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = icon when connections are established", "iconActive"); break; } /* iconError */ switch(cmd) { case CMD_BEGIN: new_global_options.icon[ICON_ERROR]=load_icon_default(ICON_ERROR); break; case CMD_EXEC: if(strcasecmp(opt, "iconError")) break; if(!(new_global_options.icon[ICON_ERROR]=load_icon_file(arg))) return "Failed to load the specified icon"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = icon for invalid configuration file", "iconError"); break; } /* iconIdle */ switch(cmd) { case CMD_BEGIN: new_global_options.icon[ICON_IDLE]=load_icon_default(ICON_IDLE); break; case CMD_EXEC: if(strcasecmp(opt, "iconIdle")) break; if(!(new_global_options.icon[ICON_IDLE]=load_icon_file(arg))) return "Failed to load the specified icon"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = icon when no connections were established", "iconIdle"); break; } #endif /* ICON_IMAGE */ /* log */ switch(cmd) { case CMD_BEGIN: new_global_options.log_file_mode=FILE_MODE_APPEND; break; case CMD_EXEC: if(strcasecmp(opt, "log")) break; if(!strcasecmp(arg, "append")) new_global_options.log_file_mode=FILE_MODE_APPEND; else if(!strcasecmp(arg, "overwrite")) new_global_options.log_file_mode=FILE_MODE_OVERWRITE; else return "The argument needs to be either 'append' or 'overwrite'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = append|overwrite log file", "log"); break; } /* output */ switch(cmd) { case CMD_BEGIN: new_global_options.output_file=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "output")) break; new_global_options.output_file=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = file to append log messages", "output"); break; } /* pid */ #ifndef USE_WIN32 switch(cmd) { case CMD_BEGIN: new_global_options.pidfile=NULL; /* do not create a pid file */ break; case CMD_EXEC: if(strcasecmp(opt, "pid")) break; if(arg[0]) /* is argument not empty? */ new_global_options.pidfile=str_dup(arg); else new_global_options.pidfile=NULL; /* empty -> do not create a pid file */ return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = pid file", "pid"); break; } #endif /* RNDbytes */ switch(cmd) { case CMD_BEGIN: new_global_options.random_bytes=RANDOM_BYTES; break; case CMD_EXEC: if(strcasecmp(opt, "RNDbytes")) break; { char *tmp_str; new_global_options.random_bytes=(long)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal number of bytes to read from random seed files"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %d", "RNDbytes", RANDOM_BYTES); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = bytes to read from random seed files", "RNDbytes"); break; } /* RNDfile */ switch(cmd) { case CMD_BEGIN: new_global_options.rand_file=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "RNDfile")) break; new_global_options.rand_file=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: #ifdef RANDOM_FILE s_log(LOG_NOTICE, "%-22s = %s", "RNDfile", RANDOM_FILE); #endif break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = path to file with random seed data", "RNDfile"); break; } /* RNDoverwrite */ switch(cmd) { case CMD_BEGIN: new_global_options.option.rand_write=1; break; case CMD_EXEC: if(strcasecmp(opt, "RNDoverwrite")) break; if(!strcasecmp(arg, "yes")) new_global_options.option.rand_write=1; else if(!strcasecmp(arg, "no")) new_global_options.option.rand_write=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = yes", "RNDoverwrite"); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no overwrite seed datafiles with new random data", "RNDoverwrite"); break; } #ifndef USE_WIN32 /* service */ switch(cmd) { case CMD_BEGIN: new_service_options.servname=str_dup("stunnel"); break; case CMD_EXEC: if(strcasecmp(opt, "service")) break; new_service_options.servname=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = service name", "service"); break; } #endif /* socket */ switch(cmd) { case CMD_BEGIN: init_socket_options(); break; case CMD_EXEC: if(strcasecmp(opt, "socket")) break; if(parse_socket_option(arg)) return "Illegal socket option"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = a|l|r:option=value[:value]", "socket"); s_log(LOG_NOTICE, "%25sset an option on accept/local/remote socket", ""); break; } /* syslog */ #ifndef USE_WIN32 switch(cmd) { case CMD_BEGIN: new_global_options.option.log_syslog=1; break; case CMD_EXEC: if(strcasecmp(opt, "syslog")) break; if(!strcasecmp(arg, "yes")) new_global_options.option.log_syslog=1; else if(!strcasecmp(arg, "no")) new_global_options.option.log_syslog=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no send logging messages to syslog", "syslog"); break; } #endif /* taskbar */ #ifdef USE_WIN32 switch(cmd) { case CMD_BEGIN: new_global_options.option.taskbar=1; break; case CMD_EXEC: if(strcasecmp(opt, "taskbar")) break; if(!strcasecmp(arg, "yes")) new_global_options.option.taskbar=1; else if(!strcasecmp(arg, "no")) new_global_options.option.taskbar=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = yes", "taskbar"); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no enable the taskbar icon", "taskbar"); break; } #endif if(cmd==CMD_EXEC) return option_not_found; if(cmd==CMD_END) { /* FIPS needs to be initialized as early as possible */ if(ssl_configure(&new_global_options)) /* configure global TLS settings */ return "Failed to initialize TLS"; } return NULL; /* OK */ } /**************************************** service-level options */ NOEXPORT char *parse_service_option(CMD cmd, SERVICE_OPTIONS *section, char *opt, char *arg) { int endpoints=0; #ifndef USE_WIN32 struct group *gr; struct passwd *pw; #endif if(cmd==CMD_DEFAULT || cmd==CMD_HELP) { s_log(LOG_NOTICE, " "); s_log(LOG_NOTICE, "Service-level options:"); } /* accept */ switch(cmd) { case CMD_BEGIN: addrlist_clear(§ion->local_addr, 1); break; case CMD_EXEC: if(strcasecmp(opt, "accept")) break; section->option.accept=1; name_list_append(§ion->local_addr.names, arg); return NULL; /* OK */ case CMD_END: if(section->local_addr.names) { if(!addrlist_resolve(§ion->local_addr)) return "Cannot resolve accept target"; ++endpoints; } break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = [host:]port accept connections on specified host:port", "accept"); break; } /* CApath */ switch(cmd) { case CMD_BEGIN: #if 0 section->ca_dir=(char *)X509_get_default_cert_dir(); #endif section->ca_dir=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "CApath")) break; if(arg[0]) /* not empty */ section->ca_dir=str_dup(arg); else section->ca_dir=NULL; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: #if 0 s_log(LOG_NOTICE, "%-22s = %s", "CApath", section->ca_dir ? section->ca_dir : "(none)"); #endif break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = CA certificate directory for 'verify' option", "CApath"); break; } /* CAfile */ switch(cmd) { case CMD_BEGIN: #if 0 section->ca_file=(char *)X509_get_default_certfile(); #endif section->ca_file=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "CAfile")) break; if(arg[0]) /* not empty */ section->ca_file=str_dup(arg); else section->ca_file=NULL; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: #if 0 s_log(LOG_NOTICE, "%-22s = %s", "CAfile", section->ca_file ? section->ca_file : "(none)"); #endif break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = CA certificate file for 'verify' option", "CAfile"); break; } /* cert */ switch(cmd) { case CMD_BEGIN: section->cert=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "cert")) break; section->cert=str_dup(arg); return NULL; /* OK */ case CMD_END: #ifndef OPENSSL_NO_PSK if(section->psk_keys) break; #endif /* !defined(OPENSSL_NO_PSK) */ #ifndef OPENSSL_NO_ENGINE if(section->engine) break; #endif /* !defined(OPENSSL_NO_ENGINE) */ if(!section->option.client && !section->cert) return "TLS server needs a certificate"; break; case CMD_FREE: break; case CMD_DEFAULT: break; /* no default certificate */ case CMD_HELP: s_log(LOG_NOTICE, "%-22s = certificate chain", "cert"); break; } #if OPENSSL_VERSION_NUMBER>=0x10002000L /* checkEmail */ switch(cmd) { case CMD_BEGIN: section->check_email=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "checkEmail")) break; name_list_append(§ion->check_email, arg); return NULL; /* OK */ case CMD_END: if(section->check_email && !section->option.verify_chain && !section->option.verify_peer) return "Either \"verifyChain\" or \"verifyPeer\" has to be enabled"; break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = peer certificate email address", "checkEmail"); break; } /* checkHost */ switch(cmd) { case CMD_BEGIN: section->check_host=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "checkHost")) break; name_list_append(§ion->check_host, arg); return NULL; /* OK */ case CMD_END: if(section->check_host && !section->option.verify_chain && !section->option.verify_peer) return "Either \"verifyChain\" or \"verifyPeer\" has to be enabled"; break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = peer certificate host name pattern", "checkHost"); break; } /* checkIP */ switch(cmd) { case CMD_BEGIN: section->check_ip=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "checkIP")) break; name_list_append(§ion->check_ip, arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = peer certificate IP address", "checkIP"); break; } #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */ /* ciphers */ switch(cmd) { case CMD_BEGIN: section->cipher_list=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "ciphers")) break; section->cipher_list=str_dup(arg); return NULL; /* OK */ case CMD_END: if(!section->cipher_list) { /* this is only executed for global options, * because section->cipher_list is no longer NULL */ #ifdef USE_FIPS if(new_global_options.option.fips) section->cipher_list="FIPS"; else #endif /* USE_FIPS */ section->cipher_list=stunnel_cipher_list; } break; case CMD_FREE: break; case CMD_DEFAULT: #ifdef USE_FIPS s_log(LOG_NOTICE, "%-22s = %s %s", "ciphers", "FIPS", "(with \"fips = yes\")"); s_log(LOG_NOTICE, "%-22s = %s %s", "ciphers", stunnel_cipher_list, "(with \"fips = no\")"); #else s_log(LOG_NOTICE, "%-22s = %s", "ciphers", stunnel_cipher_list); #endif /* USE_FIPS */ break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = list of permitted TLS ciphers", "ciphers"); break; } /* client */ switch(cmd) { case CMD_BEGIN: section->option.client=0; break; case CMD_EXEC: if(strcasecmp(opt, "client")) break; if(!strcasecmp(arg, "yes")) section->option.client=1; else if(!strcasecmp(arg, "no")) section->option.client=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no client mode (remote service uses TLS)", "client"); break; } #if OPENSSL_VERSION_NUMBER>=0x10002000L /* config */ switch(cmd) { case CMD_BEGIN: section->config=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "config")) break; name_list_append(§ion->config, arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = command[:parameter] to execute", "config"); break; } #endif /* OPENSSL_VERSION_NUMBER>=0x10002000L */ /* connect */ switch(cmd) { case CMD_BEGIN: addrlist_clear(§ion->connect_addr, 0); break; case CMD_EXEC: if(strcasecmp(opt, "connect")) break; name_list_append(§ion->connect_addr.names, arg); return NULL; /* OK */ case CMD_END: if(section->connect_addr.names) { if(!section->option.delayed_lookup && !addrlist_resolve(§ion->connect_addr)) { s_log(LOG_INFO, "Cannot resolve connect target - delaying DNS lookup"); section->redirect_addr.num=0; str_free(section->redirect_addr.names); section->redirect_addr.names=NULL; section->option.delayed_lookup=1; } ++endpoints; } break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = [host:]port to connect", "connect"); break; } /* CRLpath */ switch(cmd) { case CMD_BEGIN: section->crl_dir=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "CRLpath")) break; if(arg[0]) /* not empty */ section->crl_dir=str_dup(arg); else section->crl_dir=NULL; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = CRL directory", "CRLpath"); break; } /* CRLfile */ switch(cmd) { case CMD_BEGIN: section->crl_file=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "CRLfile")) break; if(arg[0]) /* not empty */ section->crl_file=str_dup(arg); else section->crl_file=NULL; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = CRL file", "CRLfile"); break; } #ifndef OPENSSL_NO_ECDH /* curve */ #define DEFAULT_CURVE NID_X9_62_prime256v1 switch(cmd) { case CMD_BEGIN: section->curve=DEFAULT_CURVE; break; case CMD_EXEC: if(strcasecmp(opt, "curve")) break; section->curve=OBJ_txt2nid(arg); if(section->curve==NID_undef) return "Curve name not supported"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %s", "curve", OBJ_nid2ln(DEFAULT_CURVE)); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = ECDH curve name", "curve"); break; } #endif /* !defined(OPENSSL_NO_ECDH) */ /* debug */ switch(cmd) { case CMD_BEGIN: section->log_level=LOG_NOTICE; #if !defined (USE_WIN32) && !defined (__vms) new_global_options.log_facility=LOG_DAEMON; #endif break; case CMD_EXEC: if(strcasecmp(opt, "debug")) break; return parse_debug_level(arg, section); case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: #if !defined (USE_WIN32) && !defined (__vms) s_log(LOG_NOTICE, "%-22s = %s", "debug", "daemon.notice"); #else s_log(LOG_NOTICE, "%-22s = %s", "debug", "notice"); #endif break; case CMD_HELP: #if !defined (USE_WIN32) && !defined (__vms) s_log(LOG_NOTICE, "%-22s = [facility].level (e.g. daemon.info)", "debug"); #else s_log(LOG_NOTICE, "%-22s = level (e.g. info)", "debug"); #endif break; } /* delay */ switch(cmd) { case CMD_BEGIN: section->option.delayed_lookup=0; break; case CMD_EXEC: if(strcasecmp(opt, "delay")) break; if(!strcasecmp(arg, "yes")) section->option.delayed_lookup=1; else if(!strcasecmp(arg, "no")) section->option.delayed_lookup=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no delay DNS lookup for 'connect' option", "delay"); break; } #ifndef OPENSSL_NO_ENGINE /* engineId */ switch(cmd) { case CMD_BEGIN: break; case CMD_EXEC: if(strcasecmp(opt, "engineId")) break; section->engine=engine_get_by_id(arg); if(!section->engine) return "Engine ID not found"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = ID of engine to read the key from", "engineId"); break; } /* engineNum */ switch(cmd) { case CMD_BEGIN: break; case CMD_EXEC: if(strcasecmp(opt, "engineNum")) break; { char *tmp_str; int tmp_int=(int)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal engine number"; section->engine=engine_get_by_num(tmp_int-1); } if(!section->engine) return "Illegal engine number"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = number of engine to read the key from", "engineNum"); break; } #endif /* !defined(OPENSSL_NO_ENGINE) */ /* exec */ switch(cmd) { case CMD_BEGIN: section->exec_name=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "exec")) break; section->exec_name=str_dup(arg); #ifdef USE_WIN32 section->exec_args=str_dup(arg); #else if(!section->exec_args) { section->exec_args=str_alloc(2*sizeof(char *)); section->exec_args[0]=section->exec_name; section->exec_args[1]=NULL; /* to show that it's null-terminated */ } #endif return NULL; /* OK */ case CMD_END: if(section->exec_name) ++endpoints; break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = file execute local inetd-type program", "exec"); break; } /* execArgs */ switch(cmd) { case CMD_BEGIN: section->exec_args=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "execArgs")) break; #ifdef USE_WIN32 section->exec_args=str_dup(arg); #else section->exec_args=argalloc(arg); #endif return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = arguments for 'exec' (including $0)", "execArgs"); break; } /* failover */ switch(cmd) { case CMD_BEGIN: section->failover=FAILOVER_RR; section->seq=0; break; case CMD_EXEC: if(strcasecmp(opt, "failover")) break; if(!strcasecmp(arg, "rr")) section->failover=FAILOVER_RR; else if(!strcasecmp(arg, "prio")) section->failover=FAILOVER_PRIO; else return "The argument needs to be either 'rr' or 'prio'"; return NULL; /* OK */ case CMD_END: if(section->option.delayed_lookup) section->failover=FAILOVER_PRIO; break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = rr|prio failover strategy", "failover"); break; } /* ident */ switch(cmd) { case CMD_BEGIN: section->username=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "ident")) break; section->username=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = username for IDENT (RFC 1413) checking", "ident"); break; } /* key */ switch(cmd) { case CMD_BEGIN: section->key=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "key")) break; section->key=str_dup(arg); return NULL; /* OK */ case CMD_END: if(section->cert && !section->key) section->key=str_dup(section->cert); break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = certificate private key", "key"); break; } #ifdef USE_LIBWRAP switch(cmd) { case CMD_BEGIN: section->option.libwrap=0; /* disable libwrap by default */ break; case CMD_EXEC: if(strcasecmp(opt, "libwrap")) break; if(!strcasecmp(arg, "yes")) section->option.libwrap=1; else if(!strcasecmp(arg, "no")) section->option.libwrap=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no use /etc/hosts.allow and /etc/hosts.deny", "libwrap"); break; } #endif /* USE_LIBWRAP */ /* local */ switch(cmd) { case CMD_BEGIN: section->option.local=0; break; case CMD_EXEC: if(strcasecmp(opt, "local")) break; if(!hostport2addr(§ion->source_addr, arg, "0", 1)) return "Failed to resolve local address"; section->option.local=1; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = IP address to be used as source for remote" " connections", "local"); break; } /* logId */ switch(cmd) { case CMD_BEGIN: section->log_id=LOG_ID_SEQUENTIAL; break; case CMD_EXEC: if(strcasecmp(opt, "logId")) break; if(!strcasecmp(arg, "sequential")) section->log_id=LOG_ID_SEQUENTIAL; else if(!strcasecmp(arg, "unique")) section->log_id=LOG_ID_UNIQUE; else if(!strcasecmp(arg, "thread")) section->log_id=LOG_ID_THREAD; else if(!strcasecmp(arg, "process")) section->log_id=LOG_ID_PROCESS; else return "Invalid connection identifier type"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %s", "logId", "sequential"); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = connection identifier type", "logId"); break; } #ifndef OPENSSL_NO_OCSP /* OCSP */ switch(cmd) { case CMD_BEGIN: section->ocsp_url=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "ocsp")) break; section->ocsp_url=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = OCSP responder URL", "ocsp"); break; } /* OCSPaia */ switch(cmd) { case CMD_BEGIN: section->option.aia=0; /* disable AIA by default */ break; case CMD_EXEC: if(strcasecmp(opt, "OCSPaia")) break; if(!strcasecmp(arg, "yes")) section->option.aia=1; else if(!strcasecmp(arg, "no")) section->option.aia=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no check the AIA responders from certificates", "OCSPaia"); break; } /* OCSPflag */ switch(cmd) { case CMD_BEGIN: section->ocsp_flags=0; break; case CMD_EXEC: if(strcasecmp(opt, "OCSPflag")) break; { unsigned long tmp_ulong=parse_ocsp_flag(arg); if(!tmp_ulong) return "Illegal OCSP flag"; section->ocsp_flags|=tmp_ulong; } return NULL; case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = OCSP responder flags", "OCSPflag"); break; } /* OCSPnonce */ switch(cmd) { case CMD_BEGIN: section->option.nonce=0; /* disable OCSP nonce by default */ break; case CMD_EXEC: if(strcasecmp(opt, "OCSPnonce")) break; if(!strcasecmp(arg, "yes")) section->option.nonce=1; else if(!strcasecmp(arg, "no")) section->option.nonce=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no send and verify the OCSP nonce extension", "OCSPnonce"); break; } #endif /* !defined(OPENSSL_NO_OCSP) */ /* options */ switch(cmd) { case CMD_BEGIN: section->ssl_options_set|=SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3; #if OPENSSL_VERSION_NUMBER>=0x009080dfL section->ssl_options_clear=0; #endif /* OpenSSL 0.9.8m or later */ break; case CMD_EXEC: if(strcasecmp(opt, "options")) break; #if OPENSSL_VERSION_NUMBER>=0x009080dfL if(*arg=='-') { long unsigned tmp=parse_ssl_option(arg+1); if(!tmp) return "Illegal TLS option"; section->ssl_options_clear|=tmp; return NULL; /* OK */ } #endif /* OpenSSL 0.9.8m or later */ { long unsigned tmp=parse_ssl_option(arg); if(!tmp) return "Illegal TLS option"; section->ssl_options_set|=tmp; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %s", "options", "NO_SSLv2"); s_log(LOG_NOTICE, "%-22s = %s", "options", "NO_SSLv3"); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = TLS option to set/reset", "options"); break; } /* protocol */ switch(cmd) { case CMD_BEGIN: section->protocol=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "protocol")) break; section->protocol=str_dup(arg); return NULL; /* OK */ case CMD_END: /* PROTOCOL_CHECK also initializes: section->option.connect_before_ssl section->option.protocol_endpoint */ { char *tmp_str=protocol(NULL, section, PROTOCOL_CHECK); if(tmp_str) return tmp_str; } endpoints+=section->option.protocol_endpoint; #ifdef SSL_OP_NO_TICKET /* disable RFC4507 support introduced in OpenSSL 0.9.8f */ /* session tickets do not support SSL_SESSION_*_ex_data() */ if(!section->option.connect_before_ssl) /* address cache can be used */ section->ssl_options_set|=SSL_OP_NO_TICKET; #endif break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = protocol to negotiate before TLS initialization", "protocol"); s_log(LOG_NOTICE, "%25scurrently supported: cifs, connect, imap,", ""); s_log(LOG_NOTICE, "%25s nntp, pgsql, pop3, proxy, smtp, socks", ""); break; } /* protocolAuthentication */ switch(cmd) { case CMD_BEGIN: section->protocol_authentication="basic"; break; case CMD_EXEC: if(strcasecmp(opt, "protocolAuthentication")) break; section->protocol_authentication=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = authentication type for protocol negotiations", "protocolAuthentication"); break; } /* protocolDomain */ switch(cmd) { case CMD_BEGIN: section->protocol_domain=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "protocolDomain")) break; section->protocol_domain=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = domain for protocol negotiations", "protocolDomain"); break; } /* protocolHost */ switch(cmd) { case CMD_BEGIN: section->protocol_host=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "protocolHost")) break; section->protocol_host=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = host:port for protocol negotiations", "protocolHost"); break; } /* protocolPassword */ switch(cmd) { case CMD_BEGIN: section->protocol_password=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "protocolPassword")) break; section->protocol_password=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = password for protocol negotiations", "protocolPassword"); break; } /* protocolUsername */ switch(cmd) { case CMD_BEGIN: section->protocol_username=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "protocolUsername")) break; section->protocol_username=str_dup(arg); return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = username for protocol negotiations", "protocolUsername"); break; } #ifndef OPENSSL_NO_PSK /* PSKidentity */ switch(cmd) { case CMD_BEGIN: section->psk_identity=NULL; section->psk_selected=NULL; section->psk_sorted.val=NULL; section->psk_sorted.num=0; break; case CMD_EXEC: if(strcasecmp(opt, "PSKidentity")) break; section->psk_identity=str_dup(arg); return NULL; /* OK */ case CMD_END: if(!section->psk_keys) /* PSK not configured */ break; psk_sort(§ion->psk_sorted, section->psk_keys); if(section->option.client) { if(section->psk_identity) { section->psk_selected= psk_find(§ion->psk_sorted, section->psk_identity); if(!section->psk_selected) return "No key found for the specified PSK identity"; } else { /* take the first specified identity as default */ section->psk_selected=section->psk_keys; } } else { if(section->psk_identity) s_log(LOG_NOTICE, "PSK identity is ignored in the server mode"); } break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = identity for PSK authentication", "PSKidentity"); break; } /* PSKsecrets */ switch(cmd) { case CMD_BEGIN: section->psk_keys=NULL; break; case CMD_EXEC: if(strcasecmp(opt, "PSKsecrets")) break; section->psk_keys=psk_read(arg); if(!section->psk_keys) return "Failed to read PSK secrets"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: psk_free(section->psk_keys); break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = secrets for PSK authentication", "PSKsecrets"); break; } #endif /* !defined(OPENSSL_NO_PSK) */ /* pty */ #ifndef USE_WIN32 switch(cmd) { case CMD_BEGIN: section->option.pty=0; break; case CMD_EXEC: if(strcasecmp(opt, "pty")) break; if(!strcasecmp(arg, "yes")) section->option.pty=1; else if(!strcasecmp(arg, "no")) section->option.pty=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no allocate pseudo terminal for 'exec' option", "pty"); break; } #endif /* redirect */ switch(cmd) { case CMD_BEGIN: addrlist_clear(§ion->redirect_addr, 0); break; case CMD_EXEC: if(strcasecmp(opt, "redirect")) break; #ifdef SSL_OP_NO_TICKET /* disable RFC4507 support introduced in OpenSSL 0.9.8f */ /* session tickets do not support SSL_SESSION_*_ex_data() */ section->ssl_options_set|=SSL_OP_NO_TICKET; #endif name_list_append(§ion->redirect_addr.names, arg); return NULL; /* OK */ case CMD_END: if(section->redirect_addr.names) { if(!section->option.delayed_lookup && !addrlist_resolve(§ion->redirect_addr)) { s_log(LOG_INFO, "Cannot resolve redirect target - delaying DNS lookup"); section->connect_addr.num=0; str_free(section->connect_addr.names); section->connect_addr.names=NULL; section->option.delayed_lookup=1; } if(!section->option.verify_chain && !section->option.verify_peer) return "Either \"verifyChain\" or \"verifyPeer\" has to be enabled for \"redirect\" to work"; } break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = [host:]port to redirect on authentication failures", "redirect"); break; } /* renegotiation */ switch(cmd) { case CMD_BEGIN: section->option.renegotiation=1; break; case CMD_EXEC: if(strcasecmp(opt, "renegotiation")) break; if(!strcasecmp(arg, "yes")) section->option.renegotiation=1; else if(!strcasecmp(arg, "no")) section->option.renegotiation=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no support renegotiation", "renegotiation"); break; } /* requireCert */ switch(cmd) { case CMD_BEGIN: section->option.require_cert=0; break; case CMD_EXEC: if(strcasecmp(opt, "requireCert")) break; if(!strcasecmp(arg, "yes")) { section->option.request_cert=1; section->option.require_cert=1; } else if(!strcasecmp(arg, "no")) { section->option.require_cert=0; } else { return "The argument needs to be either 'yes' or 'no'"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no require client certificate", "requireCert"); break; } /* reset */ switch(cmd) { case CMD_BEGIN: section->option.reset=1; /* enabled by default */ break; case CMD_EXEC: if(strcasecmp(opt, "reset")) break; if(!strcasecmp(arg, "yes")) section->option.reset=1; else if(!strcasecmp(arg, "no")) section->option.reset=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no send TCP RST on error", "retry"); break; } /* retry */ switch(cmd) { case CMD_BEGIN: section->option.retry=0; break; case CMD_EXEC: if(strcasecmp(opt, "retry")) break; if(!strcasecmp(arg, "yes")) section->option.retry=1; else if(!strcasecmp(arg, "no")) section->option.retry=0; else return "The argument needs to be either 'yes' or 'no'"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no retry connect+exec section", "retry"); break; } #ifndef USE_WIN32 /* setgid */ switch(cmd) { case CMD_BEGIN: section->gid=0; break; case CMD_EXEC: if(strcasecmp(opt, "setgid")) break; gr=getgrnam(arg); if(gr) { section->gid=gr->gr_gid; return NULL; /* OK */ } { char *tmp_str; section->gid=(gid_t)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal GID"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = groupname for setgid()", "setgid"); break; } #endif #ifndef USE_WIN32 /* setuid */ switch(cmd) { case CMD_BEGIN: section->uid=0; break; case CMD_EXEC: if(strcasecmp(opt, "setuid")) break; pw=getpwnam(arg); if(pw) { section->uid=pw->pw_uid; return NULL; /* OK */ } { char *tmp_str; section->uid=(uid_t)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal UID"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = username for setuid()", "setuid"); break; } #endif /* sessionCacheSize */ switch(cmd) { case CMD_BEGIN: section->session_size=1000L; break; case CMD_EXEC: if(strcasecmp(opt, "sessionCacheSize")) break; { char *tmp_str; section->session_size=strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal session cache size"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %ld", "sessionCacheSize", 1000L); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = session cache size", "sessionCacheSize"); break; } /* sessionCacheTimeout */ switch(cmd) { case CMD_BEGIN: section->session_timeout=300L; break; case CMD_EXEC: if(strcasecmp(opt, "sessionCacheTimeout") && strcasecmp(opt, "session")) break; { char *tmp_str; section->session_timeout=strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal session cache timeout"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %ld seconds", "sessionCacheTimeout", 300L); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = session cache timeout (in seconds)", "sessionCacheTimeout"); break; } /* sessiond */ switch(cmd) { case CMD_BEGIN: section->option.sessiond=0; memset(§ion->sessiond_addr, 0, sizeof(SOCKADDR_UNION)); section->sessiond_addr.in.sin_family=AF_INET; break; case CMD_EXEC: if(strcasecmp(opt, "sessiond")) break; section->option.sessiond=1; #ifdef SSL_OP_NO_TICKET /* disable RFC4507 support introduced in OpenSSL 0.9.8f */ /* this prevents session callbacks from being executed */ section->ssl_options_set|=SSL_OP_NO_TICKET; #endif if(!name2addr(§ion->sessiond_addr, arg, 0)) return "Failed to resolve sessiond server address"; return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = [host:]port use sessiond at host:port", "sessiond"); break; } #ifndef OPENSSL_NO_TLSEXT /* sni */ switch(cmd) { case CMD_BEGIN: section->servername_list_head=NULL; section->servername_list_tail=NULL; section->option.sni=0; break; case CMD_EXEC: if(strcasecmp(opt, "sni")) break; section->sni=str_dup(arg); return NULL; /* OK */ case CMD_END: { char *tmp_str=sni_init(section); if(tmp_str) return tmp_str; } if(section->option.sni) ++endpoints; break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = master_service:host_name for an SNI virtual service", "sni"); break; } #endif /* !defined(OPENSSL_NO_TLSEXT) */ /* sslVersion */ switch(cmd) { case CMD_BEGIN: #if OPENSSL_VERSION_NUMBER>=0x10100000L section->client_method=(SSL_METHOD *)TLS_client_method(); section->server_method=(SSL_METHOD *)TLS_server_method(); #else section->client_method=(SSL_METHOD *)SSLv23_client_method(); section->server_method=(SSL_METHOD *)SSLv23_server_method(); #endif break; case CMD_EXEC: if(strcasecmp(opt, "sslVersion")) break; if(!strcasecmp(arg, "all")) { #if OPENSSL_VERSION_NUMBER>=0x10100000L section->client_method=(SSL_METHOD *)TLS_client_method(); section->server_method=(SSL_METHOD *)TLS_server_method(); #else section->client_method=(SSL_METHOD *)SSLv23_client_method(); section->server_method=(SSL_METHOD *)SSLv23_server_method(); #endif #if OPENSSL_API_COMPAT<0x10100000L } else if(!strcasecmp(arg, "SSLv2")) { #ifndef OPENSSL_NO_SSL2 section->client_method=(SSL_METHOD *)SSLv2_client_method(); section->server_method=(SSL_METHOD *)SSLv2_server_method(); #else /* defined(OPENSSL_NO_SSL2) */ return "SSLv2 not supported"; #endif /* !defined(OPENSSL_NO_SSL2) */ } else if(!strcasecmp(arg, "SSLv3")) { #ifndef OPENSSL_NO_SSL3 section->client_method=(SSL_METHOD *)SSLv3_client_method(); section->server_method=(SSL_METHOD *)SSLv3_server_method(); #else /* defined(OPENSSL_NO_SSL3) */ return "SSLv3 not supported"; #endif /* !defined(OPENSSL_NO_SSL3) */ } else if(!strcasecmp(arg, "TLSv1")) { #ifndef OPENSSL_NO_TLS1 section->client_method=(SSL_METHOD *)TLSv1_client_method(); section->server_method=(SSL_METHOD *)TLSv1_server_method(); #else /* defined(OPENSSL_NO_TLS1) */ return "TLSv1 not supported"; #endif /* !defined(OPENSSL_NO_TLS1) */ } else if(!strcasecmp(arg, "TLSv1.1")) { #ifndef OPENSSL_NO_TLS1_1 section->client_method=(SSL_METHOD *)TLSv1_1_client_method(); section->server_method=(SSL_METHOD *)TLSv1_1_server_method(); #else /* defined(OPENSSL_NO_TLS1_1) */ return "TLSv1.1 not supported"; #endif /* !defined(OPENSSL_NO_TLS1_1) */ } else if(!strcasecmp(arg, "TLSv1.2")) { #ifndef OPENSSL_NO_TLS1_2 section->client_method=(SSL_METHOD *)TLSv1_2_client_method(); section->server_method=(SSL_METHOD *)TLSv1_2_server_method(); #else /* defined(OPENSSL_NO_TLS1_2) */ return "TLSv1.2 not supported"; #endif /* !defined(OPENSSL_NO_TLS1_2) */ #endif /* OPENSSL_API_COMPAT<0x10100000L */ } else return "Incorrect version of TLS protocol"; return NULL; /* OK */ case CMD_END: #ifdef USE_FIPS if(new_global_options.option.fips) { #ifndef OPENSSL_NO_SSL2 if(section->option.client ? section->client_method==(SSL_METHOD *)SSLv2_client_method() : section->server_method==(SSL_METHOD *)SSLv2_server_method()) return "\"sslVersion = SSLv2\" not supported in FIPS mode"; #endif /* !defined(OPENSSL_NO_SSL2) */ #ifndef OPENSSL_NO_SSL3 if(section->option.client ? section->client_method==(SSL_METHOD *)SSLv3_client_method() : section->server_method==(SSL_METHOD *)SSLv3_server_method()) return "\"sslVersion = SSLv3\" not supported in FIPS mode"; #endif /* !defined(OPENSSL_NO_SSL3) */ } #endif /* USE_FIPS */ break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = all" #if OPENSSL_VERSION_NUMBER<0x10100000L "|SSLv2|SSLv3|TLSv1" #if OPENSSL_VERSION_NUMBER>=0x10001000L "|TLSv1.1|TLSv1.2" #endif /* OPENSSL_VERSION_NUMBER>=0x10001000L */ #endif /* OPENSSL_VERSION_NUMBER<0x10100000L */ " TLS method", "sslVersion"); break; } #ifndef USE_FORK /* stack */ switch(cmd) { case CMD_BEGIN: section->stack_size=DEFAULT_STACK_SIZE; break; case CMD_EXEC: if(strcasecmp(opt, "stack")) break; { char *tmp_str; section->stack_size=(size_t)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal thread stack size"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %d bytes", "stack", DEFAULT_STACK_SIZE); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = thread stack size (in bytes)", "stack"); break; } #endif /* TIMEOUTbusy */ switch(cmd) { case CMD_BEGIN: section->timeout_busy=300; /* 5 minutes */ break; case CMD_EXEC: if(strcasecmp(opt, "TIMEOUTbusy")) break; { char *tmp_str; section->timeout_busy=(int)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal busy timeout"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %d seconds", "TIMEOUTbusy", 300); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = seconds to wait for expected data", "TIMEOUTbusy"); break; } /* TIMEOUTclose */ switch(cmd) { case CMD_BEGIN: section->timeout_close=60; /* 1 minute */ break; case CMD_EXEC: if(strcasecmp(opt, "TIMEOUTclose")) break; { char *tmp_str; section->timeout_close=(int)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal close timeout"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %d seconds", "TIMEOUTclose", 60); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = seconds to wait for close_notify", "TIMEOUTclose"); break; } /* TIMEOUTconnect */ switch(cmd) { case CMD_BEGIN: section->timeout_connect=10; /* 10 seconds */ break; case CMD_EXEC: if(strcasecmp(opt, "TIMEOUTconnect")) break; { char *tmp_str; section->timeout_connect=(int)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal connect timeout"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %d seconds", "TIMEOUTconnect", 10); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = seconds to connect remote host", "TIMEOUTconnect"); break; } /* TIMEOUTidle */ switch(cmd) { case CMD_BEGIN: section->timeout_idle=43200; /* 12 hours */ break; case CMD_EXEC: if(strcasecmp(opt, "TIMEOUTidle")) break; { char *tmp_str; section->timeout_idle=(int)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return "Illegal idle timeout"; return NULL; /* OK */ } case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = %d seconds", "TIMEOUTidle", 43200); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = seconds to keep an idle connection", "TIMEOUTidle"); break; } /* transparent */ #ifndef USE_WIN32 switch(cmd) { case CMD_BEGIN: section->option.transparent_src=0; section->option.transparent_dst=0; break; case CMD_EXEC: if(strcasecmp(opt, "transparent")) break; if(!strcasecmp(arg, "none") || !strcasecmp(arg, "no")) { section->option.transparent_src=0; section->option.transparent_dst=0; } else if(!strcasecmp(arg, "source") || !strcasecmp(arg, "yes")) { section->option.transparent_src=1; section->option.transparent_dst=0; } else if(!strcasecmp(arg, "destination")) { section->option.transparent_src=0; section->option.transparent_dst=1; } else if(!strcasecmp(arg, "both")) { section->option.transparent_src=1; section->option.transparent_dst=1; } else return "Selected transparent proxy mode is not available"; return NULL; /* OK */ case CMD_END: if(section->option.transparent_dst) ++endpoints; break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = none|source|destination|both transparent proxy mode", "transparent"); break; } #endif /* verify */ switch(cmd) { case CMD_BEGIN: section->option.request_cert=0; break; case CMD_EXEC: if(strcasecmp(opt, "verify")) break; { char *tmp_str; int tmp_int=(int)strtol(arg, &tmp_str, 10); if(tmp_str==arg || *tmp_str || tmp_int<0 || tmp_int>4) return "Bad verify level"; section->option.request_cert=1; section->option.require_cert=(tmp_int>=2); section->option.verify_chain=(tmp_int>=1 && tmp_int<=3); section->option.verify_peer=(tmp_int>=3); } return NULL; /* OK */ case CMD_END: if((section->option.verify_chain || section->option.verify_peer) && !section->ca_file && !section->ca_dir) return "Either \"CAfile\" or \"CApath\" has to be configured"; break; case CMD_FREE: break; case CMD_DEFAULT: s_log(LOG_NOTICE, "%-22s = none", "verify"); break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = level of peer certificate verification", "verify"); s_log(LOG_NOTICE, "%25slevel 0 - request and ignore peer cert", ""); s_log(LOG_NOTICE, "%25slevel 1 - only validate peer cert if present", ""); s_log(LOG_NOTICE, "%25slevel 2 - always require a valid peer cert", ""); s_log(LOG_NOTICE, "%25slevel 3 - verify peer with locally installed cert", ""); s_log(LOG_NOTICE, "%25slevel 4 - ignore CA chain and only verify peer cert", ""); break; } /* verifyChain */ switch(cmd) { case CMD_BEGIN: section->option.verify_chain=0; break; case CMD_EXEC: if(strcasecmp(opt, "verifyChain")) break; if(!strcasecmp(arg, "yes")) { section->option.request_cert=1; section->option.require_cert=1; section->option.verify_chain=1; } else if(!strcasecmp(arg, "no")) { section->option.verify_chain=0; } else { return "The argument needs to be either 'yes' or 'no'"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no verify certificate chain", "verifyChain"); break; } /* verifyPeer */ switch(cmd) { case CMD_BEGIN: section->option.verify_peer=0; break; case CMD_EXEC: if(strcasecmp(opt, "verifyPeer")) break; if(!strcasecmp(arg, "yes")) { section->option.request_cert=1; section->option.require_cert=1; section->option.verify_peer=1; } else if(!strcasecmp(arg, "no")) { section->option.verify_peer=0; } else { return "The argument needs to be either 'yes' or 'no'"; } return NULL; /* OK */ case CMD_END: break; case CMD_FREE: break; case CMD_DEFAULT: break; case CMD_HELP: s_log(LOG_NOTICE, "%-22s = yes|no verify peer certificate", "verifyPeer"); break; } /* final checks */ switch(cmd) { case CMD_BEGIN: break; case CMD_EXEC: return option_not_found; case CMD_END: if(new_service_options.next) { /* daemon mode checks */ if(endpoints!=2) return "Each service must define two endpoints"; } else { /* inetd mode checks */ if(section->option.accept) return "'accept' option is only allowed in a [section]"; /* no need to check for section->option.sni in inetd mode, as it requires valid sections to be set */ if(endpoints!=1) return "Inetd mode must define one endpoint"; } if(context_init(section)) /* initialize TLS context */ return "Failed to initialize TLS context"; case CMD_FREE: case CMD_DEFAULT: case CMD_HELP: break; } return NULL; /* OK */ } /**************************************** validate and initialize configuration */ #ifndef OPENSSL_NO_TLSEXT NOEXPORT char *sni_init(SERVICE_OPTIONS *section) { char *tmp_str; SERVICE_OPTIONS *tmpsrv; /* server mode: update servername_list based on the SNI option */ if(!section->option.client && section->sni) { tmp_str=strchr(section->sni, ':'); if(!tmp_str) return "Invalid SNI parameter format"; *tmp_str++='\0'; for(tmpsrv=new_service_options.next; tmpsrv; tmpsrv=tmpsrv->next) if(!strcmp(tmpsrv->servname, section->sni)) break; if(!tmpsrv) return "SNI section name not found"; if(tmpsrv->option.client) return "SNI master service is a TLS client"; if(tmpsrv->servername_list_tail) { tmpsrv->servername_list_tail->next=str_alloc(sizeof(SERVERNAME_LIST)); tmpsrv->servername_list_tail=tmpsrv->servername_list_tail->next; } else { /* first virtual service */ tmpsrv->servername_list_head= tmpsrv->servername_list_tail= str_alloc(sizeof(SERVERNAME_LIST)); tmpsrv->ssl_options_set|= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; } tmpsrv->servername_list_tail->servername=str_dup(tmp_str); tmpsrv->servername_list_tail->opt=section; tmpsrv->servername_list_tail->next=NULL; section->option.sni=1; /* always negotiate a new session on renegotiation, as the TLS * context settings (including access control) may be different */ section->ssl_options_set|= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; } /* client mode: setup SNI default based on 'protocolHost' and 'connect' options */ if(section->option.client && !section->sni) { /* setup host_name for SNI, prefer SNI and protocolHost if specified */ if(section->protocol_host) /* 'protocolHost' option */ section->sni=str_dup(section->protocol_host); else if(section->connect_addr.names) /* 'connect' option */ section->sni=str_dup(section->connect_addr.names->name); /* first hostname */ if(section->sni) { /* either 'protocolHost' or 'connect' specified */ tmp_str=strrchr(section->sni, ':'); if(tmp_str) { /* 'host:port' -> drop ':port' */ *tmp_str='\0'; } else { /* 'port' -> default to 'localhost' */ str_free(section->sni); section->sni=str_dup("localhost"); } } } return NULL; } #endif /* !defined(OPENSSL_NO_TLSEXT) */ /**************************************** facility/debug level */ typedef struct { char *name; int value; } facilitylevel; NOEXPORT char *parse_debug_level(char *arg, SERVICE_OPTIONS *section) { char *arg_copy; char *string; facilitylevel *fl; /* facilities only make sense on unix */ #if !defined (USE_WIN32) && !defined (__vms) facilitylevel facilities[] = { {"auth", LOG_AUTH}, {"cron", LOG_CRON}, {"daemon", LOG_DAEMON}, {"kern", LOG_KERN}, {"lpr", LOG_LPR}, {"mail", LOG_MAIL}, {"news", LOG_NEWS}, {"syslog", LOG_SYSLOG}, {"user", LOG_USER}, {"uucp", LOG_UUCP}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, /* some facilities are not defined on all Unices */ #ifdef LOG_AUTHPRIV {"authpriv", LOG_AUTHPRIV}, #endif #ifdef LOG_FTP {"ftp", LOG_FTP}, #endif #ifdef LOG_NTP {"ntp", LOG_NTP}, #endif {NULL, 0} }; #endif /* USE_WIN32, __vms */ facilitylevel levels[] = { {"emerg", LOG_EMERG}, {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"err", LOG_ERR}, {"warning", LOG_WARNING}, {"notice", LOG_NOTICE}, {"info", LOG_INFO}, {"debug", LOG_DEBUG}, {NULL, -1} }; arg_copy=str_dup(arg); string=arg_copy; /* facilities only make sense on Unix */ #if !defined (USE_WIN32) && !defined (__vms) if(section==&new_service_options && strchr(string, '.')) { /* a facility was specified in the global options */ new_global_options.log_facility=-1; string=strtok(arg_copy, "."); /* break it up */ for(fl=facilities; fl->name; ++fl) { if(!strcasecmp(fl->name, string)) { new_global_options.log_facility=fl->value; break; } } if(new_global_options.log_facility==-1) return "Illegal syslog facility"; string=strtok(NULL, "."); /* set to the remainder */ } #endif /* USE_WIN32, __vms */ /* time to check the syslog level */ if(string && strlen(string)==1 && *string>='0' && *string<='7') { section->log_level=*string-'0'; return NULL; /* OK */ } section->log_level=8; /* illegal level */ for(fl=levels; fl->name; ++fl) { if(!strcasecmp(fl->name, string)) { section->log_level=fl->value; break; } } if(section->log_level==8) return "Illegal debug level"; /* FAILED */ return NULL; /* OK */ } /**************************************** TLS options */ NOEXPORT long unsigned parse_ssl_option(char *arg) { SSL_OPTION *option; for(option=(SSL_OPTION *)ssl_opts; option->name; ++option) if(!strcasecmp(option->name, arg)) return option->value; return 0; /* FAILED */ } NOEXPORT void print_ssl_options(void) { SSL_OPTION *option; s_log(LOG_NOTICE, " "); s_log(LOG_NOTICE, "Supported TLS options:"); for(option=(SSL_OPTION *)ssl_opts; option->name; ++option) s_log(LOG_NOTICE, "options = %s", option->name); } /**************************************** read PSK file */ #ifndef OPENSSL_NO_PSK NOEXPORT PSK_KEYS *psk_read(char *key_file) { DISK_FILE *df; char line[CONFLINELEN], *key_val; size_t key_len; PSK_KEYS *head=NULL, *tail=NULL, *curr; int line_number=0; if(file_permissions(key_file)) return NULL; df=file_open(key_file, FILE_MODE_READ); if(!df) { s_log(LOG_ERR, "Cannot open PSKsecrets file"); return NULL; } while(file_getline(df, line, CONFLINELEN)>=0) { ++line_number; if(!line[0]) /* empty line */ continue; key_val=strchr(line, ':'); if(!key_val) { s_log(LOG_ERR, "PSKsecrets line %d: Not in identity:key format", line_number); file_close(df); psk_free(head); return NULL; } *key_val++='\0'; key_len=strlen(key_val); if(strlen(line)+1>PSK_MAX_IDENTITY_LEN) { /* with the trailing '\0' */ s_log(LOG_ERR, "PSKsecrets line %d: Identity longer than %d characters", line_number, PSK_MAX_IDENTITY_LEN); file_close(df); psk_free(head); return NULL; } if(key_len>PSK_MAX_PSK_LEN) { s_log(LOG_ERR, "PSKsecrets line %d: Key longer than %d characters", line_number, PSK_MAX_PSK_LEN); file_close(df); psk_free(head); return NULL; } if(key_len<20) { /* shorter keys are unlikely to have sufficient entropy */ s_log(LOG_ERR, "PSKsecrets line %d: Key shorter than 20 characters", line_number); file_close(df); psk_free(head); return NULL; } curr=str_alloc(sizeof(PSK_KEYS)); curr->identity=str_dup(line); curr->key_val=(unsigned char *)str_dup(key_val); curr->key_len=key_len; curr->next=NULL; if(head) tail->next=curr; else head=curr; tail=curr; } file_close(df); return head; } NOEXPORT void psk_free(PSK_KEYS *head) { PSK_KEYS *next; while(head) { next=head->next; str_free(head->identity); str_free(head->key_val); str_free(head); head=next; } } #endif /**************************************** socket options */ static int on=1; #define DEF_ON ((void *)&on) SOCK_OPT *sock_opts=NULL, sock_opts_def[]= { {"SO_DEBUG", SOL_SOCKET, SO_DEBUG, TYPE_FLAG, {NULL, NULL, NULL}}, {"SO_DONTROUTE", SOL_SOCKET, SO_DONTROUTE, TYPE_FLAG, {NULL, NULL, NULL}}, {"SO_KEEPALIVE", SOL_SOCKET, SO_KEEPALIVE, TYPE_FLAG, {NULL, NULL, NULL}}, {"SO_LINGER", SOL_SOCKET, SO_LINGER, TYPE_LINGER, {NULL, NULL, NULL}}, {"SO_OOBINLINE", SOL_SOCKET, SO_OOBINLINE, TYPE_FLAG, {NULL, NULL, NULL}}, {"SO_RCVBUF", SOL_SOCKET, SO_RCVBUF, TYPE_INT, {NULL, NULL, NULL}}, {"SO_SNDBUF", SOL_SOCKET, SO_SNDBUF, TYPE_INT, {NULL, NULL, NULL}}, #ifdef SO_RCVLOWAT {"SO_RCVLOWAT", SOL_SOCKET, SO_RCVLOWAT, TYPE_INT, {NULL, NULL, NULL}}, #endif #ifdef SO_SNDLOWAT {"SO_SNDLOWAT", SOL_SOCKET, SO_SNDLOWAT, TYPE_INT, {NULL, NULL, NULL}}, #endif #ifdef SO_RCVTIMEO {"SO_RCVTIMEO", SOL_SOCKET, SO_RCVTIMEO, TYPE_TIMEVAL, {NULL, NULL, NULL}}, #endif #ifdef SO_SNDTIMEO {"SO_SNDTIMEO", SOL_SOCKET, SO_SNDTIMEO, TYPE_TIMEVAL, {NULL, NULL, NULL}}, #endif #ifdef USE_WIN32 {"SO_EXCLUSIVEADDRUSE", SOL_SOCKET, SO_EXCLUSIVEADDRUSE, TYPE_FLAG, {DEF_ON, NULL, NULL}}, {"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, TYPE_FLAG, {NULL, NULL, NULL}}, #else {"SO_REUSEADDR", SOL_SOCKET, SO_REUSEADDR, TYPE_FLAG, {DEF_ON, NULL, NULL}}, #endif #ifdef SO_BINDTODEVICE {"SO_BINDTODEVICE", SOL_SOCKET, SO_BINDTODEVICE, TYPE_STRING, {NULL, NULL, NULL}}, #endif #ifdef SOL_TCP #ifdef TCP_KEEPCNT {"TCP_KEEPCNT", SOL_TCP, TCP_KEEPCNT, TYPE_INT, {NULL, NULL, NULL}}, #endif #ifdef TCP_KEEPIDLE {"TCP_KEEPIDLE", SOL_TCP, TCP_KEEPIDLE, TYPE_INT, {NULL, NULL, NULL}}, #endif #ifdef TCP_KEEPINTVL {"TCP_KEEPINTVL", SOL_TCP, TCP_KEEPINTVL, TYPE_INT, {NULL, NULL, NULL}}, #endif #endif /* SOL_TCP */ #ifdef IP_TOS {"IP_TOS", IPPROTO_IP, IP_TOS, TYPE_INT, {NULL, NULL, NULL}}, #endif #ifdef IP_TTL {"IP_TTL", IPPROTO_IP, IP_TTL, TYPE_INT, {NULL, NULL, NULL}}, #endif #ifdef IP_MAXSEG {"TCP_MAXSEG", IPPROTO_TCP, TCP_MAXSEG, TYPE_INT, {NULL, NULL, NULL}}, #endif {"TCP_NODELAY", IPPROTO_TCP, TCP_NODELAY, TYPE_FLAG, {NULL, DEF_ON, DEF_ON}}, #ifdef IP_FREEBIND {"IP_FREEBIND", IPPROTO_IP, IP_FREEBIND, TYPE_FLAG, {NULL, NULL, NULL}}, #endif #ifdef IP_BINDANY {"IP_BINDANY", IPPROTO_IP, IP_BINDANY, TYPE_FLAG, {NULL, NULL, NULL}}, #endif #ifdef IPV6_BINDANY {"IPV6_BINDANY", IPPROTO_IPV6, IPV6_BINDANY, TYPE_FLAG, {NULL, NULL, NULL}}, #endif #ifdef IPV6_V6ONLY {"IPV6_V6ONLY", IPPROTO_IPV6, IPV6_V6ONLY, TYPE_FLAG, {NULL, NULL, NULL}}, #endif {NULL, 0, 0, TYPE_NONE, {NULL, NULL, NULL}} }; NOEXPORT void init_socket_options(void) { #ifdef USE_WIN32 DWORD version; int major, minor; SOCK_OPT *ptr; version=GetVersion(); major=LOBYTE(LOWORD(version)); minor=HIBYTE(LOWORD(version)); s_log(LOG_DEBUG, "Running on Windows %d.%d", major, minor); for(ptr=sock_opts_def; ptr->opt_str; ++ptr) if(ptr->opt_level==SOL_SOCKET && ptr->opt_name==SO_EXCLUSIVEADDRUSE) ptr->opt_val[0]=major>5 ? DEF_ON : NULL; /* Vista or later */ #endif if(!sock_opts) sock_opts=str_alloc_detached(sizeof sock_opts_def); memcpy(sock_opts, sock_opts_def, sizeof sock_opts_def); } NOEXPORT int print_socket_options(void) { SOCKET fd; socklen_t optlen; SOCK_OPT *ptr; OPT_UNION val; char *ta, *tl, *tr, *td; fd=socket(AF_INET, SOCK_STREAM, 0); init_socket_options(); s_log(LOG_NOTICE, " "); s_log(LOG_NOTICE, "Socket option defaults:"); s_log(LOG_NOTICE, " Option Name | Accept | Local | Remote |OS default"); s_log(LOG_NOTICE, " --------------------+----------+----------+----------+----------"); for(ptr=sock_opts; ptr->opt_str; ++ptr) { /* get OS default value */ optlen=sizeof val; if(getsockopt(fd, ptr->opt_level, ptr->opt_name, (void *)&val, &optlen)) { switch(get_last_socket_error()) { case S_ENOPROTOOPT: case S_EOPNOTSUPP: td=str_dup("write-only"); break; default: s_log(LOG_ERR, "Failed to get %s OS default", ptr->opt_str); sockerror("getsockopt"); closesocket(fd); return 1; /* FAILED */ } } else td=print_option(ptr->opt_type, &val); /* get stunnel default values */ ta=print_option(ptr->opt_type, ptr->opt_val[0]); tl=print_option(ptr->opt_type, ptr->opt_val[1]); tr=print_option(ptr->opt_type, ptr->opt_val[2]); /* print collected data and fee the memory */ s_log(LOG_NOTICE, " %-20s|%10s|%10s|%10s|%10s", ptr->opt_str, ta, tl, tr, td); str_free(ta); str_free(tl); str_free(tr); str_free(td); } closesocket(fd); return 0; /* OK */ } NOEXPORT char *print_option(int type, OPT_UNION *val) { if(!val) return str_dup(" -- "); switch(type) { case TYPE_FLAG: return str_printf("%s", val->i_val ? "yes" : "no"); case TYPE_INT: return str_printf("%d", val->i_val); case TYPE_LINGER: return str_printf("%d:%d", val->linger_val.l_onoff, val->linger_val.l_linger); case TYPE_TIMEVAL: return str_printf("%d:%d", (int)val->timeval_val.tv_sec, (int)val->timeval_val.tv_usec); case TYPE_STRING: return str_printf("%s", val->c_val); } return str_dup(" Ooops? "); /* internal error? */ } NOEXPORT int parse_socket_option(char *arg) { int socket_type; /* 0-accept, 1-local, 2-remote */ char *opt_val_str, *opt_val2_str, *tmp_str; SOCK_OPT *ptr; if(arg[1]!=':') return 1; /* FAILED */ switch(arg[0]) { case 'a': socket_type=0; break; case 'l': socket_type=1; break; case 'r': socket_type=2; break; default: return 1; /* FAILED */ } arg+=2; opt_val_str=strchr(arg, '='); if(!opt_val_str) /* no '='? */ return 1; /* FAILED */ *opt_val_str++='\0'; ptr=sock_opts; for(;;) { if(!ptr->opt_str) return 1; /* FAILED */ if(!strcmp(arg, ptr->opt_str)) break; /* option name found */ ++ptr; } ptr->opt_val[socket_type]=str_alloc(sizeof(OPT_UNION)); switch(ptr->opt_type) { case TYPE_FLAG: if(!strcasecmp(opt_val_str, "yes") || !strcmp(opt_val_str, "1")) { ptr->opt_val[socket_type]->i_val=1; return 0; /* OK */ } if(!strcasecmp(opt_val_str, "no") || !strcmp(opt_val_str, "0")) { ptr->opt_val[socket_type]->i_val=0; return 0; /* OK */ } return 1; /* FAILED */ case TYPE_INT: ptr->opt_val[socket_type]->i_val=(int)strtol(opt_val_str, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return 1; /* FAILED */ return 0; /* OK */ case TYPE_LINGER: opt_val2_str=strchr(opt_val_str, ':'); if(opt_val2_str) { *opt_val2_str++='\0'; ptr->opt_val[socket_type]->linger_val.l_linger= (u_short)strtol(opt_val2_str, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return 1; /* FAILED */ } else { ptr->opt_val[socket_type]->linger_val.l_linger=0; } ptr->opt_val[socket_type]->linger_val.l_onoff= (u_short)strtol(opt_val_str, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return 1; /* FAILED */ return 0; /* OK */ case TYPE_TIMEVAL: opt_val2_str=strchr(opt_val_str, ':'); if(opt_val2_str) { *opt_val2_str++='\0'; ptr->opt_val[socket_type]->timeval_val.tv_usec= (int)strtol(opt_val2_str, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return 1; /* FAILED */ } else { ptr->opt_val[socket_type]->timeval_val.tv_usec=0; } ptr->opt_val[socket_type]->timeval_val.tv_sec= (int)strtol(opt_val_str, &tmp_str, 10); if(tmp_str==arg || *tmp_str) /* not a number */ return 1; /* FAILED */ return 0; /* OK */ case TYPE_STRING: if(strlen(opt_val_str)+1>sizeof(OPT_UNION)) return 1; /* FAILED */ strcpy(ptr->opt_val[socket_type]->c_val, opt_val_str); return 0; /* OK */ default: ; /* ANSI C compiler needs it */ } return 1; /* FAILED */ } /**************************************** OCSP */ #ifndef OPENSSL_NO_OCSP NOEXPORT unsigned long parse_ocsp_flag(char *arg) { struct { char *name; unsigned long value; } ocsp_opts[] = { {"NOCERTS", OCSP_NOCERTS}, {"NOINTERN", OCSP_NOINTERN}, {"NOSIGS", OCSP_NOSIGS}, {"NOCHAIN", OCSP_NOCHAIN}, {"NOVERIFY", OCSP_NOVERIFY}, {"NOEXPLICIT", OCSP_NOEXPLICIT}, {"NOCASIGN", OCSP_NOCASIGN}, {"NODELEGATED", OCSP_NODELEGATED}, {"NOCHECKS", OCSP_NOCHECKS}, {"TRUSTOTHER", OCSP_TRUSTOTHER}, {"RESPID_KEY", OCSP_RESPID_KEY}, {"NOTIME", OCSP_NOTIME}, {NULL, 0} }, *option; for(option=ocsp_opts; option->name; ++option) if(!strcasecmp(option->name, arg)) return option->value; return 0; /* FAILED */ } #endif /* !defined(OPENSSL_NO_OCSP) */ /**************************************** engine */ #ifndef OPENSSL_NO_ENGINE #define MAX_ENGINES 256 static ENGINE *engines[MAX_ENGINES]; /* table of engines for config parser */ static int current_engine; static int engine_initialized; NOEXPORT void engine_reset_list(void) { current_engine=-1; engine_initialized=1; } NOEXPORT char *engine_auto(void) { ENGINE *e; s_log(LOG_INFO, "Enabling automatic engine support"); ENGINE_register_all_complete(); /* rebuild the internal list of engines */ engine_reset_list(); for(e=ENGINE_get_first(); e; e=ENGINE_get_next(e)) { if(++current_engine>=MAX_ENGINES) return "Too many open engines"; engines[current_engine]=e; s_log(LOG_INFO, "Engine #%d (%s) registered", current_engine+1, ENGINE_get_id(e)); } s_log(LOG_INFO, "Automatic engine support enabled"); return NULL; /* OK */ } NOEXPORT char *engine_open(const char *name) { engine_init(); /* initialize the previous engine (if any) */ if(++current_engine>=MAX_ENGINES) return "Too many open engines"; s_log(LOG_DEBUG, "Enabling support for engine \"%s\"", name); engines[current_engine]=ENGINE_by_id(name); if(!engines[current_engine]) { sslerror("ENGINE_by_id"); return "Failed to open the engine"; } engine_initialized=0; if(ENGINE_ctrl(engines[current_engine], ENGINE_CTRL_SET_USER_INTERFACE, 0, UI_stunnel(), NULL)) { s_log(LOG_NOTICE, "UI set for engine #%d (%s)", current_engine+1, ENGINE_get_id(engines[current_engine])); } else { ERR_clear_error(); s_log(LOG_INFO, "UI not supported by engine #%d (%s)", current_engine+1, ENGINE_get_id(engines[current_engine])); } return NULL; /* OK */ } NOEXPORT char *engine_ctrl(const char *cmd, const char *arg) { if(current_engine<0) return "No engine was defined"; if(!strcasecmp(cmd, "INIT")) /* special control command */ return engine_init(); if(arg) s_log(LOG_DEBUG, "Executing engine control command %s:%s", cmd, arg); else s_log(LOG_DEBUG, "Executing engine control command %s", cmd); if(!ENGINE_ctrl_cmd_string(engines[current_engine], cmd, arg, 0)) { sslerror("ENGINE_ctrl_cmd_string"); return "Failed to execute the engine control command"; } return NULL; /* OK */ } NOEXPORT char *engine_default(const char *list) { if(current_engine<0) return "No engine was defined"; if(!ENGINE_set_default_string(engines[current_engine], list)) { sslerror("ENGINE_set_default_string"); return "Failed to set engine as default"; } s_log(LOG_INFO, "Engine #%d (%s) set as default for %s", current_engine+1, ENGINE_get_id(engines[current_engine]), list); return NULL; } NOEXPORT char *engine_init(void) { if(engine_initialized) /* either first or already initialized */ return NULL; /* OK */ s_log(LOG_DEBUG, "Initializing engine #%d (%s)", current_engine+1, ENGINE_get_id(engines[current_engine])); if(!ENGINE_init(engines[current_engine])) { if(ERR_peek_last_error()) /* really an error */ sslerror("ENGINE_init"); else s_log(LOG_ERR, "Engine #%d (%s) not initialized", current_engine+1, ENGINE_get_id(engines[current_engine])); return "Engine initialization failed"; } #if 0 /* it is a bad idea to set the engine as default for all sections */ /* the "engine=auto" or "engineDefault" options should be used instead */ if(!ENGINE_set_default(engines[current_engine], ENGINE_METHOD_ALL)) { sslerror("ENGINE_set_default"); return "Selecting default engine failed"; } #endif /* engines can add new algorithms */ #if OPENSSL_VERSION_NUMBER>=0x10100000L OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS| OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); #else OpenSSL_add_all_algorithms(); #endif s_log(LOG_INFO, "Engine #%d (%s) initialized", current_engine+1, ENGINE_get_id(engines[current_engine])); engine_initialized=1; return NULL; /* OK */ } NOEXPORT ENGINE *engine_get_by_id(const char *id) { int i; for(i=0; i<=current_engine; ++i) if(!strcmp(id, ENGINE_get_id(engines[i]))) return engines[i]; return NULL; } NOEXPORT ENGINE *engine_get_by_num(const int i) { if(i<0 || i>current_engine) return NULL; return engines[i]; } #endif /* !defined(OPENSSL_NO_ENGINE) */ /**************************************** fatal error */ NOEXPORT void print_syntax(void) { s_log(LOG_NOTICE, " "); s_log(LOG_NOTICE, "Syntax:"); s_log(LOG_NOTICE, "stunnel " #ifdef USE_WIN32 #ifndef _WIN32_WCE "[ [-install | -uninstall | -start | -stop | -reload | -reopen | -exit] " #endif "[-quiet] " #endif "[] ] " #ifndef USE_WIN32 "-fd " #endif "| -help | -version | -sockets | -options"); s_log(LOG_NOTICE, " - use specified config file"); #ifdef USE_WIN32 #ifndef _WIN32_WCE s_log(LOG_NOTICE, " -install - install NT service"); s_log(LOG_NOTICE, " -uninstall - uninstall NT service"); s_log(LOG_NOTICE, " -start - start NT service"); s_log(LOG_NOTICE, " -stop - stop NT service"); s_log(LOG_NOTICE, " -reload - reload configuration for NT service"); s_log(LOG_NOTICE, " -reopen - reopen log file for NT service"); s_log(LOG_NOTICE, " -exit - exit an already started stunnel"); #endif s_log(LOG_NOTICE, " -quiet - don't display message boxes"); #else s_log(LOG_NOTICE, " -fd - read the config file from a file descriptor"); #endif s_log(LOG_NOTICE, " -help - get config file help"); s_log(LOG_NOTICE, " -version - display version and defaults"); s_log(LOG_NOTICE, " -sockets - display default socket options"); s_log(LOG_NOTICE, " -options - display supported TLS options"); } /**************************************** various supporting functions */ NOEXPORT void name_list_append(NAME_LIST **ptr, char *name) { while(*ptr) /* find the null pointer */ ptr=&(*ptr)->next; *ptr=str_alloc(sizeof(NAME_LIST)); (*ptr)->name=str_dup(name); (*ptr)->next=NULL; } #ifndef USE_WIN32 NOEXPORT char **argalloc(char *str) { /* allocate 'exec' argumets */ size_t max_arg, i; char *ptr, **retval; max_arg=strlen(str)/2+1; ptr=str_dup(str); retval=str_alloc((max_arg+1)*sizeof(char *)); i=0; while(*ptr && i