xref: /netbsd-src/crypto/external/bsd/netpgp/dist/src/netpgpkeys/netpgpkeys.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
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-keys [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--get-key keyid [options] OR\n"
63 	"\t--version\n"
64 	"where options are:\n"
65 	"\t[--coredumps] AND/OR\n"
66 	"\t[--hash=<hash alg>] AND/OR\n"
67 	"\t[--homedir=<homedir>] AND/OR\n"
68 	"\t[--keyring=<keyring>] AND/OR\n"
69 	"\t[--userid=<userid>] AND/OR\n"
70 	"\t[--verbose]\n";
71 
72 enum optdefs {
73 	/* commands */
74 	LIST_KEYS = 260,
75 	LIST_SIGS,
76 	FIND_KEY,
77 	EXPORT_KEY,
78 	IMPORT_KEY,
79 	GENERATE_KEY,
80 	VERSION_CMD,
81 	HELP_CMD,
82 	GET_KEY,
83 
84 	/* options */
85 	SSHKEYS,
86 	KEYRING,
87 	USERID,
88 	HOMEDIR,
89 	NUMBITS,
90 	HASH_ALG,
91 	VERBOSE,
92 	COREDUMPS,
93 	PASSWDFD,
94 	RESULTS,
95 	SSHKEYFILE,
96 
97 	/* debug */
98 	OPS_DEBUG
99 
100 };
101 
102 #define EXIT_ERROR	2
103 
104 static struct option options[] = {
105 	/* key-management commands */
106 	{"list-keys",	no_argument,		NULL,	LIST_KEYS},
107 	{"list-sigs",	no_argument,		NULL,	LIST_SIGS},
108 	{"find-key",	no_argument,		NULL,	FIND_KEY},
109 	{"export",	no_argument,		NULL,	EXPORT_KEY},
110 	{"export-key",	no_argument,		NULL,	EXPORT_KEY},
111 	{"import",	no_argument,		NULL,	IMPORT_KEY},
112 	{"import-key",	no_argument,		NULL,	IMPORT_KEY},
113 	{"gen",		optional_argument,	NULL,	GENERATE_KEY},
114 	{"gen-key",	optional_argument,	NULL,	GENERATE_KEY},
115 	{"generate",	optional_argument,	NULL,	GENERATE_KEY},
116 	{"generate-key", optional_argument,	NULL,	GENERATE_KEY},
117 	{"get-key", 	no_argument,		NULL,	GET_KEY},
118 	/* debugging commands */
119 	{"help",	no_argument,		NULL,	HELP_CMD},
120 	{"version",	no_argument,		NULL,	VERSION_CMD},
121 	{"debug",	required_argument, 	NULL,	OPS_DEBUG},
122 	/* options */
123 	{"coredumps",	no_argument, 		NULL,	COREDUMPS},
124 	{"keyring",	required_argument, 	NULL,	KEYRING},
125 	{"userid",	required_argument, 	NULL,	USERID},
126 	{"hash-alg",	required_argument, 	NULL,	HASH_ALG},
127 	{"hash",	required_argument, 	NULL,	HASH_ALG},
128 	{"algorithm",	required_argument, 	NULL,	HASH_ALG},
129 	{"home",	required_argument, 	NULL,	HOMEDIR},
130 	{"homedir",	required_argument, 	NULL,	HOMEDIR},
131 	{"numbits",	required_argument, 	NULL,	NUMBITS},
132 	{"ssh",		no_argument, 		NULL,	SSHKEYS},
133 	{"ssh-keys",	no_argument, 		NULL,	SSHKEYS},
134 	{"sshkeyfile",	required_argument, 	NULL,	SSHKEYFILE},
135 	{"verbose",	no_argument, 		NULL,	VERBOSE},
136 	{"pass-fd",	required_argument, 	NULL,	PASSWDFD},
137 	{"results",	required_argument, 	NULL,	RESULTS},
138 	{ NULL,		0,			NULL,	0},
139 };
140 
141 /* gather up program variables into one struct */
142 typedef struct prog_t {
143 	char	 keyring[MAXPATHLEN + 1];	/* name of keyring */
144 	char	*progname;			/* program name */
145 	int	 numbits;			/* # of bits */
146 	int	 cmd;				/* netpgpkeys command */
147 } prog_t;
148 
149 
150 /* print a usage message */
151 static void
152 print_usage(const char *usagemsg, char *progname)
153 {
154 	(void) fprintf(stderr,
155 	"%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
156 				netpgp_get_info("version"),
157 				netpgp_get_info("maintainer"));
158 	(void) fprintf(stderr, "Usage: %s COMMAND OPTIONS:\n%s %s",
159 		progname, progname, usagemsg);
160 }
161 
162 /* match keys, decoding from json if we do find any */
163 static int
164 match_keys(netpgp_t *netpgp, FILE *fp, char *f, const int psigs)
165 {
166 	char	*json;
167 	int	 idc;
168 
169 	if (f == NULL) {
170 		if (!netpgp_list_keys_json(netpgp, &json, psigs)) {
171 			return 0;
172 		}
173 	} else {
174 		if (netpgp_match_keys_json(netpgp, &json, f, "human", psigs) == 0) {
175 			return 0;
176 		}
177 	}
178 	if (__ops_get_debug_level(__FILE__)) {
179 		(void) fprintf(stderr, "match_keys: json is '%s'\n", json);
180 	}
181 	idc = netpgp_format_json(fp, json, psigs);
182 	/* clean up */
183 	free(json);
184 	return idc;
185 }
186 
187 /* do a command once for a specified file 'f' */
188 static int
189 netpgp_cmd(netpgp_t *netpgp, prog_t *p, char *f)
190 {
191 	char	*key;
192 
193 	switch (p->cmd) {
194 	case LIST_KEYS:
195 	case LIST_SIGS:
196 		return match_keys(netpgp, stdout, f, (p->cmd == LIST_SIGS));
197 	case FIND_KEY:
198 		return netpgp_find_key(netpgp, netpgp_getvar(netpgp, "userid"));
199 	case EXPORT_KEY:
200 		key = netpgp_export_key(netpgp, netpgp_getvar(netpgp, "userid"));
201 		if (key) {
202 			printf("%s", key);
203 			return 1;
204 		}
205 		(void) fprintf(stderr, "key '%s' not found\n", f);
206 		return 0;
207 	case IMPORT_KEY:
208 		return netpgp_import_key(netpgp, f);
209 	case GENERATE_KEY:
210 		return netpgp_generate_key(netpgp, f, p->numbits);
211 	case GET_KEY:
212 		key = netpgp_get_key(netpgp, f, "human");
213 		if (key) {
214 			printf("%s", key);
215 			return 1;
216 		}
217 		(void) fprintf(stderr, "key '%s' not found\n", f);
218 		return 0;
219 	case HELP_CMD:
220 	default:
221 		print_usage(usage, p->progname);
222 		exit(EXIT_SUCCESS);
223 	}
224 }
225 
226 /* set the option */
227 static int
228 setoption(netpgp_t *netpgp, prog_t *p, int val, char *arg, int *homeset)
229 {
230 	switch (val) {
231 	case COREDUMPS:
232 		netpgp_setvar(netpgp, "coredumps", "allowed");
233 		break;
234 	case GENERATE_KEY:
235 		netpgp_setvar(netpgp, "userid checks", "skip");
236 		p->cmd = val;
237 		break;
238 	case LIST_KEYS:
239 	case LIST_SIGS:
240 	case FIND_KEY:
241 	case EXPORT_KEY:
242 	case IMPORT_KEY:
243 	case GET_KEY:
244 	case HELP_CMD:
245 		p->cmd = val;
246 		break;
247 	case VERSION_CMD:
248 		printf(
249 "%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
250 			netpgp_get_info("version"),
251 			netpgp_get_info("maintainer"));
252 		exit(EXIT_SUCCESS);
253 		/* options */
254 	case SSHKEYS:
255 		netpgp_setvar(netpgp, "ssh keys", "1");
256 		break;
257 	case KEYRING:
258 		if (arg == NULL) {
259 			(void) fprintf(stderr,
260 				"No keyring argument provided\n");
261 			exit(EXIT_ERROR);
262 		}
263 		snprintf(p->keyring, sizeof(p->keyring), "%s", arg);
264 		break;
265 	case USERID:
266 		if (optarg == NULL) {
267 			(void) fprintf(stderr,
268 				"no userid argument provided\n");
269 			exit(EXIT_ERROR);
270 		}
271 		netpgp_setvar(netpgp, "userid", arg);
272 		break;
273 	case VERBOSE:
274 		netpgp_incvar(netpgp, "verbose", 1);
275 		break;
276 	case HOMEDIR:
277 		if (arg == NULL) {
278 			(void) fprintf(stderr,
279 			"no home directory argument provided\n");
280 			exit(EXIT_ERROR);
281 		}
282 		netpgp_set_homedir(netpgp, arg, NULL, 0);
283 		*homeset = 1;
284 		break;
285 	case NUMBITS:
286 		if (arg == NULL) {
287 			(void) fprintf(stderr,
288 			"no number of bits argument provided\n");
289 			exit(EXIT_ERROR);
290 		}
291 		p->numbits = atoi(arg);
292 		break;
293 	case HASH_ALG:
294 		if (arg == NULL) {
295 			(void) fprintf(stderr,
296 			"No hash algorithm argument provided\n");
297 			exit(EXIT_ERROR);
298 		}
299 		netpgp_setvar(netpgp, "hash", arg);
300 		break;
301 	case PASSWDFD:
302 		if (arg == NULL) {
303 			(void) fprintf(stderr,
304 			"no pass-fd argument provided\n");
305 			exit(EXIT_ERROR);
306 		}
307 		netpgp_setvar(netpgp, "pass-fd", arg);
308 		break;
309 	case RESULTS:
310 		if (arg == NULL) {
311 			(void) fprintf(stderr,
312 			"No output filename argument provided\n");
313 			exit(EXIT_ERROR);
314 		}
315 		netpgp_setvar(netpgp, "res", arg);
316 		break;
317 	case SSHKEYFILE:
318 		netpgp_setvar(netpgp, "ssh keys", "1");
319 		netpgp_setvar(netpgp, "sshkeyfile", arg);
320 		break;
321 	case OPS_DEBUG:
322 		netpgp_set_debug(arg);
323 		break;
324 	default:
325 		p->cmd = HELP_CMD;
326 		break;
327 	}
328 	return 1;
329 }
330 
331 /* we have -o option=value -- parse, and process */
332 static int
333 parse_option(netpgp_t *netpgp, prog_t *p, const char *s, int *homeset)
334 {
335 	static regex_t	 opt;
336 	struct option	*op;
337 	static int	 compiled;
338 	regmatch_t	 matches[10];
339 	char		 option[128];
340 	char		 value[128];
341 
342 	if (!compiled) {
343 		compiled = 1;
344 		(void) regcomp(&opt, "([^=]{1,128})(=(.*))?", REG_EXTENDED);
345 	}
346 	if (regexec(&opt, s, 10, matches, 0) == 0) {
347 		(void) snprintf(option, sizeof(option), "%.*s",
348 			(int)(matches[1].rm_eo - matches[1].rm_so), &s[matches[1].rm_so]);
349 		if (matches[2].rm_so > 0) {
350 			(void) snprintf(value, sizeof(value), "%.*s",
351 				(int)(matches[3].rm_eo - matches[3].rm_so), &s[matches[3].rm_so]);
352 		} else {
353 			value[0] = 0x0;
354 		}
355 		for (op = options ; op->name ; op++) {
356 			if (strcmp(op->name, option) == 0) {
357 				return setoption(netpgp, p, op->val, value, homeset);
358 			}
359 		}
360 	}
361 	return 0;
362 }
363 
364 int
365 main(int argc, char **argv)
366 {
367 	struct stat	st;
368 	netpgp_t	netpgp;
369 	prog_t          p;
370 	int             homeset;
371 	int             optindex;
372 	int             ret;
373 	int             ch;
374 	int             i;
375 
376 	(void) memset(&p, 0x0, sizeof(p));
377 	(void) memset(&netpgp, 0x0, sizeof(netpgp));
378 	homeset = 0;
379 	p.progname = argv[0];
380 	p.numbits = DEFAULT_NUMBITS;
381 	if (argc < 2) {
382 		print_usage(usage, p.progname);
383 		exit(EXIT_ERROR);
384 	}
385 	/* set some defaults */
386 	netpgp_setvar(&netpgp, "sshkeydir", "/etc/ssh");
387 	netpgp_setvar(&netpgp, "res", "<stdout>");
388 	netpgp_setvar(&netpgp, "hash", DEFAULT_HASH_ALG);
389 	optindex = 0;
390 	while ((ch = getopt_long(argc, argv, "S:Vglo:s", options, &optindex)) != -1) {
391 		if (ch >= LIST_KEYS) {
392 			/* getopt_long returns 0 for long options */
393 			if (!setoption(&netpgp, &p, options[optindex].val, optarg, &homeset)) {
394 				(void) fprintf(stderr, "Bad setoption result %d\n", ch);
395 			}
396 		} else {
397 			switch (ch) {
398 			case 'S':
399 				netpgp_setvar(&netpgp, "ssh keys", "1");
400 				netpgp_setvar(&netpgp, "sshkeyfile", optarg);
401 				break;
402 			case 'V':
403 				printf(
404 	"%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
405 					netpgp_get_info("version"),
406 					netpgp_get_info("maintainer"));
407 				exit(EXIT_SUCCESS);
408 			case 'g':
409 				p.cmd = GENERATE_KEY;
410 				break;
411 			case 'l':
412 				p.cmd = LIST_KEYS;
413 				break;
414 			case 'o':
415 				if (!parse_option(&netpgp, &p, optarg, &homeset)) {
416 					(void) fprintf(stderr, "Bad parse_option\n");
417 				}
418 				break;
419 			case 's':
420 				p.cmd = LIST_SIGS;
421 				break;
422 			default:
423 				p.cmd = HELP_CMD;
424 				break;
425 			}
426 		}
427 	}
428 	if (!homeset) {
429 		netpgp_set_homedir(&netpgp, getenv("HOME"),
430 			netpgp_getvar(&netpgp, "ssh keys") ? "/.ssh" : "/.gnupg", 1);
431 	}
432 	/* initialise, and read keys from file */
433 	if (!netpgp_init(&netpgp)) {
434 		if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) {
435 			(void) mkdir(netpgp_getvar(&netpgp, "homedir"), 0700);
436 		}
437 		if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) {
438 			(void) fprintf(stderr, "can't create home directory '%s'\n",
439 				netpgp_getvar(&netpgp, "homedir"));
440 			exit(EXIT_ERROR);
441 		}
442 	}
443 	/* now do the required action for each of the command line args */
444 	ret = EXIT_SUCCESS;
445 	if (optind == argc) {
446 		if (!netpgp_cmd(&netpgp, &p, NULL)) {
447 			ret = EXIT_FAILURE;
448 		}
449 	} else {
450 		for (i = optind; i < argc; i++) {
451 			if (!netpgp_cmd(&netpgp, &p, argv[i])) {
452 				ret = EXIT_FAILURE;
453 			}
454 		}
455 	}
456 	netpgp_end(&netpgp);
457 	exit(ret);
458 }
459