/** * \file client/config_init.c * * \brief Command-line and config file processing for fwknop client. */ /* Fwknop is developed primarily by the people listed in the file 'AUTHORS'. * Copyright (C) 2009-2015 fwknop developers and contributors. For a full * list of contributors, see the file 'CREDITS'. * * License (GNU General Public License): * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * ****************************************************************************** */ #include "fwknop_common.h" #include "netinet_common.h" #include "config_init.h" #include "cmd_opts.h" #include "utils.h" #include #include #ifdef WIN32 #define STDIN_FILENO 0 #endif #define RC_PARAM_TEMPLATE "%-24s %s\n" /*!< Template to define param = val in a rc file */ #define RC_SECTION_DEFAULT "default" /*!< Name of the default section in fwknoprc */ #define RC_SECTION_TEMPLATE "[%s]\n" /*!< Template to define a section in a rc file */ #define FWKNOPRC_OFLAGS (O_WRONLY|O_CREAT|O_EXCL) /*!< O_flags used to create an fwknoprc file with the open function */ #define FWKNOPRC_MODE (S_IRUSR|S_IWUSR) /*!< mode used to create an fwknoprc file with the open function */ #define PARAM_YES_VALUE "Y" /*!< String which represents a YES value for a parameter in fwknoprc */ #define PARAM_NO_VALUE "N" /*!< String which represents a NO value for a parameter in fwknoprc */ #define POSITION_TO_BITMASK(x) ((uint32_t)(1) << ((x) % 32)) /*!< Macro do get a bitmask from a position */ #define BITMASK_ARRAY_SIZE 2 /*!< Number of 32bits integer used to handle bitmask in the fko_var_bitmask_t structure */ #define LF_CHAR 0x0A /*!< Hexadecimal value associated to the LF char */ #ifdef HAVE_C_UNIT_TESTS #include "cunit_common.h" DECLARE_TEST_SUITE(config_init, "Config init test suite"); #endif /** * Structure to handle long bitmask. * * The structure is built as an array of unsigned 32 bits integer to be able to * easily increase the size of the bitmask. * This bitmask can contains at most (BITMASK_ARRAY_SIZE * 32) values. */ typedef struct fko_var_bitmask { uint32_t dw[BITMASK_ARRAY_SIZE]; /*!< Array of bitmasks */ } fko_var_bitmask_t; /** * Structure to handle a variable in an rcfile (name and value) */ typedef struct rc_file_param { char name[MAX_LINE_LEN]; /*!< Variable name */ char val[MAX_LINE_LEN]; /*!< Variable value */ } rc_file_param_t; /** * Structure to identify a configuration variable (name and position) */ typedef struct fko_var { const char name[32]; /*!< Variable name in fwknoprc */ unsigned int pos; /*!< Variable position from the fwknop_cli_arg_t enumeration */ } fko_var_t; enum { FWKNOP_CLI_FIRST_ARG = 0, FWKNOP_CLI_ARG_DIGEST_TYPE = 0, FWKNOP_CLI_ARG_SPA_SERVER_PROTO, FWKNOP_CLI_ARG_SPA_SERVER_PORT, FWKNOP_CLI_ARG_SPA_SOURCE_PORT, FWKNOP_CLI_ARG_FW_TIMEOUT, FWKNOP_CLI_ARG_ALLOW_IP, FWKNOP_CLI_ARG_TIME_OFFSET, FWKNOP_CLI_ARG_ENCRYPTION_MODE, FWKNOP_CLI_ARG_USE_GPG, FWKNOP_CLI_ARG_USE_GPG_AGENT, FWKNOP_CLI_ARG_GPG_NO_SIGNING_PW, FWKNOP_CLI_ARG_GPG_RECIPIENT, FWKNOP_CLI_ARG_GPG_SIGNER, FWKNOP_CLI_ARG_GPG_HOMEDIR, FWKNOP_CLI_ARG_GPG_EXE_PATH, FWKNOP_CLI_ARG_SPOOF_USER, FWKNOP_CLI_ARG_SPOOF_SOURCE_IP, FWKNOP_CLI_ARG_ACCESS, FWKNOP_CLI_ARG_SPA_SERVER, FWKNOP_CLI_ARG_RAND_PORT, FWKNOP_CLI_ARG_KEY_RIJNDAEL, FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64, FWKNOP_CLI_ARG_GPG_SIGNING_PW, FWKNOP_CLI_ARG_GPG_SIGNING_PW_BASE64, FWKNOP_CLI_ARG_HMAC_DIGEST_TYPE, FWKNOP_CLI_ARG_KEY_HMAC_BASE64, FWKNOP_CLI_ARG_KEY_HMAC, FWKNOP_CLI_ARG_USE_HMAC, FWKNOP_CLI_ARG_USE_WGET_USER_AGENT, FWKNOP_CLI_ARG_KEY_FILE, FWKNOP_CLI_ARG_HMAC_KEY_FILE, FWKNOP_CLI_ARG_NAT_ACCESS, FWKNOP_CLI_ARG_HTTP_USER_AGENT, FWKNOP_CLI_ARG_RESOLVE_URL, FWKNOP_CLI_ARG_SERVER_RESOLVE_IPV4, FWKNOP_CLI_ARG_NAT_LOCAL, FWKNOP_CLI_ARG_NAT_RAND_PORT, FWKNOP_CLI_ARG_NAT_PORT, FWKNOP_CLI_ARG_VERBOSE, FWKNOP_CLI_ARG_RESOLVE_IP_HTTP, FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS, FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY, FWKNOP_CLI_ARG_WGET_CMD, FWKNOP_CLI_ARG_NO_SAVE_ARGS, FWKNOP_CLI_LAST_ARG } fwknop_cli_arg_t; static fko_var_t fko_var_array[FWKNOP_CLI_LAST_ARG] = { { "DIGEST_TYPE", FWKNOP_CLI_ARG_DIGEST_TYPE }, { "SPA_SERVER_PROTO", FWKNOP_CLI_ARG_SPA_SERVER_PROTO }, { "SPA_SERVER_PORT", FWKNOP_CLI_ARG_SPA_SERVER_PORT }, { "SPA_SOURCE_PORT", FWKNOP_CLI_ARG_SPA_SOURCE_PORT }, { "FW_TIMEOUT", FWKNOP_CLI_ARG_FW_TIMEOUT }, { "ALLOW_IP", FWKNOP_CLI_ARG_ALLOW_IP }, { "TIME_OFFSET", FWKNOP_CLI_ARG_TIME_OFFSET }, { "ENCRYPTION_MODE", FWKNOP_CLI_ARG_ENCRYPTION_MODE }, { "USE_GPG", FWKNOP_CLI_ARG_USE_GPG }, { "USE_GPG_AGENT", FWKNOP_CLI_ARG_USE_GPG_AGENT }, { "GPG_RECIPIENT", FWKNOP_CLI_ARG_GPG_RECIPIENT }, { "GPG_SIGNER", FWKNOP_CLI_ARG_GPG_SIGNER }, { "GPG_HOMEDIR", FWKNOP_CLI_ARG_GPG_HOMEDIR }, { "GPG_EXE", FWKNOP_CLI_ARG_GPG_EXE_PATH }, { "GPG_SIGNING_PW", FWKNOP_CLI_ARG_GPG_SIGNING_PW }, { "GPG_SIGNING_PW_BASE64", FWKNOP_CLI_ARG_GPG_SIGNING_PW_BASE64 }, { "GPG_NO_SIGNING_PW", FWKNOP_CLI_ARG_GPG_NO_SIGNING_PW }, { "SPOOF_USER", FWKNOP_CLI_ARG_SPOOF_USER }, { "SPOOF_SOURCE_IP", FWKNOP_CLI_ARG_SPOOF_SOURCE_IP }, { "ACCESS", FWKNOP_CLI_ARG_ACCESS }, { "SPA_SERVER", FWKNOP_CLI_ARG_SPA_SERVER }, { "RAND_PORT", FWKNOP_CLI_ARG_RAND_PORT }, { "KEY", FWKNOP_CLI_ARG_KEY_RIJNDAEL }, { "KEY_BASE64", FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64 }, { "HMAC_DIGEST_TYPE", FWKNOP_CLI_ARG_HMAC_DIGEST_TYPE }, { "HMAC_KEY_BASE64", FWKNOP_CLI_ARG_KEY_HMAC_BASE64 }, { "HMAC_KEY", FWKNOP_CLI_ARG_KEY_HMAC }, { "USE_HMAC", FWKNOP_CLI_ARG_USE_HMAC }, { "USE_WGET_USER_AGENT", FWKNOP_CLI_ARG_USE_WGET_USER_AGENT }, { "KEY_FILE", FWKNOP_CLI_ARG_KEY_FILE }, { "HMAC_KEY_FILE", FWKNOP_CLI_ARG_HMAC_KEY_FILE }, { "NAT_ACCESS", FWKNOP_CLI_ARG_NAT_ACCESS }, { "HTTP_USER_AGENT", FWKNOP_CLI_ARG_HTTP_USER_AGENT }, { "RESOLVE_URL", FWKNOP_CLI_ARG_RESOLVE_URL }, { "SERVER_RESOLVE_IPV4", FWKNOP_CLI_ARG_SERVER_RESOLVE_IPV4 }, { "NAT_LOCAL", FWKNOP_CLI_ARG_NAT_LOCAL }, { "NAT_RAND_PORT", FWKNOP_CLI_ARG_NAT_RAND_PORT }, { "NAT_PORT", FWKNOP_CLI_ARG_NAT_PORT }, { "VERBOSE", FWKNOP_CLI_ARG_VERBOSE }, { "RESOLVE_IP_HTTP", FWKNOP_CLI_ARG_RESOLVE_IP_HTTP }, { "RESOLVE_IP_HTTPS", FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS }, { "RESOLVE_HTTP_ONLY", FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY }, { "WGET_CMD", FWKNOP_CLI_ARG_WGET_CMD }, { "NO_SAVE_ARGS", FWKNOP_CLI_ARG_NO_SAVE_ARGS } }; /* Array to define which conf. variables are critical and should not be * overwritten when a stanza is updated using the --save-rc-stanza arg * without the user validation */ static int critical_var_array[] = { FWKNOP_CLI_ARG_KEY_RIJNDAEL, FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64, FWKNOP_CLI_ARG_KEY_HMAC, FWKNOP_CLI_ARG_KEY_HMAC_BASE64, FWKNOP_CLI_ARG_GPG_RECIPIENT, FWKNOP_CLI_ARG_GPG_SIGNER, FWKNOP_CLI_ARG_GPG_SIGNING_PW, FWKNOP_CLI_ARG_GPG_SIGNING_PW_BASE64 }; /** * @brief Generate Rijndael + HMAC keys from /dev/urandom (base64 encoded). * * @param options FKO command line option structure */ static void generate_keys(fko_cli_options_t *options) { int res; /* If asked, we have to generate the keys */ if(options->key_gen) { /* Zero out the key buffers */ memset(&(options->key_base64), 0x00, sizeof(options->key_base64)); memset(&(options->hmac_key_base64), 0x00, sizeof(options->hmac_key_base64)); /* Generate the key through libfko */ res = fko_key_gen(options->key_base64, options->key_len, options->hmac_key_base64, options->hmac_key_len, options->hmac_type); /* Exit upon key generation failure*/ if(res != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "%s: fko_key_gen: Error %i - %s", MY_NAME, res, fko_errstr(res)); exit(EXIT_FAILURE); } /* Everything is ok - nothing to do */ else; } /* No key generation asked - nothing to do */ else; } /** * @brief Check if a variable is a critical var. * * This function check the critical_var_array table to find if the variable * position is available. * * @param var_pos Fwknop configuration variable position * * @return 1 the variable is critical, 0 otherwise */ static int var_is_critical(short var_pos) { int ndx; /* Index on the critical_var_array array */ int var_found = 0; /* Go through the array of critical vars */ for (ndx=0 ; ndxdw[bitmask_ndx] |= POSITION_TO_BITMASK(var_pos); /* The index on the uint32_t bitmask is invalid */ else log_msg(LOG_VERBOSITY_WARNING, "add_var_to_bitmask() : Bad variable position %u", var_pos); } /** * @brief Remove a variable from a bitmask * * This function removes the bitmask associated to the variable position from a * bitmask. * * @param var_pos Fwknop configuration variable position * @param bm fko_var_bitmask_t structure to update */ static void remove_var_from_bitmask(short var_pos, fko_var_bitmask_t *bm) { unsigned int bitmask_ndx; /* Look for the index on the uint32_t array we have to process */ bitmask_ndx = var_pos / 32; /* Set the bitmask according to the index found */ if (bitmask_ndx < BITMASK_ARRAY_SIZE) bm->dw[bitmask_ndx] &= ~POSITION_TO_BITMASK(var_pos); /* The index on the uint32_t bitmask is invalid */ else log_msg(LOG_VERBOSITY_WARNING, "remove_from_bitmask() : Bad variable position %u", var_pos); } /** * @brief Return whether a variable is available in a bitmask * * The variable bitmask is looked for in the bitmask. * * @param var_pos Fwknop configuration variable position * @param bm fko_var_bitmask_t structure to check * * @return 1 if the bitmsk contains the variable, 0 otherwise. */ static int bitmask_has_var(short var_pos, fko_var_bitmask_t *bm) { unsigned int bitmask_ndx; int var_found = 0; /* Look for the index on the uint32_t array we have to process */ bitmask_ndx = var_pos / 32; /* Check the bitmask according to the index found */ if (bitmask_ndx < BITMASK_ARRAY_SIZE) { if ( bm->dw[bitmask_ndx] & POSITION_TO_BITMASK(var_pos) ) var_found = 1; } /* The index on the uint32_t bitmask is invalid */ else log_msg(LOG_VERBOSITY_WARNING, "bitmask_has_var_ndx() : Bad variable position %u", var_pos); return var_found; } /** * @brief Ask the user if a variable must be overwritten or not for a specific stanza * * If the user sets other chars than a 'y' char, we assume he does not want to * overwrite the variable. * * @param var Variable which should be overwritten * @param stanza Stanza where the variable should be overwritten * * @return 1 if the user wants to overwrite the variable, 0 otherwise */ static int ask_overwrite_var(const char *var, const char *stanza) { char user_input = 'N'; int overwrite = 0; int c; int first_char = 1;; log_msg(LOG_VERBOSITY_NORMAL, "Variable '%s' found in stanza '%s'. Overwrite [N/y] ? ", var, stanza); while ((c=getchar()) != LF_CHAR) { if (first_char) user_input = c; first_char = 0; } if (user_input == 'y') overwrite = 1; return overwrite; } /** * @brief Lookup a variable in the variable array according to its name * * This function parses the fko_var_array table and try to find a match * for the user string, which indicates we have found a configuration variable. * * @param str String to compare against every fwknop conf variables * * @return A pointer on the variable structure, or NULL if not found */ static fko_var_t * lookup_var_by_name(const char *var_name) { short ndx; /* Index on the the fko_var_array table */ fko_var_t *var = NULL; /* Check str against each variable available in fko_var_array */ for (ndx=0 ; ndxname, var, sizeof(param->name)); strlcpy(param->val, val, sizeof(param->val)); return 1; } /** * \brief Dump available stanzas from a fwknoprc file * * This function parses a rcfile and looks for configured stanzas. * They are all displayed except the default stanza. * * \param rcfile full path to the rcfile to parse */ static int dump_configured_stanzas_from_rcfile(const char* rcfile) { FILE *rc; char line[MAX_LINE_LEN] = {0}; char curr_stanza[MAX_LINE_LEN] = {0}; /* Open the rcfile in read mode */ if ((rc = fopen(rcfile, "r")) == NULL) { log_msg(LOG_VERBOSITY_WARNING, "Unable to open rc file: %s: %s", rcfile, strerror(errno)); return EXIT_FAILURE; } log_msg(LOG_VERBOSITY_NORMAL, "The following stanzas are configured in %s :", rcfile); /* Parse the rcfile line by line to find stanza */ while ((fgets(line, MAX_LINE_LEN, rc)) != NULL) { line[MAX_LINE_LEN-1] = '\0'; /* Get past comments and empty lines (note: we only look at the first * character. */ if(IS_EMPTY_LINE(line[0])) continue; /* Check which section we are working on */ else if (is_rc_section(line, strlen(line), curr_stanza, sizeof(curr_stanza))) { /* Print the stanza and continue - we exclude the default stanza */ if (strcasecmp(curr_stanza, RC_SECTION_DEFAULT) != 0) log_msg(LOG_VERBOSITY_NORMAL, " - %s", curr_stanza); continue; } /* Nothing we care about */ else; } fclose(rc); return EXIT_SUCCESS; } /* Assign path to fwknop rc file */ static void set_rc_file(char *rcfile, fko_cli_options_t *options) { int rcf_offset; char *homedir; memset(rcfile, 0x0, MAX_PATH_LEN); if(options->rc_file[0] == 0x0) { #ifdef WIN32 homedir = getenv("USERPROFILE"); #else homedir = getenv("HOME"); #endif if(homedir == NULL) { log_msg(LOG_VERBOSITY_ERROR, "Warning: Unable to determine HOME directory.\n" " No .fwknoprc file processed."); exit(EXIT_FAILURE); } strlcpy(rcfile, homedir, MAX_PATH_LEN); rcf_offset = strlen(rcfile); /* Sanity check the path to .fwknoprc. * The preceeding path plus the path separator and '.fwknoprc' = 11 * cannot exceed MAX_PATH_LEN. */ if(rcf_offset > (MAX_PATH_LEN - 11)) { log_msg(LOG_VERBOSITY_ERROR, "Warning: Path to .fwknoprc file is too long.\n" " No .fwknoprc file processed."); exit(EXIT_FAILURE); } rcfile[rcf_offset] = PATH_SEP; strlcat(rcfile, ".fwknoprc", MAX_PATH_LEN); } else { strlcpy(rcfile, options->rc_file, MAX_PATH_LEN); } /* Check rc file permissions - if anything other than user read/write, * then throw a warning. This change was made to help ensure that the * client consumes a proper rc file with strict permissions set (thanks * to Fernando Arnaboldi from IOActive for pointing this out). */ if(verify_file_perms_ownership(rcfile) != 1) exit(EXIT_FAILURE); return; } static void keys_status(fko_cli_options_t *options) { FILE *key_gen_file_ptr = NULL; char rcfile[MAX_PATH_LEN] = {0}; if(options->key_gen == 1) { if(options->key_gen_file[0] != '\0') { if ((key_gen_file_ptr = fopen(options->key_gen_file, "w")) == NULL) { log_msg(LOG_VERBOSITY_ERROR, "Unable to create key gen file: %s: %s", options->key_gen_file, strerror(errno)); exit(EXIT_FAILURE); } fprintf(key_gen_file_ptr, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\n", options->key_base64, options->hmac_key_base64); fclose(key_gen_file_ptr); log_msg(LOG_VERBOSITY_NORMAL, "[+] Wrote Rijndael and HMAC keys to: %s", options->key_gen_file); } else { if(options->save_rc_stanza == 1) { set_rc_file(rcfile, options); log_msg(LOG_VERBOSITY_NORMAL, "[+] Wrote Rijndael and HMAC keys to rc file: %s", rcfile); } else log_msg(LOG_VERBOSITY_NORMAL, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s", options->key_base64, options->hmac_key_base64); } /* Always exit out in --key-gen mode since the fwknopd server * has no way to know what the new keys are */ exit(EXIT_SUCCESS); } } /* Parse any time offset from the command line */ static int parse_time_offset(const char *offset_str, int *offset) { int i, j; int offset_type = TIME_OFFSET_SECONDS; int os_len = strlen(offset_str); int is_err = 0; char offset_digits[MAX_TIME_STR_LEN] = {0}; j=0; for (i=0; i < os_len; i++) { if (isdigit(offset_str[i])) { offset_digits[j] = offset_str[i]; j++; if(j >= MAX_TIME_STR_LEN) { return 0; } } else if (offset_str[i] == 'm' || offset_str[i] == 'M') { offset_type = TIME_OFFSET_MINUTES; break; } else if (offset_str[i] == 'h' || offset_str[i] == 'H') { offset_type = TIME_OFFSET_HOURS; break; } else if (offset_str[i] == 'd' || offset_str[i] == 'D') { offset_type = TIME_OFFSET_DAYS; break; } } offset_digits[j] = '\0'; if (j < 1) return 0; *offset = strtol_wrapper(offset_digits, 0, (2 << 15), NO_EXIT_UPON_ERR, &is_err); /* Apply the offset_type multiplier */ *offset *= offset_type; return is_err == 0 ? 1 : 0; } static int create_fwknoprc(const char *rcfile) { FILE *rc = NULL; int rcfile_fd = -1; log_msg(LOG_VERBOSITY_NORMAL, "[*] Creating initial rc file: %s.", rcfile); /* Try to create the initial rcfile with user read/write rights only. * If the rcfile already exists, an error is returned */ rcfile_fd = open(rcfile, FWKNOPRC_OFLAGS ,FWKNOPRC_MODE); // If an error occured ... if (rcfile_fd == -1) { log_msg(LOG_VERBOSITY_WARNING, "Unable to create initial rc file: %s: %s", rcfile, strerror(errno)); return(-1); } // Free the rcfile descriptor close(rcfile_fd); if ((rc = fopen(rcfile, "w")) == NULL) { log_msg(LOG_VERBOSITY_WARNING, "Unable to write default setup to rcfile: %s: %s", rcfile, strerror(errno)); return(-1); } fprintf(rc, "# .fwknoprc\n" "##############################################################################\n" "#\n" "# Firewall Knock Operator (fwknop) client rc file.\n" "#\n" "# This file contains user-specific fwknop client configuration default\n" "# and named parameter sets for specific invocations of the fwknop client.\n" "#\n" "# Each section (or stanza) is identified and started by a line in this\n" "# file that contains a single identifier surrounded by square brackets.\n" "# It is this identifier (or name) that is used from the fwknop command line\n" "# via the '-n ' argument to reference the corresponding stanza.\n" "#\n" "# The parameters within the stanza typically match corresponding client \n" "# command-line parameters.\n" "#\n" "# The first one should always be `[default]' as it defines the global\n" "# default settings for the user. These override the program defaults\n" "# for these parameters. If a named stanza is used, its entries will\n" "# override any of the default values. Command-line options will trump them\n" "# all.\n" "#\n" "# Subsequent stanzas will have only the overriding and destination\n" "# specific parameters.\n" "#\n" "# Lines starting with `#' and empty lines are ignored.\n" "#\n" "# See the fwknop.8 man page for a complete list of valid parameters\n" "# and their values.\n" "#\n" "##############################################################################\n" "#\n" "# We start with the 'default' stanza. Uncomment and edit for your\n" "# preferences. The client will use its built-in default for those items\n" "# that are commented out.\n" "#\n" "[default]\n" "\n" "#DIGEST_TYPE sha256\n" "#FW_TIMEOUT 30\n" "#SPA_SERVER_PORT 62201\n" "#SPA_SERVER_PROTO udp\n" "#ALLOW_IP \n" "#SPOOF_USER \n" "#SPOOF_SOURCE_IP \n" "#TIME_OFFSET 0\n" "#USE_GPG N\n" "#GPG_HOMEDIR /path/to/.gnupg\n" "#GPG_EXE /path/to/gpg\n" "#GPG_SIGNER \n" "#GPG_RECIPIENT \n" "#NO_SAVE_ARGS N\n" "\n" "# User-provided named stanzas:\n" "\n" "# Example for a destination server of 192.168.1.20 to open access to\n" "# SSH for an IP that is resolved externally, and one with a NAT request\n" "# for a specific source IP that maps port 8088 on the server\n" "# to port 88 on 192.168.1.55 with timeout.\n" "#\n" "#[myssh]\n" "#SPA_SERVER 192.168.1.20\n" "#ACCESS tcp/22\n" "#ALLOW_IP resolve\n" "#\n" "#[mynatreq]\n" "#SPA_SERVER 192.168.1.20\n" "#ACCESS tcp/8088\n" "#ALLOW_IP 10.21.2.6\n" "#NAT_ACCESS 192.168.1.55,88\n" "#CLIENT_TIMEOUT 60\n" "#\n" "\n" ); fclose(rc); return(0); } static int parse_rc_param(fko_cli_options_t *options, const char *var_name, char * val) { int tmpint, is_err; int parse_error = 0; /* 0 if the variable has been successfully processed, < 0 otherwise */ fko_var_t *var; /* Pointer on an fwknop variable structure */ log_msg(LOG_VERBOSITY_DEBUG, "parse_rc_param() : Parsing variable %s...", var_name); /* Lookup the variable according to its name. */ var = lookup_var_by_name(var_name); /* The variable is not handled if its pointer is NULL */ if (var == NULL) parse_error = -1; /* Digest Type */ else if (var->pos == FWKNOP_CLI_ARG_DIGEST_TYPE) { tmpint = digest_strtoint(val); if(tmpint < 0) parse_error = -1; else options->digest_type = tmpint; } /* Server protocol */ else if (var->pos == FWKNOP_CLI_ARG_SPA_SERVER_PROTO) { tmpint = proto_strtoint(val); if(tmpint < 0) parse_error = -1; else options->spa_proto = tmpint; } /* Server port */ else if (var->pos == FWKNOP_CLI_ARG_SPA_SERVER_PORT) { tmpint = strtol_wrapper(val, 0, MAX_PORT, NO_EXIT_UPON_ERR, &is_err); if(is_err == FKO_SUCCESS) options->spa_dst_port = tmpint; else parse_error = -1; } /* Source port */ else if (var->pos == FWKNOP_CLI_ARG_SPA_SOURCE_PORT) { tmpint = strtol_wrapper(val, 0, MAX_PORT, NO_EXIT_UPON_ERR, &is_err); if(is_err == FKO_SUCCESS) options->spa_src_port = tmpint; else parse_error = -1; } /* Firewall rule timeout */ else if (var->pos == FWKNOP_CLI_ARG_FW_TIMEOUT) { tmpint = strtol_wrapper(val, 0, (2 << 15), NO_EXIT_UPON_ERR, &is_err); if(is_err == FKO_SUCCESS) options->fw_timeout = tmpint; else parse_error = -1; } /* Allow IP */ else if (var->pos == FWKNOP_CLI_ARG_ALLOW_IP) { /* In case this was set previously */ options->resolve_ip_http_https = 0; /* use source, resolve, or an actual IP */ if(strcasecmp(val, "source") == 0) strlcpy(options->allow_ip_str, "0.0.0.0", sizeof(options->allow_ip_str)); else if(strcasecmp(val, "resolve") == 0) options->resolve_ip_http_https = 1; else /* Assume IP address and validate */ { strlcpy(options->allow_ip_str, val, sizeof(options->allow_ip_str)); if(! is_valid_ipv4_addr(options->allow_ip_str)) parse_error = -1; } } /* Time Offset */ else if (var->pos == FWKNOP_CLI_ARG_TIME_OFFSET) { if(val[0] == '-') { val++; if(! parse_time_offset(val, &options->time_offset_minus)) parse_error = -1; } else if (! parse_time_offset(val, &options->time_offset_plus)) parse_error = -1; if(parse_error == -1) log_msg(LOG_VERBOSITY_WARNING, "TIME_OFFSET argument '%s' invalid.", val); } /* symmetric encryption mode */ else if (var->pos == FWKNOP_CLI_ARG_ENCRYPTION_MODE) { tmpint = enc_mode_strtoint(val); if(tmpint < 0) parse_error = -1; else options->encryption_mode = tmpint; } /* Use GPG ? */ else if (var->pos == FWKNOP_CLI_ARG_USE_GPG) { if (is_yes_str(val)) options->use_gpg = 1; else; } /* Use GPG Agent ? */ else if (var->pos == FWKNOP_CLI_ARG_USE_GPG_AGENT) { if (is_yes_str(val)) options->use_gpg_agent = 1; else; } /* No GPG signing passphrase ? */ else if (var->pos == FWKNOP_CLI_ARG_GPG_NO_SIGNING_PW) { if (is_yes_str(val)) options->gpg_no_signing_pw = 1; else; } /* GPG Recipient */ else if (var->pos == FWKNOP_CLI_ARG_GPG_RECIPIENT) { strlcpy(options->gpg_recipient_key, val, sizeof(options->gpg_recipient_key)); } /* GPG Signer */ else if (var->pos == FWKNOP_CLI_ARG_GPG_SIGNER) { strlcpy(options->gpg_signer_key, val, sizeof(options->gpg_signer_key)); } /* GPG Homedir */ else if (var->pos == FWKNOP_CLI_ARG_GPG_HOMEDIR) { strlcpy(options->gpg_home_dir, val, sizeof(options->gpg_home_dir)); } /* GPG path */ else if (var->pos == FWKNOP_CLI_ARG_GPG_EXE_PATH) { strlcpy(options->gpg_exe, val, sizeof(options->gpg_exe)); } /* Spoof User */ else if (var->pos == FWKNOP_CLI_ARG_SPOOF_USER) { strlcpy(options->spoof_user, val, sizeof(options->spoof_user)); } /* Spoof Source IP */ else if (var->pos == FWKNOP_CLI_ARG_SPOOF_SOURCE_IP) { strlcpy(options->spoof_ip_src_str, val, sizeof(options->spoof_ip_src_str)); } /* ACCESS request */ else if (var->pos == FWKNOP_CLI_ARG_ACCESS) { strlcpy(options->access_str, val, sizeof(options->access_str)); } /* SPA Server (destination) */ else if (var->pos == FWKNOP_CLI_ARG_SPA_SERVER) { strlcpy(options->spa_server_str, val, sizeof(options->spa_server_str)); } /* Rand port ? */ else if (var->pos == FWKNOP_CLI_ARG_RAND_PORT) { if (is_yes_str(val)) options->rand_port = 1; else; } /* Rijndael key */ else if (var->pos == FWKNOP_CLI_ARG_KEY_RIJNDAEL) { strlcpy(options->key, val, sizeof(options->key)); options->have_key = 1; } /* Rijndael key (base-64 encoded) */ else if (var->pos == FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64) { if (! is_base64((unsigned char *) val, strlen(val))) { log_msg(LOG_VERBOSITY_WARNING, "KEY_BASE64 argument '%s' doesn't look like base64-encoded data.", val); parse_error = -1; } strlcpy(options->key_base64, val, sizeof(options->key_base64)); options->have_base64_key = 1; } /* GnuPG signing passphrase */ else if (var->pos == FWKNOP_CLI_ARG_GPG_SIGNING_PW) { strlcpy(options->key, val, sizeof(options->key)); options->have_key = 1; } /* GnuPG signing passphrase (base-64 encoded) */ else if (var->pos == FWKNOP_CLI_ARG_GPG_SIGNING_PW_BASE64) { if (! is_base64((unsigned char *) val, strlen(val))) { log_msg(LOG_VERBOSITY_WARNING, "GPG_SIGNING_KEY_BASE64 argument '%s' doesn't look like base64-encoded data.", val); parse_error = -1; } strlcpy(options->key_base64, val, sizeof(options->key_base64)); options->have_base64_key = 1; } /* HMAC digest type */ else if (var->pos == FWKNOP_CLI_ARG_HMAC_DIGEST_TYPE) { tmpint = hmac_digest_strtoint(val); if(tmpint < 0) { log_msg(LOG_VERBOSITY_WARNING, "HMAC_DIGEST_TYPE argument '%s' must be one of {md5,sha1,sha256,sha384,sha512,sha3_256,sha3_512}", val); parse_error = -1; } else { options->hmac_type = tmpint; } } /* HMAC key (base64 encoded) */ else if (var->pos == FWKNOP_CLI_ARG_KEY_HMAC_BASE64) { if (! is_base64((unsigned char *) val, strlen(val))) { log_msg(LOG_VERBOSITY_WARNING, "HMAC_KEY_BASE64 argument '%s' doesn't look like base64-encoded data.", val); parse_error = -1; } strlcpy(options->hmac_key_base64, val, sizeof(options->hmac_key_base64)); options->have_hmac_base64_key = 1; options->use_hmac = 1; } /* HMAC key */ else if (var->pos == FWKNOP_CLI_ARG_KEY_HMAC) { strlcpy(options->hmac_key, val, sizeof(options->hmac_key)); options->have_hmac_key = 1; options->use_hmac = 1; } /* --use-hmac */ else if (var->pos == FWKNOP_CLI_ARG_USE_HMAC) { if (is_yes_str(val)) options->use_hmac = 1; } /* --use-wget-user-agent */ else if (var->pos == FWKNOP_CLI_ARG_USE_WGET_USER_AGENT) { if (is_yes_str(val)) options->use_wget_user_agent = 1; } /* Key file */ else if (var->pos == FWKNOP_CLI_ARG_KEY_FILE) { strlcpy(options->get_key_file, val, sizeof(options->get_key_file)); } /* HMAC key file */ else if (var->pos == FWKNOP_CLI_ARG_HMAC_KEY_FILE) { strlcpy(options->get_key_file, val, sizeof(options->get_hmac_key_file)); } /* NAT Access Request */ else if (var->pos == FWKNOP_CLI_ARG_NAT_ACCESS) { strlcpy(options->nat_access_str, val, sizeof(options->nat_access_str)); } /* HTTP User Agent */ else if (var->pos == FWKNOP_CLI_ARG_HTTP_USER_AGENT) { strlcpy(options->http_user_agent, val, sizeof(options->http_user_agent)); } /* Resolve URL */ else if (var->pos == FWKNOP_CLI_ARG_RESOLVE_URL) { if(options->resolve_url != NULL) free(options->resolve_url); tmpint = strlen(val)+1; options->resolve_url = calloc(1, tmpint); if(options->resolve_url == NULL) { log_msg(LOG_VERBOSITY_ERROR,"Memory allocation error for resolve URL."); exit(EXIT_FAILURE); } strlcpy(options->resolve_url, val, tmpint); } /* Resolve the SPA server (via DNS) - accept IPv4 addresses only ? */ else if (var->pos == FWKNOP_CLI_ARG_SERVER_RESOLVE_IPV4) { if (is_yes_str(val)) { options->spa_server_resolve_ipv4 = 1; } } /* wget command */ else if (var->pos == FWKNOP_CLI_ARG_WGET_CMD) { if(options->wget_bin != NULL) free(options->wget_bin); tmpint = strlen(val)+1; options->wget_bin = calloc(1, tmpint); if(options->wget_bin == NULL) { log_msg(LOG_VERBOSITY_ERROR,"Memory allocation error for wget command path."); exit(EXIT_FAILURE); } strlcpy(options->wget_bin, val, tmpint); } /* NAT Local ? */ else if (var->pos == FWKNOP_CLI_ARG_NAT_LOCAL) { if (is_yes_str(val)) options->nat_local = 1; else; } /* NAT rand port ? */ else if (var->pos == FWKNOP_CLI_ARG_NAT_RAND_PORT) { if (is_yes_str(val)) options->nat_rand_port = 1; else; } /* NAT port */ else if (var->pos == FWKNOP_CLI_ARG_NAT_PORT) { tmpint = strtol_wrapper(val, 0, MAX_PORT, NO_EXIT_UPON_ERR, &is_err); if(is_err == FKO_SUCCESS) options->nat_port = tmpint; else parse_error = -1; } /* VERBOSE level */ else if (var->pos == FWKNOP_CLI_ARG_VERBOSE) { if (is_yes_str(val)) options->verbose = 1; else { tmpint = strtol_wrapper(val, 0, LOG_LAST_VERBOSITY - 1, NO_EXIT_UPON_ERR, &is_err); if(is_err == FKO_SUCCESS) options->verbose = tmpint; else parse_error = -1; } if (parse_error == 0) log_set_verbosity(LOG_DEFAULT_VERBOSITY + options->verbose); } /* RESOLVE_IP_HTTPS ? */ else if (var->pos == FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS) { if (is_yes_str(val)) options->resolve_ip_http_https = 1; else; } /* RESOLVE_IP_HTTP ? This actually results in HTTPS resolution by default * unless --resolve-http-only is also given */ else if (var->pos == FWKNOP_CLI_ARG_RESOLVE_IP_HTTP) { if (is_yes_str(val)) options->resolve_ip_http_https = 1; else; } /* RESOLVE_HTTP_ONLY ? Force HTTP instead of HTTPS IP resolution. */ else if (var->pos == FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY) { if (is_yes_str(val)) options->resolve_http_only = 1; else; } /* avoid saving .fwknop.run by default */ else if (var->pos == FWKNOP_CLI_ARG_NO_SAVE_ARGS) { if (is_yes_str(val)) options->no_save_args = 1; else; } /* The variable is not a configuration variable */ else { parse_error = -1; } return(parse_error); } /** * @brief Write a cli parameter to a file handle * * This function writes into a file handle a command line parameter * * @param fhandle File handle to write the new parameter to * @param var_pos Variable position * @param options FKO command line option structure */ static void add_single_var_to_rc(FILE* fhandle, short var_pos, fko_cli_options_t *options) { char val[MAX_LINE_LEN] = {0}; fko_var_t *var; var = lookup_var_by_position(var_pos); if (var == NULL) return; if (fhandle == NULL) return; /* Select the argument to add and store its string value into val */ switch (var->pos) { case FWKNOP_CLI_ARG_DIGEST_TYPE : digest_inttostr(options->digest_type, val, sizeof(val)); break; case FWKNOP_CLI_ARG_SPA_SERVER_PROTO : proto_inttostr(options->spa_proto, val, sizeof(val)); break; case FWKNOP_CLI_ARG_SPA_SERVER_PORT : snprintf(val, sizeof(val)-1, "%d", options->spa_dst_port); break; case FWKNOP_CLI_ARG_SPA_SOURCE_PORT : snprintf(val, sizeof(val)-1, "%d", options->spa_src_port); break; case FWKNOP_CLI_ARG_FW_TIMEOUT : snprintf(val, sizeof(val)-1, "%d", options->fw_timeout); break; case FWKNOP_CLI_ARG_ALLOW_IP : strlcpy(val, options->allow_ip_str, sizeof(val)); break; case FWKNOP_CLI_ARG_TIME_OFFSET : if (options->time_offset_minus != 0) snprintf(val, sizeof(val)-1, "-%d", options->time_offset_minus); else if (options->time_offset_plus != 0) snprintf(val, sizeof(val)-1, "%d", options->time_offset_plus); else; break; case FWKNOP_CLI_ARG_ENCRYPTION_MODE : enc_mode_inttostr(options->encryption_mode, val, sizeof(val)); break; case FWKNOP_CLI_ARG_USE_GPG : bool_to_yesno(options->use_gpg, val, sizeof(val)); break; case FWKNOP_CLI_ARG_USE_GPG_AGENT : bool_to_yesno(options->use_gpg_agent, val, sizeof(val)); break; case FWKNOP_CLI_ARG_GPG_RECIPIENT : strlcpy(val, options->gpg_recipient_key, sizeof(val)); break; case FWKNOP_CLI_ARG_GPG_SIGNER : strlcpy(val, options->gpg_signer_key, sizeof(val)); break; case FWKNOP_CLI_ARG_GPG_HOMEDIR : strlcpy(val, options->gpg_home_dir, sizeof(val)); break; case FWKNOP_CLI_ARG_GPG_EXE_PATH : strlcpy(val, options->gpg_exe, sizeof(val)); break; case FWKNOP_CLI_ARG_GPG_NO_SIGNING_PW : bool_to_yesno(options->gpg_no_signing_pw, val, sizeof(val)); break; case FWKNOP_CLI_ARG_SPOOF_USER : strlcpy(val, options->spoof_user, sizeof(val)); break; case FWKNOP_CLI_ARG_SPOOF_SOURCE_IP : strlcpy(val, options->spoof_ip_src_str, sizeof(val)); break; case FWKNOP_CLI_ARG_ACCESS : strlcpy(val, options->access_str, sizeof(val)); break; case FWKNOP_CLI_ARG_SPA_SERVER : strlcpy(val, options->spa_server_str, sizeof(val)); break; case FWKNOP_CLI_ARG_RAND_PORT : bool_to_yesno(options->rand_port, val, sizeof(val)); break; case FWKNOP_CLI_ARG_KEY_FILE : strlcpy(val, options->get_key_file, sizeof(val)); break; case FWKNOP_CLI_ARG_HMAC_KEY_FILE : strlcpy(val, options->get_hmac_key_file, sizeof(val)); break; case FWKNOP_CLI_ARG_KEY_RIJNDAEL: strlcpy(val, options->key, sizeof(val)); break; case FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64: strlcpy(val, options->key_base64, sizeof(val)); break; case FWKNOP_CLI_ARG_KEY_HMAC_BASE64: strlcpy(val, options->hmac_key_base64, sizeof(val)); break; case FWKNOP_CLI_ARG_KEY_HMAC: strlcpy(val, options->hmac_key, sizeof(val)); break; case FWKNOP_CLI_ARG_HMAC_DIGEST_TYPE : hmac_digest_inttostr(options->hmac_type, val, sizeof(val)); break; case FWKNOP_CLI_ARG_USE_HMAC : bool_to_yesno(options->use_hmac, val, sizeof(val)); break; case FWKNOP_CLI_ARG_USE_WGET_USER_AGENT : bool_to_yesno(options->use_wget_user_agent, val, sizeof(val)); break; case FWKNOP_CLI_ARG_NAT_ACCESS : strlcpy(val, options->nat_access_str, sizeof(val)); break; case FWKNOP_CLI_ARG_HTTP_USER_AGENT : strlcpy(val, options->http_user_agent, sizeof(val)); break; case FWKNOP_CLI_ARG_RESOLVE_URL : if (options->resolve_url != NULL) strlcpy(val, options->resolve_url, sizeof(val)); else; break; case FWKNOP_CLI_ARG_SERVER_RESOLVE_IPV4: bool_to_yesno(options->spa_server_resolve_ipv4, val, sizeof(val)); break; case FWKNOP_CLI_ARG_NAT_LOCAL : bool_to_yesno(options->nat_local, val, sizeof(val)); break; case FWKNOP_CLI_ARG_NAT_RAND_PORT : bool_to_yesno(options->nat_rand_port, val, sizeof(val)); break; case FWKNOP_CLI_ARG_NAT_PORT : snprintf(val, sizeof(val)-1, "%d", options->nat_port); break; case FWKNOP_CLI_ARG_VERBOSE: if((options->verbose == 0) || (options->verbose == 1)) bool_to_yesno(options->verbose, val, sizeof(val)); else snprintf(val, sizeof(val)-1, "%d", options->verbose); break; case FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS: bool_to_yesno(options->resolve_ip_http_https, val, sizeof(val)); break; case FWKNOP_CLI_ARG_RESOLVE_IP_HTTP: bool_to_yesno(options->resolve_ip_http_https, val, sizeof(val)); break; case FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY: bool_to_yesno(options->resolve_http_only, val, sizeof(val)); break; case FWKNOP_CLI_ARG_WGET_CMD : if (options->wget_bin != NULL) strlcpy(val, options->wget_bin, sizeof(val)); break; case FWKNOP_CLI_ARG_NO_SAVE_ARGS : bool_to_yesno(options->no_save_args, val, sizeof(val)); break; default: log_msg(LOG_VERBOSITY_WARNING, "Warning from add_single_var_to_rc() : Bad variable position %u", var->pos); return; } log_msg(LOG_VERBOSITY_DEBUG, "add_single_var_to_rc() : Updating param (%u) %s to %s", var->pos, var->name, val); fprintf(fhandle, RC_PARAM_TEMPLATE, var->name, val); } /** * @brief Add configuration variables in a file * * The parameters are selected by a bitmask and extracted from the * fko_cli_options_t structure. * * @param rc File handle on the file to write to * @param options fko_cli_options_t structure containing the values of the parameters * @param bitmask Bitmask used to select the parameters to add */ static void add_multiple_vars_to_rc(FILE* rc, fko_cli_options_t *options, fko_var_bitmask_t *bitmask) { short ndx = 0; /* Index of a configuration variable in fko_var_array table */ short position; /* Position of the configuration variable */ for (ndx=0 ; ndxuse_rc_stanza) == 0) options->got_named_stanza = 1; continue; } /* We are not in the good section */ else if (rc_section_found == 0) continue; /* We have not found a valid parameter */ else if (is_rc_param(line, ¶m) == 0) { do_exit = 1; /* We don't allow improperly formatted lines */ break; } /* We have a valid parameter */ else { if(parse_rc_param(options, param.name, param.val) < 0) { log_msg(LOG_VERBOSITY_WARNING, "Parameter error in %s, line %i: var=%s, val=%s", rcfile, line_num, param.name, param.val); do_exit = 1; } } } fclose(rc); if (do_exit) exit(EXIT_FAILURE); return 0; } /** * @brief Update the user rc file with the new parameters for a selected stanza. * * This function writes the new configuration in a temporary file and renames it * as the new rc file afterwards. All of the previous parameters for the * selected stanza are removed and replaced by the arguments from the command * line. * * @param options structure containing all of the fko settings * @param args_bitmask command line argument bitmask */ static void update_rc(fko_cli_options_t *options, fko_var_bitmask_t *bitmask) { FILE *rc; FILE *rc_update; int rcfile_fd = -1; int stanza_found = 0; int stanza_updated = 0; char line[MAX_LINE_LEN] = {0}; char rcfile[MAX_PATH_LEN] = {0}; char rcfile_update[MAX_PATH_LEN] = {0}; char curr_stanza[MAX_LINE_LEN] = {0}; rc_file_param_t param; /* Structure to contain a conf. variable name with its value */ fko_var_t *var; set_rc_file(rcfile, options); strlcpy(rcfile_update, rcfile, sizeof(rcfile_update)); strlcat(rcfile_update, ".updated", sizeof(rcfile_update)); /* Create a new temporary rc file */ rcfile_fd = open(rcfile_update, FWKNOPRC_OFLAGS, FWKNOPRC_MODE); if (rcfile_fd == -1) { log_msg(LOG_VERBOSITY_WARNING, "update_rc() : Unable to create temporary rc file: %s: %s", rcfile_update, strerror(errno)); return; } close(rcfile_fd); /* Open the current rcfile and a temporary one respectively in read and * write mode */ if ((rc = fopen(rcfile, "r")) == NULL) { log_msg(LOG_VERBOSITY_WARNING, "update_rc() : Unable to open rc file: %s: %s", rcfile, strerror(errno)); return; } if ((rc_update = fopen(rcfile_update, "w")) == NULL) { log_msg(LOG_VERBOSITY_WARNING, "update_rc() : Unable to open rc file: %s: %s", rcfile_update, strerror(errno)); fclose(rc); return; } /* Go through the file line by line */ stanza_found = 0; while ((fgets(line, MAX_LINE_LEN, rc)) != NULL) { line[MAX_LINE_LEN-1] = '\0'; /* Get past comments and empty lines (note: we only look at the * first character. */ if(IS_EMPTY_LINE(line[0])) continue; /* If we find a section... */ if(is_rc_section(line, strlen(line), curr_stanza, sizeof(curr_stanza)) == 1) { /* and we have already parsed the section we wanted to save, we * can update our parameters */ if (stanza_found) { log_msg(LOG_VERBOSITY_DEBUG, "update_rc() : Updating %s stanza", options->use_rc_stanza); add_multiple_vars_to_rc(rc_update, options, bitmask); fprintf(rc_update, "\n"); stanza_found = 0; stanza_updated = 1; } /* and this is the one we are looking for, we set the stanza * as found */ else if (strncasecmp(curr_stanza, options->use_rc_stanza, MAX_LINE_LEN) == 0) stanza_found = 1; /* otherwise we disable the stanza */ else stanza_found = 0; } /* If we are processing a parameter for our stanza */ else if (stanza_found) { /* and the user has specified a force option, there is no need to * check for critical variables */ if (options->force_save_rc_stanza) continue; /* ask the user what to do with the critical var found in the * rcfile */ else if (is_rc_param(line, ¶m)) { if ( ((var=lookup_var_by_name(param.name)) != NULL) && var_is_critical(var->pos) ) { if (ask_overwrite_var(var->name, curr_stanza)) continue; else remove_var_from_bitmask(var->pos, bitmask); } else continue; } else { /* is_rc_param() returns false only when there is an * improperly formatted line - bail */ fclose(rc); fclose(rc_update); return; } } /* We're not processing any important variables from our stanza and no new * stanza */ else; /* Add the line to the new rcfile */ fprintf(rc_update, "%s", line); } /* The configuration has not been updated yet */ if (stanza_updated == 0) { /* but the stanza has been found, We update it now. */ if (stanza_found == 1) log_msg(LOG_VERBOSITY_DEBUG, "update_rc() : Updating %s stanza", options->use_rc_stanza); /* otherwise we append the new settings to the file */ else { fprintf(rc_update, "\n"); log_msg(LOG_VERBOSITY_DEBUG, "update_rc() : Inserting new %s stanza", options->use_rc_stanza); fprintf(rc_update, RC_SECTION_TEMPLATE, options->use_rc_stanza); } add_multiple_vars_to_rc(rc_update, options, bitmask); } /* otherwise we have already done everything. Nothing to do. */ else; /* Close file handles */ fclose(rc); fclose(rc_update); /* Renamed the temporary file as the new rc file */ if (remove(rcfile) != 0) { log_msg(LOG_VERBOSITY_WARNING, "update_rc() : Unable to remove %s to %s : %s", rcfile_update, rcfile, strerror(errno)); } if (rename(rcfile_update, rcfile) != 0) { log_msg(LOG_VERBOSITY_WARNING, "update_rc() : Unable to rename %s to %s", rcfile_update, rcfile); } } /* Sanity and bounds checks for the various options. */ static void validate_options(fko_cli_options_t *options) { if ( (options->use_rc_stanza[0] != 0x0) && (options->got_named_stanza == 0) && (options->save_rc_stanza == 0) ) { log_msg(LOG_VERBOSITY_ERROR, "Named configuration stanza: [%s] was not found.", options->use_rc_stanza); exit(EXIT_FAILURE); } if ( (options->save_rc_stanza == 1) && (options->use_rc_stanza[0] == 0) ) { /* Set the stanza name to the -D arg value */ if (options->spa_server_str[0] == 0x0) { log_msg(LOG_VERBOSITY_ERROR, "Must use --destination unless --test mode is used"); exit(EXIT_FAILURE); } strlcpy(options->use_rc_stanza, options->spa_server_str, sizeof(options->use_rc_stanza)); } /* Must have a destination unless we are just testing or getting the * the version, and must use one of [-s|-R|-a]. */ if(!options->test && !options->key_gen && !options->version && !options->show_last_command && !options->run_last_command) { if (options->spa_server_str[0] == 0x0) { log_msg(LOG_VERBOSITY_ERROR, "Must use --destination unless --test mode is used"); exit(EXIT_FAILURE); } if (options->resolve_url != NULL) options->resolve_ip_http_https = 1; if (!options->resolve_ip_http_https) { if(options->allow_ip_str[0] == 0x0) { log_msg(LOG_VERBOSITY_ERROR, "Must use one of [-s|-R|-a] to specify IP for SPA access."); exit(EXIT_FAILURE); } else if(options->verbose && strncmp(options->allow_ip_str, "0.0.0.0", strlen("0.0.0.0")) == 0) { log_msg(LOG_VERBOSITY_WARNING, "[-] WARNING: Should use -a or -R to harden SPA against potential MITM attacks"); } } } /* Make sure -a overrides IP resolution */ if(options->allow_ip_str[0] != 0x0 && strncasecmp(options->allow_ip_str, "resolve", strlen("resolve")) != 0) { options->resolve_ip_http_https = 0; if(! is_valid_ipv4_addr(options->allow_ip_str)) { log_msg(LOG_VERBOSITY_ERROR, "Invalid allow IP specified for SPA access"); exit(EXIT_FAILURE); } } if (options->spoof_ip_src_str[0] != 0x00) { if(! is_valid_ipv4_addr(options->spoof_ip_src_str)) { log_msg(LOG_VERBOSITY_ERROR, "Invalid spoof IP"); exit(EXIT_FAILURE); } if(options->spa_proto != FKO_PROTO_TCP_RAW && options->spa_proto != FKO_PROTO_UDP_RAW && options->spa_proto != FKO_PROTO_ICMP) { log_msg(LOG_VERBOSITY_ERROR, "Must set -P with a spoofed source IP"); exit(EXIT_FAILURE); } } if(options->resolve_ip_http_https || options->spa_proto == FKO_PROTO_HTTP) if (options->http_user_agent[0] == '\0') snprintf(options->http_user_agent, HTTP_MAX_USER_AGENT_LEN, "%s%s", "Fwknop/", MY_VERSION); if(options->http_proxy[0] != 0x0 && options->spa_proto != FKO_PROTO_HTTP) { log_msg(LOG_VERBOSITY_ERROR, "Cannot set --http-proxy with a non-HTTP protocol."); exit(EXIT_FAILURE); } /* If we are using gpg, we must at least have the recipient set. */ if(options->use_gpg) { if(strlen(options->gpg_recipient_key) == 0) { log_msg(LOG_VERBOSITY_ERROR, "Must specify --gpg-recipient-key when GPG is used."); exit(EXIT_FAILURE); } } if(options->encryption_mode == FKO_ENC_MODE_ASYMMETRIC && ! options->use_gpg) { log_msg(LOG_VERBOSITY_ERROR, "Must specify GPG recipient/signing keys when Asymmetric encryption mode is used."); exit(EXIT_FAILURE); } if(options->encryption_mode == FKO_ENC_MODE_CBC_LEGACY_IV && options->use_hmac) { log_msg(LOG_VERBOSITY_ERROR, "Legacy encryption mode is incompatible with HMAC usage."); exit(EXIT_FAILURE); } /* Validate HMAC digest type */ if(options->use_hmac && options->hmac_type == FKO_HMAC_UNKNOWN) options->hmac_type = FKO_DEFAULT_HMAC_MODE; if(options->key_gen && options->hmac_type == FKO_HMAC_UNKNOWN) options->hmac_type = FKO_DEFAULT_HMAC_MODE; return; } /* Establish a few defaults such as UDP/62201 for sending the SPA * packet (can be changed with --server-proto/--server-port) */ static void set_defaults(fko_cli_options_t *options) { options->spa_proto = FKO_DEFAULT_PROTO; options->spa_dst_port = FKO_DEFAULT_PORT; options->fw_timeout = -1; options->key_len = FKO_DEFAULT_KEY_LEN; options->hmac_key_len = FKO_DEFAULT_HMAC_KEY_LEN; options->hmac_type = FKO_HMAC_UNKNOWN; /* updated when HMAC key is used */ options->spa_icmp_type = ICMP_ECHOREPLY; /* only used in '-P icmp' mode */ options->spa_icmp_code = 0; /* only used in '-P icmp' mode */ options->input_fd = FD_INVALID; return; } /* Initialize program configuration via config file and/or command-line * switches. */ void config_init(fko_cli_options_t *options, int argc, char **argv) { int cmd_arg, index, is_err, rlen=0; fko_var_bitmask_t var_bitmask; char rcfile[MAX_PATH_LEN] = {0}; /* Zero out options, opts_track and bitmask. */ memset(options, 0x00, sizeof(fko_cli_options_t)); memset(&var_bitmask, 0x00, sizeof(fko_var_bitmask_t)); /* Make sure a few reasonable defaults are set */ set_defaults(options); /* First pass over cmd_line args to see if a named-stanza in the * rc file is used. */ while ((cmd_arg = getopt_long(argc, argv, GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) { switch(cmd_arg) { case 'h': usage(); exit(EXIT_SUCCESS); case NO_SAVE_ARGS: options->no_save_args = 1; break; case 'n': strlcpy(options->use_rc_stanza, optarg, sizeof(options->use_rc_stanza)); break; case SAVE_RC_STANZA: options->save_rc_stanza = 1; break; case STANZA_LIST: options->stanza_list = 1; break; case 'E': strlcpy(options->args_save_file, optarg, sizeof(options->args_save_file)); break; case RC_FILE_PATH: strlcpy(options->rc_file, optarg, sizeof(options->rc_file)); break; case 'v': options->verbose++; add_var_to_bitmask(FWKNOP_CLI_ARG_VERBOSE, &var_bitmask); break; } } /* Update the verbosity level for the log module */ log_set_verbosity(LOG_DEFAULT_VERBOSITY + options->verbose); /* Dump the configured stanzas from an rcfile */ if (options->stanza_list == 1) { set_rc_file(rcfile, options); exit(dump_configured_stanzas_from_rcfile(rcfile)); } /* First process the .fwknoprc file. */ process_rc_section(RC_SECTION_DEFAULT, options); /* Load the user specified stanza from .fwknoprc file */ if ( (options->got_named_stanza) && (options->save_rc_stanza == 0) ) process_rc_section(options->use_rc_stanza, options); /* Reset the options index so we can run through them again. */ optind = 0; while ((cmd_arg = getopt_long(argc, argv, GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) { switch(cmd_arg) { case 'a': strlcpy(options->allow_ip_str, optarg, sizeof(options->allow_ip_str)); add_var_to_bitmask(FWKNOP_CLI_ARG_ALLOW_IP, &var_bitmask); break; case 'A': strlcpy(options->access_str, optarg, sizeof(options->access_str)); add_var_to_bitmask(FWKNOP_CLI_ARG_ACCESS, &var_bitmask); break; case 'b': options->save_packet_file_append = 1; break; case 'B': strlcpy(options->save_packet_file, optarg, sizeof(options->save_packet_file)); break; case 'C': strlcpy(options->server_command, optarg, sizeof(options->server_command)); break; case 'D': strlcpy(options->spa_server_str, optarg, sizeof(options->spa_server_str)); add_var_to_bitmask(FWKNOP_CLI_ARG_SPA_SERVER, &var_bitmask); break; case 'E': strlcpy(options->args_save_file, optarg, sizeof(options->args_save_file)); break; case 'f': options->fw_timeout = strtol_wrapper(optarg, 0, (2 << 16), NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "--fw-timeout must be within [%d-%d]", 0, (2 << 16)); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_FW_TIMEOUT, &var_bitmask); break; case FAULT_INJECTION_TAG: #if HAVE_LIBFIU strlcpy(options->fault_injection_tag, optarg, sizeof(options->fault_injection_tag)); #else log_msg(LOG_VERBOSITY_ERROR, "fwknop not compiled with fault injection support.", optarg); exit(EXIT_FAILURE); #endif break; case 'g': case GPG_ENCRYPTION: options->use_gpg = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); break; case 'G': strlcpy(options->get_key_file, optarg, sizeof(options->get_key_file)); add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_FILE, &var_bitmask); break; case GET_HMAC_KEY: strlcpy(options->get_hmac_key_file, optarg, sizeof(options->get_hmac_key_file)); options->use_hmac = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_HMAC_KEY_FILE, &var_bitmask); break; case 'H': options->spa_proto = FKO_PROTO_HTTP; strlcpy(options->http_proxy, optarg, sizeof(options->http_proxy)); break; case 'k': options->key_gen = 1; break; case 'K': options->key_gen = 1; strlcpy(options->key_gen_file, optarg, sizeof(options->key_gen_file)); break; case KEY_RIJNDAEL: strlcpy(options->key, optarg, sizeof(options->key)); options->have_key = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_RIJNDAEL, &var_bitmask); break; case KEY_RIJNDAEL_BASE64: if (! is_base64((unsigned char *) optarg, strlen(optarg))) { log_msg(LOG_VERBOSITY_ERROR, "Base64 encoded Rijndael argument '%s' doesn't look like base64-encoded data.", optarg); exit(EXIT_FAILURE); } strlcpy(options->key_base64, optarg, sizeof(options->key_base64)); options->have_base64_key = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64, &var_bitmask); break; case KEY_HMAC_BASE64: if (! is_base64((unsigned char *) optarg, strlen(optarg))) { log_msg(LOG_VERBOSITY_ERROR, "Base64 encoded HMAC argument '%s' doesn't look like base64-encoded data.", optarg); exit(EXIT_FAILURE); } strlcpy(options->hmac_key_base64, optarg, sizeof(options->hmac_key_base64)); options->have_hmac_base64_key = 1; options->use_hmac = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_HMAC_BASE64, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_HMAC, &var_bitmask); break; case KEY_HMAC: strlcpy(options->hmac_key, optarg, sizeof(options->hmac_key)); options->have_hmac_key = 1; options->use_hmac = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_HMAC, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_HMAC, &var_bitmask); break; case KEY_LEN: options->key_len = strtol_wrapper(optarg, 1, MAX_KEY_LEN, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "Invalid key length '%s', must be in [%d-%d]", optarg, 1, MAX_KEY_LEN); exit(EXIT_FAILURE); } break; case HMAC_DIGEST_TYPE: if((options->hmac_type = hmac_digest_strtoint(optarg)) < 0) { log_msg(LOG_VERBOSITY_ERROR, "* Invalid hmac digest type: %s, use {md5,sha1,sha256,sha384,sha512,sha3_256,sha3_512}", optarg); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_HMAC_DIGEST_TYPE, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_HMAC, &var_bitmask); options->use_hmac = 1; break; case HMAC_KEY_LEN: options->hmac_key_len = strtol_wrapper(optarg, 1, MAX_KEY_LEN, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "Invalid hmac key length '%s', must be in [%d-%d]", optarg, 1, MAX_KEY_LEN); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_USE_HMAC, &var_bitmask); options->use_hmac = 1; break; case SPA_ICMP_TYPE: options->spa_icmp_type = strtol_wrapper(optarg, 0, MAX_ICMP_TYPE, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "Invalid icmp type '%s', must be in [%d-%d]", optarg, 0, MAX_ICMP_TYPE); exit(EXIT_FAILURE); } break; case SPA_ICMP_CODE: options->spa_icmp_code = strtol_wrapper(optarg, 0, MAX_ICMP_CODE, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_VERBOSITY_ERROR, "Invalid icmp code '%s', must be in [%d-%d]", optarg, 0, MAX_ICMP_CODE); exit(EXIT_FAILURE); } break; case 'l': options->run_last_command = 1; break; case 'm': case FKO_DIGEST_NAME: if((options->digest_type = digest_strtoint(optarg)) < 0) { log_msg(LOG_VERBOSITY_ERROR, "* Invalid digest type: %s, use {md5,sha1,sha256,sha384,sha512,sha3_256,sha3_512}", optarg); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_DIGEST_TYPE, &var_bitmask); break; case 'M': case ENCRYPTION_MODE: if((options->encryption_mode = enc_mode_strtoint(optarg)) < 0) { log_msg(LOG_VERBOSITY_ERROR, "* Invalid encryption mode: %s, use {CBC,CTR,legacy,Asymmetric}", optarg); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_ENCRYPTION_MODE, &var_bitmask); break; case NO_SAVE_ARGS: options->no_save_args = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_NO_SAVE_ARGS, &var_bitmask); break; case 'n': /* We already handled this earlier, so we do nothing here */ break; case 'N': strlcpy(options->nat_access_str, optarg, sizeof(options->nat_access_str)); add_var_to_bitmask(FWKNOP_CLI_ARG_NAT_ACCESS, &var_bitmask); break; case 'p': options->spa_dst_port = strtol_wrapper(optarg, 0, MAX_PORT, EXIT_UPON_ERR, &is_err); add_var_to_bitmask(FWKNOP_CLI_ARG_SPA_SERVER_PORT, &var_bitmask); break; case 'P': if((options->spa_proto = proto_strtoint(optarg)) < 0) { log_msg(LOG_VERBOSITY_ERROR, "Unrecognized protocol: %s", optarg); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_SPA_SERVER_PROTO, &var_bitmask); break; case 'Q': strlcpy(options->spoof_ip_src_str, optarg, sizeof(options->spoof_ip_src_str)); add_var_to_bitmask(FWKNOP_CLI_ARG_SPOOF_SOURCE_IP, &var_bitmask); break; case RC_FILE_PATH: strlcpy(options->rc_file, optarg, sizeof(options->rc_file)); break; case 'r': options->rand_port = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_RAND_PORT, &var_bitmask); break; case 'R': options->resolve_ip_http_https = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS, &var_bitmask); break; case RESOLVE_HTTP_ONLY: options->resolve_http_only = 1; options->resolve_ip_http_https = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS, &var_bitmask); break; case RESOLVE_URL: if(options->resolve_url != NULL) free(options->resolve_url); rlen = strlen(optarg) + 1; options->resolve_url = calloc(1, rlen); if(options->resolve_url == NULL) { log_msg(LOG_VERBOSITY_ERROR, "Memory allocation error for resolve URL."); exit(EXIT_FAILURE); } strlcpy(options->resolve_url, optarg, rlen); add_var_to_bitmask(FWKNOP_CLI_ARG_RESOLVE_URL, &var_bitmask); break; case SERVER_RESOLVE_IPV4: options->spa_server_resolve_ipv4 = 1; break; case 'w': if(options->wget_bin != NULL) free(options->wget_bin); rlen = strlen(optarg) + 1; options->wget_bin = calloc(1, rlen); if(options->wget_bin == NULL) { log_msg(LOG_VERBOSITY_ERROR, "Memory allocation error for resolve URL."); exit(EXIT_FAILURE); } strlcpy(options->wget_bin, optarg, rlen); add_var_to_bitmask(FWKNOP_CLI_ARG_WGET_CMD, &var_bitmask); break; case SHOW_LAST_ARGS: options->show_last_command = 1; break; case 's': strlcpy(options->allow_ip_str, "0.0.0.0", sizeof(options->allow_ip_str)); break; case 'S': options->spa_src_port = strtol_wrapper(optarg, 0, MAX_PORT, EXIT_UPON_ERR, &is_err); add_var_to_bitmask(FWKNOP_CLI_ARG_SPA_SOURCE_PORT, &var_bitmask); break; case SAVE_RC_STANZA: /* We already handled this earlier, so we do nothing here */ break; case 'T': options->test = 1; break; case 'u': strlcpy(options->http_user_agent, optarg, sizeof(options->http_user_agent)); add_var_to_bitmask(FWKNOP_CLI_ARG_HTTP_USER_AGENT, &var_bitmask); break; case 'U': strlcpy(options->spoof_user, optarg, sizeof(options->spoof_user)); add_var_to_bitmask(FWKNOP_CLI_ARG_SPOOF_USER, &var_bitmask); break; case 'v': /* Handled earlier. */ break; case 'V': options->version = 1; break; case GPG_RECIP_KEY: options->use_gpg = 1; strlcpy(options->gpg_recipient_key, optarg, sizeof(options->gpg_recipient_key)); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_GPG_RECIPIENT, &var_bitmask); break; case GPG_SIGNER_KEY: options->use_gpg = 1; strlcpy(options->gpg_signer_key, optarg, sizeof(options->gpg_signer_key)); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_GPG_SIGNER, &var_bitmask); break; case GPG_HOME_DIR: options->use_gpg = 1; strlcpy(options->gpg_home_dir, optarg, sizeof(options->gpg_home_dir)); chop_char(options->gpg_home_dir, PATH_SEP); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_GPG_HOMEDIR, &var_bitmask); break; case GPG_EXE_PATH: options->use_gpg = 1; strlcpy(options->gpg_exe, optarg, sizeof(options->gpg_exe)); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_GPG_EXE_PATH, &var_bitmask); break; case GPG_AGENT: options->use_gpg = 1; options->use_gpg_agent = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG_AGENT, &var_bitmask); break; case GPG_ALLOW_NO_SIGNING_PW: options->use_gpg = 1; options->gpg_no_signing_pw = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_USE_GPG, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_GPG_NO_SIGNING_PW, &var_bitmask); break; case NAT_LOCAL: options->nat_local = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_NAT_LOCAL, &var_bitmask); break; case NAT_RAND_PORT: options->nat_rand_port = 1; add_var_to_bitmask(FWKNOP_CLI_ARG_NAT_RAND_PORT, &var_bitmask); break; case NAT_PORT: options->nat_port = strtol_wrapper(optarg, 0, MAX_PORT, EXIT_UPON_ERR, &is_err); add_var_to_bitmask(FWKNOP_CLI_ARG_NAT_PORT, &var_bitmask); break; case TIME_OFFSET_PLUS: if (! parse_time_offset(optarg, &options->time_offset_plus)) { log_msg(LOG_VERBOSITY_WARNING, "Invalid time offset: '%s'", optarg); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_TIME_OFFSET, &var_bitmask); break; case TIME_OFFSET_MINUS: if (! parse_time_offset(optarg, &options->time_offset_minus)) { log_msg(LOG_VERBOSITY_WARNING, "Invalid time offset: '%s'", optarg); exit(EXIT_FAILURE); } add_var_to_bitmask(FWKNOP_CLI_ARG_TIME_OFFSET, &var_bitmask); break; case USE_HMAC: add_var_to_bitmask(FWKNOP_CLI_ARG_USE_HMAC, &var_bitmask); options->use_hmac = 1; break; case USE_WGET_USER_AGENT: add_var_to_bitmask(FWKNOP_CLI_ARG_USE_WGET_USER_AGENT, &var_bitmask); options->use_wget_user_agent = 1; break; case FORCE_SAVE_RC_STANZA: options->force_save_rc_stanza = 1; break; case FD_SET_STDIN: options->input_fd = STDIN_FILENO; break; case FD_SET_ALT: #ifdef WIN32 log_msg(LOG_VERBOSITY_ERROR, "Read password from FD not supported on Windows"); exit(EXIT_FAILURE); #endif options->input_fd = strtol_wrapper(optarg, 0, -1, EXIT_UPON_ERR, &is_err); break; default: usage(); exit(EXIT_FAILURE); } } /* Now that we have all of our options set, we can validate them */ validate_options(options); /* Generate Rijndael + HMAC keys from /dev/random and base64 encode */ generate_keys(options); /* We can upgrade our settings with the parameters set on the command * line by the user */ if (options->save_rc_stanza == 1) { /* If we are asked to generate keys, we add them to the bitmask so * that they can be added to the stanza when updated */ if (options->key_gen == 1) { add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_HMAC_BASE64, &var_bitmask); } else; update_rc(options, &var_bitmask); } else; keys_status(options); return; } /* Print usage message... */ void usage(void) { log_msg(LOG_VERBOSITY_NORMAL, "\n%s client version %s\n%s - http://%s/fwknop/\n", MY_NAME, MY_VERSION, MY_DESC, HTTP_RESOLVE_HOST); log_msg(LOG_VERBOSITY_NORMAL, "Usage: fwknop -A [-s|-R|-a] -D [options]\n\n" " -n, --named-config Specify a named configuration stanza in the\n" " '$HOME/.fwknoprc' file to provide some of all\n" " of the configuration parameters.\n" " If more arguments are set through the command\n" " line, the configuration is updated accordingly.\n" " -A, --access Provide a list of ports/protocols to open\n" " on the server (e.g. 'tcp/22').\n" " -a, --allow-ip Specify IP address to allow within the SPA\n" " packet (e.g. '123.2.3.4'). If \n" " -D, --destination Specify the hostname or IP address of the\n" " fwknop server.\n" " --use-hmac Add an HMAC to the outbound SPA packet for\n" " authenticated encryption.\n" " -h, --help Print this usage message and exit.\n" " -B, --save-packet Save the generated packet data to the\n" " specified file.\n" " -b, --save-packet-append Append the generated packet data to the\n" " file specified with the -B option.\n" " -C, --server-cmd Specify a command that the fwknop server will\n" " execute on behalf of the fwknop client..\n" " -N, --nat-access Gain NAT access to an internal service.\n" " -p, --server-port Set the destination port for outgoing SPA\n" " packet.\n" " -P, --server-proto Set the protocol (udp, tcp, http, tcpraw,\n" " icmp) for the outgoing SPA packet.\n" " Note: The 'tcpraw' and 'icmp' modes use raw\n" " sockets and thus require root access to use.\n" " -s, --source-ip Tell the fwknopd server to accept whatever\n" " source IP the SPA packet has as the IP that\n" " needs access (not recommended, and the\n" " fwknopd server can ignore such requests).\n" " -S, --source-port Set the source port for outgoing SPA packet.\n" " -Q, --spoof-source Set the source IP for outgoing SPA packet.\n" " -R, --resolve-ip-https Resolve the external network IP by\n" " connecting to a URL such as the default of:\n" " https://" HTTP_RESOLVE_HOST HTTP_RESOLVE_URL "\n" " with wget in --secure-protocol mode (SSL).\n" " The URL can be overridden with the\n" " --resolve-url option.\n" " --resolve-http-only Force external IP resolution via HTTP instead\n" " HTTPS (via the URL mentioned above). This is\n" " not recommended since it would open fwknop\n" " to potential MITM attacks if the IP resolution\n" " HTTP connection is altered en-route by a third\n" " party.\n" " --resolve-url Override the default URL used for resolving\n" " the source IP address.\n" " -u, --user-agent Set the HTTP User-Agent for resolving the\n" " external IP via -R, or for sending SPA\n" " packets over HTTP. The default is\n" " Fwknop/ if this option is not used.\n" " --use-wget-user-agent Use the default wget User-Agent string instead\n" " of Fwknop/.\n" " -w, --wget-cmd Manually set the path to wget in -R mode.\n" " -H, --http-proxy Specify an HTTP proxy host through which the\n" " SPA packet will be sent. The port can also be\n" " specified here by following the host/ip with\n" " \":\".\n" " -U, --spoof-user Set the username within outgoing SPA packet.\n" " -l, --last-cmd Run the fwknop client with the same command\n" " line args as the last time it was executed\n" " (args are read from the ~/.fwknop.run file).\n" " -G, --get-key Load an encryption key/password from a file.\n" " --stdin Read the encryption key/password from stdin\n" " --fd Specify the file descriptor to read the\n" " encryption key/password from.\n" " -k, --key-gen Generate SPA Rijndael + HMAC keys.\n" " -K, --key-gen-file Write generated Rijndael + HMAC keys to a\n" " file\n" " --key-rijndael Specify the Rijndael key. Since the password is\n" " visible to utilities (like 'ps' under Unix)\n" " this form should only be used where security is\n" " not important.\n" " --key-base64-rijndael Specify the base64 encoded Rijndael key. Since\n" " the password is visible to utilities (like 'ps'\n" " under Unix) this form should only be used where\n" " security is not important.\n" " --key-base64-hmac Specify the base64 encoded HMAC key. Since the\n" " password is visible to utilities (like 'ps'\n" " under Unix) this form should only be used where\n" " security is not important.\n" " -r, --rand-port Send the SPA packet over a randomly assigned\n" " port (requires a broader pcap filter on the\n" " server side than the default of udp 62201).\n" " -T, --test Build the SPA packet but do not send it over\n" " the network.\n" " -v, --verbose Set verbose mode (may specify multiple times).\n" " -V, --version Print version number.\n" " -m, --digest-type Specify the message digest algorithm to use.\n" " (md5, sha1, sha256, sha384, or sha512). The\n" " default is sha256.\n" " -M, --encryption-mode Specify the encryption mode when AES is used\n" " for encrypting SPA packets.The default is CBC\n" " mode, but others can be chosen such as CFB or\n" " OFB as long as this is also specified in the\n" " access.conf file on the server side. Note that\n" " the string ``legacy'' can be specified in order\n" " to generate SPA packets with the old initialization\n" " vector strategy used by versions of *fwknop*\n" " before 2.5.\n" " -f, --fw-timeout Specify SPA server firewall timeout from the\n" " client side.\n" " --hmac-digest-type Set the HMAC digest algorithm (default is\n" " sha256). Options are md5, sha1, sha256,\n" " sha384, or sha512.\n" " --icmp-type Set the ICMP type (used with '-P icmp')\n" " --icmp-code Set the ICMP code (used with '-P icmp')\n" " --gpg-encryption Use GPG encryption (default is Rijndael).\n" " --gpg-recipient-key Specify the recipient GPG key name or ID.\n" " --gpg-signer-key Specify the signer's GPG key name or ID.\n" " --gpg-home-dir Specify the GPG home directory.\n" " --gpg-agent Use GPG agent if available.\n" " --gpg-exe Set path to GPG binary.\n" " --no-save-args Do not save fwknop command line args to the\n" " $HOME/fwknop.run file\n" " --rc-file Specify path to the fwknop rc file (default\n" " is $HOME/.fwknoprc)\n" " --server-resolve-ipv4 Force IPv4 address resolution from DNS for\n" " SPA server when using a hostname.\n" " --save-rc-stanza Save command line arguments to the\n" " $HOME/.fwknoprc stanza specified with the\n" " -n option.\n" " --force-stanza Used with --save-rc-stanza to overwrite all of\n" " the variables for the specified stanza\n" " --stanza-list Dump a list of the stanzas found in\n" " $HOME/.fwknoprc\n" " --nat-local Access a local service via a forwarded port\n" " on the fwknopd server system.\n" " --nat-port Specify the port to forward to access a\n" " service via NAT.\n" " --nat-rand-port Have the fwknop client assign a random port\n" " for NAT access.\n" " --show-last Show the last fwknop command line arguments.\n" " --time-offset-plus Add time to outgoing SPA packet timestamp.\n" " --time-offset-minus Subtract time from outgoing SPA packet\n" " timestamp.\n" ); return; } #ifdef HAVE_C_UNIT_TESTS DECLARE_TEST_SUITE_INIT(config_init) { log_set_verbosity(LOG_VERBOSITY_ERROR); return 0; } DECLARE_TEST_SUITE_CLEANUP(config_init) { return 0; } DECLARE_UTEST(critical_var, "Check critcial vars") { CU_ASSERT(var_is_critical(FWKNOP_CLI_ARG_KEY_RIJNDAEL) == 1); CU_ASSERT(var_is_critical(FWKNOP_CLI_ARG_WGET_CMD) == 0); } DECLARE_UTEST(check_var_bitmask, "Check var_bitmask functions") { fko_var_bitmask_t var_bitmask; memset(&var_bitmask, 0x00, sizeof(fko_var_bitmask_t)); add_var_to_bitmask(FWKNOP_CLI_FIRST_ARG, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_FIRST_ARG, &var_bitmask) == 1); CU_ASSERT(var_bitmask.dw[0] == 1); remove_var_from_bitmask(FWKNOP_CLI_FIRST_ARG, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_FIRST_ARG, &var_bitmask) == 0); CU_ASSERT(var_bitmask.dw[0] == 0); add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_RIJNDAEL, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_ARG_KEY_RIJNDAEL, &var_bitmask) == 1); remove_var_from_bitmask(FWKNOP_CLI_ARG_KEY_RIJNDAEL, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_ARG_KEY_RIJNDAEL, &var_bitmask) == 0); add_var_to_bitmask(FWKNOP_CLI_LAST_ARG, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_LAST_ARG, &var_bitmask) == 1); remove_var_from_bitmask(FWKNOP_CLI_LAST_ARG, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_LAST_ARG, &var_bitmask) == 0); add_var_to_bitmask(FWKNOP_CLI_LAST_ARG+32, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_LAST_ARG+32, &var_bitmask) == 0); add_var_to_bitmask(FWKNOP_CLI_LAST_ARG+34, &var_bitmask); CU_ASSERT(bitmask_has_var(FWKNOP_CLI_LAST_ARG+34, &var_bitmask) == 0); } int register_ts_config_init(void) { ts_init(&TEST_SUITE(config_init), TEST_SUITE_DESCR(config_init), TEST_SUITE_INIT(config_init), TEST_SUITE_CLEANUP(config_init)); ts_add_utest(&TEST_SUITE(config_init), UTEST_FCT(critical_var), UTEST_DESCR(critical_var)); ts_add_utest(&TEST_SUITE(config_init), UTEST_FCT(check_var_bitmask), UTEST_DESCR(check_var_bitmask)); return register_ts(&TEST_SUITE(config_init)); } #endif /* HAVE_C_UNIT_TESTS */