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