implemented a couple of minor stronger bounds checks
[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             if(j >= MAX_TIME_STR_LEN)
92             {
93                 fprintf(stderr, "Invalid time offset: %s", offset_str);
94                 exit(EXIT_FAILURE);
95             }
96         } else if (offset_str[i] == 'm' || offset_str[i] == 'M') {
97             offset_type = TIME_OFFSET_MINUTES;
98             break;
99         } else if (offset_str[i] == 'h' || offset_str[i] == 'H') {
100             offset_type = TIME_OFFSET_HOURS;
101             break;
102         } else if (offset_str[i] == 'd' || offset_str[i] == 'D') {
103             offset_type = TIME_OFFSET_DAYS;
104             break;
105         }
106     }
107
108     offset_digits[j] = '\0';
109
110     if (j < 1) {
111         fprintf(stderr, "Invalid time offset: %s", offset_str);
112         exit(EXIT_FAILURE);
113     }
114
115     offset = atoi(offset_digits);
116
117     if (offset < 0) {
118         fprintf(stderr, "Invalid time offset: %s", offset_str);
119         exit(EXIT_FAILURE);
120     }
121
122     /* Apply the offset_type value
123     */
124     offset *= offset_type;
125
126     return offset;
127 }
128
129 static int
130 create_fwknoprc(const char *rcfile)
131 {
132     FILE    *rc = NULL;
133
134     fprintf(stdout, "[*] Creating initial rc file: %s.\n", rcfile);
135
136     if ((rc = fopen(rcfile, "w")) == NULL)
137     {
138         fprintf(stderr, "Unable to create rc file: %s: %s\n",
139             rcfile, strerror(errno));
140         return(-1);
141     }
142
143     fprintf(rc,
144         "# .fwknoprc\n"
145         "##############################################################################\n"
146         "#\n"
147         "# Firewall Knock Operator (fwknop) client rc file.\n"
148         "#\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"
151         "#\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"
154         "#\n"
155         "# The parameters within the stanza typicaly match corresponding client \n"
156         "# command-line parameters.\n"
157         "#\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"
162         "# all.\n"
163         "#\n"
164         "# Subsequent stanzas will have only the overriding and destination\n"
165         "# specific parameters.\n"
166         "#\n"
167         "# Lines starting with `#' and empty lines are ignored.\n"
168         "#\n"
169         "# See the fwknop.8 man page for a complete list of valid parameters\n"
170         "# and their values.\n"
171         "#\n"
172         "##############################################################################\n"
173         "#\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"
177         "#\n"
178         "[default]\n"
179         "\n"
180         "#DIGEST_TYPE         sha256\n"
181         "#FW_TIMEOUT          30\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"
187         "#TIME_OFFSET         0\n"
188         "#USE_GPG             N\n"
189         "#GPG_HOMEDIR         /path/to/.gnupg\n"
190         "#GPG_SIGNER          <signer ID>\n"
191         "#GPG_RECIPIENT       <recipient ID>\n"
192         "\n"
193         "# User-provided named stanzas:\n"
194         "\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"
199         "#\n"
200         "#[myssh]\n"
201         "#SPA_SERVER          192.168.1.20\n"
202         "#ACCESS              tcp/22\n"
203         "#ALLOW_IP            resolve\n"
204         "#\n"
205         "#[mynatreq]\n"
206         "#SPA_SERVER          192.168.1.20\n"
207         "#ACCESS              tcp/8088\n"
208         "#ALLOW_IP            10.21.2.6\n"
209         "#NAT_ACCESS          192.168.1.55,88\n"
210         "#CLIENT_TIMEOUT      60\n"
211         "#\n"
212         "\n"
213         "###EOF###\n"
214     );
215
216     fclose(rc);
217
218     set_file_perms(rcfile);
219
220     return(0);
221 }
222
223 static int
224 parse_rc_param(fko_cli_options_t *options, const char *var, char * val)
225 {
226     int     tmpint;
227
228     /* Digest Type */
229     if(CONF_VAR_IS(var, "DIGEST_TYPE"))
230     {
231         tmpint = digest_strtoint(val);
232         if(tmpint < 0)
233             return(-1);
234         else
235             options->digest_type = tmpint;
236     }
237     /* Server protocol */
238     else if(CONF_VAR_IS(var, "SPA_SERVER_PROTO"))
239     {
240         tmpint = proto_strtoint(val);
241         if(tmpint < 0)
242             return(-1);
243         else
244             options->spa_proto = tmpint;
245     }
246     /* Server port */
247     else if(CONF_VAR_IS(var, "SPA_SERVER_PORT"))
248     {
249         tmpint = atoi(val);
250         if(tmpint < 0 || tmpint > MAX_PORT)
251             return(-1);
252         else
253             options->spa_dst_port = tmpint;
254     }
255     /* Source port */
256     else if(CONF_VAR_IS(var, "SPA_SOURCE_PORT"))
257     {
258         tmpint = atoi(val);
259         if(tmpint < 0 || tmpint > MAX_PORT)
260             return(-1);
261         else
262             options->spa_src_port = tmpint;
263     }
264     /* Firewall rule timeout */
265     else if(CONF_VAR_IS(var, "FW_TIMEOUT"))
266     {
267         tmpint = atoi(val);
268         if(tmpint < 0)
269             return(-1);
270         else
271             options->fw_timeout = tmpint;
272     }
273     /* Allow IP */
274     else if(CONF_VAR_IS(var, "ALLOW_IP"))
275     {
276         /* In case this was set previously
277         */
278         options->resolve_ip_http = 0;
279
280         /* use source, resolve, or an actual IP
281         */
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);
288     }
289     /* Time Offset */
290     else if(CONF_VAR_IS(var, "TIME_OFFSET"))
291     {
292         if(val[0] == '-')
293         {
294             val++;
295             options->time_offset_minus = parse_time_offset(val);
296         }
297         else
298             options->time_offset_plus = parse_time_offset(val);
299     }
300     /* Use GPG ? */
301     else if(CONF_VAR_IS(var, "USE_GPG"))
302     {
303         if(val[0] == 'y' || val[0] == 'Y')
304             options->use_gpg = 1;
305     }
306     /* GPG Recipient */
307     else if(CONF_VAR_IS(var, "GPG_RECIPIENT"))
308     {
309         strlcpy(options->gpg_recipient_key, val, MAX_GPG_KEY_ID);
310     }
311     /* GPG Signer */
312     else if(CONF_VAR_IS(var, "GPG_SIGNER"))
313     {
314         strlcpy(options->gpg_signer_key, val, MAX_GPG_KEY_ID);
315     }
316     /* GPG Homedir */
317     else if(CONF_VAR_IS(var, "GPG_HOMEDIR"))
318     {
319         strlcpy(options->gpg_home_dir, val, MAX_PATH_LEN);
320     }
321     /* Spoof User */
322     else if(CONF_VAR_IS(var, "SPOOF_USER"))
323     {
324         strlcpy(options->spoof_user, val, MAX_USERNAME_LEN);
325     }
326     /* Spoof Source IP */
327     else if(CONF_VAR_IS(var, "SPOOF_SOURCE_IP"))
328     {
329         strlcpy(options->spoof_ip_src_str, val, MAX_IPV4_STR_LEN);
330     }
331     /* ACCESS request */
332     else if(CONF_VAR_IS(var, "ACCESS"))
333     {
334         strlcpy(options->access_str, val, MAX_LINE_LEN);
335     }
336     /* SPA Server (destination) */
337     else if(CONF_VAR_IS(var, "SPA_SERVER"))
338     {
339         strlcpy(options->spa_server_str, val, MAX_SERVER_STR_LEN);
340     }
341     /* Rand port ? */
342     else if(CONF_VAR_IS(var, "RAND_PORT"))
343     {
344         if(val[0] == 'y' || val[0] == 'Y')
345             options->rand_port = 1;
346     }
347     /* Key file */
348     else if(CONF_VAR_IS(var, "KEY_FILE"))
349     {
350         strlcpy(options->get_key_file, val, MAX_PATH_LEN);
351     }
352     /* NAT Access Request */
353     else if(CONF_VAR_IS(var, "NAT_ACCESS"))
354     {
355         strlcpy(options->nat_access_str, val, MAX_PATH_LEN);
356     }
357     /* HTTP User Agent */
358     else if(CONF_VAR_IS(var, "HTTP_USER_AGENT"))
359     {
360         strlcpy(options->http_user_agent, val, HTTP_MAX_USER_AGENT_LEN);
361     }
362     /* Resolve URL */
363     else if(CONF_VAR_IS(var, "RESOLVE_URL"))
364     {
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)
370         {
371             fprintf(stderr, "Memory allocation error for resolve URL.\n");
372             exit(EXIT_FAILURE);
373         }
374         strlcpy(options->resolve_url, val, tmpint);
375     }
376     /* NAT Local ? */
377     else if(CONF_VAR_IS(var, "NAT_LOCAL"))
378     {
379         if(val[0] == 'y' || val[0] == 'Y')
380             options->nat_local = 1;
381     }
382     /* NAT rand port ? */
383     else if(CONF_VAR_IS(var, "NAT_RAND_PORT"))
384     {
385         if(val[0] == 'y' || val[0] == 'Y')
386             options->nat_rand_port = 1;
387     }
388     /* NAT port */
389     else if(CONF_VAR_IS(var, "NAT_PORT"))
390     {
391         tmpint = atoi(val);
392         if(tmpint < 0 || tmpint > MAX_PORT)
393             return(-1);
394         else
395             options->nat_port = tmpint;
396     }
397
398     return(0);
399 }
400
401 /* Process (create if necessary) the users ~/.fwknoprc file.
402 */
403 static void
404 process_rc(fko_cli_options_t *options)
405 {
406     FILE    *rc;
407     int     line_num = 0;
408     int     rcf_offset;
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};
414
415     char    *ndx, *emark, *homedir;
416
417 #ifdef WIN32
418     homedir = getenv("USERPROFILE");
419 #else
420     homedir = getenv("HOME");
421 #endif
422
423     if(homedir == NULL)
424     {
425         fprintf(stderr, "Warning: Unable to determine HOME directory.\n"
426             " No .fwknoprc file processed.\n");
427         return;
428     }
429
430     memset(rcfile, 0x0, MAX_PATH_LEN);
431
432     strlcpy(rcfile, homedir, MAX_PATH_LEN);
433
434     rcf_offset = strlen(rcfile);
435
436     /* Sanity check the path to .fwknoprc.
437      * The preceeding path plus the path separator and '.fwknoprc' = 11
438      * cannot exceed MAX_PATH_LEN.
439     */
440     if(rcf_offset > (MAX_PATH_LEN - 11))
441     {
442         fprintf(stderr, "Warning: Path to .fwknoprc file is too long.\n"
443             " No .fwknoprc file processed.\n");
444         return;
445     }
446
447     rcfile[rcf_offset] = PATH_SEP;
448     strlcat(rcfile, ".fwknoprc", MAX_PATH_LEN);
449
450     /* Check rc file permissions - if anything other than user read/write,
451      * then don't process it.  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).
454     */
455     verify_file_perms_ownership(rcfile);
456
457     /* Open the rc file for reading, if it does not exist, then create
458      * an initial .fwknoprc file with defaults and go on.
459     */
460     if ((rc = fopen(rcfile, "r")) == NULL)
461     {
462         if(errno == ENOENT)
463         {
464             if(create_fwknoprc(rcfile) != 0)
465                 return;
466         }
467         else
468             fprintf(stderr, "Unable to open rc file: %s: %s\n",
469                 rcfile, strerror(errno));
470
471         return;
472     }
473
474     /* Read in and parse the rc file parameters.
475     */
476     while ((fgets(line, MAX_LINE_LEN, rc)) != NULL)
477     {
478         line_num++;
479         line[MAX_LINE_LEN-1] = '\0';
480
481         ndx = line;
482
483         /* Skip any leading whitespace.
484         */
485         while(isspace(*ndx))
486             ndx++;
487
488         /* Get past comments and empty lines (note: we only look at the
489          * first character.
490         */
491         if(IS_EMPTY_LINE(line[0]))
492             continue;
493
494         if(*ndx == '[')
495         {
496             ndx++;
497             emark = strchr(ndx, ']');
498             if(emark == NULL)
499             {
500                 fprintf(stderr, "Unterminated stanza line: '%s'.  Skipping.\n",
501                     line);
502                 continue;
503             }
504
505             *emark = '\0';
506
507             strlcpy(curr_stanza, ndx, MAX_LINE_LEN);
508
509             if(options->verbose > 3)
510                 fprintf(stderr,
511                     "RC FILE: %s, LINE: %s\tSTANZA: %s:\n",
512                     rcfile, line, curr_stanza
513                 );
514
515             continue;
516         }
517
518         if(sscanf(line, "%s %[^;\n\r#]", var, val) != 2)
519         {
520             fprintf(stderr,
521                 "*Invalid entry in %s at line %i.\n - '%s'",
522                 rcfile, line_num, line
523             );
524             continue;
525         }
526
527         /* Remove any colon that may be on the end of the var
528         */
529         if((ndx = strrchr(var, ':')) != NULL)
530             *ndx = '\0';
531
532         if(options->verbose > 3)
533             fprintf(stderr,
534                 "RC FILE: %s, LINE: %s\tVar: %s, Val: '%s'\n",
535                 rcfile, line, var, val
536             );
537
538         /* We do not proceed with parsing until we know we are in
539          * a stanza.
540         */
541         if(strlen(curr_stanza) < 1)
542             continue;
543
544         /* Process the values. We assume we will see the default stanza
545          * first, then if a named-stanza is specified, we process its
546          * entries as well.
547         */
548         if(strcasecmp(curr_stanza, "default") == 0)
549         {
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);
553         }
554         else if(options->use_rc_stanza[0] != '\0'
555           && strncasecmp(curr_stanza, options->use_rc_stanza, MAX_LINE_LEN)==0)
556         {
557             options->got_named_stanza = 1;
558             if(parse_rc_param(options, var, val) < 0)
559                 fprintf(stderr,
560                     "Parameter error in %s, stanza: %s, line %i: var=%s, val=%s\n",
561                     rcfile, curr_stanza, line_num, var, val);
562         }
563
564     } /* end while fgets rc */
565     fclose(rc);
566 }
567
568 /* Sanity and bounds checks for the various options.
569 */
570 static void
571 validate_options(fko_cli_options_t *options)
572 {
573     /* Gotta have a Destination unless we are just testing or getting the
574      * the version, and must use one of [-s|-R|-a].
575     */
576     if(!options->test
577         && !options->version
578         && !options->show_last_command
579         && !options->run_last_command)
580     {
581         if(options->use_rc_stanza[0] != 0x0 && options->got_named_stanza == 0)
582         {
583             fprintf(stderr, "Named configuration stanza: [%s] was not found.\n",
584                 options->use_rc_stanza);
585
586             exit(EXIT_FAILURE);
587         }
588
589         if (options->spa_server_str[0] == 0x0)
590         {
591             fprintf(stderr,
592                 "Must use --destination unless --test mode is used\n");
593             exit(EXIT_FAILURE);
594         }
595
596         if (options->resolve_url != NULL)
597             options->resolve_ip_http = 1;
598
599         if (!options->resolve_ip_http && options->allow_ip_str[0] == 0x0)
600         {
601             fprintf(stderr,
602                 "Must use one of [-s|-R|-a] to specify IP for SPA access.\n");
603             exit(EXIT_FAILURE);
604         }
605     }
606
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);
611
612     if(options->http_proxy[0] != 0x0 && options->spa_proto != FKO_PROTO_HTTP)
613     {
614         fprintf(stderr,
615             "Cannot set --http-proxy with a non-HTTP protocol.\n");
616         exit(EXIT_FAILURE);
617     }
618
619     /* If we are using gpg, we must at least have the recipient set.
620     */
621     if(options->use_gpg)
622     {
623         if(options->gpg_recipient_key == NULL
624             || strlen(options->gpg_recipient_key) == 0)
625         {
626             fprintf(stderr,
627                 "Must specify --gpg-recipient-key when GPG is used.\n");
628             exit(EXIT_FAILURE);
629         }
630     }
631
632     return;
633 }
634
635 /* Establish a few defaults such as UDP/62201 for sending the SPA
636  * packet (can be changed with --server-proto/--server-port)
637 */
638 static void
639 set_defaults(fko_cli_options_t *options)
640 {
641     options->spa_proto    = FKO_DEFAULT_PROTO;
642     options->spa_dst_port = FKO_DEFAULT_PORT;
643     options->fw_timeout   = -1;
644
645     return;
646 }
647
648 /* Initialize program configuration via config file and/or command-line
649  * switches.
650 */
651 void
652 config_init(fko_cli_options_t *options, int argc, char **argv)
653 {
654     int                 cmd_arg, index;
655
656     /* Zero out options and opts_track.
657     */
658     memset(options, 0x00, sizeof(fko_cli_options_t));
659
660     /* Make sure a few reasonable defaults are set
661     */
662     set_defaults(options);
663
664     /* First pass over cmd_line args to see if a named-stanza in the 
665      * rc file is used.
666     */
667     while ((cmd_arg = getopt_long(argc, argv,
668             GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
669         switch(cmd_arg) {
670             case 'h':
671                 usage();
672                 exit(EXIT_SUCCESS);
673             case 'n':
674                 options->no_save_args = 1;
675                 strlcpy(options->use_rc_stanza, optarg, MAX_LINE_LEN);
676                 break;
677             case 'v':
678                 options->verbose++;
679                 break;
680         }
681     }
682
683     /* First process the .fwknoprc file.
684     */
685     process_rc(options);
686
687     /* Reset the options index so we can run through them again.
688     */
689     optind = 0;
690
691     while ((cmd_arg = getopt_long(argc, argv,
692             GETOPTS_OPTION_STRING, cmd_opts, &index)) != -1) {
693
694         switch(cmd_arg) {
695             case 'a':
696                 strlcpy(options->allow_ip_str, optarg, MAX_IPV4_STR_LEN);
697                 break;
698             case 'A':
699                 strlcpy(options->access_str, optarg, MAX_LINE_LEN);
700                 break;
701             case 'b':
702                 options->save_packet_file_append = 1;
703                 break;
704             case 'B':
705                 strlcpy(options->save_packet_file, optarg, MAX_PATH_LEN);
706                 break;
707             case 'C':
708                 strlcpy(options->server_command, optarg, MAX_LINE_LEN);
709                 break;
710             case 'D':
711                 strlcpy(options->spa_server_str, optarg, MAX_SERVER_STR_LEN);
712                 break;
713             case 'f':
714                 options->fw_timeout = atoi(optarg);
715                 if (options->fw_timeout < 0) {
716                     fprintf(stderr, "--fw-timeout must be >= 0\n");
717                     exit(EXIT_FAILURE);
718                 }
719                 break;
720             case 'g':
721             case GPG_ENCRYPTION:
722                 options->use_gpg = 1;
723                 break;
724             case 'G':
725                 strlcpy(options->get_key_file, optarg, MAX_PATH_LEN);
726                 break;
727             case 'h':
728                 usage();
729                 exit(EXIT_SUCCESS);
730             case 'H':
731                 options->spa_proto = FKO_PROTO_HTTP;
732                 strlcpy(options->http_proxy, optarg, MAX_PATH_LEN);
733                 break;
734             case 'l':
735                 options->run_last_command = 1;
736                 break;
737             case 'm':
738             case FKO_DIGEST_NAME:
739                 if((options->digest_type = digest_strtoint(optarg)) < 0)
740                 {
741                     fprintf(stderr, "* Invalid digest type: %s\n", optarg);
742                     exit(EXIT_FAILURE);
743                 }
744                 break;
745             case NO_SAVE_ARGS:
746                 options->no_save_args = 1;
747                 break;
748             case 'n':
749                 /* We already handled this earlier, so we do nothing here
750                 */
751                 break;
752             case 'N':
753                 strlcpy(options->nat_access_str, optarg, MAX_LINE_LEN);
754                 break;
755             case 'p':
756                 options->spa_dst_port = atoi(optarg);
757                 if (options->spa_dst_port < 0 || options->spa_dst_port > MAX_PORT)
758                 {
759                     fprintf(stderr, "Unrecognized port: %s\n", optarg);
760                     exit(EXIT_FAILURE);
761                 }
762                 break;
763             case 'P':
764                 if((options->spa_proto = proto_strtoint(optarg)) < 0)
765                 {
766                     fprintf(stderr, "Unrecognized protocol: %s\n", optarg);
767                     exit(EXIT_FAILURE);
768                 }
769                 break;
770             case 'Q':
771                 strlcpy(options->spoof_ip_src_str, optarg, MAX_IPV4_STR_LEN);
772                 break;
773             case 'r':
774                 options->rand_port = 1;
775                 break;
776             case 'R':
777                 options->resolve_ip_http = 1;
778                 break;
779             case RESOLVE_URL:
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)
784                 {
785                     fprintf(stderr, "Memory allocation error for resolve URL.\n");
786                     exit(EXIT_FAILURE);
787                 }
788                 strlcpy(options->resolve_url, optarg, strlen(optarg)+1);
789                 break;
790             case SHOW_LAST_ARGS:
791                 options->show_last_command = 1;
792                 break;
793             case 's':
794                 strlcpy(options->allow_ip_str, "0.0.0.0", MAX_IPV4_STR_LEN);
795                 break;
796             case 'S':
797                 options->spa_src_port = atoi(optarg);
798                 if (options->spa_src_port < 0 || options->spa_src_port > MAX_PORT)
799                 {
800                     fprintf(stderr, "Unrecognized port: %s\n", optarg);
801                     exit(EXIT_FAILURE);
802                 }
803                 break;
804             case 'T':
805                 options->test = 1;
806                 break;
807             case 'u':
808                 strlcpy(options->http_user_agent, optarg, HTTP_MAX_USER_AGENT_LEN);
809                 break;
810             case 'U':
811                 strlcpy(options->spoof_user, optarg, MAX_USERNAME_LEN);
812                 break;
813             case 'v':
814                 /* Handled earlier.
815                 */
816                 break;
817             case 'V':
818                 options->version = 1;
819                 break;
820             case GPG_RECIP_KEY:
821                 options->use_gpg = 1;
822                 strlcpy(options->gpg_recipient_key, optarg, MAX_GPG_KEY_ID);
823                 break;
824             case GPG_SIGNER_KEY:
825                 options->use_gpg = 1;
826                 strlcpy(options->gpg_signer_key, optarg, MAX_GPG_KEY_ID);
827                 break;
828             case GPG_HOME_DIR:
829                 options->use_gpg = 1;
830                 strlcpy(options->gpg_home_dir, optarg, MAX_PATH_LEN);
831                 break;
832             case GPG_AGENT:
833                 options->use_gpg = 1;
834                 options->use_gpg_agent = 1;
835                 break;
836             case NAT_LOCAL:
837                 options->nat_local = 1;
838                 break;
839             case NAT_RAND_PORT:
840                 options->nat_rand_port = 1;
841                 break;
842             case NAT_PORT:
843                 options->nat_port = atoi(optarg);
844                 if (options->nat_port < 0 || options->nat_port > MAX_PORT)
845                 {
846                     fprintf(stderr, "Unrecognized port: %s\n", optarg);
847                     exit(EXIT_FAILURE);
848                 }
849                 break;
850             case TIME_OFFSET_PLUS:
851                 options->time_offset_plus = parse_time_offset(optarg);
852                 break;
853             case TIME_OFFSET_MINUS:
854                 options->time_offset_minus = parse_time_offset(optarg);
855                 break;
856             default:
857                 usage();
858                 exit(EXIT_FAILURE);
859         }
860     }
861
862     /* Now that we have all of our options set, we can validate them.
863     */
864     validate_options(options);
865
866     return;
867 }
868
869 /* Print usage message...
870 */
871 void
872 usage(void)
873 {
874     fprintf(stdout, "\n%s client version %s\n%s\n\n", MY_NAME, MY_VERSION, MY_DESC);
875     fprintf(stdout,
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"
879       "                             on the server.\n"
880       " -B, --save-packet           Save the generated packet data to the\n"
881       "                             specified file.\n"
882       " -a, --allow-ip              Specify IP address to allow within the SPA\n"
883       "                             packet.\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"
893       "                             packet.\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"
908       "                             option.\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"
917       "                             \":<port>\".\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"
927       "                             the network.\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"
933       "                             client side.\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"
946       "                             for NAT access.\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"
950       "                             timestamp.\n"
951       "\n"
952     );
953
954     return;
955 }
956
957 /***EOF***/