1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * Copyright (C) 1998 by the FundsXpress, INC. 4 * 5 * All rights reserved. 6 * 7 * Export of this software from the United States of America may require 8 * a specific license from the United States Government. It is the 9 * responsibility of any person or organization contemplating export to 10 * obtain such a license before exporting. 11 * 12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 13 * distribute this software and its documentation for any purpose and 14 * without fee is hereby granted, provided that the above copyright 15 * notice appear in all copies and that both that copyright notice and 16 * this permission notice appear in supporting documentation, and that 17 * the name of FundsXpress. not be used in advertising or publicity pertaining 18 * to distribution of the software without specific, written prior 19 * permission. FundsXpress makes no representations about the suitability of 20 * this software for any purpose. It is provided "as is" without express 21 * or implied warranty. 22 * 23 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 24 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 25 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 26 */ 27 28 #include "k5-platform.h" 29 #include "k5-buf.h" 30 #include "k5-base64.h" 31 #include <locale.h> 32 #ifdef HAVE_UNISTD_H 33 #include <unistd.h> 34 #endif 35 #include <string.h> 36 #include <ctype.h> 37 38 static char *prog; 39 static int quiet = 0; 40 41 static void 42 xusage(void) 43 { 44 fprintf(stderr, _("usage: %s [-c ccache] [-e etype] [-k keytab] [-q] " 45 "[-u | -S sname]\n" 46 "\t[[{-F cert_file | {-I | -U} for_user} [-P]] | " 47 "--u2u ccache]\n" 48 "\t[--cached-only] [--no-store] [--out-cache] " 49 "service1 service2 ...\n"), 50 prog); 51 exit(1); 52 } 53 54 static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr, 55 char *keytab_name, char *sname, int cached_only, 56 int canon, int no_store, int unknown, char *for_user, 57 int for_user_enterprise, char *for_user_cert_file, 58 int proxy, const char *out_ccname, 59 const char *u2u_ccname); 60 61 #include <com_err.h> 62 static void extended_com_err_fn(const char *myprog, errcode_t code, 63 const char *fmt, va_list args); 64 65 int 66 main(int argc, char *argv[]) 67 { 68 enum { OPTION_U2U = 256, OPTION_OUT_CACHE = 257 }; 69 const char *shopts = "uCc:e:hk:qPS:I:U:F:"; 70 int option; 71 char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL; 72 char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL; 73 char *for_user_cert_file = NULL, *out_ccname = NULL; 74 int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0; 75 int impersonate = 0, cached_only = 0, no_store = 0; 76 struct option lopts[] = { 77 { "cached-only", 0, &cached_only, 1 }, 78 { "no-store", 0, &no_store, 1 }, 79 { "out-cache", 1, NULL, OPTION_OUT_CACHE }, 80 { "u2u", 1, NULL, OPTION_U2U }, 81 { NULL, 0, NULL, 0 } 82 }; 83 84 setlocale(LC_ALL, ""); 85 set_com_err_hook(extended_com_err_fn); 86 87 prog = strrchr(argv[0], '/'); 88 prog = prog ? (prog + 1) : argv[0]; 89 90 while ((option = getopt_long(argc, argv, shopts, lopts, NULL)) != -1) { 91 switch (option) { 92 case 'C': 93 canon = 1; 94 break; 95 case 'c': 96 ccachestr = optarg; 97 break; 98 case 'e': 99 etypestr = optarg; 100 break; 101 case 'h': 102 xusage(); 103 break; 104 case 'k': 105 keytab_name = optarg; 106 break; 107 case 'q': 108 quiet = 1; 109 break; 110 case 'P': 111 proxy = 1; /* S4U2Proxy - constrained delegation */ 112 break; 113 case 'S': 114 sname = optarg; 115 if (unknown == 1) { 116 fprintf(stderr, 117 _("Options -u and -S are mutually exclusive\n")); 118 xusage(); 119 } 120 break; 121 case 'u': 122 unknown = 1; 123 if (sname != NULL) { 124 fprintf(stderr, 125 _("Options -u and -S are mutually exclusive\n")); 126 xusage(); 127 } 128 break; 129 case 'I': 130 impersonate = 1; 131 for_user = optarg; 132 break; 133 case 'U': 134 impersonate = 1; 135 for_user_enterprise = 1; 136 for_user = optarg; 137 break; 138 case 'F': 139 impersonate = 1; 140 for_user_cert_file = optarg; 141 break; 142 case OPTION_U2U: 143 u2u_ccname = optarg; 144 break; 145 case OPTION_OUT_CACHE: 146 out_ccname = optarg; 147 break; 148 case 0: 149 /* If this option set a flag, do nothing else now. */ 150 break; 151 default: 152 xusage(); 153 break; 154 } 155 } 156 157 if (u2u_ccname != NULL && impersonate) { 158 fprintf(stderr, 159 _("Options --u2u and -I|-U|-F are mutually exclusive\n")); 160 xusage(); 161 } 162 163 if (proxy) { 164 if (!impersonate) { 165 fprintf(stderr, _("Option -P (constrained delegation) requires " 166 "option -I|-U|-F (protocol transition)\n")); 167 xusage(); 168 } 169 } 170 171 if (argc - optind < 1) 172 xusage(); 173 174 do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name, 175 sname, cached_only, canon, no_store, unknown, for_user, 176 for_user_enterprise, for_user_cert_file, proxy, out_ccname, 177 u2u_ccname); 178 return 0; 179 } 180 181 #include <k5-int.h> 182 static krb5_context context; 183 static void extended_com_err_fn(const char *myprog, errcode_t code, 184 const char *fmt, va_list args) 185 { 186 const char *emsg; 187 188 emsg = krb5_get_error_message(context, code); 189 fprintf(stderr, "%s: %s ", myprog, emsg); 190 krb5_free_error_message(context, emsg); 191 vfprintf(stderr, fmt, args); 192 fprintf(stderr, "\n"); 193 } 194 195 /* Read a line from fp into buf. Trim any trailing whitespace, and return a 196 * pointer to the first non-whitespace character. */ 197 static const char * 198 read_line(FILE *fp, char *buf, size_t bufsize) 199 { 200 char *end, *begin; 201 202 if (fgets(buf, bufsize, fp) == NULL) 203 return NULL; 204 205 end = buf + strlen(buf); 206 while (end > buf && isspace((uint8_t)end[-1])) 207 *--end = '\0'; 208 209 begin = buf; 210 while (isspace((uint8_t)*begin)) 211 begin++; 212 213 return begin; 214 } 215 216 /* Read a certificate from file_name in PEM format, placing the DER 217 * representation of the certificate in *der_out. */ 218 static krb5_error_code 219 read_pem_file(char *file_name, krb5_data *der_out) 220 { 221 krb5_error_code ret = 0; 222 FILE *fp = NULL; 223 const char *begin_line = "-----BEGIN CERTIFICATE-----"; 224 const char *end_line = "-----END ", *line; 225 char linebuf[256], *b64; 226 struct k5buf buf = EMPTY_K5BUF; 227 uint8_t *der_cert; 228 size_t dlen; 229 230 *der_out = empty_data(); 231 232 fp = fopen(file_name, "r"); 233 if (fp == NULL) 234 return errno; 235 236 for (;;) { 237 line = read_line(fp, linebuf, sizeof(linebuf)); 238 if (line == NULL) { 239 ret = EINVAL; 240 k5_setmsg(context, ret, _("No begin line not found")); 241 goto cleanup; 242 } 243 if (strncmp(line, begin_line, strlen(begin_line)) == 0) 244 break; 245 } 246 247 k5_buf_init_dynamic(&buf); 248 for (;;) { 249 line = read_line(fp, linebuf, sizeof(linebuf)); 250 if (line == NULL) { 251 ret = EINVAL; 252 k5_setmsg(context, ret, _("No end line found")); 253 goto cleanup; 254 } 255 256 if (strncmp(line, end_line, strlen(end_line)) == 0) 257 break; 258 259 /* Header lines would be expected for an actual privacy-enhanced mail 260 * message, but not for a certificate. */ 261 if (*line == '\0' || strchr(line, ':') != NULL) { 262 ret = EINVAL; 263 k5_setmsg(context, ret, _("Unexpected header line")); 264 goto cleanup; 265 } 266 267 k5_buf_add(&buf, line); 268 } 269 270 b64 = k5_buf_cstring(&buf); 271 if (b64 == NULL) { 272 ret = ENOMEM; 273 goto cleanup; 274 } 275 der_cert = k5_base64_decode(b64, &dlen); 276 if (der_cert == NULL) { 277 ret = EINVAL; 278 k5_setmsg(context, ret, _("Invalid base64")); 279 goto cleanup; 280 } 281 282 *der_out = make_data(der_cert, dlen); 283 284 cleanup: 285 fclose(fp); 286 k5_buf_free(&buf); 287 return ret; 288 } 289 290 /* Request a single service ticket and display its status (unless quiet is 291 * set). On failure, display an error message and return non-zero. */ 292 static krb5_error_code 293 kvno(const char *name, krb5_ccache ccache, krb5_principal me, 294 krb5_enctype etype, krb5_keytab keytab, const char *sname, 295 krb5_flags options, int unknown, krb5_principal for_user_princ, 296 krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket, 297 krb5_creds **creds_out) 298 { 299 krb5_error_code ret; 300 krb5_principal server = NULL; 301 krb5_ticket *ticket = NULL; 302 krb5_creds in_creds, *creds = NULL; 303 char *princ = NULL; 304 305 *creds_out = NULL; 306 memset(&in_creds, 0, sizeof(in_creds)); 307 308 if (sname != NULL) { 309 ret = krb5_sname_to_principal(context, name, sname, KRB5_NT_SRV_HST, 310 &server); 311 } else { 312 ret = krb5_parse_name(context, name, &server); 313 } 314 if (ret) { 315 if (!quiet) 316 com_err(prog, ret, _("while parsing principal name %s"), name); 317 goto cleanup; 318 } 319 if (unknown) 320 krb5_princ_type(context, server) = KRB5_NT_UNKNOWN; 321 322 ret = krb5_unparse_name(context, server, &princ); 323 if (ret) { 324 com_err(prog, ret, _("while formatting parsed principal name for " 325 "'%s'"), name); 326 goto cleanup; 327 } 328 329 in_creds.keyblock.enctype = etype; 330 331 if (u2u_ticket != NULL) 332 in_creds.second_ticket = *u2u_ticket; 333 334 if (for_user_princ != NULL || for_user_cert != NULL) { 335 if (!proxy && !krb5_principal_compare(context, me, server)) { 336 ret = EINVAL; 337 com_err(prog, ret, 338 _("client and server principal names must match")); 339 goto cleanup; 340 } 341 342 in_creds.client = for_user_princ; 343 in_creds.server = me; 344 ret = krb5_get_credentials_for_user(context, options, ccache, 345 &in_creds, for_user_cert, &creds); 346 } else { 347 in_creds.client = me; 348 in_creds.server = server; 349 ret = krb5_get_credentials(context, options, ccache, &in_creds, 350 &creds); 351 } 352 353 if (ret) { 354 com_err(prog, ret, _("while getting credentials for %s"), princ); 355 goto cleanup; 356 } 357 358 /* We need a native ticket. */ 359 ret = krb5_decode_ticket(&creds->ticket, &ticket); 360 if (ret) { 361 com_err(prog, ret, _("while decoding ticket for %s"), princ); 362 goto cleanup; 363 } 364 365 if (keytab != NULL) { 366 ret = krb5_server_decrypt_ticket_keytab(context, keytab, ticket); 367 if (ret) { 368 if (!quiet) { 369 fprintf(stderr, "%s: kvno = %d, keytab entry invalid\n", princ, 370 ticket->enc_part.kvno); 371 } 372 com_err(prog, ret, _("while decrypting ticket for %s"), princ); 373 goto cleanup; 374 } 375 if (!quiet) { 376 printf(_("%s: kvno = %d, keytab entry valid\n"), princ, 377 ticket->enc_part.kvno); 378 } 379 } else { 380 if (!quiet) 381 printf(_("%s: kvno = %d\n"), princ, ticket->enc_part.kvno); 382 } 383 384 if (proxy) { 385 in_creds.client = creds->client; 386 creds->client = NULL; 387 krb5_free_creds(context, creds); 388 creds = NULL; 389 in_creds.server = server; 390 391 ret = krb5_get_credentials_for_proxy(context, KRB5_GC_CANONICALIZE, 392 ccache, &in_creds, ticket, 393 &creds); 394 krb5_free_principal(context, in_creds.client); 395 if (ret) { 396 com_err(prog, ret, _("%s: constrained delegation failed"), 397 princ); 398 goto cleanup; 399 } 400 } 401 402 *creds_out = creds; 403 creds = NULL; 404 405 cleanup: 406 krb5_free_principal(context, server); 407 krb5_free_ticket(context, ticket); 408 krb5_free_creds(context, creds); 409 krb5_free_unparsed_name(context, princ); 410 return ret; 411 } 412 413 /* Fetch the encoded local TGT for ccname's default client principal. */ 414 static krb5_error_code 415 get_u2u_ticket(const char *ccname, krb5_data **ticket_out) 416 { 417 krb5_error_code ret; 418 krb5_ccache cc = NULL; 419 krb5_creds mcred, *creds = NULL; 420 421 *ticket_out = NULL; 422 memset(&mcred, 0, sizeof(mcred)); 423 424 ret = krb5_cc_resolve(context, ccname, &cc); 425 if (ret) 426 goto cleanup; 427 ret = krb5_cc_get_principal(context, cc, &mcred.client); 428 if (ret) 429 goto cleanup; 430 ret = krb5_build_principal_ext(context, &mcred.server, 431 mcred.client->realm.length, 432 mcred.client->realm.data, 433 KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, 434 mcred.client->realm.length, 435 mcred.client->realm.data, 0); 436 if (ret) 437 goto cleanup; 438 ret = krb5_get_credentials(context, KRB5_GC_CACHED, cc, &mcred, &creds); 439 if (ret) 440 goto cleanup; 441 442 ret = krb5_copy_data(context, &creds->ticket, ticket_out); 443 444 cleanup: 445 if (cc != NULL) 446 krb5_cc_close(context, cc); 447 krb5_free_cred_contents(context, &mcred); 448 krb5_free_creds(context, creds); 449 return ret; 450 } 451 452 static void 453 do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, 454 char *keytab_name, char *sname, int cached_only, int canon, 455 int no_store, int unknown, char *for_user, int for_user_enterprise, 456 char *for_user_cert_file, int proxy, const char *out_ccname, 457 const char *u2u_ccname) 458 { 459 krb5_error_code ret; 460 int i, errors, flags, initialized = 0; 461 krb5_enctype etype; 462 krb5_ccache ccache, mcc, out_ccache = NULL; 463 krb5_principal me; 464 krb5_keytab keytab = NULL; 465 krb5_principal for_user_princ = NULL; 466 krb5_flags options = 0; 467 krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL; 468 krb5_creds *creds; 469 470 if (canon) 471 options |= KRB5_GC_CANONICALIZE; 472 if (cached_only) 473 options |= KRB5_GC_CACHED; 474 if (no_store || out_ccname != NULL) 475 options |= KRB5_GC_NO_STORE; 476 477 ret = krb5_init_context(&context); 478 if (ret) { 479 com_err(prog, ret, _("while initializing krb5 library")); 480 exit(1); 481 } 482 483 if (etypestr) { 484 ret = krb5_string_to_enctype(etypestr, &etype); 485 if (ret) { 486 com_err(prog, ret, _("while converting etype")); 487 exit(1); 488 } 489 } else { 490 etype = 0; 491 } 492 493 if (ccachestr) 494 ret = krb5_cc_resolve(context, ccachestr, &ccache); 495 else 496 ret = krb5_cc_default(context, &ccache); 497 if (ret) { 498 com_err(prog, ret, _("while opening ccache")); 499 exit(1); 500 } 501 502 if (out_ccname != NULL) { 503 ret = krb5_cc_resolve(context, out_ccname, &out_ccache); 504 if (ret) { 505 com_err(prog, ret, _("while resolving output ccache")); 506 exit(1); 507 } 508 } 509 510 if (keytab_name != NULL) { 511 ret = krb5_kt_resolve(context, keytab_name, &keytab); 512 if (ret) { 513 com_err(prog, ret, _("resolving keytab %s"), keytab_name); 514 exit(1); 515 } 516 } 517 518 if (for_user) { 519 flags = for_user_enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0; 520 ret = krb5_parse_name_flags(context, for_user, flags, &for_user_princ); 521 if (ret) { 522 com_err(prog, ret, _("while parsing principal name %s"), for_user); 523 exit(1); 524 } 525 } 526 527 if (for_user_cert_file != NULL) { 528 ret = read_pem_file(for_user_cert_file, &cert_data); 529 if (ret) { 530 com_err(prog, ret, _("while reading certificate file %s"), 531 for_user_cert_file); 532 exit(1); 533 } 534 user_cert = &cert_data; 535 } 536 537 if (u2u_ccname != NULL) { 538 ret = get_u2u_ticket(u2u_ccname, &u2u_ticket); 539 if (ret) { 540 com_err(prog, ret, _("while getting user-to-user ticket from %s"), 541 u2u_ccname); 542 exit(1); 543 } 544 options |= KRB5_GC_USER_USER; 545 } 546 547 ret = krb5_cc_get_principal(context, ccache, &me); 548 if (ret) { 549 com_err(prog, ret, _("while getting client principal name")); 550 exit(1); 551 } 552 553 if (out_ccache != NULL) { 554 ret = krb5_cc_new_unique(context, "MEMORY", NULL, &mcc); 555 if (ret) { 556 com_err(prog, ret, _("while creating temporary output ccache")); 557 exit(1); 558 } 559 } 560 561 errors = 0; 562 for (i = 0; i < count; i++) { 563 if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown, 564 for_user_princ, user_cert, proxy, u2u_ticket, &creds) != 0) { 565 errors++; 566 } else if (out_ccache != NULL) { 567 if (!initialized) { 568 ret = krb5_cc_initialize(context, mcc, creds->client); 569 if (ret) { 570 com_err(prog, ret, _("while initializing output ccache")); 571 exit(1); 572 } 573 initialized = 1; 574 } 575 if (count == 1) 576 ret = k5_cc_store_primary_cred(context, mcc, creds); 577 else 578 ret = krb5_cc_store_cred(context, mcc, creds); 579 if (ret) { 580 com_err(prog, ret, _("while storing creds in output ccache")); 581 exit(1); 582 } 583 } 584 585 krb5_free_creds(context, creds); 586 } 587 588 if (!errors && out_ccache != NULL) { 589 ret = krb5_cc_move(context, mcc, out_ccache); 590 if (ret) { 591 com_err(prog, ret, _("while writing output ccache")); 592 exit(1); 593 } 594 } 595 596 if (keytab != NULL) 597 krb5_kt_close(context, keytab); 598 krb5_free_principal(context, me); 599 krb5_free_principal(context, for_user_princ); 600 krb5_cc_close(context, ccache); 601 krb5_free_data(context, u2u_ticket); 602 krb5_free_data_contents(context, &cert_data); 603 krb5_free_context(context); 604 605 if (errors) 606 exit(1); 607 608 exit(0); 609 } 610