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 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 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 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 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 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 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 if (p.keyring[0] != '\0') { 458 netpgp_setvar(&netpgp, "pubring", p.keyring); 459 } 460 /* initialise, and read keys from file */ 461 if (!netpgp_init(&netpgp)) { 462 if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) { 463 (void) mkdir(netpgp_getvar(&netpgp, "homedir"), 0700); 464 } 465 if (stat(netpgp_getvar(&netpgp, "homedir"), &st) < 0) { 466 (void) fprintf(stderr, "can't create home directory '%s'\n", 467 netpgp_getvar(&netpgp, "homedir")); 468 exit(EXIT_ERROR); 469 } 470 } 471 /* now do the required action for each of the command line args */ 472 ret = EXIT_SUCCESS; 473 if (optind == argc) { 474 if (!netpgp_cmd(&netpgp, &p, NULL)) { 475 ret = EXIT_FAILURE; 476 } 477 } else { 478 for (i = optind; i < argc; i++) { 479 if (!netpgp_cmd(&netpgp, &p, argv[i])) { 480 ret = EXIT_FAILURE; 481 } 482 } 483 } 484 netpgp_end(&netpgp); 485 exit(ret); 486 } 487