/* ****************************************************************************** * * File: config_init.c * * Author: Damien Stuart * * Purpose: Command-line and config file processing for fwknop client. * * Copyright 2009-2010 Damien Stuart (dstuart@dstuart.org) * * License (GNU 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 /* Convert a digest_type string to its integer value. */ static short digest_strtoint(const char *dt_str) { if(strcasecmp(dt_str, "md5") == 0) return(FKO_DIGEST_MD5); else if(strcasecmp(dt_str, "sha1") == 0) return(FKO_DIGEST_SHA1); else if(strcasecmp(dt_str, "sha256") == 0) return(FKO_DIGEST_SHA256); else if(strcasecmp(dt_str, "sha384") == 0) return(FKO_DIGEST_SHA384); else if(strcasecmp(dt_str, "sha512") == 0) return(FKO_DIGEST_SHA512); else return(-1); } /* Convert a protocol string to its integer value. */ static int proto_strtoint(const char *pr_str) { if (strcasecmp(pr_str, "udpraw") == 0) return(FKO_PROTO_UDP_RAW); else if (strcasecmp(pr_str, "udp") == 0) return(FKO_PROTO_UDP); else if (strcasecmp(pr_str, "tcpraw") == 0) return(FKO_PROTO_TCP_RAW); else if (strcasecmp(pr_str, "tcp") == 0) return(FKO_PROTO_TCP); else if (strcasecmp(pr_str, "icmp") == 0) return(FKO_PROTO_ICMP); else if (strcasecmp(pr_str, "http") == 0) return(FKO_PROTO_HTTP); else return(-1); } /* Parse any time offset from the command line */ static int parse_time_offset(const char *offset_str) { int i, j; int offset = 0; int offset_type = TIME_OFFSET_SECONDS; int os_len = strlen(offset_str); int is_err; char offset_digits[MAX_TIME_STR_LEN]; 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) { fprintf(stderr, "Invalid time offset: %s", offset_str); exit(EXIT_FAILURE); } } 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) { fprintf(stderr, "Invalid time offset: %s", offset_str); exit(EXIT_FAILURE); } offset = strtol_wrapper(offset_digits, 0, (2 << 15), EXIT_UPON_ERR, &is_err); /* Apply the offset_type value */ offset *= offset_type; return offset; } static int create_fwknoprc(const char *rcfile) { FILE *rc = NULL; int rcfile_fd = -1; fprintf(stdout, "[*] Creating initial rc file: %s.\n", 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, O_WRONLY|O_CREAT|O_EXCL , S_IRUSR|S_IWUSR); // If an error occured ... if (rcfile_fd == -1) { fprintf(stderr, "Unable to create initial rc file: %s: %s\n", rcfile, strerror(errno)); return(-1); } // Free the rcfile descriptor close(rcfile_fd); if ((rc = fopen(rcfile, "w")) == NULL) { fprintf(stderr, "Unable to write default setup to rcfile: %s: %s\n", 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" "#\n" "# The parameters within the stanza typicaly 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 parameter. If a named stanza is used, its entries will\n" "# override any of the default. 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 build-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_SIGNER \n" "#GPG_RECIPIENT \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" "###EOF###\n" ); fclose(rc); return(0); } static int parse_rc_param(fko_cli_options_t *options, const char *var, char * val) { int tmpint, is_err; /* Digest Type */ if(CONF_VAR_IS(var, "DIGEST_TYPE")) { tmpint = digest_strtoint(val); if(tmpint < 0) return(-1); else options->digest_type = tmpint; } /* Server protocol */ else if(CONF_VAR_IS(var, "SPA_SERVER_PROTO")) { tmpint = proto_strtoint(val); if(tmpint < 0) return(-1); else options->spa_proto = tmpint; } /* Server port */ else if(CONF_VAR_IS(var, "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 return(-1); } /* Source port */ else if(CONF_VAR_IS(var, "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 return(-1); } /* Firewall rule timeout */ else if(CONF_VAR_IS(var, "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 return(-1); } /* Allow IP */ else if(CONF_VAR_IS(var, "ALLOW_IP")) { /* In case this was set previously */ options->resolve_ip_http = 0; /* use source, resolve, or an actual IP */ if(strcasecmp(val, "source") == 0) strlcpy(options->allow_ip_str, "0.0.0.0", 8); else if(strcasecmp(val, "resolve") == 0) options->resolve_ip_http = 1; else /* Assume IP address */ strlcpy(options->allow_ip_str, val, MAX_IPV4_STR_LEN); } /* Time Offset */ else if(CONF_VAR_IS(var, "TIME_OFFSET")) { if(val[0] == '-') { val++; options->time_offset_minus = parse_time_offset(val); } else options->time_offset_plus = parse_time_offset(val); } /* symmetric encryption mode */ else if(CONF_VAR_IS(var, "ENCRYPTION_MODE")) { tmpint = enc_mode_strtoint(val); if(tmpint < 0) return(-1); else options->encryption_mode = tmpint; } /* Use GPG ? */ else if(CONF_VAR_IS(var, "USE_GPG")) { if(val[0] == 'y' || val[0] == 'Y') options->use_gpg = 1; } /* Use GPG Agent ? */ else if(CONF_VAR_IS(var, "USE_GPG_AGENT")) { if(val[0] == 'y' || val[0] == 'Y') options->use_gpg_agent = 1; } /* GPG Recipient */ else if(CONF_VAR_IS(var, "GPG_RECIPIENT")) { strlcpy(options->gpg_recipient_key, val, MAX_GPG_KEY_ID); } /* GPG Signer */ else if(CONF_VAR_IS(var, "GPG_SIGNER")) { strlcpy(options->gpg_signer_key, val, MAX_GPG_KEY_ID); } /* GPG Homedir */ else if(CONF_VAR_IS(var, "GPG_HOMEDIR")) { strlcpy(options->gpg_home_dir, val, MAX_PATH_LEN); } /* Spoof User */ else if(CONF_VAR_IS(var, "SPOOF_USER")) { strlcpy(options->spoof_user, val, MAX_USERNAME_LEN); } /* Spoof Source IP */ else if(CONF_VAR_IS(var, "SPOOF_SOURCE_IP")) { strlcpy(options->spoof_ip_src_str, val, MAX_IPV4_STR_LEN); } /* ACCESS request */ else if(CONF_VAR_IS(var, "ACCESS")) { strlcpy(options->access_str, val, MAX_LINE_LEN); } /* SPA Server (destination) */ else if(CONF_VAR_IS(var, "SPA_SERVER")) { strlcpy(options->spa_server_str, val, MAX_SERVER_STR_LEN); } /* Rand port ? */ else if(CONF_VAR_IS(var, "RAND_PORT")) { if(val[0] == 'y' || val[0] == 'Y') options->rand_port = 1; } /* Rijndael key */ else if(CONF_VAR_IS(var, "KEY")) { strlcpy(options->key, val, MAX_KEY_LEN); options->have_key = 1; } /* Rijndael key (base-64 encoded) */ else if(CONF_VAR_IS(var, "KEY_BASE64")) { if (! is_base64((unsigned char *) val, strlen(val))) { fprintf(stderr, "KEY_BASE64 argument '%s' doesn't look like base64-encoded data.\n", val); return(-1); } strlcpy(options->key_base64, val, MAX_KEY_LEN); options->have_base64_key = 1; } /* HMAC key */ else if(CONF_VAR_IS(var, "HMAC_KEY_BASE64")) { if (! is_base64((unsigned char *) val, strlen(val))) { fprintf(stderr, "HMAC_KEY_BASE64 argument '%s' doesn't look like base64-encoded data.\n", val); return(-1); } strlcpy(options->hmac_key_base64, val, MAX_KEY_LEN); options->have_hmac_base64_key = 1; } /* Key file */ else if(CONF_VAR_IS(var, "KEY_FILE")) { strlcpy(options->get_key_file, val, MAX_PATH_LEN); } /* NAT Access Request */ else if(CONF_VAR_IS(var, "NAT_ACCESS")) { strlcpy(options->nat_access_str, val, MAX_PATH_LEN); } /* HTTP User Agent */ else if(CONF_VAR_IS(var, "HTTP_USER_AGENT")) { strlcpy(options->http_user_agent, val, HTTP_MAX_USER_AGENT_LEN); } /* Resolve URL */ else if(CONF_VAR_IS(var, "RESOLVE_URL")) { if(options->resolve_url != NULL) free(options->resolve_url); tmpint = strlen(val)+1; options->resolve_url = malloc(tmpint); if(options->resolve_url == NULL) { fprintf(stderr, "Memory allocation error for resolve URL.\n"); exit(EXIT_FAILURE); } strlcpy(options->resolve_url, val, tmpint); } /* NAT Local ? */ else if(CONF_VAR_IS(var, "NAT_LOCAL")) { if(val[0] == 'y' || val[0] == 'Y') options->nat_local = 1; } /* NAT rand port ? */ else if(CONF_VAR_IS(var, "NAT_RAND_PORT")) { if(val[0] == 'y' || val[0] == 'Y') options->nat_rand_port = 1; } /* NAT port */ else if(CONF_VAR_IS(var, "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 return(-1); } return(0); } /* Process (create if necessary) the users ~/.fwknoprc file. */ static void process_rc(fko_cli_options_t *options) { FILE *rc; int line_num = 0, do_exit = 0; int rcf_offset; char line[MAX_LINE_LEN]; char rcfile[MAX_PATH_LEN]; char curr_stanza[MAX_LINE_LEN] = {0}; char var[MAX_LINE_LEN] = {0}; char val[MAX_LINE_LEN] = {0}; char *ndx, *emark, *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) { fprintf(stderr, "Warning: Unable to determine HOME directory.\n" " No .fwknoprc file processed.\n"); return; } 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)) { fprintf(stderr, "Warning: Path to .fwknoprc file is too long.\n" " No .fwknoprc file processed.\n"); return; } 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). */ verify_file_perms_ownership(rcfile); /* Open the rc file for reading, if it does not exist, then create * an initial .fwknoprc file with defaults and go on. */ if ((rc = fopen(rcfile, "r")) == NULL) { if(errno == ENOENT) { if(create_fwknoprc(rcfile) != 0) return; } else fprintf(stderr, "Unable to open rc file: %s: %s\n", rcfile, strerror(errno)); return; } /* Read in and parse the rc file parameters. */ while ((fgets(line, MAX_LINE_LEN, rc)) != NULL) { line_num++; line[MAX_LINE_LEN-1] = '\0'; ndx = line; /* Skip any leading whitespace. */ while(isspace(*ndx)) ndx++; /* Get past comments and empty lines (note: we only look at the * first character. */ if(IS_EMPTY_LINE(line[0])) continue; if(*ndx == '[') { ndx++; emark = strchr(ndx, ']'); if(emark == NULL) { fprintf(stderr, "Unterminated stanza line: '%s'. Skipping.\n", line); continue; } *emark = '\0'; strlcpy(curr_stanza, ndx, MAX_LINE_LEN); if(options->verbose > 3) fprintf(stderr, "RC FILE: %s, LINE: %s\tSTANZA: %s:\n", rcfile, line, curr_stanza ); continue; } if(sscanf(line, "%s %[^ ;\t\n\r#]", var, val) != 2) { fprintf(stderr, "*Invalid entry in %s at line %i.\n - '%s'", rcfile, line_num, line ); continue; } /* Remove any colon that may be on the end of the var */ if((ndx = strrchr(var, ':')) != NULL) *ndx = '\0'; /* Even though sscanf should automatically add a terminating * NULL byte, an assumption is made that the input arrays are * big enough, so we'll force a terminating NULL byte regardless */ var[MAX_LINE_LEN-1] = 0x0; val[MAX_LINE_LEN-1] = 0x0; if(options->verbose > 3) fprintf(stderr, "RC FILE: %s, LINE: %s\tVar: %s, Val: '%s'\n", rcfile, line, var, val ); /* We do not proceed with parsing until we know we are in * a stanza. */ if(strlen(curr_stanza) < 1) continue; /* Process the values. We assume we will see the default stanza * first, then if a named-stanza is specified, we process its * entries as well. */ if(strcasecmp(curr_stanza, "default") == 0) { if(parse_rc_param(options, var, val) < 0) { fprintf(stderr, "Parameter error in %s, line %i: var=%s, val=%s\n", rcfile, line_num, var, val); do_exit = 1; } } else if(options->use_rc_stanza[0] != '\0' && strncasecmp(curr_stanza, options->use_rc_stanza, MAX_LINE_LEN)==0) { options->got_named_stanza = 1; if(parse_rc_param(options, var, val) < 0) { fprintf(stderr, "Parameter error in %s, stanza: %s, line %i: var=%s, val=%s\n", rcfile, curr_stanza, line_num, var, val); do_exit = 1; } } } /* end while fgets rc */ fclose(rc); if(do_exit) exit(EXIT_FAILURE); } /* Sanity and bounds checks for the various options. */ static void validate_options(fko_cli_options_t *options) { /* Gotta 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->use_rc_stanza[0] != 0x0 && options->got_named_stanza == 0) { fprintf(stderr, "Named configuration stanza: [%s] was not found.\n", options->use_rc_stanza); exit(EXIT_FAILURE); } if (options->spa_server_str[0] == 0x0) { fprintf(stderr, "Must use --destination unless --test mode is used\n"); exit(EXIT_FAILURE); } if (options->resolve_url != NULL) options->resolve_ip_http = 1; if (!options->resolve_ip_http && options->allow_ip_str[0] == 0x0) { fprintf(stderr, "Must use one of [-s|-R|-a] to specify IP for SPA access.\n"); exit(EXIT_FAILURE); } } if(options->resolve_ip_http || 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) { fprintf(stderr, "Cannot set --http-proxy with a non-HTTP protocol.\n"); exit(EXIT_FAILURE); } /* If we are using gpg, we must at least have the recipient set. */ if(options->use_gpg) { if(options->gpg_recipient_key == NULL || strlen(options->gpg_recipient_key) == 0) { fprintf(stderr, "Must specify --gpg-recipient-key when GPG is used.\n"); exit(EXIT_FAILURE); } } 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->spa_icmp_type = ICMP_ECHOREPLY; /* only used in '-P icmp' mode */ options->spa_icmp_code = 0; /* only used in '-P icmp' mode */ 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; /* Zero out options and opts_track. */ memset(options, 0x00, sizeof(fko_cli_options_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 'n': options->no_save_args = 1; strlcpy(options->use_rc_stanza, optarg, MAX_LINE_LEN); break; case 'E': strlcpy(options->args_save_file, optarg, MAX_PATH_LEN); break; case RC_FILE_PATH: strlcpy(options->rc_file, optarg, MAX_PATH_LEN); break; case 'v': options->verbose++; break; } } /* First process the .fwknoprc file. */ process_rc(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, MAX_IPV4_STR_LEN); break; case 'A': strlcpy(options->access_str, optarg, MAX_LINE_LEN); break; case 'b': options->save_packet_file_append = 1; break; case 'B': strlcpy(options->save_packet_file, optarg, MAX_PATH_LEN); break; case 'C': strlcpy(options->server_command, optarg, MAX_LINE_LEN); break; case 'D': strlcpy(options->spa_server_str, optarg, MAX_SERVER_STR_LEN); break; case 'E': strlcpy(options->args_save_file, optarg, MAX_PATH_LEN); break; case 'f': options->fw_timeout = strtol_wrapper(optarg, 0, (2 << 16), NO_EXIT_UPON_ERR, &is_err); if(is_err != FKO_SUCCESS) { fprintf(stderr, "--fw-timeout must be within [%d-%d]\n", 0, (2 << 16)); exit(EXIT_FAILURE); } break; case 'g': case GPG_ENCRYPTION: options->use_gpg = 1; break; case 'G': strlcpy(options->get_key_file, optarg, MAX_PATH_LEN); break; case 'h': usage(); exit(EXIT_SUCCESS); case 'H': options->spa_proto = FKO_PROTO_HTTP; strlcpy(options->http_proxy, optarg, MAX_PATH_LEN); break; case 'k': options->key_gen = 1; break; case 'K': options->key_gen = 1; strlcpy(options->key_gen_file, optarg, MAX_PATH_LEN); 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) { fprintf(stderr, "Invalid icmp type '%s', must be in [%d-%d]\n", 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) { fprintf(stderr, "Invalid icmp code '%s', must be in [%d-%d]\n", 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) { fprintf(stderr, "* Invalid digest type: %s, use {md5,sha1,sha256,sha384,sha512}\n", optarg); exit(EXIT_FAILURE); } break; case 'M': case ENCRYPTION_MODE: if((options->encryption_mode = enc_mode_strtoint(optarg)) < 0) { fprintf(stderr, "* Invalid encryption mode: %s, use {cbc,ecb}\n", optarg); exit(EXIT_FAILURE); } break; case NO_SAVE_ARGS: options->no_save_args = 1; break; case 'n': /* We already handled this earlier, so we do nothing here */ break; case 'N': strlcpy(options->nat_access_str, optarg, MAX_LINE_LEN); break; case 'p': options->spa_dst_port = strtol_wrapper(optarg, 0, MAX_PORT, EXIT_UPON_ERR, &is_err); break; case 'P': if((options->spa_proto = proto_strtoint(optarg)) < 0) { fprintf(stderr, "Unrecognized protocol: %s\n", optarg); exit(EXIT_FAILURE); } break; case 'Q': strlcpy(options->spoof_ip_src_str, optarg, MAX_IPV4_STR_LEN); break; case RC_FILE_PATH: strlcpy(options->rc_file, optarg, MAX_PATH_LEN); break; case 'r': options->rand_port = 1; break; case 'R': options->resolve_ip_http = 1; break; case RESOLVE_URL: if(options->resolve_url != NULL) free(options->resolve_url); options->resolve_url = malloc(strlen(optarg)+1); if(options->resolve_url == NULL) { fprintf(stderr, "Memory allocation error for resolve URL.\n"); exit(EXIT_FAILURE); } strlcpy(options->resolve_url, optarg, strlen(optarg)+1); break; case SHOW_LAST_ARGS: options->show_last_command = 1; break; case 's': strlcpy(options->allow_ip_str, "0.0.0.0", MAX_IPV4_STR_LEN); break; case 'S': options->spa_src_port = strtol_wrapper(optarg, 0, MAX_PORT, EXIT_UPON_ERR, &is_err); break; case 'T': options->test = 1; break; case 'u': strlcpy(options->http_user_agent, optarg, HTTP_MAX_USER_AGENT_LEN); break; case 'U': strlcpy(options->spoof_user, optarg, MAX_USERNAME_LEN); 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, MAX_GPG_KEY_ID); break; case GPG_SIGNER_KEY: options->use_gpg = 1; strlcpy(options->gpg_signer_key, optarg, MAX_GPG_KEY_ID); break; case GPG_HOME_DIR: options->use_gpg = 1; strlcpy(options->gpg_home_dir, optarg, MAX_PATH_LEN); break; case GPG_AGENT: options->use_gpg = 1; options->use_gpg_agent = 1; break; case NAT_LOCAL: options->nat_local = 1; break; case NAT_RAND_PORT: options->nat_rand_port = 1; break; case NAT_PORT: options->nat_port = strtol_wrapper(optarg, 0, MAX_PORT, EXIT_UPON_ERR, &is_err); break; case TIME_OFFSET_PLUS: options->time_offset_plus = parse_time_offset(optarg); break; case TIME_OFFSET_MINUS: options->time_offset_minus = parse_time_offset(optarg); break; case USE_HMAC: options->use_hmac = 1; break; default: usage(); exit(EXIT_FAILURE); } } /* Now that we have all of our options set, we can validate them. */ validate_options(options); return; } /* Print usage message... */ void usage(void) { fprintf(stdout, "\n%s client version %s\n%s\n\n", MY_NAME, MY_VERSION, MY_DESC); fprintf(stdout, "Usage: fwknop -A [-s|-R|-a] -D [options]\n\n" " -h, --help Print this usage message and exit.\n" " -A, --access Provide a list of ports/protocols to open\n" " on the server.\n" " -B, --save-packet Save the generated packet data to the\n" " specified file.\n" " -a, --allow-ip Specify IP address to allow within the SPA\n" " packet.\n" " -C, --server-cmd Specify a command that the fwknop server will\n" " execute on behalf of the fwknop client..\n" " -D, --destination Specify the IP address of the fwknop server.\n" " -n, --named-config Specify an named configuration stanza in the\n" " '$HOME/.fwknoprc' file to provide some of all\n" " of the configuration parameters.\n" " -N, --nat-access Gain NAT access to an internal service\n" " protected by the fwknop server.\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-http Resolve the external network IP by\n" " connecting to a URL such as the default of:\n" " http://" HTTP_RESOLVE_HOST HTTP_RESOLVE_URL "\n" " This can be overridden with the --resolve-url\n" " option.\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.\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" " -k, --key-gen Generate SPA Rijndael + HMAC keys.\n" " -K, --key-gen-file Write generated Rijndael + HMAC keys to a\n" " file\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.\n" " -V, --version Print version number.\n" " -m, --digest-type Specify the message digest algorithm to use.\n" " (md5, sha1, or sha256 (default)).\n" " -f, --fw-timeout Specify SPA server firewall timeout from the\n" " client side.\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" " --no-save-args Do not save fwknop command line args to the\n" " $HOME/fwknop.run file\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" "\n" ); return; } /***EOF***/