Bug fix for multi-stanza key use and replay attack detection
[fwknop.git] / client / config_init.c
1 /*
2  ******************************************************************************
3  *
4  * File:    config_init.c
5  *
6  * Author:  Damien Stuart
7  *
8  * Purpose: Command-line and config file processing for fwknop client.
9  *
10  * Copyright 2009-2010 Damien Stuart (dstuart@dstuart.org)
11  *
12  *  License (GNU Public License):
13  *
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.
18  *
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.
23  *
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
27  *  USA
28  *
29  ******************************************************************************
30 */
31 #include "fwknop_common.h"
32 #include "config_init.h"
33 #include "cmd_opts.h"
34 #include "utils.h"
35
36 /* Convert a digest_type string to its integer value.
37 */
38 static int
39 digest_strtoint(const char *dt_str)
40 {
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);
51     else
52         return(-1);
53 }
54
55 /* Convert a protocol string to its intger value.
56 */
57 static int
58 proto_strtoint(const char *pr_str)
59 {
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);
70     else
71         return(-1);
72 }
73
74 /* Parse any time offset from the command line
75 */
76 static int
77 parse_time_offset(const char *offset_str)
78 {
79     int i, j;
80     int offset      = 0;
81     int offset_type = TIME_OFFSET_SECONDS;
82     int os_len      = strlen(offset_str);
83
84     char offset_digits[MAX_TIME_STR_LEN];
85
86     j=0;
87     for (i=0; i < os_len; i++) {
88         if (isdigit(offset_str[i])) {
89             offset_digits[j] = offset_str[i];
90             j++;
91         } else if (offset_str[i] == 'm' || offset_str[i] == 'M') {
92             offset_type = TIME_OFFSET_MINUTES;
93             break;
94         } else if (offset_str[i] == 'h' || offset_str[i] == 'H') {
95             offset_type = TIME_OFFSET_HOURS;
96             break;
97         } else if (offset_str[i] == 'd' || offset_str[i] == 'D') {
98             offset_type = TIME_OFFSET_DAYS;
99             break;
100         }
101     }
102
103     offset_digits[j] = '\0';
104
105     if (j < 1) {
106         fprintf(stderr, "Invalid time offset: %s", offset_str);
107         exit(EXIT_FAILURE);
108     }
109
110     offset = atoi(offset_digits);
111
112     if (offset < 0) {
113         fprintf(stderr, "Invalid time offset: %s", offset_str);
114         exit(EXIT_FAILURE);
115     }
116
117     /* Apply the offset_type value
118     */
119     offset *= offset_type;
120
121     return offset;
122 }
123
124 static int
125 create_fwknoprc(const char *rcfile)
126 {
127     FILE    *rc;
128
129     fprintf(stderr, "Creating initial rc file: %s.\n", rcfile);
130
131     if ((rc = fopen(rcfile, "w")) == NULL)
132     {
133         fprintf(stderr, "Unable to create rc file: %s: %s\n",
134             rcfile, strerror(errno));
135         return(-1);
136     }
137
138     fprintf(rc,
139         "# .fwknoprc\n"
140         "##############################################################################\n"
141         "#\n"
142         "# Firewall Knock Operator (fwknop) client rc file.\n"
143         "#\n"
144         "# This file contains user-specific fwknop client configuration default\n"
145         "# and named parameter sets for specific invocations of the fwknop client.\n"
146         "#\n"
147         "# Each section (or stanza) is identified and started by a line in this\n"
148         "# file that contains a single identifier surrounded by square brackets.\n"
149         "#\n"
150         "# The parameters within the stanza typicaly match corresponding client \n"
151         "# command-line parameters.\n"
152         "#\n"
153         "# The first one should always be `[default]' as it defines the global\n"
154         "# default settings for the user. These override the program defaults\n"
155         "# for these parameter.  If a named stanza is used, its entries will\n"
156         "# override any of the default.  Command-line options will trump them\n"
157         "# all.\n"
158         "#\n"
159         "# Subsequent stanzas will have only the overriding and destination\n"
160         "# specific parameters.\n"
161         "#\n"
162         "# Lines starting with `#' and empty lines are ignored.\n"
163         "#\n"
164         "# See the fwknop.8 man page for a complete list of valid parameters\n"
165         "# and their values.\n"
166         "#\n"
167         "##############################################################################\n"
168         "#\n"
169         "# We start with the 'default' stanza.  Uncomment and edit for your\n"
170         "# preferences.  The client will use its build-in default for those items\n"
171         "# that are commented out.\n"
172         "#\n"
173         "[default]\n"
174         "\n"
175         "#DIGEST_TYPE         sha256\n"
176         "#FW_TIMEOUT          30\n"
177         "#SPA_SERVER_PORT     62201\n"
178         "#SPA_SERVER_PROTO    udp\n"
179         "#ALLOW_IP            <ip addr>\n"
180         "#SPOOF_USER          <username>\n"
181         "#SPOOF_SOURCE_IP     <IPaddr>\n"
182         "#TIME_OFFSET         0\n"
183         "#USE_GPG             N\n"
184         "#GPG_HOMEDIR         /path/to/.gnupg\n"
185         "#GPG_SIGNER          <signer ID>\n"
186         "#GPG_RECIPIENT       <recipient ID>\n"
187         "\n"
188         "# User-provided named stanzas:\n"
189         "\n"
190         "# Example for a destination server of 192.168.1.20 to open access to \n"
191         "# SSH for an IP that is resoved exteranlly, and one with a NAT request\n"
192         "# for a specific source IP that maps port 8088 on the server\n"
193         "# to port 88 on 192.168.1.55 with timeout.\n"
194         "#\n"
195         "#[myssh]\n"
196         "#SPA_SERVER          192.168.1.20\n"
197         "#ACCESS              tcp/22\n"
198         "#ALLOW_IP            resolve\n"
199         "#\n"
200         "#[mynatreq]\n"
201         "#SPA_SERVER          192.168.1.20\n"
202         "#ACCESS              tcp/8088\n"
203         "#ALLOW_IP            10.21.2.6\n"
204         "#NAT_ACCESS          192.168.1.55,88\n"
205         "#CLIENT_TIMEOUT      60\n"
206         "#\n"
207         "\n"
208         "###EOF###\n"
209     );
210
211     fclose(rc);
212
213     return(0);
214 }
215
216 static int
217 parse_rc_param(fko_cli_options_t *options, const char *var, char * val)
218 {
219     int     tmpint;
220
221     /* Digest Type */
222     if(CONF_VAR_IS(var, "DIGEST_TYPE"))
223     {
224         tmpint = digest_strtoint(val);
225         if(tmpint < 0)
226             return(-1);
227         else
228             options->digest_type = tmpint;
229     }
230     /* Server protocol */
231     else if(CONF_VAR_IS(var, "SPA_SERVER_PROTO"))
232     {
233         tmpint = proto_strtoint(val);
234         if(tmpint < 0)
235             return(-1);
236         else
237             options->spa_proto = tmpint;
238     }
239     /* Server port */
240     else if(CONF_VAR_IS(var, "SPA_SERVER_PORT"))
241     {
242         tmpint = atoi(val);
243         if(tmpint < 0 || tmpint > MAX_PORT)
244             return(-1);
245         else
246             options->spa_dst_port = tmpint;
247     }
248     /* Source port */
249     else if(CONF_VAR_IS(var, "SPA_SOURCE_PORT"))
250     {
251         tmpint = atoi(val);
252         if(tmpint < 0 || tmpint > MAX_PORT)
253             return(-1);
254         else
255             options->spa_src_port = tmpint;
256     }
257     /* Firewall rule timeout */
258     else if(CONF_VAR_IS(var, "FW_TIMEOUT"))
259     {
260         tmpint = atoi(val);
261         if(tmpint < 0)
262             return(-1);
263         else
264             options->fw_timeout = tmpint;
265     }
266     /* Allow IP */
267     else if(CONF_VAR_IS(var, "ALLOW_IP"))
268     {
269         /* In case this was set previously
270         */
271         options->resolve_ip_http = 0;
272
273         /* use source, resolve, or an actual IP
274         */
275         if(strcasecmp(val, "source") == 0)
276             strlcpy(options->allow_ip_str, "0.0.0.0", 8);
277         else if(strcasecmp(val, "resolve") == 0)
278             options->resolve_ip_http = 1;
279         else /* Assume IP address */
280             strlcpy(options->allow_ip_str, val, MAX_IPV4_STR_LEN);
281     }
282     /* Time Offset */
283     else if(CONF_VAR_IS(var, "TIME_OFFSET"))
284     {
285         if(val[0] == '-')
286         {
287             val++;
288             options->time_offset_minus = parse_time_offset(val);
289         }
290         else
291             options->time_offset_plus = parse_time_offset(val);
292     }
293     /* Use GPG ? */
294     else if(CONF_VAR_IS(var, "USE_GPG"))
295     {
296         if(val[0] == 'y' || val[0] == 'Y')
297             options->use_gpg = 1;
298     }
299     /* GPG Recipient */
300     else if(CONF_VAR_IS(var, "GPG_RECIPIENT"))
301     {
302         strlcpy(options->gpg_recipient_key, val, MAX_GPG_KEY_ID);
303     }
304     /* GPG Signer */
305     else if(CONF_VAR_IS(var, "GPG_SIGNER"))
306     {
307         strlcpy(options->gpg_signer_key, val, MAX_GPG_KEY_ID);
308     }
309     /* GPG Homedir */
310     else if(CONF_VAR_IS(var, "GPG_HOMEDIR"))
311     {
312         strlcpy(options->gpg_home_dir, val, MAX_PATH_LEN);
313     }
314     /* Spoof User */
315     else if(CONF_VAR_IS(var, "SPOOF_USER"))
316     {
317         strlcpy(options->spoof_user, val, MAX_USERNAME_LEN);
318     }
319     /* Spoof Source IP */
320     else if(CONF_VAR_IS(var, "SPOOF_SOURCE_IP"))
321     {
322         strlcpy(options->spoof_ip_src_str, val, MAX_IPV4_STR_LEN);
323     }
324     /* ACCESS request */
325     else if(CONF_VAR_IS(var, "ACCESS"))
326     {
327         strlcpy(options->access_str, val, MAX_LINE_LEN);
328     }
329     /* SPA Server (destination) */
330     else if(CONF_VAR_IS(var, "SPA_SERVER"))
331     {
332         strlcpy(options->spa_server_str, val, MAX_SERVER_STR_LEN);
333     }
334     /* Rand port ? */
335     else if(CONF_VAR_IS(var, "RAND_PORT"))
336     {
337         if(val[0] == 'y' || val[0] == 'Y')
338             options->rand_port = 1;
339     }
340     /* Key file */
341     else if(CONF_VAR_IS(var, "KEY_FILE"))
342     {
343         strlcpy(options->get_key_file, val, MAX_PATH_LEN);
344     }
345     /* NAT Access Request */
346     else if(CONF_VAR_IS(var, "NAT_ACCESS"))
347     {
348         strlcpy(options->nat_access_str, val, MAX_PATH_LEN);
349     }
350     /* HTTP User Agent */
351     else if(CONF_VAR_IS(var, "HTTP_USER_AGENT"))
352     {
353         strlcpy(options->http_user_agent, val, HTTP_MAX_USER_AGENT_LEN);
354     }
355     /* Resolve URL */
356     else if(CONF_VAR_IS(var, "RESOLVE_URL"))
357     {
358         if(options->resolve_url != NULL)
359             free(options->resolve_url);
360         tmpint = strlen(val)+1;
361         options->resolve_url = malloc(tmpint);
362         if(options->resolve_url == NULL)
363         {
364             fprintf(stderr, "Memory allocation error for resolve URL.\n");
365             exit(EXIT_FAILURE);
366         }
367         strlcpy(options->resolve_url, val, tmpint);
368     }
369     /* NAT Local ? */
370     else if(CONF_VAR_IS(var, "NAT_LOCAL"))
371     {
372         if(val[0] == 'y' || val[0] == 'Y')
373             options->nat_local = 1;
374     }
375     /* NAT rand port ? */
376     else if(CONF_VAR_IS(var, "NAT_RAND_PORT"))
377     {
378         if(val[0] == 'y' || val[0] == 'Y')
379             options->nat_rand_port = 1;
380     }
381     /* NAT port */
382     else if(CONF_VAR_IS(var, "NAT_PORT"))
383     {
384         tmpint = atoi(val);
385         if(tmpint < 0 || tmpint > MAX_PORT)
386             return(-1);
387         else
388             options->nat_port = tmpint;
389     }
390
391     return(0);
392 }
393
394 /* Process (create if necessary) the users ~/.fwknoprc file.
395 */
396 static void
397 process_rc(fko_cli_options_t *options)
398 {
399     FILE    *rc;
400     int     line_num = 0;
401     int     rcf_offset;
402     char    line[MAX_LINE_LEN];
403     char    rcfile[MAX_PATH_LEN];
404     char    curr_stanza[MAX_LINE_LEN] = {0};
405     char    var[MAX_LINE_LEN]  = {0};
406     char    val[MAX_LINE_LEN]  = {0};
407
408     char    *ndx, *emark, *homedir;
409
410 #ifdef WIN32
411     homedir = getenv("USERPROFILE");
412 #else
413     homedir = getenv("HOME");
414 #endif
415
416     if(homedir == NULL)
417     {
418         fprintf(stderr, "Warning: Unable to determine HOME directory.\n"
419             " No .fwknoprc file processed.\n");
420         return;
421     }
422
423     memset(rcfile, 0x0, MAX_PATH_LEN);
424
425     strlcpy(rcfile, homedir, MAX_PATH_LEN);
426
427     rcf_offset = strlen(rcfile);
428
429     /* Sanity check the path to .fwknoprc.
430      * The preceeding path plus the path separator and '.fwknoprc' = 11
431      * cannot exceed MAX_PATH_LEN.
432     */
433     if(rcf_offset > (MAX_PATH_LEN - 11))
434     {
435         fprintf(stderr, "Warning: Path to .fwknoprc file is too long.\n"
436             " No .fwknoprc file processed.\n");
437         return;
438     }
439
440     rcfile[rcf_offset] = PATH_SEP;
441     strlcat(rcfile, ".fwknoprc", MAX_PATH_LEN);
442
443     /* Open the rc file for reading, if it does not exist, then create
444      * an initial .fwknoprc file with defaults and go on.
445     */
446     if ((rc = fopen(rcfile, "r")) == NULL)
447     {
448         if(errno == ENOENT)
449         {
450             if(create_fwknoprc(rcfile) != 0)
451                 return;
452         }
453         else
454             fprintf(stderr, "Unable to open rc file: %s: %s\n",
455                 rcfile, strerror(errno));
456
457         return;
458     }
459
460     /* Read in and parse the rc file parameters.
461     */
462     while ((fgets(line, MAX_LINE_LEN, rc)) != NULL)
463     {
464         line_num++;
465         line[MAX_LINE_LEN-1] = '\0';
466
467         ndx = line;
468
469         /* Skip any leading whitespace.
470         */
471         while(isspace(*ndx))
472             ndx++;
473
474         /* Get past comments and empty lines (note: we only look at the
475          * first character.
476         */
477         if(IS_EMPTY_LINE(line[0]))
478             continue;
479
480         if(*ndx == '[')
481         {
482             ndx++;
483             emark = strchr(ndx, ']');
484             if(emark == NULL)
485             {
486                 fprintf(stderr, "Unterminated stanza line: '%s'.  Skipping.\n",
487                     line);
488                 continue;
489             }
490
491             *emark = '\0';
492
493             strlcpy(curr_stanza, ndx, MAX_LINE_LEN);
494
495             if(options->verbose > 3)
496                 fprintf(stderr,
497                     "RC FILE: %s, LINE: %s\tSTANZA: %s:\n",
498                     rcfile, line, curr_stanza
499                 );
500
501             continue;
502         }
503
504         if(sscanf(line, "%s %[^;\n\r#]", var, val) != 2)
505         {
506             fprintf(stderr,
507                 "*Invalid entry in %s at line %i.\n - '%s'",
508                 rcfile, line_num, line
509             );
510             continue;
511         }
512
513         /* Remove any colon that may be on the end of the var
514         */
515         if((ndx = strrchr(var, ':')) != NULL)
516             *ndx = '\0';
517
518         if(options->verbose > 3)
519             fprintf(stderr,
520                 "RC FILE: %s, LINE: %s\tVar: %s, Val: '%s'\n",
521                 rcfile, line, var, val
522             );
523
524         /* We do not proceed with parsing until we know we are in
525          * a stanza.
526         */
527         if(strlen(curr_stanza) < 1)
528             continue;
529
530         /* Process the values. We assume we will see the default stanza
531          * first, then if a named-stanza is specified, we process its
532          * entries as well.
533         */
534         if(strcasecmp(curr_stanza, "default") == 0)
535         {
536             if(parse_rc_param(options, var, val) < 0)
537                 fprintf(stderr, "Parameter error in %s, line %i: var=%s, val=%s\n",
538                     rcfile, line_num, var, val);
539         }
540         else if(options->use_rc_stanza[0] != '\0'
541           && strncasecmp(curr_stanza, options->use_rc_stanza, MAX_LINE_LEN)==0)
542         {
543             options->got_named_stanza = 1;
544             if(parse_rc_param(options, var, val) < 0)
545                 fprintf(stderr,
546                     "Parameter error in %s, stanza: %s, line %i: var=%s, val=%s\n",
547                     rcfile, curr_stanza, line_num, var, val);
548         }
549
550     } /* end while fgets rc */
551     fclose(rc);
552 }
553
554 /* Sanity and bounds checks for the various options.
555 */
556 static void
557 validate_options(fko_cli_options_t *options)
558 {
559     /* Gotta have a Destination unless we are just testing or getting the
560      * the version, and must use one of [-s|-R|-a].
561     */
562     if(!options->test
563         && !options->version
564         && !options->show_last_command
565         && !options->run_last_command)
566     {
567         if(options->use_rc_stanza[0] != 0x0 && options->got_named_stanza == 0)
568         {
569             fprintf(stderr, "Named configuration stanza: [%s] was not found.\n",
570                 options->use_rc_stanza);
571
572             exit(EXIT_FAILURE);
573         }
574
575         if (options->spa_server_str[0] == 0x0)
576         {
577             fprintf(stderr,
578                 "Must use --destination unless --test mode is used\n");
579             exit(EXIT_FAILURE);
580         }
581
582         if (options->resolve_url != NULL)
583             options->resolve_ip_http = 1;
584
585         if (!options->resolve_ip_http && options->allow_ip_str[0] == 0x0)
586         {
587             fprintf(stderr,
588                 "Must use one of [-s|-R|-a] to specify IP for SPA access.\n");
589             exit(EXIT_FAILURE);
590         }
591     }
592
593     if(options->resolve_ip_http || options->spa_proto == FKO_PROTO_HTTP)
594         if (options->http_user_agent[0] == '\0')
595             snprintf(options->http_user_agent, HTTP_MAX_USER_AGENT_LEN,
596                 "%s%s", "Fwknop/", MY_VERSION);
597
598     if(options->http_proxy[0] != 0x0 && options->spa_proto != FKO_PROTO_HTTP)
599     {
600         fprintf(stderr,
601             "Cannot set --http-proxy with a non-HTTP protocol.\n");
602         exit(EXIT_FAILURE);
603     }
604
605     /* If we are using gpg, we must at least have the recipient set.
606     */
607     if(options->use_gpg)
608     {
609         if(options->gpg_recipient_key == NULL
610             || strlen(options->gpg_recipient_key) == 0)
611         {
612             fprintf(stderr,
613                 "Must specify --gpg-recipient-key when GPG is used.\n");
614             exit(EXIT_FAILURE);
615         }
616     }
617
618     return;
619 }
620
621 /* Establish a few defaults such as UDP/62201 for sending the SPA
622  * packet (can be changed with --server-proto/--server-port)
623 */
624 static void
625 set_defaults(fko_cli_options_t *options)
626 {
627     options->spa_proto    = FKO_DEFAULT_PROTO;
628     options->spa_dst_port = FKO_DEFAULT_PORT;
629     options->fw_timeout   = -1;
630
631     return;
632 }
633
634 /* Initialize program configuration via config file and/or command-line
635  * switches.
636 */
637 void
638 config_init(fko_cli_options_t *options, int argc, char **argv)
639 {
640     int                 cmd_arg, index;
641
642     /* Zero out options and opts_track.
643     */
644     memset(options, 0x00, sizeof(fko_cli_options_t));
645
646     /* Make sure a few reasonable defaults are set
647     */
648     set_defaults(options);
649
650     /* First pass over cmd_line args to see if a named-stanza in the 
651      * rc file is used.
652     */
653     while ((cmd_arg = getopt_long(argc, argv,
654             GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
655         switch(cmd_arg) {
656             case 'h':
657                 usage();
658                 exit(EXIT_SUCCESS);
659             case 'n':
660                 options->no_save_args = 1;
661                 strlcpy(options->use_rc_stanza, optarg, MAX_LINE_LEN);
662                 break;
663             case 'v':
664                 options->verbose++;
665                 break;
666         }
667     }
668
669     /* First process the .fwknoprc file.
670     */
671     process_rc(options);
672
673     /* Reset the options index so we can run through them again.
674     */
675     optind = 0;
676
677     while ((cmd_arg = getopt_long(argc, argv,
678             GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
679
680         switch(cmd_arg) {
681             case 'a':
682                 strlcpy(options->allow_ip_str, optarg, MAX_IPV4_STR_LEN);
683                 break;
684             case 'A':
685                 strlcpy(options->access_str, optarg, MAX_LINE_LEN);
686                 break;
687             case 'b':
688                 options->save_packet_file_append = 1;
689                 break;
690             case 'B':
691                 strlcpy(options->save_packet_file, optarg, MAX_PATH_LEN);
692                 break;
693             case 'C':
694                 strlcpy(options->server_command, optarg, MAX_LINE_LEN);
695                 break;
696             case 'D':
697                 strlcpy(options->spa_server_str, optarg, MAX_SERVER_STR_LEN);
698                 break;
699             case 'f':
700                 options->fw_timeout = atoi(optarg);
701                 if (options->fw_timeout < 0) {
702                     fprintf(stderr, "--fw-timeout must be >= 0\n");
703                     exit(EXIT_FAILURE);
704                 }
705                 break;
706             case 'g':
707             case GPG_ENCRYPTION:
708                 options->use_gpg = 1;
709                 break;
710             case 'G':
711                 strlcpy(options->get_key_file, optarg, MAX_PATH_LEN);
712                 break;
713             case 'h':
714                 usage();
715                 exit(EXIT_SUCCESS);
716             case 'H':
717                 options->spa_proto = FKO_PROTO_HTTP;
718                 strlcpy(options->http_proxy, optarg, MAX_PATH_LEN);
719                 break;
720             case 'l':
721                 options->run_last_command = 1;
722                 break;
723             case 'm':
724             case FKO_DIGEST_NAME:
725                 if((options->digest_type = digest_strtoint(optarg)) < 0)
726                 {
727                     fprintf(stderr, "* Invalid digest type: %s\n", optarg);
728                     exit(EXIT_FAILURE);
729                 }
730                 break;
731             case NO_SAVE_ARGS:
732                 options->no_save_args = 1;
733                 break;
734             case 'n':
735                 /* We already handled this earlier, so we do nothing here
736                 */
737                 break;
738             case 'N':
739                 strlcpy(options->nat_access_str, optarg, MAX_LINE_LEN);
740                 break;
741             case 'p':
742                 options->spa_dst_port = atoi(optarg);
743                 if (options->spa_dst_port < 0 || options->spa_dst_port > MAX_PORT)
744                 {
745                     fprintf(stderr, "Unrecognized port: %s\n", optarg);
746                     exit(EXIT_FAILURE);
747                 }
748                 break;
749             case 'P':
750                 if((options->spa_proto = proto_strtoint(optarg)) < 0)
751                 {
752                     fprintf(stderr, "Unrecognized protocol: %s\n", optarg);
753                     exit(EXIT_FAILURE);
754                 }
755                 break;
756             case 'Q':
757                 strlcpy(options->spoof_ip_src_str, optarg, MAX_IPV4_STR_LEN);
758                 break;
759             case 'r':
760                 options->rand_port = 1;
761                 break;
762             case 'R':
763                 options->resolve_ip_http = 1;
764                 break;
765             case RESOLVE_URL:
766                 if(options->resolve_url != NULL)
767                     free(options->resolve_url);
768                 options->resolve_url = malloc(strlen(optarg)+1);
769                 if(options->resolve_url == NULL)
770                 {
771                     fprintf(stderr, "Memory allocation error for resolve URL.\n");
772                     exit(EXIT_FAILURE);
773                 }
774                 strlcpy(options->resolve_url, optarg, strlen(optarg)+1);
775                 break;
776             case SHOW_LAST_ARGS:
777                 options->show_last_command = 1;
778                 break;
779             case 's':
780                 strlcpy(options->allow_ip_str, "0.0.0.0", MAX_IPV4_STR_LEN);
781                 break;
782             case 'S':
783                 options->spa_src_port = atoi(optarg);
784                 if (options->spa_src_port < 0 || options->spa_src_port > MAX_PORT)
785                 {
786                     fprintf(stderr, "Unrecognized port: %s\n", optarg);
787                     exit(EXIT_FAILURE);
788                 }
789                 break;
790             case 'T':
791                 options->test = 1;
792                 break;
793             case 'u':
794                 strlcpy(options->http_user_agent, optarg, HTTP_MAX_USER_AGENT_LEN);
795                 break;
796             case 'U':
797                 strlcpy(options->spoof_user, optarg, MAX_USERNAME_LEN);
798                 break;
799             case 'v':
800                 /* Handled earlier.
801                 */
802                 break;
803             case 'V':
804                 options->version = 1;
805                 break;
806             case GPG_RECIP_KEY:
807                 options->use_gpg = 1;
808                 strlcpy(options->gpg_recipient_key, optarg, MAX_GPG_KEY_ID);
809                 break;
810             case GPG_SIGNER_KEY:
811                 options->use_gpg = 1;
812                 strlcpy(options->gpg_signer_key, optarg, MAX_GPG_KEY_ID);
813                 break;
814             case GPG_HOME_DIR:
815                 options->use_gpg = 1;
816                 strlcpy(options->gpg_home_dir, optarg, MAX_PATH_LEN);
817                 break;
818             case GPG_AGENT:
819                 options->use_gpg = 1;
820                 options->use_gpg_agent = 1;
821                 break;
822             case NAT_LOCAL:
823                 options->nat_local = 1;
824                 break;
825             case NAT_RAND_PORT:
826                 options->nat_rand_port = 1;
827                 break;
828             case NAT_PORT:
829                 options->nat_port = atoi(optarg);
830                 if (options->nat_port < 0 || options->nat_port > MAX_PORT)
831                 {
832                     fprintf(stderr, "Unrecognized port: %s\n", optarg);
833                     exit(EXIT_FAILURE);
834                 }
835                 break;
836             case TIME_OFFSET_PLUS:
837                 options->time_offset_plus = parse_time_offset(optarg);
838                 break;
839             case TIME_OFFSET_MINUS:
840                 options->time_offset_minus = parse_time_offset(optarg);
841                 break;
842             default:
843                 usage();
844                 exit(EXIT_FAILURE);
845         }
846     }
847
848     /* Now that we have all of our options set, we can validate them.
849     */
850     validate_options(options);
851
852     return;
853 }
854
855 /* Print usage message...
856 */
857 void
858 usage(void)
859 {
860     fprintf(stdout, "\n%s client version %s\n%s\n\n", MY_NAME, MY_VERSION, MY_DESC);
861     fprintf(stdout,
862       "Usage: fwknop -A <port list> [-s|-R|-a] -D <spa_server> [options]\n\n"
863       " -h, --help                  Print this usage message and exit.\n"
864       " -A, --access                Provide a list of ports/protocols to open\n"
865       "                             on the server.\n"
866       " -B, --save-packet           Save the generated packet data to the\n"
867       "                             specified file.\n"
868       " -a, --allow-ip              Specify IP address to allow within the SPA\n"
869       "                             packet.\n"
870       " -C, --server-cmd            Specify a command that the fwknop server will\n"
871       "                             execute on behalf of the fwknop client..\n"
872       " -D, --destination           Specify the IP address of the fwknop server.\n"
873       " -n, --named-config          Specify an named configuration stanza in the\n"
874       "                             '$HOME/.fwknoprc' file to provide some of all\n"
875       "                             of the configuration parameters.\n"
876       " -N, --nat-access            Gain NAT access to an internal service\n"
877       "                             protected by the fwknop server.\n"
878       " -p, --server-port           Set the destination port for outgoing SPA\n"
879       "                             packet.\n"
880       " -P, --server-proto          Set the protocol (udp, tcp, http, tcpraw,\n"
881       "                             icmp) for the outgoing SPA packet.\n"
882       "                             Note: The 'tcpraw' and 'icmp' modes use raw\n"
883       "                             sockets and thus require root access to use.\n"
884       " -s, --source-ip             Tell the fwknopd server to accept whatever\n"
885       "                             source IP the SPA packet has as the IP that\n"
886       "                             needs access (not recommended, and the\n"
887       "                             fwknopd server can ignore such requests).\n"
888       " -S, --source-port           Set the source port for outgoing SPA packet.\n"
889       " -Q, --spoof-source          Set the source IP for outgoing SPA packet.\n"
890       " -R, --resolve-ip-http       Resolve the external network IP by\n"
891       "                             connecting to a URL such as the default of:\n"
892       "                             http://" HTTP_RESOLVE_HOST HTTP_RESOLVE_URL "\n"
893       "                             This can be overridden with the --resolve-url\n"
894       "                             option.\n"
895       "     --resolve-url           Override the default URL used for resolving\n"
896       "                             the source IP address.\n"
897       " -u, --user-agent            Set the HTTP User-Agent for resolving the\n"
898       "                             external IP via -R, or for sending SPA\n"
899       "                             packets over HTTP.\n"
900       " -H, --http-proxy            Specify an HTTP proxy host through which the\n"
901       "                             SPA packet will be sent.  The port can also be\n"
902       "                             specified here by following the host/ip with\n"
903       "                             \":<port>\".\n"
904       " -U, --spoof-user            Set the username within outgoing SPA packet.\n"
905       " -l, --last-cmd              Run the fwknop client with the same command\n"
906       "                             line args as the last time it was executed\n"
907       "                             (args are read from the ~/.fwknop.run file).\n"
908       " -G, --get-key               Load an encryption key/password from a file.\n"
909       " -r, --rand-port             Send the SPA packet over a randomly assigned\n"
910       "                             port (requires a broader pcap filter on the\n"
911       "                             server side than the default of udp 62201).\n"
912       " -T, --test                  Build the SPA packet but do not send it over\n"
913       "                             the network.\n"
914       " -v, --verbose               Set verbose mode.\n"
915       " -V, --version               Print version number.\n"
916       " -m, --digest-type           Specify the message digest algorithm to use.\n"
917       "                             (md5, sha1, or sha256 (default)).\n"
918       " -f, --fw-timeout            Specify SPA server firewall timeout from the\n"
919       "                             client side.\n"
920       "     --gpg-encryption        Use GPG encryption (default is Rijndael).\n"
921       "     --gpg-recipient-key     Specify the recipient GPG key name or ID.\n"
922       "     --gpg-signer-key        Specify the signer's GPG key name or ID.\n"
923       "     --gpg-home-dir          Specify the GPG home directory.\n"
924       "     --gpg-agent             Use GPG agent if available.\n"
925       "     --no-save-args          Do not save fwknop command line args to the\n"
926       "                             $HOME/fwknop.run file\n"
927       "     --nat-local             Access a local service via a forwarded port\n"
928       "                             on the fwknopd server system.\n"
929       "     --nat-port              Specify the port to forward to access a\n"
930       "                             service via NAT.\n"
931       "     --nat-rand-port         Have the fwknop client assign a random port\n"
932       "                             for NAT access.\n"
933       "     --show-last             Show the last fwknop command line arguments.\n"
934       "     --time-offset-plus      Add time to outgoing SPA packet timestamp.\n"
935       "     --time-offset-minus     Subtract time from outgoing SPA packet\n"
936       "                             timestamp.\n"
937       "\n"
938     );
939
940     return;
941 }
942
943 /***EOF***/