1/* 2 * (c) Copyright 1995 HEWLETT-PACKARD COMPANY 3 * 4 * To anyone who acknowledges that this file is provided 5 * "AS IS" without any express or implied warranty: 6 * permission to use, copy, modify, and distribute this 7 * file for any purpose is hereby granted without fee, 8 * provided that the above copyright notice and this 9 * notice appears in all copies, and that the name of 10 * Hewlett-Packard Company not be used in advertising or 11 * publicity pertaining to distribution of the software 12 * without specific, written prior permission. Hewlett- 13 * Packard Company makes no representations about the 14 * suitability of this software for any purpose. 15 * 16 */ 17/* 18 * k5dcecon - Program to convert a K5 TGT to a DCE context, 19 * for use with DFS and its PAG. 20 * 21 * The program is designed to be called as a sub process, 22 * and return via stdout the name of the cache which implies 23 * the PAG which should be used. This program itself does not 24 * use the cache or PAG itself, so the PAG in the kernel for 25 * this program may not be set. 26 * 27 * The calling program can then use the name of the cache 28 * to set the KRB5CCNAME and PAG for itself and its children. 29 * 30 * If no ticket was passed, an attemplt to join an existing 31 * PAG will be made. 32 * 33 * If a forwarded K5 TGT is passed in, either a new DCE 34 * context will be created, or an existing one will be updated. 35 * If the same ticket was already used to create an existing 36 * context, it will be joined instead. 37 * 38 * Parts of this program are based on k5dceauth,c which was 39 * given to me by HP and by the k5dcelogin.c which I developed. 40 * A slightly different version of k5dcelogin.c, was added to 41 * DCE 1.2.2 42 * 43 * D. E. Engert 6/17/97 ANL 44 */ 45 46#include <stdio.h> 47#include <stdlib.h> 48#include <fcntl.h> 49#include <sys/types.h> 50#include <dirent.h> 51#include <sys/stat.h> 52#include <locale.h> 53#include <pwd.h> 54#include <string.h> 55#include <time.h> 56 57#include <errno.h> 58#include "k5dce.h" 59 60#include <dce/sec_login.h> 61#include <dce/dce_error.h> 62#include <dce/passwd.h> 63 64/* #define DEBUG */ 65#if defined(DEBUG) 66#define DEEDEBUG(A) fprintf(stderr,A); fflush(stderr) 67#define DEEDEBUG2(A,B) fprintf(stderr,A,B); fflush(stderr) 68#else 69#define DEEDEBUG(A) 70#define DEEDEBUG2(A,B) 71#endif 72 73#ifdef __hpux 74#define seteuid(A) setresuid(-1,A,-1) 75#endif 76 77 78int k5dcecreate (uid_t, char *, char*, krb5_creds **); 79int k5dcecon (uid_t, char *, char *); 80int k5dcegettgt (krb5_ccache *, char *, char *, krb5_creds **); 81int k5dcematch (uid_t, char *, char *, off_t *, krb5_creds **); 82int k5dcesession (uid_t, char *, krb5_creds **, int *,krb5_flags); 83 84 85char *progname = "k5dcecon"; 86static time_t now; 87 88#ifdef notdef 89#ifdef _AIX 90/*---------------------------------------------*/ 91 /* AIX with DCE 1.1 does not have the com_err in the libdce.a 92 * do a half hearted job of substituting for it. 93 */ 94void com_err(char *p1, int code, ...) 95{ 96 int lst; 97 dce_error_string_t err_string; 98 dce_error_inq_text(code, err_string, &lst); 99 fprintf(stderr,"Error %d in %s: %s\n", code, p1, err_string ); 100} 101 102/*---------------------------------------------*/ 103void krb5_init_ets() 104{ 105 106} 107#endif 108#endif 109 110 111/*------------------------------------------------*/ 112/* find a cache to use for our new pag */ 113/* Since there is no simple way to determine which 114 * caches are associated with a pag, we will have 115 * do look around and see what makes most sense on 116 * different systems. 117 * on a Solaris system, and in the DCE source, 118 * the pags always start with a 41. 119 * this is not true on the IBM, where there does not 120 * appear to be any pattern. 121 * 122 * But since we are always certifing our creds when 123 * they are received, we can us that fact, and look 124 * at the first word of the associated data file 125 * to see that it has a "5". If not don't use. 126 */ 127 128int k5dcesession(luid, pname, tgt, ppag, tflags) 129 uid_t luid; 130 char *pname; 131 krb5_creds **tgt; 132 int *ppag; 133 krb5_flags tflags; 134{ 135 DIR *dirp; 136 struct dirent *direntp; 137 off_t size; 138 krb5_timestamp endtime; 139 int better = 0; 140 krb5_creds *xtgt; 141 142 char prev_name[17] = ""; 143 krb5_timestamp prev_endtime; 144 off_t prev_size; 145 u_long prev_pag = 0; 146 147 char ccname[64] = "FILE:/opt/dcelocal/var/security/creds/"; 148 149 error_status_t st; 150 sec_login_handle_t lcontext = 0; 151 dce_error_string_t err_string; 152 int lst; 153 154 DEEDEBUG2("k5dcesession looking for flags %8.8x\n",tflags); 155 156 dirp = opendir("/opt/dcelocal/var/security/creds/"); 157 if (dirp == NULL) { 158 return 1; 159 } 160 161 while ( (direntp = readdir( dirp )) != NULL ) { 162 163/* 164 * (but root has the ffffffff which we are not interested in) 165 */ 166 if (!strncmp(direntp->d_name,"dcecred_",8) 167 && (strlen(direntp->d_name) == 16)) { 168 169 /* looks like a cache name, lets do the stat, etc */ 170 171 strcpy(ccname+38,direntp->d_name); 172 if (!k5dcematch(luid, pname, ccname, &size, &xtgt)) { 173 174 /* it's one of our caches, see if it is better 175 * i.e. the endtime is farther, and if the endtimes 176 * are the same, take the larger, as he who has the 177 * most tickets wins. 178 * it must also had the same set of flags at least 179 * i.e. if the forwarded TGT is forwardable, this one must 180 * be as well. 181 */ 182 183 DEEDEBUG2("Cache:%s",direntp->d_name); 184 DEEDEBUG2(" size:%d",size); 185 DEEDEBUG2(" flags:%8.8x",xtgt->ticket_flags); 186 DEEDEBUG2(" %s",ctime((time_t *)&xtgt->times.endtime)); 187 188 if ((xtgt->ticket_flags & tflags) == tflags ) { 189 if (prev_name[0]) { 190 if (xtgt->times.endtime > prev_endtime) { 191 better = 1; 192 } else if ((xtgt->times.endtime = prev_endtime) 193 && (size > prev_size)){ 194 better = 1; 195 } 196 } else { /* the first */ 197 if (xtgt->times.endtime >= now) { 198 better = 1; 199 } 200 } 201 if (better) { 202 strcpy(prev_name, direntp->d_name); 203 prev_endtime = xtgt->times.endtime; 204 prev_size = size; 205 sscanf(prev_name+8,"%8X",&prev_pag); 206 *tgt = xtgt; 207 better = 0; 208 } 209 } 210 } 211 } 212 } 213 (void)closedir( dirp ); 214 215 if (!prev_name[0]) 216 return 1; /* failed to find one */ 217 218 DEEDEBUG2("Best: %s\n",prev_name); 219 220 if (ppag) 221 *ppag = prev_pag; 222 223 strcpy(ccname+38,prev_name); 224 setenv("KRB5CCNAME",ccname,1); 225 226 return(0); 227} 228 229 230/*----------------------------------------------*/ 231/* see if this cache is for this this principal */ 232 233int k5dcematch(luid, pname, ccname, sizep, tgt) 234 uid_t luid; 235 char *pname; 236 char *ccname; 237 off_t *sizep; /* size of the file */ 238 krb5_creds **tgt; 239{ 240 241 krb5_ccache cache; 242 struct stat stbuf; 243 char ccdata[256]; 244 int fd; 245 int status; 246 247 /* DEEDEBUG2("k5dcematch called: cache=%s\n",ccname+38); */ 248 249 if (!strncmp(ccname,"FILE:",5)) { 250 251 strcpy(ccdata,ccname+5); 252 strcat(ccdata,".data"); 253 254 /* DEEDEBUG2("Checking the .data file for %s\n",ccdata); */ 255 256 if (stat(ccdata, &stbuf)) 257 return(1); 258 259 if (stbuf.st_uid != luid) 260 return(1); 261 262 if ((fd = open(ccdata,O_RDONLY)) == -1) 263 return(1); 264 265 if ((read(fd,&status,4)) != 4) { 266 close(fd); 267 return(1); 268 } 269 270 /* DEEDEBUG2(".data file status = %d\n", status); */ 271 272 if (status != 5) 273 return(1); 274 275 if (stat(ccname+5, &stbuf)) 276 return(1); 277 278 if (stbuf.st_uid != luid) 279 return(1); 280 281 *sizep = stbuf.st_size; 282 } 283 284 return(k5dcegettgt(&cache, ccname, pname, tgt)); 285} 286 287 288/*----------------------------------------*/ 289/* k5dcegettgt - get the tgt from a cache */ 290 291int k5dcegettgt(pcache, ccname, pname, tgt) 292 krb5_ccache *pcache; 293 char *ccname; 294 char *pname; 295 krb5_creds **tgt; 296 297{ 298 krb5_ccache cache; 299 krb5_cc_cursor cur; 300 krb5_creds creds; 301 int code; 302 int found = 1; 303 krb5_principal princ; 304 char *kusername; 305 krb5_flags flags; 306 char *sname, *realm, *tgtname = NULL; 307 308 /* Since DCE does not expose much of the Kerberos interface, 309 * we will have to use what we can. This means setting the 310 * KRB5CCNAME for each file we want to test 311 * We will also not worry about freeing extra cache structures 312 * as this this routine is also not exposed, and this should not 313 * effect this module. 314 * We should also free the creds contents, but that is not exposed 315 * either. 316 */ 317 318 setenv("KRB5CCNAME",ccname,1); 319 cache = NULL; 320 *tgt = NULL; 321 322 if (code = krb5_cc_default(pcache)) { 323 com_err(progname, code, "while getting ccache"); 324 goto return2; 325 } 326 327 DEEDEBUG("Got cache\n"); 328 flags = 0; 329 if (code = krb5_cc_set_flags(*pcache, flags)) { 330 com_err(progname, code,"While setting flags"); 331 goto return2; 332 } 333 DEEDEBUG("Set flags\n"); 334 if (code = krb5_cc_get_principal(*pcache, &princ)) { 335 com_err(progname, code, "While getting princ"); 336 goto return1; 337 } 338 DEEDEBUG("Got principal\n"); 339 if (code = krb5_unparse_name(princ, &kusername)) { 340 com_err(progname, code, "While unparsing principal"); 341 goto return1; 342 } 343 344 DEEDEBUG2("Unparsed to \"%s\"\n", kusername); 345 DEEDEBUG2("pname is \"%s\"\n", pname); 346 if (strcmp(kusername, pname)) { 347 DEEDEBUG("Principals not equal\n"); 348 goto return1; 349 } 350 DEEDEBUG("Principals equal\n"); 351 352 realm = strchr(pname,'@'); 353 realm++; 354 355 if ((tgtname = malloc(9 + 2 * strlen(realm))) == 0) { 356 fprintf(stderr,"Malloc failed for tgtname\n"); 357 goto return1; 358 } 359 360 strcpy(tgtname,"krbtgt/"); 361 strcat(tgtname,realm); 362 strcat(tgtname,"@"); 363 strcat(tgtname,realm); 364 365 DEEDEBUG2("Getting tgt %s\n", tgtname); 366 if (code = krb5_cc_start_seq_get(*pcache, &cur)) { 367 com_err(progname, code, "while starting to retrieve tickets"); 368 goto return1; 369 } 370 371 while (!(code = krb5_cc_next_cred(*pcache, &cur, &creds))) { 372 krb5_creds *cred = &creds; 373 374 if (code = krb5_unparse_name(cred->server, &sname)) { 375 com_err(progname, code, "while unparsing server name"); 376 continue; 377 } 378 379 if (strncmp(sname, tgtname, strlen(tgtname)) == 0) { 380 DEEDEBUG("FOUND\n"); 381 if (code = krb5_copy_creds(&creds, tgt)) { 382 com_err(progname, code, "while copying TGT"); 383 goto return1; 384 } 385 found = 0; 386 break; 387 } 388 /* we should do a krb5_free_cred_contents(creds); */ 389 } 390 391 if (code = krb5_cc_end_seq_get(*pcache, &cur)) { 392 com_err(progname, code, "while finishing retrieval"); 393 goto return2; 394 } 395 396return1: 397 flags = KRB5_TC_OPENCLOSE; 398 krb5_cc_set_flags(*pcache, flags); /* force a close */ 399 400return2: 401 if (tgtname) 402 free(tgtname); 403 404 return(found); 405} 406 407 408/*------------------------------------------*/ 409/* Convert a forwarded TGT to a DCE context */ 410int k5dcecon(luid, luser, pname) 411 uid_t luid; 412 char *luser; 413 char *pname; 414{ 415 416 krb5_creds *ftgt = NULL; 417 krb5_creds *tgt = NULL; 418 unsigned32 dfspag; 419 boolean32 reset_passwd = 0; 420 int lst; 421 dce_error_string_t err_string; 422 char *shell_prog; 423 krb5_ccache fcache; 424 char *ccname; 425 char *kusername; 426 char *urealm; 427 char *cp; 428 int pag; 429 int code; 430 krb5_timestamp endtime; 431 432 433 /* If there is no cache to be converted, we should not be here */ 434 435 if ((ccname = getenv("KRB5CCNAME")) == NULL) { 436 DEEDEBUG("No KRB5CCNAME\n"); 437 return(1); 438 } 439 440 if (k5dcegettgt(&fcache, ccname, pname, &ftgt)) { 441 fprintf(stderr, "%s: Did not find TGT\n", progname); 442 return(1); 443 } 444 445 446 DEEDEBUG2("flags=%x\n",ftgt->ticket_flags); 447 if (!(ftgt->ticket_flags & TKT_FLG_FORWARDABLE)){ 448 fprintf(stderr,"Ticket not forwardable\n"); 449 return(0); /* but OK to continue */ 450 } 451 452 setenv("KRB5CCNAME","",1); 453 454#define TKT_ACCEPTABLE (TKT_FLG_FORWARDABLE | TKT_FLG_PROXIABLE \ 455 | TKT_FLG_MAY_POSTDATE | TKT_FLG_RENEWABLE | TKT_FLG_HW_AUTH \ 456 | TKT_FLG_PRE_AUTH) 457 458 if (!k5dcesession(luid, pname, &tgt, &pag, 459 (ftgt->ticket_flags & TKT_ACCEPTABLE))) { 460 if (ftgt->times.endtime > tgt->times.endtime) { 461 DEEDEBUG("Updating existing cache\n"); 462 return(k5dceupdate(&ftgt, pag)); 463 } else { 464 DEEDEBUG("Using existing cache\n"); 465 return(0); /* use the original one */ 466 } 467 } 468 /* see if the tgts match up */ 469 470 if ((code = k5dcecreate(luid, luser, pname, &ftgt))) { 471 return (code); 472 } 473 474 /* 475 * Destroy the Kerberos5 cred cache file. 476 * but dont care aout the return code. 477 */ 478 479 DEEDEBUG("Destroying the old cache\n"); 480 if ((code = krb5_cc_destroy(fcache))) { 481 com_err(progname, code, "while destroying Kerberos5 ccache"); 482 } 483 return (0); 484} 485 486 487/*--------------------------------------------------*/ 488/* k5dceupdate - update the cache with a new TGT */ 489/* Assumed that the KRB5CCNAME has been set */ 490 491int k5dceupdate(krbtgt, pag) 492 krb5_creds **krbtgt; 493 int pag; 494{ 495 496 krb5_ccache ccache; 497 int code; 498 499 if (code = krb5_cc_default(&ccache)) { 500 com_err(progname, code, "while opening cache for update"); 501 return(2); 502 } 503 504 if (code = ccache->ops->init(ccache,(*krbtgt)->client)) { 505 com_err(progname, code, "while reinitilizing cache"); 506 return(3); 507 } 508 509 /* krb5_cc_store_cred */ 510 if (code = ccache->ops->store(ccache, *krbtgt)) { 511 com_err(progname, code, "while updating cache"); 512 return(2); 513 } 514 515 sec_login_pag_new_tgt(pag, (*krbtgt)->times.endtime); 516 return(0); 517} 518/*--------------------------------------------------*/ 519/* k5dcecreate - create a new DCE context */ 520 521int k5dcecreate(luid, luser, pname, krbtgt) 522 uid_t luid; 523 char *luser; 524 char *pname; 525 krb5_creds **krbtgt; 526{ 527 528 char *cp; 529 char *urealm; 530 char *username; 531 char *defrealm; 532 uid_t uid; 533 534 error_status_t st; 535 sec_login_handle_t lcontext = 0; 536 sec_login_auth_src_t auth_src = 0; 537 boolean32 reset_passwd = 0; 538 int lst; 539 dce_error_string_t err_string; 540 541 setenv("KRB5CCNAME","",1); /* make sure it not misused */ 542 543 uid = getuid(); 544 DEEDEBUG2("uid=%d\n",uid); 545 546 /* if run as root, change to user, so as to have the 547 * cache created for the local user even if cross-cell 548 * If run as a user, let standard file protection work. 549 */ 550 551 if (uid == 0) { 552 if (seteuid(luid) < 0) 553 goto abort; 554 } 555 556 cp = strchr(pname,'@'); 557 *cp = '\0'; 558 urealm = ++cp; 559 560 DEEDEBUG2("basename=%s\n",cp); 561 DEEDEBUG2("realm=%s\n",urealm); 562 563 /* now build the username as a single string or a /.../cell/user 564 * if this is a cross cell 565 */ 566 567 if ((username = malloc(7+strlen(pname)+strlen(urealm))) == 0) { 568 fprintf(stderr,"Malloc failed for username\n"); 569 goto abort; 570 } 571 if (krb5_get_default_realm(&defrealm)) { 572 DEEDEBUG("krb5_get_default_realm failed\n"); 573 goto abort; 574 } 575 576 577 if (!strcmp(urealm,defrealm)) { 578 strcpy(username,pname); 579 } else { 580 strcpy(username,"/.../"); 581 strcat(username,urealm); 582 strcat(username,"/"); 583 strcat(username,pname); 584 } 585 586 /* 587 * Setup a DCE login context 588 */ 589 590 if (sec_login_setup_identity((unsigned_char_p_t)username, 591 (sec_login_external_tgt|sec_login_proxy_cred), 592 &lcontext, &st)) { 593 /* 594 * Add our TGT. 595 */ 596 DEEDEBUG("Adding our new TGT\n"); 597 sec_login_krb5_add_cred(lcontext, *krbtgt, &st); 598 if (st) { 599 dce_error_inq_text(st, err_string, &lst); 600 fprintf(stderr, 601 "Error while adding credentials for %s because %s\n", 602 username, err_string); 603 goto abort; 604 } 605 DEEDEBUG("validating and certifying\n"); 606 /* 607 * Now "validate" and certify the identity, 608 * usually we would pass a password here, but... 609 * sec_login_valid_and_cert_ident 610 * sec_login_validate_identity 611 */ 612 613 if (sec_login_validate_identity(lcontext, 0, &reset_passwd, 614 &auth_src, &st)) { 615 DEEDEBUG2("validate_identity st=%d\n",st); 616 if (st) { 617 dce_error_inq_text(st, err_string, &lst); 618 fprintf(stderr, "Validation error for %s because %s\n", 619 username, err_string); 620 goto abort; 621 } 622 if (!sec_login_certify_identity(lcontext,&st)) { 623 dce_error_inq_text(st, err_string, &lst); 624 fprintf(stderr, 625 "Credentials not certified because %s\n",err_string); 626 } 627 if (reset_passwd) { 628 fprintf(stderr, 629 "Password must be changed for %s\n", username); 630 } 631 if (auth_src == sec_login_auth_src_local) { 632 fprintf(stderr, 633 "Credentials obtained from local registry for %s\n", 634 username); 635 } 636 if (auth_src == sec_login_auth_src_overridden) { 637 fprintf(stderr, "Validated %s from local override entry, no network credentials obtained\n", username); 638 goto abort; 639 640 } 641 /* 642 * Actually create the cred files. 643 */ 644 DEEDEBUG("Ceating new cred files.\n"); 645 sec_login_set_context(lcontext, &st); 646 if (st) { 647 dce_error_inq_text(st, err_string, &lst); 648 fprintf(stderr, 649 "Unable to set context for %s because %s\n", 650 username, err_string); 651 goto abort; 652 } 653 654 /* 655 * Now free up the local context and leave the 656 * network context with its pag 657 */ 658#if 0 659 sec_login_release_context(&lcontext, &st); 660 if (st) { 661 dce_error_inq_text(st, err_string, &lst); 662 fprintf(stderr, 663 "Unable to release context for %s because %s\n", 664 username, err_string); 665 goto abort; 666 } 667#endif 668 } 669 else { 670 DEEDEBUG2("validate failed %d\n",st); 671 dce_error_inq_text(st, err_string, &lst); 672 fprintf(stderr, 673 "Unable to validate %s because %s\n", username, 674 err_string); 675 goto abort; 676 } 677 } 678 else { 679 dce_error_inq_text(st, err_string, &lst); 680 fprintf(stderr, 681 "Unable to setup login entry for %s because %s\n", 682 username, err_string); 683 goto abort; 684 } 685 686 done: 687 /* if we were root, get back to root */ 688 689 DEEDEBUG2("sec_login_inq_pag %8.8x\n", 690 sec_login_inq_pag(lcontext, &st)); 691 692 if (uid == 0) { 693 seteuid(0); 694 } 695 696 DEEDEBUG("completed\n"); 697 return(0); 698 699 abort: 700 if (uid == 0) { 701 seteuid(0); 702 } 703 704 DEEDEBUG("Aborting\n"); 705 return(2); 706} 707 708 709 710/*-------------------------------------------------*/ 711main(argc, argv) 712 int argc; 713 char *argv[]; 714{ 715 int status; 716 extern int optind; 717 extern char *optarg; 718 int rv; 719 720 char *lusername = NULL; 721 char *pname = NULL; 722 int fflag = 0; 723 struct passwd *pw; 724 uid_t luid; 725 uid_t myuid; 726 char *ccname; 727 krb5_creds *tgt = NULL; 728 729#ifdef DEBUG 730 close(2); 731 open("/tmp/k5dce.debug",O_WRONLY|O_CREAT|O_APPEND, 0600); 732#endif 733 734 if (myuid = getuid()) { 735 DEEDEBUG2("UID = %d\n",myuid); 736 exit(33); /* must be root to run this, get out now */ 737 } 738 739 while ((rv = getopt(argc,argv,"l:p:fs")) != -1) { 740 DEEDEBUG2("Arg = %c\n", rv); 741 switch(rv) { 742 case 'l': /* user name */ 743 lusername = optarg; 744 DEEDEBUG2("Optarg = %s\n", optarg); 745 break; 746 case 'p': /* principal name */ 747 pname = optarg; 748 DEEDEBUG2("Optarg = %s\n", optarg); 749 break; 750 case 'f': /* convert a forwarded TGT to a context */ 751 fflag++; 752 break; 753 case 's': /* old test parameter, ignore it */ 754 break; 755 } 756 } 757 758 setlocale(LC_ALL, ""); 759 krb5_init_ets(); 760 time(&now); /* set time to check expired tickets */ 761 762 /* if lusername == NULL, Then user is passed as the USER= variable */ 763 764 if (!lusername) { 765 lusername = getenv("USER"); 766 if (!lusername) { 767 fprintf(stderr, "USER not in environment\n"); 768 return(3); 769 } 770 } 771 772 if ((pw = getpwnam(lusername)) == NULL) { 773 fprintf(stderr, "Who are you?\n"); 774 return(44); 775 } 776 777 luid = pw->pw_uid; 778 779 if (fflag) { 780 status = k5dcecon(luid, lusername, pname); 781 } else { 782 status = k5dcesession(luid, pname, &tgt, NULL, 0); 783 } 784 785 if (!status) { 786 printf("%s",getenv("KRB5CCNAME")); /* return via stdout to caller */ 787 DEEDEBUG2("KRB5CCNAME=%s\n",getenv("KRB5CCNAME")); 788 } 789 790 DEEDEBUG2("Returning status %d\n",status); 791 return (status); 792} 793