2 ******************************************************************************
6 * Author: Damien Stuart
8 * Purpose: Command-line and config file processing for fwknop client.
10 * Copyright 2009-2010 Damien Stuart (dstuart@dstuart.org)
12 * License (GNU Public License):
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 ******************************************************************************
31 #include "fwknop_common.h"
32 #include "config_init.h"
36 /* Convert a digest_type string to its integer value.
39 digest_strtoint(const char *dt_str)
41 if(strcasecmp(dt_str, "md5") == 0)
42 return(FKO_DIGEST_MD5);
43 else if(strcasecmp(dt_str, "sha1") == 0)
44 return(FKO_DIGEST_SHA1);
45 else if(strcasecmp(dt_str, "sha256") == 0)
46 return(FKO_DIGEST_SHA256);
47 else if(strcasecmp(dt_str, "sha384") == 0)
48 return(FKO_DIGEST_SHA384);
49 else if(strcasecmp(dt_str, "sha512") == 0)
50 return(FKO_DIGEST_SHA512);
55 /* Convert a protocol string to its intger value.
58 proto_strtoint(const char *pr_str)
60 if (strcasecmp(pr_str, "udp") == 0)
61 return(FKO_PROTO_UDP);
62 else if (strcasecmp(pr_str, "tcpraw") == 0)
63 return(FKO_PROTO_TCP_RAW);
64 else if (strcasecmp(pr_str, "tcp") == 0)
65 return(FKO_PROTO_TCP);
66 else if (strcasecmp(pr_str, "icmp") == 0)
67 return(FKO_PROTO_ICMP);
68 else if (strcasecmp(pr_str, "http") == 0)
69 return(FKO_PROTO_HTTP);
74 /* Parse any time offset from the command line
77 parse_time_offset(const char *offset_str)
81 int offset_type = TIME_OFFSET_SECONDS;
82 int os_len = strlen(offset_str);
84 char offset_digits[MAX_TIME_STR_LEN];
87 for (i=0; i < os_len; i++) {
88 if (isdigit(offset_str[i])) {
89 offset_digits[j] = offset_str[i];
91 if(j >= MAX_TIME_STR_LEN)
93 fprintf(stderr, "Invalid time offset: %s", offset_str);
96 } else if (offset_str[i] == 'm' || offset_str[i] == 'M') {
97 offset_type = TIME_OFFSET_MINUTES;
99 } else if (offset_str[i] == 'h' || offset_str[i] == 'H') {
100 offset_type = TIME_OFFSET_HOURS;
102 } else if (offset_str[i] == 'd' || offset_str[i] == 'D') {
103 offset_type = TIME_OFFSET_DAYS;
108 offset_digits[j] = '\0';
111 fprintf(stderr, "Invalid time offset: %s", offset_str);
115 offset = atoi(offset_digits);
118 fprintf(stderr, "Invalid time offset: %s", offset_str);
122 /* Apply the offset_type value
124 offset *= offset_type;
130 create_fwknoprc(const char *rcfile)
134 fprintf(stdout, "[*] Creating initial rc file: %s.\n", rcfile);
136 if ((rc = fopen(rcfile, "w")) == NULL)
138 fprintf(stderr, "Unable to create rc file: %s: %s\n",
139 rcfile, strerror(errno));
145 "##############################################################################\n"
147 "# Firewall Knock Operator (fwknop) client rc file.\n"
149 "# This file contains user-specific fwknop client configuration default\n"
150 "# and named parameter sets for specific invocations of the fwknop client.\n"
152 "# Each section (or stanza) is identified and started by a line in this\n"
153 "# file that contains a single identifier surrounded by square brackets.\n"
155 "# The parameters within the stanza typicaly match corresponding client \n"
156 "# command-line parameters.\n"
158 "# The first one should always be `[default]' as it defines the global\n"
159 "# default settings for the user. These override the program defaults\n"
160 "# for these parameter. If a named stanza is used, its entries will\n"
161 "# override any of the default. Command-line options will trump them\n"
164 "# Subsequent stanzas will have only the overriding and destination\n"
165 "# specific parameters.\n"
167 "# Lines starting with `#' and empty lines are ignored.\n"
169 "# See the fwknop.8 man page for a complete list of valid parameters\n"
170 "# and their values.\n"
172 "##############################################################################\n"
174 "# We start with the 'default' stanza. Uncomment and edit for your\n"
175 "# preferences. The client will use its build-in default for those items\n"
176 "# that are commented out.\n"
180 "#DIGEST_TYPE sha256\n"
182 "#SPA_SERVER_PORT 62201\n"
183 "#SPA_SERVER_PROTO udp\n"
184 "#ALLOW_IP <ip addr>\n"
185 "#SPOOF_USER <username>\n"
186 "#SPOOF_SOURCE_IP <IPaddr>\n"
189 "#GPG_HOMEDIR /path/to/.gnupg\n"
190 "#GPG_SIGNER <signer ID>\n"
191 "#GPG_RECIPIENT <recipient ID>\n"
193 "# User-provided named stanzas:\n"
195 "# Example for a destination server of 192.168.1.20 to open access to \n"
196 "# SSH for an IP that is resolved externally, and one with a NAT request\n"
197 "# for a specific source IP that maps port 8088 on the server\n"
198 "# to port 88 on 192.168.1.55 with timeout.\n"
201 "#SPA_SERVER 192.168.1.20\n"
203 "#ALLOW_IP resolve\n"
206 "#SPA_SERVER 192.168.1.20\n"
208 "#ALLOW_IP 10.21.2.6\n"
209 "#NAT_ACCESS 192.168.1.55,88\n"
210 "#CLIENT_TIMEOUT 60\n"
218 set_file_perms(rcfile);
224 parse_rc_param(fko_cli_options_t *options, const char *var, char * val)
229 if(CONF_VAR_IS(var, "DIGEST_TYPE"))
231 tmpint = digest_strtoint(val);
235 options->digest_type = tmpint;
237 /* Server protocol */
238 else if(CONF_VAR_IS(var, "SPA_SERVER_PROTO"))
240 tmpint = proto_strtoint(val);
244 options->spa_proto = tmpint;
247 else if(CONF_VAR_IS(var, "SPA_SERVER_PORT"))
250 if(tmpint < 0 || tmpint > MAX_PORT)
253 options->spa_dst_port = tmpint;
256 else if(CONF_VAR_IS(var, "SPA_SOURCE_PORT"))
259 if(tmpint < 0 || tmpint > MAX_PORT)
262 options->spa_src_port = tmpint;
264 /* Firewall rule timeout */
265 else if(CONF_VAR_IS(var, "FW_TIMEOUT"))
271 options->fw_timeout = tmpint;
274 else if(CONF_VAR_IS(var, "ALLOW_IP"))
276 /* In case this was set previously
278 options->resolve_ip_http = 0;
280 /* use source, resolve, or an actual IP
282 if(strcasecmp(val, "source") == 0)
283 strlcpy(options->allow_ip_str, "0.0.0.0", 8);
284 else if(strcasecmp(val, "resolve") == 0)
285 options->resolve_ip_http = 1;
286 else /* Assume IP address */
287 strlcpy(options->allow_ip_str, val, MAX_IPV4_STR_LEN);
290 else if(CONF_VAR_IS(var, "TIME_OFFSET"))
295 options->time_offset_minus = parse_time_offset(val);
298 options->time_offset_plus = parse_time_offset(val);
301 else if(CONF_VAR_IS(var, "USE_GPG"))
303 if(val[0] == 'y' || val[0] == 'Y')
304 options->use_gpg = 1;
307 else if(CONF_VAR_IS(var, "GPG_RECIPIENT"))
309 strlcpy(options->gpg_recipient_key, val, MAX_GPG_KEY_ID);
312 else if(CONF_VAR_IS(var, "GPG_SIGNER"))
314 strlcpy(options->gpg_signer_key, val, MAX_GPG_KEY_ID);
317 else if(CONF_VAR_IS(var, "GPG_HOMEDIR"))
319 strlcpy(options->gpg_home_dir, val, MAX_PATH_LEN);
322 else if(CONF_VAR_IS(var, "SPOOF_USER"))
324 strlcpy(options->spoof_user, val, MAX_USERNAME_LEN);
326 /* Spoof Source IP */
327 else if(CONF_VAR_IS(var, "SPOOF_SOURCE_IP"))
329 strlcpy(options->spoof_ip_src_str, val, MAX_IPV4_STR_LEN);
332 else if(CONF_VAR_IS(var, "ACCESS"))
334 strlcpy(options->access_str, val, MAX_LINE_LEN);
336 /* SPA Server (destination) */
337 else if(CONF_VAR_IS(var, "SPA_SERVER"))
339 strlcpy(options->spa_server_str, val, MAX_SERVER_STR_LEN);
342 else if(CONF_VAR_IS(var, "RAND_PORT"))
344 if(val[0] == 'y' || val[0] == 'Y')
345 options->rand_port = 1;
348 else if(CONF_VAR_IS(var, "KEY_FILE"))
350 strlcpy(options->get_key_file, val, MAX_PATH_LEN);
352 /* NAT Access Request */
353 else if(CONF_VAR_IS(var, "NAT_ACCESS"))
355 strlcpy(options->nat_access_str, val, MAX_PATH_LEN);
357 /* HTTP User Agent */
358 else if(CONF_VAR_IS(var, "HTTP_USER_AGENT"))
360 strlcpy(options->http_user_agent, val, HTTP_MAX_USER_AGENT_LEN);
363 else if(CONF_VAR_IS(var, "RESOLVE_URL"))
365 if(options->resolve_url != NULL)
366 free(options->resolve_url);
367 tmpint = strlen(val)+1;
368 options->resolve_url = malloc(tmpint);
369 if(options->resolve_url == NULL)
371 fprintf(stderr, "Memory allocation error for resolve URL.\n");
374 strlcpy(options->resolve_url, val, tmpint);
377 else if(CONF_VAR_IS(var, "NAT_LOCAL"))
379 if(val[0] == 'y' || val[0] == 'Y')
380 options->nat_local = 1;
382 /* NAT rand port ? */
383 else if(CONF_VAR_IS(var, "NAT_RAND_PORT"))
385 if(val[0] == 'y' || val[0] == 'Y')
386 options->nat_rand_port = 1;
389 else if(CONF_VAR_IS(var, "NAT_PORT"))
392 if(tmpint < 0 || tmpint > MAX_PORT)
395 options->nat_port = tmpint;
401 /* Process (create if necessary) the users ~/.fwknoprc file.
404 process_rc(fko_cli_options_t *options)
409 char line[MAX_LINE_LEN];
410 char rcfile[MAX_PATH_LEN];
411 char curr_stanza[MAX_LINE_LEN] = {0};
412 char var[MAX_LINE_LEN] = {0};
413 char val[MAX_LINE_LEN] = {0};
415 char *ndx, *emark, *homedir;
418 homedir = getenv("USERPROFILE");
420 homedir = getenv("HOME");
425 fprintf(stderr, "Warning: Unable to determine HOME directory.\n"
426 " No .fwknoprc file processed.\n");
430 memset(rcfile, 0x0, MAX_PATH_LEN);
432 strlcpy(rcfile, homedir, MAX_PATH_LEN);
434 rcf_offset = strlen(rcfile);
436 /* Sanity check the path to .fwknoprc.
437 * The preceeding path plus the path separator and '.fwknoprc' = 11
438 * cannot exceed MAX_PATH_LEN.
440 if(rcf_offset > (MAX_PATH_LEN - 11))
442 fprintf(stderr, "Warning: Path to .fwknoprc file is too long.\n"
443 " No .fwknoprc file processed.\n");
447 rcfile[rcf_offset] = PATH_SEP;
448 strlcat(rcfile, ".fwknoprc", MAX_PATH_LEN);
450 /* Check rc file permissions - if anything other than user read/write,
451 * then throw a warning. This change was made to help ensure that the
452 * client consumes a proper rc file with strict permissions set (thanks
453 * to Fernando Arnaboldi from IOActive for pointing this out).
455 verify_file_perms_ownership(rcfile);
457 /* Open the rc file for reading, if it does not exist, then create
458 * an initial .fwknoprc file with defaults and go on.
460 if ((rc = fopen(rcfile, "r")) == NULL)
464 if(create_fwknoprc(rcfile) != 0)
468 fprintf(stderr, "Unable to open rc file: %s: %s\n",
469 rcfile, strerror(errno));
474 /* Read in and parse the rc file parameters.
476 while ((fgets(line, MAX_LINE_LEN, rc)) != NULL)
479 line[MAX_LINE_LEN-1] = '\0';
483 /* Skip any leading whitespace.
488 /* Get past comments and empty lines (note: we only look at the
491 if(IS_EMPTY_LINE(line[0]))
497 emark = strchr(ndx, ']');
500 fprintf(stderr, "Unterminated stanza line: '%s'. Skipping.\n",
507 strlcpy(curr_stanza, ndx, MAX_LINE_LEN);
509 if(options->verbose > 3)
511 "RC FILE: %s, LINE: %s\tSTANZA: %s:\n",
512 rcfile, line, curr_stanza
518 if(sscanf(line, "%s %[^;\n\r#]", var, val) != 2)
521 "*Invalid entry in %s at line %i.\n - '%s'",
522 rcfile, line_num, line
527 /* Remove any colon that may be on the end of the var
529 if((ndx = strrchr(var, ':')) != NULL)
532 if(options->verbose > 3)
534 "RC FILE: %s, LINE: %s\tVar: %s, Val: '%s'\n",
535 rcfile, line, var, val
538 /* We do not proceed with parsing until we know we are in
541 if(strlen(curr_stanza) < 1)
544 /* Process the values. We assume we will see the default stanza
545 * first, then if a named-stanza is specified, we process its
548 if(strcasecmp(curr_stanza, "default") == 0)
550 if(parse_rc_param(options, var, val) < 0)
551 fprintf(stderr, "Parameter error in %s, line %i: var=%s, val=%s\n",
552 rcfile, line_num, var, val);
554 else if(options->use_rc_stanza[0] != '\0'
555 && strncasecmp(curr_stanza, options->use_rc_stanza, MAX_LINE_LEN)==0)
557 options->got_named_stanza = 1;
558 if(parse_rc_param(options, var, val) < 0)
560 "Parameter error in %s, stanza: %s, line %i: var=%s, val=%s\n",
561 rcfile, curr_stanza, line_num, var, val);
564 } /* end while fgets rc */
568 /* Sanity and bounds checks for the various options.
571 validate_options(fko_cli_options_t *options)
573 /* Gotta have a Destination unless we are just testing or getting the
574 * the version, and must use one of [-s|-R|-a].
578 && !options->show_last_command
579 && !options->run_last_command)
581 if(options->use_rc_stanza[0] != 0x0 && options->got_named_stanza == 0)
583 fprintf(stderr, "Named configuration stanza: [%s] was not found.\n",
584 options->use_rc_stanza);
589 if (options->spa_server_str[0] == 0x0)
592 "Must use --destination unless --test mode is used\n");
596 if (options->resolve_url != NULL)
597 options->resolve_ip_http = 1;
599 if (!options->resolve_ip_http && options->allow_ip_str[0] == 0x0)
602 "Must use one of [-s|-R|-a] to specify IP for SPA access.\n");
607 if(options->resolve_ip_http || options->spa_proto == FKO_PROTO_HTTP)
608 if (options->http_user_agent[0] == '\0')
609 snprintf(options->http_user_agent, HTTP_MAX_USER_AGENT_LEN,
610 "%s%s", "Fwknop/", MY_VERSION);
612 if(options->http_proxy[0] != 0x0 && options->spa_proto != FKO_PROTO_HTTP)
615 "Cannot set --http-proxy with a non-HTTP protocol.\n");
619 /* If we are using gpg, we must at least have the recipient set.
623 if(options->gpg_recipient_key == NULL
624 || strlen(options->gpg_recipient_key) == 0)
627 "Must specify --gpg-recipient-key when GPG is used.\n");
635 /* Establish a few defaults such as UDP/62201 for sending the SPA
636 * packet (can be changed with --server-proto/--server-port)
639 set_defaults(fko_cli_options_t *options)
641 options->spa_proto = FKO_DEFAULT_PROTO;
642 options->spa_dst_port = FKO_DEFAULT_PORT;
643 options->fw_timeout = -1;
648 /* Initialize program configuration via config file and/or command-line
652 config_init(fko_cli_options_t *options, int argc, char **argv)
656 /* Zero out options and opts_track.
658 memset(options, 0x00, sizeof(fko_cli_options_t));
660 /* Make sure a few reasonable defaults are set
662 set_defaults(options);
664 /* First pass over cmd_line args to see if a named-stanza in the
667 while ((cmd_arg = getopt_long(argc, argv,
668 GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
674 options->no_save_args = 1;
675 strlcpy(options->use_rc_stanza, optarg, MAX_LINE_LEN);
683 /* First process the .fwknoprc file.
687 /* Reset the options index so we can run through them again.
691 while ((cmd_arg = getopt_long(argc, argv,
692 GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
696 strlcpy(options->allow_ip_str, optarg, MAX_IPV4_STR_LEN);
699 strlcpy(options->access_str, optarg, MAX_LINE_LEN);
702 options->save_packet_file_append = 1;
705 strlcpy(options->save_packet_file, optarg, MAX_PATH_LEN);
708 strlcpy(options->server_command, optarg, MAX_LINE_LEN);
711 strlcpy(options->spa_server_str, optarg, MAX_SERVER_STR_LEN);
714 options->fw_timeout = atoi(optarg);
715 if (options->fw_timeout < 0) {
716 fprintf(stderr, "--fw-timeout must be >= 0\n");
722 options->use_gpg = 1;
725 strlcpy(options->get_key_file, optarg, MAX_PATH_LEN);
731 options->spa_proto = FKO_PROTO_HTTP;
732 strlcpy(options->http_proxy, optarg, MAX_PATH_LEN);
735 options->run_last_command = 1;
738 case FKO_DIGEST_NAME:
739 if((options->digest_type = digest_strtoint(optarg)) < 0)
741 fprintf(stderr, "* Invalid digest type: %s\n", optarg);
746 options->no_save_args = 1;
749 /* We already handled this earlier, so we do nothing here
753 strlcpy(options->nat_access_str, optarg, MAX_LINE_LEN);
756 options->spa_dst_port = atoi(optarg);
757 if (options->spa_dst_port < 0 || options->spa_dst_port > MAX_PORT)
759 fprintf(stderr, "Unrecognized port: %s\n", optarg);
764 if((options->spa_proto = proto_strtoint(optarg)) < 0)
766 fprintf(stderr, "Unrecognized protocol: %s\n", optarg);
771 strlcpy(options->spoof_ip_src_str, optarg, MAX_IPV4_STR_LEN);
774 options->rand_port = 1;
777 options->resolve_ip_http = 1;
780 if(options->resolve_url != NULL)
781 free(options->resolve_url);
782 options->resolve_url = malloc(strlen(optarg)+1);
783 if(options->resolve_url == NULL)
785 fprintf(stderr, "Memory allocation error for resolve URL.\n");
788 strlcpy(options->resolve_url, optarg, strlen(optarg)+1);
791 options->show_last_command = 1;
794 strlcpy(options->allow_ip_str, "0.0.0.0", MAX_IPV4_STR_LEN);
797 options->spa_src_port = atoi(optarg);
798 if (options->spa_src_port < 0 || options->spa_src_port > MAX_PORT)
800 fprintf(stderr, "Unrecognized port: %s\n", optarg);
808 strlcpy(options->http_user_agent, optarg, HTTP_MAX_USER_AGENT_LEN);
811 strlcpy(options->spoof_user, optarg, MAX_USERNAME_LEN);
818 options->version = 1;
821 options->use_gpg = 1;
822 strlcpy(options->gpg_recipient_key, optarg, MAX_GPG_KEY_ID);
825 options->use_gpg = 1;
826 strlcpy(options->gpg_signer_key, optarg, MAX_GPG_KEY_ID);
829 options->use_gpg = 1;
830 strlcpy(options->gpg_home_dir, optarg, MAX_PATH_LEN);
833 options->use_gpg = 1;
834 options->use_gpg_agent = 1;
837 options->nat_local = 1;
840 options->nat_rand_port = 1;
843 options->nat_port = atoi(optarg);
844 if (options->nat_port < 0 || options->nat_port > MAX_PORT)
846 fprintf(stderr, "Unrecognized port: %s\n", optarg);
850 case TIME_OFFSET_PLUS:
851 options->time_offset_plus = parse_time_offset(optarg);
853 case TIME_OFFSET_MINUS:
854 options->time_offset_minus = parse_time_offset(optarg);
862 /* Now that we have all of our options set, we can validate them.
864 validate_options(options);
869 /* Print usage message...
874 fprintf(stdout, "\n%s client version %s\n%s\n\n", MY_NAME, MY_VERSION, MY_DESC);
876 "Usage: fwknop -A <port list> [-s|-R|-a] -D <spa_server> [options]\n\n"
877 " -h, --help Print this usage message and exit.\n"
878 " -A, --access Provide a list of ports/protocols to open\n"
880 " -B, --save-packet Save the generated packet data to the\n"
882 " -a, --allow-ip Specify IP address to allow within the SPA\n"
884 " -C, --server-cmd Specify a command that the fwknop server will\n"
885 " execute on behalf of the fwknop client..\n"
886 " -D, --destination Specify the IP address of the fwknop server.\n"
887 " -n, --named-config Specify an named configuration stanza in the\n"
888 " '$HOME/.fwknoprc' file to provide some of all\n"
889 " of the configuration parameters.\n"
890 " -N, --nat-access Gain NAT access to an internal service\n"
891 " protected by the fwknop server.\n"
892 " -p, --server-port Set the destination port for outgoing SPA\n"
894 " -P, --server-proto Set the protocol (udp, tcp, http, tcpraw,\n"
895 " icmp) for the outgoing SPA packet.\n"
896 " Note: The 'tcpraw' and 'icmp' modes use raw\n"
897 " sockets and thus require root access to use.\n"
898 " -s, --source-ip Tell the fwknopd server to accept whatever\n"
899 " source IP the SPA packet has as the IP that\n"
900 " needs access (not recommended, and the\n"
901 " fwknopd server can ignore such requests).\n"
902 " -S, --source-port Set the source port for outgoing SPA packet.\n"
903 " -Q, --spoof-source Set the source IP for outgoing SPA packet.\n"
904 " -R, --resolve-ip-http Resolve the external network IP by\n"
905 " connecting to a URL such as the default of:\n"
906 " http://" HTTP_RESOLVE_HOST HTTP_RESOLVE_URL "\n"
907 " This can be overridden with the --resolve-url\n"
909 " --resolve-url Override the default URL used for resolving\n"
910 " the source IP address.\n"
911 " -u, --user-agent Set the HTTP User-Agent for resolving the\n"
912 " external IP via -R, or for sending SPA\n"
913 " packets over HTTP.\n"
914 " -H, --http-proxy Specify an HTTP proxy host through which the\n"
915 " SPA packet will be sent. The port can also be\n"
916 " specified here by following the host/ip with\n"
918 " -U, --spoof-user Set the username within outgoing SPA packet.\n"
919 " -l, --last-cmd Run the fwknop client with the same command\n"
920 " line args as the last time it was executed\n"
921 " (args are read from the ~/.fwknop.run file).\n"
922 " -G, --get-key Load an encryption key/password from a file.\n"
923 " -r, --rand-port Send the SPA packet over a randomly assigned\n"
924 " port (requires a broader pcap filter on the\n"
925 " server side than the default of udp 62201).\n"
926 " -T, --test Build the SPA packet but do not send it over\n"
928 " -v, --verbose Set verbose mode.\n"
929 " -V, --version Print version number.\n"
930 " -m, --digest-type Specify the message digest algorithm to use.\n"
931 " (md5, sha1, or sha256 (default)).\n"
932 " -f, --fw-timeout Specify SPA server firewall timeout from the\n"
934 " --gpg-encryption Use GPG encryption (default is Rijndael).\n"
935 " --gpg-recipient-key Specify the recipient GPG key name or ID.\n"
936 " --gpg-signer-key Specify the signer's GPG key name or ID.\n"
937 " --gpg-home-dir Specify the GPG home directory.\n"
938 " --gpg-agent Use GPG agent if available.\n"
939 " --no-save-args Do not save fwknop command line args to the\n"
940 " $HOME/fwknop.run file\n"
941 " --nat-local Access a local service via a forwarded port\n"
942 " on the fwknopd server system.\n"
943 " --nat-port Specify the port to forward to access a\n"
944 " service via NAT.\n"
945 " --nat-rand-port Have the fwknop client assign a random port\n"
947 " --show-last Show the last fwknop command line arguments.\n"
948 " --time-offset-plus Add time to outgoing SPA packet timestamp.\n"
949 " --time-offset-minus Subtract time from outgoing SPA packet\n"