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