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