1/* $NetBSD: fcache.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */ 2 3/* 4 * Copyright (c) 1997 - 2008 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * All rights reserved. 7 * 8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * 3. Neither the name of the Institute nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 38#include "krb5_locl.h" 39 40typedef struct krb5_fcache{ 41 char *filename; 42 int version; 43}krb5_fcache; 44 45struct fcc_cursor { 46 int fd; 47 off_t cred_start; 48 off_t cred_end; 49 krb5_storage *sp; 50}; 51 52#define KRB5_FCC_FVNO_1 1 53#define KRB5_FCC_FVNO_2 2 54#define KRB5_FCC_FVNO_3 3 55#define KRB5_FCC_FVNO_4 4 56 57#define FCC_TAG_DELTATIME 1 58 59#define FCACHE(X) ((krb5_fcache*)(X)->data.data) 60 61#define FILENAME(X) (FCACHE(X)->filename) 62 63#define FCC_CURSOR(C) ((struct fcc_cursor*)(C)) 64 65static const char* KRB5_CALLCONV 66fcc_get_name(krb5_context context, 67 krb5_ccache id) 68{ 69 if (FCACHE(id) == NULL) 70 return NULL; 71 72 return FILENAME(id); 73} 74 75KRB5_LIB_FUNCTION int KRB5_LIB_CALL 76_krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive, 77 const char *filename) 78{ 79 int ret; 80#ifdef HAVE_FCNTL 81 struct flock l; 82 83 l.l_start = 0; 84 l.l_len = 0; 85 l.l_type = exclusive ? F_WRLCK : F_RDLCK; 86 l.l_whence = SEEK_SET; 87 ret = fcntl(fd, F_SETLKW, &l); 88#else 89 ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH); 90#endif 91 if(ret < 0) 92 ret = errno; 93 if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */ 94 ret = EAGAIN; 95 96 switch (ret) { 97 case 0: 98 break; 99 case EINVAL: /* filesystem doesn't support locking, let the user have it */ 100 ret = 0; 101 break; 102 case EAGAIN: 103 krb5_set_error_message(context, ret, 104 N_("timed out locking cache file %s", "file"), 105 filename); 106 break; 107 default: { 108 char buf[128]; 109 rk_strerror_r(ret, buf, sizeof(buf)); 110 krb5_set_error_message(context, ret, 111 N_("error locking cache file %s: %s", 112 "file, error"), filename, buf); 113 break; 114 } 115 } 116 return ret; 117} 118 119KRB5_LIB_FUNCTION int KRB5_LIB_CALL 120_krb5_xunlock(krb5_context context, int fd) 121{ 122 int ret; 123#ifdef HAVE_FCNTL 124 struct flock l; 125 l.l_start = 0; 126 l.l_len = 0; 127 l.l_type = F_UNLCK; 128 l.l_whence = SEEK_SET; 129 ret = fcntl(fd, F_SETLKW, &l); 130#else 131 ret = flock(fd, LOCK_UN); 132#endif 133 if (ret < 0) 134 ret = errno; 135 switch (ret) { 136 case 0: 137 break; 138 case EINVAL: /* filesystem doesn't support locking, let the user have it */ 139 ret = 0; 140 break; 141 default: { 142 char buf[128]; 143 rk_strerror_r(ret, buf, sizeof(buf)); 144 krb5_set_error_message(context, ret, 145 N_("Failed to unlock file: %s", ""), buf); 146 break; 147 } 148 } 149 return ret; 150} 151 152static krb5_error_code 153write_storage(krb5_context context, krb5_storage *sp, int fd) 154{ 155 krb5_error_code ret; 156 krb5_data data; 157 ssize_t sret; 158 159 ret = krb5_storage_to_data(sp, &data); 160 if (ret) { 161 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 162 return ret; 163 } 164 sret = write(fd, data.data, data.length); 165 ret = (sret != (ssize_t)data.length); 166 krb5_data_free(&data); 167 if (ret) { 168 ret = errno; 169 krb5_set_error_message(context, ret, 170 N_("Failed to write FILE credential data", "")); 171 return ret; 172 } 173 return 0; 174} 175 176 177static krb5_error_code KRB5_CALLCONV 178fcc_lock(krb5_context context, krb5_ccache id, 179 int fd, krb5_boolean exclusive) 180{ 181 return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id)); 182} 183 184static krb5_error_code KRB5_CALLCONV 185fcc_unlock(krb5_context context, int fd) 186{ 187 return _krb5_xunlock(context, fd); 188} 189 190static krb5_error_code KRB5_CALLCONV 191fcc_resolve(krb5_context context, krb5_ccache *id, const char *res) 192{ 193 krb5_fcache *f; 194 f = malloc(sizeof(*f)); 195 if(f == NULL) { 196 krb5_set_error_message(context, KRB5_CC_NOMEM, 197 N_("malloc: out of memory", "")); 198 return KRB5_CC_NOMEM; 199 } 200 f->filename = strdup(res); 201 if(f->filename == NULL){ 202 free(f); 203 krb5_set_error_message(context, KRB5_CC_NOMEM, 204 N_("malloc: out of memory", "")); 205 return KRB5_CC_NOMEM; 206 } 207 f->version = 0; 208 (*id)->data.data = f; 209 (*id)->data.length = sizeof(*f); 210 return 0; 211} 212 213/* 214 * Try to scrub the contents of `filename' safely. 215 */ 216 217static int 218scrub_file (int fd) 219{ 220 off_t pos; 221 char buf[128]; 222 223 pos = lseek(fd, 0, SEEK_END); 224 if (pos < 0) 225 return errno; 226 if (lseek(fd, 0, SEEK_SET) < 0) 227 return errno; 228 memset(buf, 0, sizeof(buf)); 229 while(pos > 0) { 230 ssize_t tmp; 231 size_t wr = sizeof(buf); 232 if (wr > pos) 233 wr = (size_t)pos; 234 tmp = write(fd, buf, wr); 235 236 if (tmp < 0) 237 return errno; 238 pos -= tmp; 239 } 240#ifdef _MSC_VER 241 _commit (fd); 242#else 243 fsync (fd); 244#endif 245 return 0; 246} 247 248/* 249 * Erase `filename' if it exists, trying to remove the contents if 250 * it's `safe'. We always try to remove the file, it it exists. It's 251 * only overwritten if it's a regular file (not a symlink and not a 252 * hardlink) 253 */ 254 255KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 256_krb5_erase_file(krb5_context context, const char *filename) 257{ 258 int fd; 259 struct stat sb1, sb2; 260 int ret; 261 262 ret = lstat (filename, &sb1); 263 if (ret < 0) 264 return errno; 265 266 fd = open(filename, O_RDWR | O_BINARY | O_CLOEXEC | O_NOFOLLOW); 267 if(fd < 0) { 268 if(errno == ENOENT) 269 return 0; 270 else 271 return errno; 272 } 273 rk_cloexec(fd); 274 ret = _krb5_xlock(context, fd, 1, filename); 275 if (ret) { 276 close(fd); 277 return ret; 278 } 279 if (unlink(filename) < 0) { 280 ret = errno; 281 _krb5_xunlock(context, fd); 282 close (fd); 283 krb5_set_error_message(context, errno, 284 N_("krb5_cc_destroy: unlinking \"%s\": %s", ""), 285 filename, strerror(ret)); 286 return ret; 287 } 288 ret = fstat(fd, &sb2); 289 if (ret < 0) { 290 ret = errno; 291 _krb5_xunlock(context, fd); 292 close (fd); 293 return ret; 294 } 295 296 /* check if someone was playing with symlinks */ 297 298 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) { 299 _krb5_xunlock(context, fd); 300 close(fd); 301 return EPERM; 302 } 303 304 /* there are still hard links to this file */ 305 306 if (sb2.st_nlink != 0) { 307 _krb5_xunlock(context, fd); 308 close(fd); 309 return 0; 310 } 311 312 ret = scrub_file(fd); 313 if (ret) { 314 _krb5_xunlock(context, fd); 315 close(fd); 316 return ret; 317 } 318 ret = _krb5_xunlock(context, fd); 319 close(fd); 320 return ret; 321} 322 323static krb5_error_code KRB5_CALLCONV 324fcc_gen_new(krb5_context context, krb5_ccache *id) 325{ 326 char *file = NULL, *exp_file = NULL; 327 krb5_error_code ret; 328 krb5_fcache *f; 329 int fd; 330 331 f = malloc(sizeof(*f)); 332 if(f == NULL) { 333 krb5_set_error_message(context, KRB5_CC_NOMEM, 334 N_("malloc: out of memory", "")); 335 return KRB5_CC_NOMEM; 336 } 337 ret = asprintf(&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT); 338 if(ret < 0 || file == NULL) { 339 free(f); 340 krb5_set_error_message(context, KRB5_CC_NOMEM, 341 N_("malloc: out of memory", "")); 342 return KRB5_CC_NOMEM; 343 } 344 ret = _krb5_expand_path_tokens(context, file, 1, &exp_file); 345 free(file); 346 if (ret) { 347 free(f); 348 return ret; 349 } 350 351 file = exp_file; 352 353 fd = mkstemp(exp_file); 354 if(fd < 0) { 355 ret = (krb5_error_code)errno; 356 krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file); 357 free(f); 358 free(exp_file); 359 return ret; 360 } 361 close(fd); 362 f->filename = exp_file; 363 f->version = 0; 364 (*id)->data.data = f; 365 (*id)->data.length = sizeof(*f); 366 return 0; 367} 368 369static void 370storage_set_flags(krb5_context context, krb5_storage *sp, int vno) 371{ 372 int flags = 0; 373 switch(vno) { 374 case KRB5_FCC_FVNO_1: 375 flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS; 376 flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE; 377 flags |= KRB5_STORAGE_HOST_BYTEORDER; 378 break; 379 case KRB5_FCC_FVNO_2: 380 flags |= KRB5_STORAGE_HOST_BYTEORDER; 381 break; 382 case KRB5_FCC_FVNO_3: 383 flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE; 384 break; 385 case KRB5_FCC_FVNO_4: 386 break; 387 default: 388 krb5_abortx(context, 389 "storage_set_flags called with bad vno (%x)", vno); 390 } 391 krb5_storage_set_flags(sp, flags); 392} 393 394static krb5_error_code KRB5_CALLCONV 395fcc_open(krb5_context context, 396 krb5_ccache id, 397 const char *operation, 398 int *fd_ret, 399 int flags, 400 mode_t mode) 401{ 402 krb5_boolean exclusive = ((flags | O_WRONLY) == flags || 403 (flags | O_RDWR) == flags); 404 krb5_error_code ret; 405 const char *filename; 406 struct stat sb1, sb2; 407#ifndef _WIN32 408 struct stat sb3; 409 size_t tries = 3; 410#endif 411 int strict_checking; 412 int fd; 413 414 flags |= O_BINARY | O_CLOEXEC | O_NOFOLLOW; 415 416 *fd_ret = -1; 417 418 if (FCACHE(id) == NULL) 419 return krb5_einval(context, 2); 420 421 filename = FILENAME(id); 422 423 strict_checking = (flags & O_CREAT) == 0 && 424 (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0; 425 426again: 427 memset(&sb1, 0, sizeof(sb1)); 428 ret = lstat(filename, &sb1); 429 if (ret == 0) { 430 if (!S_ISREG(sb1.st_mode)) { 431 krb5_set_error_message(context, EPERM, 432 N_("Refuses to open symlinks for caches FILE:%s", ""), filename); 433 return EPERM; 434 } 435 } else if (errno != ENOENT || !(flags & O_CREAT)) { 436 krb5_set_error_message(context, errno, N_("%s lstat(%s)", "file, error"), 437 operation, filename); 438 return errno; 439 } 440 441 fd = open(filename, flags, mode); 442 if(fd < 0) { 443 char buf[128]; 444 ret = errno; 445 rk_strerror_r(ret, buf, sizeof(buf)); 446 krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"), 447 operation, filename, buf); 448 return ret; 449 } 450 rk_cloexec(fd); 451 452 ret = fstat(fd, &sb2); 453 if (ret < 0) { 454 krb5_clear_error_message(context); 455 close(fd); 456 return errno; 457 } 458 459 if (!S_ISREG(sb2.st_mode)) { 460 krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename); 461 close(fd); 462 return EPERM; 463 } 464 465#ifndef _WIN32 466 if (sb1.st_dev && sb1.st_ino && 467 (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) { 468 /* 469 * Perhaps we raced with a rename(). To complain about 470 * symlinks in that case would cause unnecessary concern, so 471 * we check for that possibility and loop. This has no 472 * TOCTOU problems because we redo the open(). We could also 473 * not do any of this checking if O_NOFOLLOW != 0... 474 */ 475 close(fd); 476 ret = lstat(filename, &sb3); 477 if (ret || sb1.st_dev != sb2.st_dev || 478 sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) { 479 krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename); 480 return EPERM; 481 } 482 if (--tries == 0) { 483 krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename); 484 return EPERM; 485 } 486 goto again; 487 } 488#endif 489 490 /* 491 * /tmp (or wherever default ccaches go) might not be on its own 492 * filesystem, or on a filesystem different /etc, say, and even if 493 * it were, suppose a user hard-links another's ccache to her 494 * default ccache, then runs a set-uid program that will user her 495 * default ccache (even if it ignores KRB5CCNAME)... 496 * 497 * Default ccache locations should really be on per-user non-tmp 498 * locations on tmpfs "run" directories. But we don't know here 499 * that this is the case. Thus: no hard-links, no symlinks. 500 */ 501 if (sb2.st_nlink != 1) { 502 krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename); 503 close(fd); 504 return EPERM; 505 } 506 507 if (strict_checking) { 508#ifndef _WIN32 509 /* 510 * XXX WIN32: Needs to have ACL checking code! 511 * st_mode comes out as 100666, and st_uid is no use. 512 */ 513 /* 514 * XXX Should probably add options to improve control over this 515 * check. We might want strict checking of everything except 516 * this. 517 */ 518 if (sb2.st_uid != geteuid()) { 519 krb5_set_error_message(context, EPERM, N_("Refuses to open cache files not own by myself FILE:%s (owned by %d)", ""), filename, (int)sb2.st_uid); 520 close(fd); 521 return EPERM; 522 } 523 if ((sb2.st_mode & 077) != 0) { 524 krb5_set_error_message(context, EPERM, 525 N_("Refuses to open group/other readable files FILE:%s", ""), filename); 526 close(fd); 527 return EPERM; 528 } 529#endif 530 } 531 532 if((ret = fcc_lock(context, id, fd, exclusive)) != 0) { 533 close(fd); 534 return ret; 535 } 536 *fd_ret = fd; 537 return 0; 538} 539 540static krb5_error_code KRB5_CALLCONV 541fcc_initialize(krb5_context context, 542 krb5_ccache id, 543 krb5_principal primary_principal) 544{ 545 krb5_fcache *f = FCACHE(id); 546 int ret = 0; 547 int fd; 548 549 if (f == NULL) 550 return krb5_einval(context, 2); 551 552 unlink (f->filename); 553 554 ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL, 0600); 555 if(ret) 556 return ret; 557 { 558 krb5_storage *sp; 559 sp = krb5_storage_emem(); 560 krb5_storage_set_eof_code(sp, KRB5_CC_END); 561 if(context->fcache_vno != 0) 562 f->version = context->fcache_vno; 563 else 564 f->version = KRB5_FCC_FVNO_4; 565 ret |= krb5_store_int8(sp, 5); 566 ret |= krb5_store_int8(sp, f->version); 567 storage_set_flags(context, sp, f->version); 568 if(f->version == KRB5_FCC_FVNO_4 && ret == 0) { 569 /* V4 stuff */ 570 if (context->kdc_sec_offset) { 571 ret |= krb5_store_int16 (sp, 12); /* length */ 572 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */ 573 ret |= krb5_store_int16 (sp, 8); /* length of data */ 574 ret |= krb5_store_int32 (sp, context->kdc_sec_offset); 575 ret |= krb5_store_int32 (sp, context->kdc_usec_offset); 576 } else { 577 ret |= krb5_store_int16 (sp, 0); 578 } 579 } 580 ret |= krb5_store_principal(sp, primary_principal); 581 582 ret |= write_storage(context, sp, fd); 583 584 krb5_storage_free(sp); 585 } 586 fcc_unlock(context, fd); 587 if (close(fd) < 0) 588 if (ret == 0) { 589 char buf[128]; 590 ret = errno; 591 rk_strerror_r(ret, buf, sizeof(buf)); 592 krb5_set_error_message(context, ret, N_("close %s: %s", ""), 593 FILENAME(id), buf); 594 } 595 return ret; 596} 597 598static krb5_error_code KRB5_CALLCONV 599fcc_close(krb5_context context, 600 krb5_ccache id) 601{ 602 if (FCACHE(id) == NULL) 603 return krb5_einval(context, 2); 604 605 free (FILENAME(id)); 606 krb5_data_free(&id->data); 607 return 0; 608} 609 610static krb5_error_code KRB5_CALLCONV 611fcc_destroy(krb5_context context, 612 krb5_ccache id) 613{ 614 if (FCACHE(id) == NULL) 615 return krb5_einval(context, 2); 616 617 return _krb5_erase_file(context, FILENAME(id)); 618} 619 620static krb5_error_code KRB5_CALLCONV 621fcc_store_cred(krb5_context context, 622 krb5_ccache id, 623 krb5_creds *creds) 624{ 625 int ret; 626 int fd; 627 628 ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND, 0); 629 if(ret) 630 return ret; 631 { 632 krb5_storage *sp; 633 634 sp = krb5_storage_emem(); 635 krb5_storage_set_eof_code(sp, KRB5_CC_END); 636 storage_set_flags(context, sp, FCACHE(id)->version); 637 ret = krb5_store_creds(sp, creds); 638 if (ret == 0) 639 ret = write_storage(context, sp, fd); 640 krb5_storage_free(sp); 641 } 642 fcc_unlock(context, fd); 643 if (close(fd) < 0) { 644 if (ret == 0) { 645 char buf[128]; 646 ret = errno; 647 rk_strerror_r(ret, buf, sizeof(buf)); 648 krb5_set_error_message(context, ret, N_("close %s: %s", ""), 649 FILENAME(id), buf); 650 } 651 } 652 return ret; 653} 654 655static krb5_error_code 656init_fcc(krb5_context context, 657 krb5_ccache id, 658 const char *operation, 659 krb5_storage **ret_sp, 660 int *ret_fd, 661 krb5_deltat *kdc_offset) 662{ 663 int fd; 664 int8_t pvno, tag; 665 krb5_storage *sp; 666 krb5_error_code ret; 667 668 *ret_fd = -1; 669 *ret_sp = NULL; 670 if (kdc_offset) 671 *kdc_offset = 0; 672 673 ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0); 674 if(ret) 675 return ret; 676 677 sp = krb5_storage_from_fd(fd); 678 if(sp == NULL) { 679 krb5_clear_error_message(context); 680 ret = ENOMEM; 681 goto out; 682 } 683 krb5_storage_set_eof_code(sp, KRB5_CC_END); 684 ret = krb5_ret_int8(sp, &pvno); 685 if (ret != 0) { 686 if(ret == KRB5_CC_END) { 687 ret = ENOENT; 688 krb5_set_error_message(context, ret, 689 N_("Empty credential cache file: %s", ""), 690 FILENAME(id)); 691 } else 692 krb5_set_error_message(context, ret, N_("Error reading pvno " 693 "in cache file: %s", ""), 694 FILENAME(id)); 695 goto out; 696 } 697 if (pvno != 5) { 698 ret = KRB5_CCACHE_BADVNO; 699 krb5_set_error_message(context, ret, N_("Bad version number in credential " 700 "cache file: %s", ""), 701 FILENAME(id)); 702 goto out; 703 } 704 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */ 705 if (ret != 0) { 706 ret = KRB5_CC_FORMAT; 707 krb5_set_error_message(context, ret, "Error reading tag in " 708 "cache file: %s", FILENAME(id)); 709 goto out; 710 } 711 FCACHE(id)->version = tag; 712 storage_set_flags(context, sp, FCACHE(id)->version); 713 switch (tag) { 714 case KRB5_FCC_FVNO_4: { 715 int16_t length; 716 717 ret = krb5_ret_int16 (sp, &length); 718 if(ret) { 719 ret = KRB5_CC_FORMAT; 720 krb5_set_error_message(context, ret, 721 N_("Error reading tag length in " 722 "cache file: %s", ""), FILENAME(id)); 723 goto out; 724 } 725 while(length > 0) { 726 int16_t dtag, data_len; 727 int i; 728 int8_t dummy; 729 730 ret = krb5_ret_int16 (sp, &dtag); 731 if(ret) { 732 ret = KRB5_CC_FORMAT; 733 krb5_set_error_message(context, ret, N_("Error reading dtag in " 734 "cache file: %s", ""), 735 FILENAME(id)); 736 goto out; 737 } 738 ret = krb5_ret_int16 (sp, &data_len); 739 if(ret) { 740 ret = KRB5_CC_FORMAT; 741 krb5_set_error_message(context, ret, 742 N_("Error reading dlength " 743 "in cache file: %s",""), 744 FILENAME(id)); 745 goto out; 746 } 747 switch (dtag) { 748 case FCC_TAG_DELTATIME : { 749 int32_t offset; 750 751 ret = krb5_ret_int32 (sp, &offset); 752 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset); 753 if(ret) { 754 ret = KRB5_CC_FORMAT; 755 krb5_set_error_message(context, ret, 756 N_("Error reading kdc_sec in " 757 "cache file: %s", ""), 758 FILENAME(id)); 759 goto out; 760 } 761 context->kdc_sec_offset = offset; 762 if (kdc_offset) 763 *kdc_offset = offset; 764 break; 765 } 766 default : 767 for (i = 0; i < data_len; ++i) { 768 ret = krb5_ret_int8 (sp, &dummy); 769 if(ret) { 770 ret = KRB5_CC_FORMAT; 771 krb5_set_error_message(context, ret, 772 N_("Error reading unknown " 773 "tag in cache file: %s", ""), 774 FILENAME(id)); 775 goto out; 776 } 777 } 778 break; 779 } 780 length -= 4 + data_len; 781 } 782 break; 783 } 784 case KRB5_FCC_FVNO_3: 785 case KRB5_FCC_FVNO_2: 786 case KRB5_FCC_FVNO_1: 787 break; 788 default : 789 ret = KRB5_CCACHE_BADVNO; 790 krb5_set_error_message(context, ret, 791 N_("Unknown version number (%d) in " 792 "credential cache file: %s", ""), 793 (int)tag, FILENAME(id)); 794 goto out; 795 } 796 *ret_sp = sp; 797 *ret_fd = fd; 798 799 return 0; 800 out: 801 if(sp != NULL) 802 krb5_storage_free(sp); 803 fcc_unlock(context, fd); 804 close(fd); 805 return ret; 806} 807 808static krb5_error_code KRB5_CALLCONV 809fcc_get_principal(krb5_context context, 810 krb5_ccache id, 811 krb5_principal *principal) 812{ 813 krb5_error_code ret; 814 int fd; 815 krb5_storage *sp; 816 817 ret = init_fcc (context, id, "get-principal", &sp, &fd, NULL); 818 if (ret) 819 return ret; 820 ret = krb5_ret_principal(sp, principal); 821 if (ret) 822 krb5_clear_error_message(context); 823 krb5_storage_free(sp); 824 fcc_unlock(context, fd); 825 close(fd); 826 return ret; 827} 828 829static krb5_error_code KRB5_CALLCONV 830fcc_end_get (krb5_context context, 831 krb5_ccache id, 832 krb5_cc_cursor *cursor); 833 834static krb5_error_code KRB5_CALLCONV 835fcc_get_first (krb5_context context, 836 krb5_ccache id, 837 krb5_cc_cursor *cursor) 838{ 839 krb5_error_code ret; 840 krb5_principal principal; 841 842 if (FCACHE(id) == NULL) 843 return krb5_einval(context, 2); 844 845 *cursor = malloc(sizeof(struct fcc_cursor)); 846 if (*cursor == NULL) { 847 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", "")); 848 return ENOMEM; 849 } 850 memset(*cursor, 0, sizeof(struct fcc_cursor)); 851 852 ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp, 853 &FCC_CURSOR(*cursor)->fd, NULL); 854 if (ret) { 855 free(*cursor); 856 *cursor = NULL; 857 return ret; 858 } 859 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal); 860 if(ret) { 861 krb5_clear_error_message(context); 862 fcc_end_get(context, id, cursor); 863 return ret; 864 } 865 krb5_free_principal (context, principal); 866 fcc_unlock(context, FCC_CURSOR(*cursor)->fd); 867 return 0; 868} 869 870static krb5_error_code KRB5_CALLCONV 871fcc_get_next (krb5_context context, 872 krb5_ccache id, 873 krb5_cc_cursor *cursor, 874 krb5_creds *creds) 875{ 876 krb5_error_code ret; 877 878 if (FCACHE(id) == NULL) 879 return krb5_einval(context, 2); 880 881 if (FCC_CURSOR(*cursor) == NULL) 882 return krb5_einval(context, 3); 883 884 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0) 885 return ret; 886 FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd, 887 0, SEEK_CUR); 888 889 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds); 890 if (ret) 891 krb5_clear_error_message(context); 892 893 FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd, 894 0, SEEK_CUR); 895 896 fcc_unlock(context, FCC_CURSOR(*cursor)->fd); 897 return ret; 898} 899 900static krb5_error_code KRB5_CALLCONV 901fcc_end_get (krb5_context context, 902 krb5_ccache id, 903 krb5_cc_cursor *cursor) 904{ 905 906 if (FCACHE(id) == NULL) 907 return krb5_einval(context, 2); 908 909 if (FCC_CURSOR(*cursor) == NULL) 910 return krb5_einval(context, 3); 911 912 krb5_storage_free(FCC_CURSOR(*cursor)->sp); 913 close (FCC_CURSOR(*cursor)->fd); 914 free(*cursor); 915 *cursor = NULL; 916 return 0; 917} 918 919static void KRB5_CALLCONV 920cred_delete(krb5_context context, 921 krb5_ccache id, 922 krb5_cc_cursor *cursor, 923 krb5_creds *cred) 924{ 925 krb5_error_code ret; 926 krb5_storage *sp; 927 krb5_data orig_cred_data; 928 unsigned char *cred_data_in_file = NULL; 929 off_t new_cred_sz; 930 struct stat sb1, sb2; 931 int fd = -1; 932 ssize_t bytes; 933 krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server); 934 935 /* This is best-effort code; if we lose track of errors here it's OK */ 936 937 heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end, 938 "fcache internal error"); 939 940 krb5_data_zero(&orig_cred_data); 941 942 sp = krb5_storage_emem(); 943 if (sp == NULL) 944 return; 945 krb5_storage_set_eof_code(sp, KRB5_CC_END); 946 storage_set_flags(context, sp, FCACHE(id)->version); 947 948 /* Get a copy of what the cred should look like in the file; see below */ 949 ret = krb5_store_creds(sp, cred); 950 if (ret) 951 goto out; 952 953 ret = krb5_storage_to_data(sp, &orig_cred_data); 954 if (ret) 955 goto out; 956 krb5_storage_free(sp); 957 958 cred_data_in_file = malloc(orig_cred_data.length); 959 if (cred_data_in_file == NULL) 960 goto out; 961 962 /* 963 * Mark the cred expired; krb5_cc_retrieve_cred() callers should use 964 * KRB5_TC_MATCH_TIMES, so this should be good enough... 965 */ 966 cred->times.endtime = 0; 967 968 /* ...except for config creds because we don't check their endtimes */ 969 if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) { 970 ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:"); 971 if (ret) 972 goto out; 973 } 974 975 sp = krb5_storage_emem(); 976 if (sp == NULL) 977 goto out; 978 krb5_storage_set_eof_code(sp, KRB5_CC_END); 979 storage_set_flags(context, sp, FCACHE(id)->version); 980 981 ret = krb5_store_creds(sp, cred); 982 983 /* The new cred must be the same size as the old cred */ 984 new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END); 985 if (new_cred_sz != orig_cred_data.length || new_cred_sz != 986 (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) { 987 /* XXX This really can't happen. Assert like above? */ 988 krb5_set_error_message(context, EINVAL, 989 N_("Credential deletion failed on ccache " 990 "FILE:%s: new credential size did not " 991 "match old credential size", ""), 992 FILENAME(id)); 993 goto out; 994 } 995 996 ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0); 997 if (ret) 998 goto out; 999 1000 /* 1001 * Check that we're updating the same file where we got the 1002 * cred's offset, else we'd be corrupting a new ccache. 1003 */ 1004 if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 || 1005 fstat(fd, &sb2) == -1) 1006 goto out; 1007 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) 1008 goto out; 1009 1010 /* 1011 * Make sure what we overwrite is what we expected. 1012 * 1013 * FIXME: We *really* need the ccache v4 tag for ccache ID. This 1014 * check that we're only overwriting something that looks exactly 1015 * like what we want to is probably good enough in practice, but 1016 * it's not guaranteed to work. 1017 */ 1018 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1) 1019 goto out; 1020 bytes = read(fd, cred_data_in_file, orig_cred_data.length); 1021 if (bytes != orig_cred_data.length) 1022 goto out; 1023 if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0) 1024 goto out; 1025 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1) 1026 goto out; 1027 ret = write_storage(context, sp, fd); 1028out: 1029 if (fd > -1) { 1030 fcc_unlock(context, fd); 1031 if (close(fd) < 0 && ret == 0) { 1032 krb5_set_error_message(context, errno, N_("close %s", ""), 1033 FILENAME(id)); 1034 } 1035 } 1036 krb5_data_free(&orig_cred_data); 1037 free(cred_data_in_file); 1038 krb5_storage_free(sp); 1039 return; 1040} 1041 1042static krb5_error_code KRB5_CALLCONV 1043fcc_remove_cred(krb5_context context, 1044 krb5_ccache id, 1045 krb5_flags which, 1046 krb5_creds *mcred) 1047{ 1048 krb5_error_code ret, ret2; 1049 krb5_cc_cursor cursor; 1050 krb5_creds found_cred; 1051 1052 if (FCACHE(id) == NULL) 1053 return krb5_einval(context, 2); 1054 1055 ret = krb5_cc_start_seq_get(context, id, &cursor); 1056 if (ret) 1057 return ret; 1058 while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) { 1059 if (!krb5_compare_creds(context, which, mcred, &found_cred)) { 1060 krb5_free_cred_contents(context, &found_cred); 1061 continue; 1062 } 1063 cred_delete(context, id, &cursor, &found_cred); 1064 krb5_free_cred_contents(context, &found_cred); 1065 } 1066 ret2 = krb5_cc_end_seq_get(context, id, &cursor); 1067 if (ret == 0) 1068 return ret2; 1069 if (ret == KRB5_CC_END) 1070 return 0; 1071 return ret; 1072} 1073 1074static krb5_error_code KRB5_CALLCONV 1075fcc_set_flags(krb5_context context, 1076 krb5_ccache id, 1077 krb5_flags flags) 1078{ 1079 if (FCACHE(id) == NULL) 1080 return krb5_einval(context, 2); 1081 1082 return 0; /* XXX */ 1083} 1084 1085static int KRB5_CALLCONV 1086fcc_get_version(krb5_context context, 1087 krb5_ccache id) 1088{ 1089 if (FCACHE(id) == NULL) 1090 return -1; 1091 1092 return FCACHE(id)->version; 1093} 1094 1095struct fcache_iter { 1096 int first; 1097}; 1098 1099static krb5_error_code KRB5_CALLCONV 1100fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor) 1101{ 1102 struct fcache_iter *iter; 1103 1104 iter = calloc(1, sizeof(*iter)); 1105 if (iter == NULL) { 1106 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", "")); 1107 return ENOMEM; 1108 } 1109 iter->first = 1; 1110 *cursor = iter; 1111 return 0; 1112} 1113 1114static krb5_error_code KRB5_CALLCONV 1115fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id) 1116{ 1117 struct fcache_iter *iter = cursor; 1118 krb5_error_code ret; 1119 const char *fn, *cc_type; 1120 krb5_ccache cc; 1121 1122 if (iter == NULL) 1123 return krb5_einval(context, 2); 1124 1125 if (!iter->first) { 1126 krb5_clear_error_message(context); 1127 return KRB5_CC_END; 1128 } 1129 iter->first = 0; 1130 1131 /* 1132 * Note: do not allow krb5_cc_default_name() to recurse via 1133 * krb5_cc_cache_match(). 1134 * Note that context->default_cc_name will be NULL even though 1135 * KRB5CCNAME is set in the environment if 1136 * krb5_cc_set_default_name() hasn't 1137 */ 1138 fn = krb5_cc_default_name(context); 1139 ret = krb5_cc_resolve(context, fn, &cc); 1140 if (ret != 0) 1141 return ret; 1142 cc_type = krb5_cc_get_type(context, cc); 1143 if (strcmp(cc_type, "FILE") != 0) { 1144 krb5_cc_close(context, cc); 1145 return KRB5_CC_END; 1146 } 1147 1148 *id = cc; 1149 1150 return 0; 1151} 1152 1153static krb5_error_code KRB5_CALLCONV 1154fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor) 1155{ 1156 struct fcache_iter *iter = cursor; 1157 1158 if (iter == NULL) 1159 return krb5_einval(context, 2); 1160 1161 free(iter); 1162 return 0; 1163} 1164 1165static krb5_error_code KRB5_CALLCONV 1166fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to) 1167{ 1168 krb5_error_code ret = 0; 1169 1170 ret = rk_rename(FILENAME(from), FILENAME(to)); 1171 1172 if (ret && errno != EXDEV) { 1173 char buf[128]; 1174 ret = errno; 1175 rk_strerror_r(ret, buf, sizeof(buf)); 1176 krb5_set_error_message(context, ret, 1177 N_("Rename of file from %s " 1178 "to %s failed: %s", ""), 1179 FILENAME(from), FILENAME(to), buf); 1180 return ret; 1181 } else if (ret && errno == EXDEV) { 1182 /* make a copy and delete the orignal */ 1183 krb5_ssize_t sz1, sz2; 1184 int fd1, fd2; 1185 char buf[BUFSIZ]; 1186 1187 ret = fcc_open(context, from, "move/from", &fd1, O_RDONLY, 0); 1188 if(ret) 1189 return ret; 1190 1191 unlink(FILENAME(to)); 1192 1193 ret = fcc_open(context, to, "move/to", &fd2, 1194 O_WRONLY | O_CREAT | O_EXCL, 0600); 1195 if(ret) 1196 goto out1; 1197 1198 while((sz1 = read(fd1, buf, sizeof(buf))) > 0) { 1199 sz2 = write(fd2, buf, sz1); 1200 if (sz1 != sz2) { 1201 ret = EIO; 1202 krb5_set_error_message(context, ret, 1203 N_("Failed to write data from one file " 1204 "credential cache to the other", "")); 1205 goto out2; 1206 } 1207 } 1208 if (sz1 < 0) { 1209 ret = EIO; 1210 krb5_set_error_message(context, ret, 1211 N_("Failed to read data from one file " 1212 "credential cache to the other", "")); 1213 goto out2; 1214 } 1215 out2: 1216 fcc_unlock(context, fd2); 1217 close(fd2); 1218 1219 out1: 1220 fcc_unlock(context, fd1); 1221 close(fd1); 1222 1223 _krb5_erase_file(context, FILENAME(from)); 1224 1225 if (ret) { 1226 _krb5_erase_file(context, FILENAME(to)); 1227 return ret; 1228 } 1229 } 1230 1231 /* make sure ->version is uptodate */ 1232 { 1233 krb5_storage *sp; 1234 int fd; 1235 if ((ret = init_fcc (context, to, "move", &sp, &fd, NULL)) == 0) { 1236 if (sp) 1237 krb5_storage_free(sp); 1238 fcc_unlock(context, fd); 1239 close(fd); 1240 } 1241 } 1242 1243 fcc_close(context, from); 1244 1245 return ret; 1246} 1247 1248static krb5_error_code KRB5_CALLCONV 1249fcc_get_default_name(krb5_context context, char **str) 1250{ 1251 return _krb5_expand_default_cc_name(context, 1252 KRB5_DEFAULT_CCNAME_FILE, 1253 str); 1254} 1255 1256static krb5_error_code KRB5_CALLCONV 1257fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime) 1258{ 1259 krb5_error_code ret; 1260 struct stat sb; 1261 int fd; 1262 1263 ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY, 0); 1264 if(ret) 1265 return ret; 1266 ret = fstat(fd, &sb); 1267 close(fd); 1268 if (ret) { 1269 ret = errno; 1270 krb5_set_error_message(context, ret, N_("Failed to stat cache file", "")); 1271 return ret; 1272 } 1273 *mtime = sb.st_mtime; 1274 return 0; 1275} 1276 1277static krb5_error_code KRB5_CALLCONV 1278fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset) 1279{ 1280 return 0; 1281} 1282 1283static krb5_error_code KRB5_CALLCONV 1284fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset) 1285{ 1286 krb5_error_code ret; 1287 krb5_storage *sp = NULL; 1288 int fd; 1289 ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset); 1290 if (sp) 1291 krb5_storage_free(sp); 1292 fcc_unlock(context, fd); 1293 close(fd); 1294 1295 return ret; 1296} 1297 1298 1299/** 1300 * Variable containing the FILE based credential cache implemention. 1301 * 1302 * @ingroup krb5_ccache 1303 */ 1304 1305KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = { 1306 KRB5_CC_OPS_VERSION, 1307 "FILE", 1308 fcc_get_name, 1309 fcc_resolve, 1310 fcc_gen_new, 1311 fcc_initialize, 1312 fcc_destroy, 1313 fcc_close, 1314 fcc_store_cred, 1315 NULL, /* fcc_retrieve */ 1316 fcc_get_principal, 1317 fcc_get_first, 1318 fcc_get_next, 1319 fcc_end_get, 1320 fcc_remove_cred, 1321 fcc_set_flags, 1322 fcc_get_version, 1323 fcc_get_cache_first, 1324 fcc_get_cache_next, 1325 fcc_end_cache_get, 1326 fcc_move, 1327 fcc_get_default_name, 1328 NULL, 1329 fcc_lastchange, 1330 fcc_set_kdc_offset, 1331 fcc_get_kdc_offset 1332}; 1333