xref: /netbsd-src/crypto/external/bsd/netpgp/dist/src/netpgpkeys/netpgpkeys.c (revision 6c43668aa241f78f126ea64331e78427d593aaf4)
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 /* vararg print function */
163 static void
164 p(FILE *fp, char *s, ...)
165 {
166 	va_list	args;
167 
168 	va_start(args, s);
169 	while (s != NULL) {
170 		(void) fprintf(fp, "%s", s);
171 		s = va_arg(args, char *);
172 	}
173 	va_end(args);
174 }
175 
176 /* print a JSON object to the FILE stream */
177 static void
178 pobj(FILE *fp, mj_t *obj, int depth)
179 {
180 	int	i;
181 
182 	for (i = 0 ; i < depth ; i++) {
183 		p(fp, " ", NULL);
184 	}
185 	switch(obj->type) {
186 	case MJ_NULL:
187 	case MJ_FALSE:
188 	case MJ_TRUE:
189 		p(fp, (obj->type == MJ_NULL) ? "null" : (obj->type == MJ_FALSE) ? "false" : "true", NULL);
190 		break;
191 	case MJ_NUMBER:
192 		p(fp, obj->value.s, NULL);
193 		break;
194 	case MJ_STRING:
195 		(void) fprintf(fp, "%.*s", (int)(obj->c), obj->value.s);
196 		break;
197 	case MJ_ARRAY:
198 		for (i = 0 ; i < obj->c ; i++) {
199 			pobj(fp, &obj->value.v[i], depth + 1);
200 			if (i < obj->c - 1) {
201 				(void) fprintf(fp, ", ");
202 			}
203 		}
204 		(void) fprintf(fp, "\n");
205 		break;
206 	case MJ_OBJECT:
207 		for (i = 0 ; i < obj->c ; i += 2) {
208 			pobj(fp, &obj->value.v[i], depth + 1);
209 			p(fp, ": ", NULL);
210 			pobj(fp, &obj->value.v[i + 1], 0);
211 			if (i < obj->c - 1) {
212 				p(fp, ", ", NULL);
213 			}
214 		}
215 		p(fp, "\n", NULL);
216 		break;
217 	default:
218 		break;
219 	}
220 }
221 
222 /* return the time as a string */
223 static char *
224 ptimestr(char *dest, size_t size, time_t t)
225 {
226 	struct tm      *tm;
227 
228 	tm = gmtime(&t);
229 	(void) snprintf(dest, size, "%04d-%02d-%02d",
230 		tm->tm_year + 1900,
231 		tm->tm_mon + 1,
232 		tm->tm_mday);
233 	return dest;
234 }
235 
236 /* format a JSON object */
237 static void
238 formatobj(FILE *fp, mj_t *obj, const int psigs)
239 {
240 	int64_t	 birthtime;
241 	int64_t	 duration;
242 	time_t	 now;
243 	char	 tbuf[32];
244 	char	*s;
245 	mj_t	*sub;
246 	int	 r;
247 	int	 i;
248 
249 	if (__ops_get_debug_level(__FILE__)) {
250 		mj_asprint(&s, obj);
251 		(void) fprintf(stderr, "formatobj: json is '%s'\n", s);
252 		free(s);
253 	}
254 	pobj(fp, &obj->value.v[mj_object_find(obj, "header", 0, 2) + 1], 0);
255 	p(fp, " ", NULL);
256 	pobj(fp, &obj->value.v[mj_object_find(obj, "key bits", 0, 2) + 1], 0);
257 	p(fp, "/", NULL);
258 	pobj(fp, &obj->value.v[mj_object_find(obj, "pka", 0, 2) + 1], 0);
259 	p(fp, " ", NULL);
260 	pobj(fp, &obj->value.v[mj_object_find(obj, "key id", 0, 2) + 1], 0);
261 	birthtime = strtoll(obj->value.v[mj_object_find(obj, "birthtime", 0, 2) + 1].value.s, NULL, 10);
262 	p(fp, " ", ptimestr(tbuf, sizeof(tbuf), birthtime), NULL);
263 	duration = strtoll(obj->value.v[mj_object_find(obj, "duration", 0, 2) + 1].value.s, NULL, 10);
264 	if (duration > 0) {
265 		now = time(NULL);
266 		p(fp, " ", (birthtime + duration < now) ? "[EXPIRED " : "[EXPIRES ",
267 			ptimestr(tbuf, sizeof(tbuf), birthtime + duration), "]", NULL);
268 	}
269 	p(fp, "\n", "Key fingerprint: ", NULL);
270 	pobj(fp, &obj->value.v[mj_object_find(obj, "fingerprint", 0, 2) + 1], 0);
271 	p(fp, "\n", NULL);
272 	/* go to field after \"duration\" */
273 	for (i = mj_object_find(obj, "duration", 0, 2) + 2; i < mj_arraycount(obj) ; i += 2) {
274 		if (strcmp(obj->value.v[i].value.s, "uid") == 0) {
275 			sub = &obj->value.v[i + 1];
276 			p(fp, "uid", NULL);
277 			pobj(fp, &sub->value.v[0], (psigs) ? 4 : 14); /* human name */
278 			pobj(fp, &sub->value.v[1], 1); /* any revocation */
279 			p(fp, "\n", NULL);
280 		} else if (strcmp(obj->value.v[i].value.s, "encryption") == 0) {
281 			sub = &obj->value.v[i + 1];
282 			p(fp, "encryption", NULL);
283 			pobj(fp, &sub->value.v[0], 1);	/* size */
284 			p(fp, "/", NULL);
285 			pobj(fp, &sub->value.v[1], 0); /* alg */
286 			p(fp, " ", NULL);
287 			pobj(fp, &sub->value.v[2], 0); /* id */
288 			p(fp, " ", ptimestr(tbuf, sizeof(tbuf), strtoll(sub->value.v[3].value.s, NULL, 10)),
289 				"\n", NULL);
290 		} else if (strcmp(obj->value.v[i].value.s, "sig") == 0) {
291 			sub = &obj->value.v[i + 1];
292 			p(fp, "sig", NULL);
293 			pobj(fp, &sub->value.v[0], 8);	/* size */
294 			p(fp, "  ", ptimestr(tbuf, sizeof(tbuf), strtoll(sub->value.v[1].value.s, NULL, 10)),
295 				" ", NULL); /* time */
296 			pobj(fp, &sub->value.v[2], 0); /* human name */
297 			p(fp, "\n", NULL);
298 		} else {
299 			fprintf(stderr, "weird '%s'\n", obj->value.v[i].value.s);
300 			pobj(fp, &obj->value.v[i], 0); /* human name */
301 		}
302 	}
303 	p(fp, "\n", NULL);
304 }
305 
306 /* match keys, decoding from json if we do find any */
307 static int
308 match_keys(netpgp_t *netpgp, FILE *fp, char *f, const int psigs)
309 {
310 	char	*json;
311 	mj_t	 ids;
312 	int	 from;
313 	int	 idc;
314 	int	 tok;
315 	int	 to;
316 	int	 i;
317 
318 	if (f == NULL) {
319 		if (!netpgp_list_keys_json(netpgp, &json, psigs)) {
320 			return 0;
321 		}
322 	} else {
323 		if (netpgp_match_keys_json(netpgp, &json, f, "human", psigs) == 0) {
324 			return 0;
325 		}
326 	}
327 	if (__ops_get_debug_level(__FILE__)) {
328 		(void) fprintf(stderr, "match_keys: json is '%s'\n", json);
329 	}
330 	/* ids is an array of strings, each containing 1 entry */
331 	(void) memset(&ids, 0x0, sizeof(ids));
332 	from = to = tok = 0;
333 	/* convert from string into an mj structure */
334 	(void) mj_parse(&ids, json, &from, &to, &tok);
335 	idc = mj_arraycount(&ids);
336 	(void) fprintf(fp, "%d key%s found\n", idc, (idc == 1) ? "" : "s");
337 	for (i = 0 ; i < idc ; i++) {
338 		formatobj(fp, &ids.value.v[i], psigs);
339 	}
340 	/* clean up */
341 	free(json);
342 	mj_delete(&ids);
343 	return idc;
344 }
345 
346 /* do a command once for a specified file 'f' */
347 static int
348 netpgp_cmd(netpgp_t *netpgp, prog_t *p, char *f)
349 {
350 	char	*key;
351 
352 	switch (p->cmd) {
353 	case LIST_KEYS:
354 	case LIST_SIGS:
355 		return match_keys(netpgp, stdout, f, (p->cmd == LIST_SIGS));
356 	case FIND_KEY:
357 		return netpgp_find_key(netpgp, netpgp_getvar(netpgp, "userid"));
358 	case EXPORT_KEY:
359 		key = netpgp_export_key(netpgp, netpgp_getvar(netpgp, "userid"));
360 		if (key) {
361 			printf("%s", key);
362 			return 1;
363 		}
364 		(void) fprintf(stderr, "key '%s' not found\n", f);
365 		return 0;
366 	case IMPORT_KEY:
367 		return netpgp_import_key(netpgp, f);
368 	case GENERATE_KEY:
369 		return netpgp_generate_key(netpgp, f, p->numbits);
370 	case GET_KEY:
371 		key = netpgp_get_key(netpgp, f, "human");
372 		if (key) {
373 			printf("%s", key);
374 			return 1;
375 		}
376 		(void) fprintf(stderr, "key '%s' not found\n", f);
377 		return 0;
378 	case HELP_CMD:
379 	default:
380 		print_usage(usage, p->progname);
381 		exit(EXIT_SUCCESS);
382 	}
383 }
384 
385 /* set the option */
386 static int
387 setoption(netpgp_t *netpgp, prog_t *p, int val, char *arg, int *homeset)
388 {
389 	switch (val) {
390 	case COREDUMPS:
391 		netpgp_setvar(netpgp, "coredumps", "allowed");
392 		break;
393 	case GENERATE_KEY:
394 		netpgp_setvar(netpgp, "userid checks", "skip");
395 		p->cmd = val;
396 		break;
397 	case LIST_KEYS:
398 	case LIST_SIGS:
399 	case FIND_KEY:
400 	case EXPORT_KEY:
401 	case IMPORT_KEY:
402 	case GET_KEY:
403 	case HELP_CMD:
404 		p->cmd = val;
405 		break;
406 	case VERSION_CMD:
407 		printf(
408 "%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
409 			netpgp_get_info("version"),
410 			netpgp_get_info("maintainer"));
411 		exit(EXIT_SUCCESS);
412 		/* options */
413 	case SSHKEYS:
414 		netpgp_setvar(netpgp, "ssh keys", "1");
415 		break;
416 	case KEYRING:
417 		if (arg == NULL) {
418 			(void) fprintf(stderr,
419 				"No keyring argument provided\n");
420 			exit(EXIT_ERROR);
421 		}
422 		snprintf(p->keyring, sizeof(p->keyring), "%s", arg);
423 		break;
424 	case USERID:
425 		if (optarg == NULL) {
426 			(void) fprintf(stderr,
427 				"no userid argument provided\n");
428 			exit(EXIT_ERROR);
429 		}
430 		netpgp_setvar(netpgp, "userid", arg);
431 		break;
432 	case VERBOSE:
433 		netpgp_incvar(netpgp, "verbose", 1);
434 		break;
435 	case HOMEDIR:
436 		if (arg == NULL) {
437 			(void) fprintf(stderr,
438 			"no home directory argument provided\n");
439 			exit(EXIT_ERROR);
440 		}
441 		netpgp_set_homedir(netpgp, arg, NULL, 0);
442 		*homeset = 1;
443 		break;
444 	case NUMBITS:
445 		if (arg == NULL) {
446 			(void) fprintf(stderr,
447 			"no number of bits argument provided\n");
448 			exit(EXIT_ERROR);
449 		}
450 		p->numbits = atoi(arg);
451 		break;
452 	case HASH_ALG:
453 		if (arg == NULL) {
454 			(void) fprintf(stderr,
455 			"No hash algorithm argument provided\n");
456 			exit(EXIT_ERROR);
457 		}
458 		netpgp_setvar(netpgp, "hash", arg);
459 		break;
460 	case PASSWDFD:
461 		if (arg == NULL) {
462 			(void) fprintf(stderr,
463 			"no pass-fd argument provided\n");
464 			exit(EXIT_ERROR);
465 		}
466 		netpgp_setvar(netpgp, "pass-fd", arg);
467 		break;
468 	case RESULTS:
469 		if (arg == NULL) {
470 			(void) fprintf(stderr,
471 			"No output filename argument provided\n");
472 			exit(EXIT_ERROR);
473 		}
474 		netpgp_setvar(netpgp, "res", arg);
475 		break;
476 	case SSHKEYFILE:
477 		netpgp_setvar(netpgp, "sshkeyfile", arg);
478 		break;
479 	case OPS_DEBUG:
480 		netpgp_set_debug(arg);
481 		break;
482 	default:
483 		p->cmd = HELP_CMD;
484 		break;
485 	}
486 }
487 
488 /* we have -o option=value -- parse, and process */
489 static int
490 parse_option(netpgp_t *netpgp, prog_t *p, const char *s, int *homeset)
491 {
492 	static regex_t	 opt;
493 	struct option	*op;
494 	static int	 compiled;
495 	regmatch_t	 matches[10];
496 	char		 option[128];
497 	char		 value[128];
498 
499 	if (!compiled) {
500 		compiled = 1;
501 		(void) regcomp(&opt, "([^=]{1,128})(=(.*))?", REG_EXTENDED);
502 	}
503 	if (regexec(&opt, s, 10, matches, 0) == 0) {
504 		(void) snprintf(option, sizeof(option), "%.*s",
505 			(int)(matches[1].rm_eo - matches[1].rm_so), &s[matches[1].rm_so]);
506 		if (matches[2].rm_so > 0) {
507 			(void) snprintf(value, sizeof(value), "%.*s",
508 				(int)(matches[3].rm_eo - matches[3].rm_so), &s[matches[3].rm_so]);
509 		} else {
510 			value[0] = 0x0;
511 		}
512 		for (op = options ; op->name ; op++) {
513 			if (strcmp(op->name, option) == 0) {
514 				return setoption(netpgp, p, op->val, value, homeset);
515 			}
516 		}
517 	}
518 	return 0;
519 }
520 
521 int
522 main(int argc, char **argv)
523 {
524 	struct stat	st;
525 	netpgp_t	netpgp;
526 	prog_t          p;
527 	int             homeset;
528 	int             optindex;
529 	int             ret;
530 	int             ch;
531 	int             i;
532 
533 	(void) memset(&p, 0x0, sizeof(p));
534 	(void) memset(&netpgp, 0x0, sizeof(netpgp));
535 	homeset = 0;
536 	p.progname = argv[0];
537 	p.numbits = DEFAULT_NUMBITS;
538 	if (argc < 2) {
539 		print_usage(usage, p.progname);
540 		exit(EXIT_ERROR);
541 	}
542 	/* set some defaults */
543 	netpgp_setvar(&netpgp, "sshkeydir", "/etc/ssh");
544 	netpgp_setvar(&netpgp, "res", "<stdout>");
545 	netpgp_setvar(&netpgp, "hash", DEFAULT_HASH_ALG);
546 	optindex = 0;
547 	while ((ch = getopt_long(argc, argv, "Vglo:s", options, &optindex)) != -1) {
548 		if (ch >= LIST_KEYS) {
549 			/* getopt_long returns 0 for long options */
550 			if (!setoption(&netpgp, &p, options[optindex].val, optarg, &homeset)) {
551 				(void) fprintf(stderr, "Bad option\n");
552 			}
553 		} else {
554 			switch (ch) {
555 			case 'V':
556 				printf(
557 	"%s\nAll bug reports, praise and chocolate, please, to:\n%s\n",
558 					netpgp_get_info("version"),
559 					netpgp_get_info("maintainer"));
560 				exit(EXIT_SUCCESS);
561 			case 'g':
562 				p.cmd = GENERATE_KEY;
563 				break;
564 			case 'l':
565 				p.cmd = LIST_KEYS;
566 				break;
567 			case 'o':
568 				if (!parse_option(&netpgp, &p, optarg, &homeset)) {
569 					(void) fprintf(stderr, "Bad option\n");
570 				}
571 				break;
572 			case 's':
573 				p.cmd = LIST_SIGS;
574 				break;
575 			default:
576 				p.cmd = HELP_CMD;
577 				break;
578 			}
579 		}
580 	}
581 	if (!homeset) {
582 		netpgp_set_homedir(&netpgp, getenv("HOME"),
583 			netpgp_getvar(&netpgp, "ssh keys") ? "/.ssh" : "/.gnupg", 1);
584 	}
585 	/* initialise, and read keys from file */
586 	if (!netpgp_init(&netpgp)) {
587 		if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) {
588 			(void) mkdir(netpgp_getvar(&netpgp, "homedir"), 0700);
589 		}
590 		if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) {
591 			(void) fprintf(stderr, "can't create home directory '%s'\n",
592 				netpgp_getvar(&netpgp, "homedir"));
593 			exit(EXIT_ERROR);
594 		}
595 	}
596 	/* now do the required action for each of the command line args */
597 	ret = EXIT_SUCCESS;
598 	if (optind == argc) {
599 		if (!netpgp_cmd(&netpgp, &p, NULL)) {
600 			ret = EXIT_FAILURE;
601 		}
602 	} else {
603 		for (i = optind; i < argc; i++) {
604 			if (!netpgp_cmd(&netpgp, &p, argv[i])) {
605 				ret = EXIT_FAILURE;
606 			}
607 		}
608 	}
609 	netpgp_end(&netpgp);
610 	exit(ret);
611 }
612