/** * \file server/config_init.c * * \brief Command-line and config file processing for fwknop server. */ /* 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 "fwknopd_common.h" #include "fwknopd_errors.h" #include "config_init.h" #include "access.h" #include "cmd_opts.h" #include "utils.h" #include "log_msg.h" #if FIREWALL_FIREWALLD #include "fw_util_firewalld.h" #elif FIREWALL_IPTABLES #include "fw_util_iptables.h" #endif /* Check to see if an integer variable has a value that is within a * specific range */ static int range_check(fko_srv_options_t *opts, char *var, char *val, int low, int high) { int is_err, rv; rv = strtol_wrapper(val, low, high, NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] var %s value '%s' not in the range %d-%d", var, val, low, high); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } return rv; } /* Take an index and a string value. malloc the space for the value * and assign it to the array at the specified index. */ static void set_config_entry(fko_srv_options_t *opts, const int var_ndx, const char *value) { int space_needed; /* Sanity check the index value. */ if(var_ndx < 0 || var_ndx >= NUMBER_OF_CONFIG_ENTRIES) { log_msg(LOG_ERR, "[*] Index value of %i is not valid", var_ndx); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } /* If this particular entry was already set (i.e. not NULL), then * assume it needs to be freed first. */ if(opts->config[var_ndx] != NULL) free(opts->config[var_ndx]); /* If we are setting it to NULL, do it and be done. */ if(value == NULL) { opts->config[var_ndx] = NULL; return; } /* Otherwise, make the space we need and set it. */ space_needed = strlen(value) + 1; opts->config[var_ndx] = calloc(1, space_needed); if(opts->config[var_ndx] == NULL) { log_msg(LOG_ERR, "[*] Fatal memory allocation error!"); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } strlcpy(opts->config[var_ndx], value, space_needed); return; } /* Given a config parameter name, return its index or -1 if not found. */ static int config_entry_index(const fko_srv_options_t *opts, const char *var) { int i; for(i=0; iconfig[i] != NULL && CONF_VAR_IS(var, config_map[i])) return(i); return(-1); } /* Free the config memory */ void free_configs(fko_srv_options_t *opts) { int i; free_acc_stanzas(opts); for(i=0; iconfig[i] != NULL) free(opts->config[i]); } static void validate_int_var_ranges(fko_srv_options_t *opts) { #if FIREWALL_IPFW int is_err = FKO_SUCCESS; #endif opts->pcap_loop_sleep = range_check(opts, "PCAP_LOOP_SLEEP", opts->config[CONF_PCAP_LOOP_SLEEP], 1, RCHK_MAX_PCAP_LOOP_SLEEP); opts->pcap_dispatch_count = range_check(opts, "PCAP_DISPATCH_COUNT", opts->config[CONF_PCAP_DISPATCH_COUNT], 1, RCHK_MAX_PCAP_DISPATCH_COUNT); opts->max_spa_packet_age = range_check(opts, "MAX_SPA_PACKET_AGE", opts->config[CONF_MAX_SPA_PACKET_AGE], 1, RCHK_MAX_SPA_PACKET_AGE); opts->max_sniff_bytes = range_check(opts, "MAX_SNIFF_BYTES", opts->config[CONF_MAX_SNIFF_BYTES], 1, RCHK_MAX_SNIFF_BYTES); opts->rules_chk_threshold = range_check(opts, "RULES_CHECK_THRESHOLD", opts->config[CONF_RULES_CHECK_THRESHOLD], 0, RCHK_MAX_RULES_CHECK_THRESHOLD); opts->tcpserv_port = range_check(opts, "TCPSERV_PORT", opts->config[CONF_TCPSERV_PORT], 1, RCHK_MAX_TCPSERV_PORT); opts->udpserv_port = range_check(opts, "UDPSERV_PORT", opts->config[CONF_UDPSERV_PORT], 1, RCHK_MAX_UDPSERV_PORT); opts->udpserv_select_timeout = range_check(opts, "UDPSERV_SELECT_TIMEOUT", opts->config[CONF_UDPSERV_SELECT_TIMEOUT], 1, RCHK_MAX_UDPSERV_SELECT_TIMEOUT); #if FIREWALL_IPFW range_check(opts, "IPFW_START_RULE_NUM", opts->config[CONF_IPFW_START_RULE_NUM], 0, RCHK_MAX_IPFW_START_RULE_NUM); range_check(opts, "IPFW_MAX_RULES", opts->config[CONF_IPFW_MAX_RULES], 1, RCHK_MAX_IPFW_MAX_RULES); range_check(opts, "IPFW_ACTIVE_SET_NUM", opts->config[CONF_IPFW_ACTIVE_SET_NUM], 0, RCHK_MAX_IPFW_SET_NUM); range_check(opts, "IPFW_EXPIRE_SET_NUM", opts->config[CONF_IPFW_EXPIRE_SET_NUM], 0, RCHK_MAX_IPFW_SET_NUM); range_check(opts, "IPFW_EXPIRE_PURGE_INTERVAL", opts->config[CONF_IPFW_EXPIRE_PURGE_INTERVAL], 1, RCHK_MAX_IPFW_PURGE_INTERVAL); /* Make sure the active and expire sets are not identical whenever * they are non-zero */ if((strtol_wrapper(opts->config[CONF_IPFW_ACTIVE_SET_NUM], 0, RCHK_MAX_IPFW_SET_NUM, NO_EXIT_UPON_ERR, &is_err) > 0 && strtol_wrapper(opts->config[CONF_IPFW_EXPIRE_SET_NUM], 0, RCHK_MAX_IPFW_SET_NUM, NO_EXIT_UPON_ERR, &is_err) > 0) && strtol_wrapper(opts->config[CONF_IPFW_ACTIVE_SET_NUM], 0, RCHK_MAX_IPFW_SET_NUM, NO_EXIT_UPON_ERR, &is_err) == strtol_wrapper(opts->config[CONF_IPFW_EXPIRE_SET_NUM], 0, RCHK_MAX_IPFW_SET_NUM, NO_EXIT_UPON_ERR, &is_err)) { log_msg(LOG_ERR, "[*] Cannot set identical ipfw active and expire sets."); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } if(is_err != FKO_SUCCESS) { log_msg(LOG_ERR, "[*] invalid integer conversion error.\n"); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } #elif FIREWALL_PF range_check(opts, "PF_EXPIRE_INTERVAL", opts->config[CONF_PF_EXPIRE_INTERVAL], 1, RCHK_MAX_PF_EXPIRE_INTERVAL); #endif /* FIREWALL type */ return; } /** * @brief Generate Rijndael + HMAC keys from /dev/urandom (base64 encoded). * * @param options FKO command line option structure */ static void generate_keys(fko_srv_options_t *options) { char key_base64[MAX_B64_KEY_LEN+1]; char hmac_key_base64[MAX_B64_KEY_LEN+1]; FILE *key_gen_file_ptr = NULL; int res; /* Set defaults and validate for --key-gen mode */ if(options->key_len == 0) options->key_len = FKO_DEFAULT_KEY_LEN; if(options->hmac_key_len == 0) options->hmac_key_len = FKO_DEFAULT_HMAC_KEY_LEN; if(options->hmac_type == 0) options->hmac_type = FKO_DEFAULT_HMAC_MODE; /* Zero out the key buffers */ memset(key_base64, 0x00, sizeof(key_base64)); memset(hmac_key_base64, 0x00, sizeof(hmac_key_base64)); /* Generate the key through libfko */ res = fko_key_gen(key_base64, options->key_len, hmac_key_base64, options->hmac_key_len, options->hmac_type); if(res != FKO_SUCCESS) { log_msg(LOG_ERR, "%s: fko_key_gen: Error %i - %s", MY_NAME, res, fko_errstr(res)); clean_exit(options, NO_FW_CLEANUP, EXIT_FAILURE); } if(options->key_gen_file[0] != '\0') { if ((key_gen_file_ptr = fopen(options->key_gen_file, "w")) == NULL) { log_msg(LOG_ERR, "Unable to create key gen file: %s: %s", options->key_gen_file, strerror(errno)); clean_exit(options, NO_FW_CLEANUP, EXIT_FAILURE); } fprintf(key_gen_file_ptr, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\n", key_base64, hmac_key_base64); fclose(key_gen_file_ptr); fprintf(stdout, "[+] Wrote Rijndael and HMAC keys to: %s", options->key_gen_file); } else { fprintf(stdout, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\n", key_base64, hmac_key_base64); } clean_exit(options, NO_FW_CLEANUP, EXIT_SUCCESS); } /* Parse the config file... */ static void parse_config_file(fko_srv_options_t *opts, const char *config_file) { FILE *cfile_ptr; unsigned int numLines = 0; unsigned int i, good_ent; int cndx; char conf_line_buf[MAX_LINE_LEN] = {0}; char var[MAX_LINE_LEN] = {0}; char val[MAX_LINE_LEN] = {0}; char tmp1[MAX_LINE_LEN] = {0}; char tmp2[MAX_LINE_LEN] = {0}; /* See the comment in the parse_access_file() function regarding security * here relative to a TOCTOU bug flagged by Coverity. */ if ((cfile_ptr = fopen(config_file, "r")) == NULL) { log_msg(LOG_ERR, "[*] Could not open config file: %s", config_file); perror(NULL); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } if(verify_file_perms_ownership(config_file, fileno(cfile_ptr)) != 1) { fclose(cfile_ptr); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } while ((fgets(conf_line_buf, MAX_LINE_LEN, cfile_ptr)) != NULL) { numLines++; conf_line_buf[MAX_LINE_LEN-1] = '\0'; /* Get past comments and empty lines (note: we only look at the * first character. */ if(IS_EMPTY_LINE(conf_line_buf[0])) continue; if(sscanf(conf_line_buf, "%s %[^;\n\r]", var, val) != 2) { log_msg(LOG_ERR, "*Invalid config file entry in %s at line %i.\n - '%s'", config_file, numLines, conf_line_buf ); continue; } good_ent = 0; for(i=0; i= 0) { strlcpy(val, opts->config[cndx], sizeof(val)); strlcat(val, tmp2, sizeof(val)); } else { /* We didn't map the embedded variable to a valid * config parameter */ log_msg(LOG_ERR, "[*] Invalid embedded variable in: '%s'", val); break; } } } set_config_entry(opts, i, val); good_ent++; break; } } if(good_ent == 0) log_msg(LOG_ERR, "[*] Ignoring unknown configuration parameter: '%s' in %s", var, config_file ); } fclose(cfile_ptr); return; } /* Set defaults, and do sanity and bounds checks for the various options. */ static void validate_options(fko_srv_options_t *opts) { char tmp_path[MAX_PATH_LEN] = {0}; /* If no conf dir is set in the config file, use the default. */ if(opts->config[CONF_FWKNOP_CONF_DIR] == NULL) set_config_entry(opts, CONF_FWKNOP_CONF_DIR, DEF_CONF_DIR); /* If no access.conf path was specified on the command line or set in * the config file, use the default. */ if(opts->config[CONF_ACCESS_FILE] == NULL) set_config_entry(opts, CONF_ACCESS_FILE, DEF_ACCESS_FILE); /* If the pid and digest cache files where not set in the config file or * via command-line, then grab the defaults. Start with RUN_DIR as the * files may depend on that. */ if(opts->config[CONF_FWKNOP_RUN_DIR] == NULL) set_config_entry(opts, CONF_FWKNOP_RUN_DIR, DEF_RUN_DIR); if(opts->config[CONF_FWKNOP_PID_FILE] == NULL) { strlcpy(tmp_path, opts->config[CONF_FWKNOP_RUN_DIR], sizeof(tmp_path)); if(tmp_path[strlen(tmp_path)-1] != '/') strlcat(tmp_path, "/", sizeof(tmp_path)); strlcat(tmp_path, DEF_PID_FILENAME, sizeof(tmp_path)); set_config_entry(opts, CONF_FWKNOP_PID_FILE, tmp_path); } #if USE_FILE_CACHE if(opts->config[CONF_DIGEST_FILE] == NULL) #else if(opts->config[CONF_DIGEST_DB_FILE] == NULL) #endif { strlcpy(tmp_path, opts->config[CONF_FWKNOP_RUN_DIR], sizeof(tmp_path)); if(tmp_path[strlen(tmp_path)-1] != '/') strlcat(tmp_path, "/", sizeof(tmp_path)); #if USE_FILE_CACHE strlcat(tmp_path, DEF_DIGEST_CACHE_FILENAME, sizeof(tmp_path)); set_config_entry(opts, CONF_DIGEST_FILE, tmp_path); #else strlcat(tmp_path, DEF_DIGEST_CACHE_DB_FILENAME, sizeof(tmp_path)); set_config_entry(opts, CONF_DIGEST_DB_FILE, tmp_path); #endif } /* Set remaining require CONF_ vars if they are not already set. */ /* PCAP capture interface - note that if '-r ' is specified * on the command line, then this will override the pcap interface setting. */ if(opts->config[CONF_PCAP_INTF] == NULL) set_config_entry(opts, CONF_PCAP_INTF, DEF_INTERFACE); /* PCAP Promiscuous mode. */ if(opts->config[CONF_ENABLE_PCAP_PROMISC] == NULL) set_config_entry(opts, CONF_ENABLE_PCAP_PROMISC, DEF_ENABLE_PCAP_PROMISC); /* The packet count argument to pcap_dispatch() */ if(opts->config[CONF_PCAP_DISPATCH_COUNT] == NULL) set_config_entry(opts, CONF_PCAP_DISPATCH_COUNT, DEF_PCAP_DISPATCH_COUNT); /* Microseconds to sleep between pcap loop iterations */ if(opts->config[CONF_PCAP_LOOP_SLEEP] == NULL) set_config_entry(opts, CONF_PCAP_LOOP_SLEEP, DEF_PCAP_LOOP_SLEEP); /* Control whether to exit if the interface where we're sniffing * goes down. */ if(opts->config[CONF_EXIT_AT_INTF_DOWN] == NULL) set_config_entry(opts, CONF_EXIT_AT_INTF_DOWN, DEF_EXIT_AT_INTF_DOWN); /* PCAP Filter. */ if(opts->config[CONF_PCAP_FILTER] == NULL) set_config_entry(opts, CONF_PCAP_FILTER, DEF_PCAP_FILTER); /* Enable SPA packet aging unless we're getting packet data * directly from a pcap file */ if(opts->config[CONF_ENABLE_SPA_PACKET_AGING] == NULL) { if(opts->config[CONF_PCAP_FILE] == NULL) { set_config_entry(opts, CONF_ENABLE_SPA_PACKET_AGING, DEF_ENABLE_SPA_PACKET_AGING); } else { set_config_entry(opts, CONF_ENABLE_SPA_PACKET_AGING, "N"); } } /* SPA packet age. */ if(opts->config[CONF_MAX_SPA_PACKET_AGE] == NULL) set_config_entry(opts, CONF_MAX_SPA_PACKET_AGE, DEF_MAX_SPA_PACKET_AGE); /* Enable digest persistence. */ if(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE] == NULL) set_config_entry(opts, CONF_ENABLE_DIGEST_PERSISTENCE, DEF_ENABLE_DIGEST_PERSISTENCE); /* Set firewall rule "deep" collection interval - this allows * fwknopd to remove rules with proper _exp_