1 /*-
2  * Copyright (c) 2009 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by Alistair Crooks (agc@NetBSD.org)
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 /* Command line program to perform netpgp operations */
31 #include <sys/types.h>
32 #include <sys/param.h>
33 #include <sys/stat.h>
34 
35 #include <getopt.h>
36 #include <regex.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include <mj.h>
44 #include <netpgp.h>
45 
46 /*
47  * 2048 is the absolute minimum, really - we should really look at
48  * bumping this to 4096 or even higher - agc, 20090522
49  */
50 #define DEFAULT_NUMBITS 2048
51 
52 #define DEFAULT_HASH_ALG "SHA256"
53 
54 static const char *usage =
55 	" --help OR\n"
56 	"\t--export-key [options] OR\n"
57 	"\t--find-key [options] OR\n"
58 	"\t--generate-key [options] OR\n"
59 	"\t--import-key [options] OR\n"
60 	"\t--list-keys [options] OR\n"
61 	"\t--list-sigs [options] OR\n"
62 	"\t--trusted-keys [options] OR\n"
63 	"\t--get-key keyid [options] OR\n"
64 	"\t--version\n"
65 	"where options are:\n"
66 	"\t[--cipher=<cipher name>] AND/OR\n"
67 	"\t[--coredumps] AND/OR\n"
68 	"\t[--hash=<hash alg>] AND/OR\n"
69 	"\t[--homedir=<homedir>] AND/OR\n"
70 	"\t[--keyring=<keyring>] AND/OR\n"
71 	"\t[--userid=<userid>] AND/OR\n"
72 	"\t[--verbose]\n";
73 
74 enum optdefs {
75 	/* commands */
76 	LIST_KEYS = 260,
77 	LIST_SIGS,
78 	FIND_KEY,
79 	EXPORT_KEY,
80 	IMPORT_KEY,
81 	GENERATE_KEY,
82 	VERSION_CMD,
83 	HELP_CMD,
84 	GET_KEY,
85 	TRUSTED_KEYS,
86 
87 	/* options */
88 	SSHKEYS,
89 	KEYRING,
90 	USERID,
91 	HOMEDIR,
92 	NUMBITS,
93 	HASH_ALG,
94 	VERBOSE,
95 	COREDUMPS,
96 	PASSWDFD,
97 	RESULTS,
98 	SSHKEYFILE,
99 	CIPHER,
100 	FORMAT,
101 
102 	/* debug */
103 	OPS_DEBUG
104 
105 };
106 
107 #define EXIT_ERROR	2
108 
109 static struct option options[] = {
110 	/* key-management commands */
111 	{"list-keys",	no_argument,		NULL,	LIST_KEYS},
112 	{"list-sigs",	no_argument,		NULL,	LIST_SIGS},
113 	{"find-key",	optional_argument,	NULL,	FIND_KEY},
114 	{"export",	no_argument,		NULL,	EXPORT_KEY},
115 	{"export-key",	no_argument,		NULL,	EXPORT_KEY},
116 	{"import",	no_argument,		NULL,	IMPORT_KEY},
117 	{"import-key",	no_argument,		NULL,	IMPORT_KEY},
118 	{"gen",		optional_argument,	NULL,	GENERATE_KEY},
119 	{"gen-key",	optional_argument,	NULL,	GENERATE_KEY},
120 	{"generate",	optional_argument,	NULL,	GENERATE_KEY},
121 	{"generate-key", optional_argument,	NULL,	GENERATE_KEY},
122 	{"get-key", 	no_argument,		NULL,	GET_KEY},
123 	{"trusted-keys",optional_argument,	NULL,	TRUSTED_KEYS},
124 	{"trusted",	optional_argument,	NULL,	TRUSTED_KEYS},
125 	/* debugging commands */
126 	{"help",	no_argument,		NULL,	HELP_CMD},
127 	{"version",	no_argument,		NULL,	VERSION_CMD},
128 	{"debug",	required_argument, 	NULL,	OPS_DEBUG},
129 	/* options */
130 	{"coredumps",	no_argument, 		NULL,	COREDUMPS},
131 	{"keyring",	required_argument, 	NULL,	KEYRING},
132 	{"userid",	required_argument, 	NULL,	USERID},
133 	{"format",	required_argument, 	NULL,	FORMAT},
134 	{"hash-alg",	required_argument, 	NULL,	HASH_ALG},
135 	{"hash",	required_argument, 	NULL,	HASH_ALG},
136 	{"algorithm",	required_argument, 	NULL,	HASH_ALG},
137 	{"home",	required_argument, 	NULL,	HOMEDIR},
138 	{"homedir",	required_argument, 	NULL,	HOMEDIR},
139 	{"numbits",	required_argument, 	NULL,	NUMBITS},
140 	{"ssh",		no_argument, 		NULL,	SSHKEYS},
141 	{"ssh-keys",	no_argument, 		NULL,	SSHKEYS},
142 	{"sshkeyfile",	required_argument, 	NULL,	SSHKEYFILE},
143 	{"verbose",	no_argument, 		NULL,	VERBOSE},
144 	{"pass-fd",	required_argument, 	NULL,	PASSWDFD},
145 	{"results",	required_argument, 	NULL,	RESULTS},
146 	{"cipher",	required_argument, 	NULL,	CIPHER},
147 	{ NULL,		0,			NULL,	0},
148 };
149 
150 /* gather up program variables into one struct */
151 typedef struct prog_t {
152 	char	 keyring[MAXPATHLEN + 1];	/* name of keyring */
153 	char	*progname;			/* program name */
154 	int	 numbits;			/* # of bits */
155 	int	 cmd;				/* netpgpkeys command */
156 } prog_t;
157 
158 
159 /* print a usage message */
160 static void
print_usage(const char * usagemsg,char * progname)161 print_usage(const char *usagemsg, char *progname)
162 {
163 	(void) fprintf(stderr,
164 	"%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
165 				netpgp_get_info("version"),
166 				netpgp_get_info("maintainer"));
167 	(void) fprintf(stderr, "Usage: %s COMMAND OPTIONS:\n%s %s",
168 		progname, progname, usagemsg);
169 }
170 
171 /* match keys, decoding from json if we do find any */
172 static int
match_keys(netpgp_t * netpgp,FILE * fp,char * f,const int psigs)173 match_keys(netpgp_t *netpgp, FILE *fp, char *f, const int psigs)
174 {
175 	char	*json;
176 	int	 idc;
177 
178 	if (f == NULL) {
179 		if (!netpgp_list_keys_json(netpgp, &json, psigs)) {
180 			return 0;
181 		}
182 	} else {
183 		if (netpgp_match_keys_json(netpgp, &json, f,
184 				netpgp_getvar(netpgp, "format"), psigs) == 0) {
185 			return 0;
186 		}
187 	}
188 	idc = netpgp_format_json(fp, json, psigs);
189 	/* clean up */
190 	free(json);
191 	return idc;
192 }
193 
194 /* do a command once for a specified file 'f' */
195 static int
netpgp_cmd(netpgp_t * netpgp,prog_t * p,char * f)196 netpgp_cmd(netpgp_t *netpgp, prog_t *p, char *f)
197 {
198 	char	*key;
199 	char	*s;
200 
201 	switch (p->cmd) {
202 	case LIST_KEYS:
203 	case LIST_SIGS:
204 		return match_keys(netpgp, stdout, f, (p->cmd == LIST_SIGS));
205 	case FIND_KEY:
206 		if ((key = f) == NULL) {
207 			key = netpgp_getvar(netpgp, "userid");
208 		}
209 		return netpgp_find_key(netpgp, key);
210 	case EXPORT_KEY:
211 		if ((key = f) == NULL) {
212 			key = netpgp_getvar(netpgp, "userid");
213 		}
214 		if (key) {
215 			if ((s = netpgp_export_key(netpgp, key)) != NULL) {
216 				printf("%s", s);
217 				return 1;
218 			}
219 		}
220 		(void) fprintf(stderr, "key '%s' not found\n", f);
221 		return 0;
222 	case IMPORT_KEY:
223 		return netpgp_import_key(netpgp, f);
224 	case GENERATE_KEY:
225 		return netpgp_generate_key(netpgp, f, p->numbits);
226 	case GET_KEY:
227 		key = netpgp_get_key(netpgp, f, netpgp_getvar(netpgp, "format"));
228 		if (key) {
229 			printf("%s", key);
230 			return 1;
231 		}
232 		(void) fprintf(stderr, "key '%s' not found\n", f);
233 		return 0;
234 	case TRUSTED_KEYS:
235 		return netpgp_match_pubkeys(netpgp, f, stdout);
236 	case HELP_CMD:
237 	default:
238 		print_usage(usage, p->progname);
239 		exit(EXIT_SUCCESS);
240 	}
241 }
242 
243 /* set the option */
244 static int
setoption(netpgp_t * netpgp,prog_t * p,int val,char * arg,int * homeset)245 setoption(netpgp_t *netpgp, prog_t *p, int val, char *arg, int *homeset)
246 {
247 	switch (val) {
248 	case COREDUMPS:
249 		netpgp_setvar(netpgp, "coredumps", "allowed");
250 		break;
251 	case GENERATE_KEY:
252 		netpgp_setvar(netpgp, "userid checks", "skip");
253 		p->cmd = val;
254 		break;
255 	case LIST_KEYS:
256 	case LIST_SIGS:
257 	case FIND_KEY:
258 	case EXPORT_KEY:
259 	case IMPORT_KEY:
260 	case GET_KEY:
261 	case TRUSTED_KEYS:
262 	case HELP_CMD:
263 		p->cmd = val;
264 		break;
265 	case VERSION_CMD:
266 		printf(
267 "%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
268 			netpgp_get_info("version"),
269 			netpgp_get_info("maintainer"));
270 		exit(EXIT_SUCCESS);
271 		/* options */
272 	case SSHKEYS:
273 		netpgp_setvar(netpgp, "ssh keys", "1");
274 		break;
275 	case KEYRING:
276 		if (arg == NULL) {
277 			(void) fprintf(stderr,
278 				"No keyring argument provided\n");
279 			exit(EXIT_ERROR);
280 		}
281 		snprintf(p->keyring, sizeof(p->keyring), "%s", arg);
282 		break;
283 	case USERID:
284 		if (optarg == NULL) {
285 			(void) fprintf(stderr,
286 				"no userid argument provided\n");
287 			exit(EXIT_ERROR);
288 		}
289 		netpgp_setvar(netpgp, "userid", arg);
290 		break;
291 	case VERBOSE:
292 		netpgp_incvar(netpgp, "verbose", 1);
293 		break;
294 	case HOMEDIR:
295 		if (arg == NULL) {
296 			(void) fprintf(stderr,
297 			"no home directory argument provided\n");
298 			exit(EXIT_ERROR);
299 		}
300 		netpgp_set_homedir(netpgp, arg, NULL, 0);
301 		*homeset = 1;
302 		break;
303 	case NUMBITS:
304 		if (arg == NULL) {
305 			(void) fprintf(stderr,
306 			"no number of bits argument provided\n");
307 			exit(EXIT_ERROR);
308 		}
309 		p->numbits = atoi(arg);
310 		break;
311 	case HASH_ALG:
312 		if (arg == NULL) {
313 			(void) fprintf(stderr,
314 			"No hash algorithm argument provided\n");
315 			exit(EXIT_ERROR);
316 		}
317 		netpgp_setvar(netpgp, "hash", arg);
318 		break;
319 	case PASSWDFD:
320 		if (arg == NULL) {
321 			(void) fprintf(stderr,
322 			"no pass-fd argument provided\n");
323 			exit(EXIT_ERROR);
324 		}
325 		netpgp_setvar(netpgp, "pass-fd", arg);
326 		break;
327 	case RESULTS:
328 		if (arg == NULL) {
329 			(void) fprintf(stderr,
330 			"No output filename argument provided\n");
331 			exit(EXIT_ERROR);
332 		}
333 		netpgp_setvar(netpgp, "res", arg);
334 		break;
335 	case SSHKEYFILE:
336 		netpgp_setvar(netpgp, "ssh keys", "1");
337 		netpgp_setvar(netpgp, "sshkeyfile", arg);
338 		break;
339 	case FORMAT:
340 		netpgp_setvar(netpgp, "format", arg);
341 		break;
342 	case CIPHER:
343 		netpgp_setvar(netpgp, "cipher", arg);
344 		break;
345 	case OPS_DEBUG:
346 		netpgp_set_debug(arg);
347 		break;
348 	default:
349 		p->cmd = HELP_CMD;
350 		break;
351 	}
352 	return 1;
353 }
354 
355 /* we have -o option=value -- parse, and process */
356 static int
parse_option(netpgp_t * netpgp,prog_t * p,const char * s,int * homeset)357 parse_option(netpgp_t *netpgp, prog_t *p, const char *s, int *homeset)
358 {
359 	static regex_t	 opt;
360 	struct option	*op;
361 	static int	 compiled;
362 	regmatch_t	 matches[10];
363 	char		 option[128];
364 	char		 value[128];
365 
366 	if (!compiled) {
367 		compiled = 1;
368 		(void) regcomp(&opt, "([^=]{1,128})(=(.*))?", REG_EXTENDED);
369 	}
370 	if (regexec(&opt, s, 10, matches, 0) == 0) {
371 		(void) snprintf(option, sizeof(option), "%.*s",
372 			(int)(matches[1].rm_eo - matches[1].rm_so), &s[matches[1].rm_so]);
373 		if (matches[2].rm_so > 0) {
374 			(void) snprintf(value, sizeof(value), "%.*s",
375 				(int)(matches[3].rm_eo - matches[3].rm_so), &s[matches[3].rm_so]);
376 		} else {
377 			value[0] = 0x0;
378 		}
379 		for (op = options ; op->name ; op++) {
380 			if (strcmp(op->name, option) == 0) {
381 				return setoption(netpgp, p, op->val, value, homeset);
382 			}
383 		}
384 	}
385 	return 0;
386 }
387 
388 int
main(int argc,char ** argv)389 main(int argc, char **argv)
390 {
391 	struct stat	st;
392 	netpgp_t	netpgp;
393 	prog_t          p;
394 	int             homeset;
395 	int             optindex;
396 	int             ret;
397 	int             ch;
398 	int             i;
399 
400 	(void) memset(&p, 0x0, sizeof(p));
401 	(void) memset(&netpgp, 0x0, sizeof(netpgp));
402 	homeset = 0;
403 	p.progname = argv[0];
404 	p.numbits = DEFAULT_NUMBITS;
405 	if (argc < 2) {
406 		print_usage(usage, p.progname);
407 		exit(EXIT_ERROR);
408 	}
409 	/* set some defaults */
410 	netpgp_setvar(&netpgp, "sshkeydir", "/etc/ssh");
411 	netpgp_setvar(&netpgp, "res", "<stdout>");
412 	netpgp_setvar(&netpgp, "hash", DEFAULT_HASH_ALG);
413 	netpgp_setvar(&netpgp, "format", "human");
414 	optindex = 0;
415 	while ((ch = getopt_long(argc, argv, "S:Vglo:s", options, &optindex)) != -1) {
416 		if (ch >= LIST_KEYS) {
417 			/* getopt_long returns 0 for long options */
418 			if (!setoption(&netpgp, &p, options[optindex].val, optarg, &homeset)) {
419 				(void) fprintf(stderr, "Bad setoption result %d\n", ch);
420 			}
421 		} else {
422 			switch (ch) {
423 			case 'S':
424 				netpgp_setvar(&netpgp, "ssh keys", "1");
425 				netpgp_setvar(&netpgp, "sshkeyfile", optarg);
426 				break;
427 			case 'V':
428 				printf(
429 	"%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
430 					netpgp_get_info("version"),
431 					netpgp_get_info("maintainer"));
432 				exit(EXIT_SUCCESS);
433 			case 'g':
434 				p.cmd = GENERATE_KEY;
435 				break;
436 			case 'l':
437 				p.cmd = LIST_KEYS;
438 				break;
439 			case 'o':
440 				if (!parse_option(&netpgp, &p, optarg, &homeset)) {
441 					(void) fprintf(stderr, "Bad parse_option\n");
442 				}
443 				break;
444 			case 's':
445 				p.cmd = LIST_SIGS;
446 				break;
447 			default:
448 				p.cmd = HELP_CMD;
449 				break;
450 			}
451 		}
452 	}
453 	if (!homeset) {
454 		netpgp_set_homedir(&netpgp, getenv("HOME"),
455 			netpgp_getvar(&netpgp, "ssh keys") ? "/.ssh" : "/.gnupg", 1);
456 	}
457 	/* initialise, and read keys from file */
458 	if (!netpgp_init(&netpgp)) {
459 		if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) {
460 			(void) mkdir(netpgp_getvar(&netpgp, "homedir"), 0700);
461 		}
462 		if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) {
463 			(void) fprintf(stderr, "can't create home directory '%s'\n",
464 				netpgp_getvar(&netpgp, "homedir"));
465 			exit(EXIT_ERROR);
466 		}
467 	}
468 	/* now do the required action for each of the command line args */
469 	ret = EXIT_SUCCESS;
470 	if (optind == argc) {
471 		if (!netpgp_cmd(&netpgp, &p, NULL)) {
472 			ret = EXIT_FAILURE;
473 		}
474 	} else {
475 		for (i = optind; i < argc; i++) {
476 			if (!netpgp_cmd(&netpgp, &p, argv[i])) {
477 				ret = EXIT_FAILURE;
478 			}
479 		}
480 	}
481 	netpgp_end(&netpgp);
482 	exit(ret);
483 }
484