1 /* 2 * Copyright (c) 1997 - 2004 Kungliga Tekniska H�gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 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 * 3. Neither the name of the Institute nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include "krb5_locl.h" 35 36 RCSID("$Id: fcache.c,v 1.34.6.6 2004/03/10 13:30:59 lha Exp $"); 37 38 typedef struct krb5_fcache{ 39 char *filename; 40 int version; 41 }krb5_fcache; 42 43 struct fcc_cursor { 44 int fd; 45 krb5_storage *sp; 46 }; 47 48 #define KRB5_FCC_FVNO_1 1 49 #define KRB5_FCC_FVNO_2 2 50 #define KRB5_FCC_FVNO_3 3 51 #define KRB5_FCC_FVNO_4 4 52 53 #define FCC_TAG_DELTATIME 1 54 55 #define FCACHE(X) ((krb5_fcache*)(X)->data.data) 56 57 #define FILENAME(X) (FCACHE(X)->filename) 58 59 #define FCC_CURSOR(C) ((struct fcc_cursor*)(C)) 60 61 static const char* 62 fcc_get_name(krb5_context context, 63 krb5_ccache id) 64 { 65 return FILENAME(id); 66 } 67 68 int 69 _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive, 70 const char *filename) 71 { 72 int ret; 73 #ifdef HAVE_FCNTL 74 struct flock l; 75 76 l.l_start = 0; 77 l.l_len = 0; 78 l.l_type = exclusive ? F_WRLCK : F_RDLCK; 79 l.l_whence = SEEK_SET; 80 ret = fcntl(fd, F_SETLKW, &l); 81 #else 82 ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH); 83 #endif 84 if(ret < 0) 85 ret = errno; 86 if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */ 87 ret = EAGAIN; 88 89 switch (ret) { 90 case 0: 91 break; 92 case EINVAL: /* filesystem doesn't support locking, let the user have it */ 93 ret = 0; 94 break; 95 case EAGAIN: 96 krb5_set_error_string(context, "timed out locking cache file %s", 97 filename); 98 break; 99 default: 100 krb5_set_error_string(context, "error locking cache file %s: %s", 101 filename, strerror(ret)); 102 break; 103 } 104 return ret; 105 } 106 107 int 108 _krb5_xunlock(int fd) 109 { 110 #ifdef HAVE_FCNTL_LOCK 111 struct flock l; 112 l.l_start = 0; 113 l.l_len = 0; 114 l.l_type = F_UNLCK; 115 l.l_whence = SEEK_SET; 116 return fcntl(fd, F_SETLKW, &l); 117 #else 118 return flock(fd, LOCK_UN); 119 #endif 120 } 121 122 static krb5_error_code 123 fcc_lock(krb5_context context, krb5_ccache id, 124 int fd, krb5_boolean exclusive) 125 { 126 return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id)); 127 } 128 129 static krb5_error_code 130 fcc_unlock(krb5_context context, int fd) 131 { 132 return _krb5_xunlock(fd); 133 } 134 135 static krb5_error_code 136 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res) 137 { 138 krb5_fcache *f; 139 f = malloc(sizeof(*f)); 140 if(f == NULL) { 141 krb5_set_error_string(context, "malloc: out of memory"); 142 return KRB5_CC_NOMEM; 143 } 144 f->filename = strdup(res); 145 if(f->filename == NULL){ 146 free(f); 147 krb5_set_error_string(context, "malloc: out of memory"); 148 return KRB5_CC_NOMEM; 149 } 150 f->version = 0; 151 (*id)->data.data = f; 152 (*id)->data.length = sizeof(*f); 153 return 0; 154 } 155 156 /* 157 * Try to scrub the contents of `filename' safely. 158 */ 159 160 static int 161 scrub_file (int fd) 162 { 163 off_t pos; 164 char buf[128]; 165 166 pos = lseek(fd, 0, SEEK_END); 167 if (pos < 0) 168 return errno; 169 if (lseek(fd, 0, SEEK_SET) < 0) 170 return errno; 171 memset(buf, 0, sizeof(buf)); 172 while(pos > 0) { 173 ssize_t tmp = write(fd, buf, min(sizeof(buf), pos)); 174 175 if (tmp < 0) 176 return errno; 177 pos -= tmp; 178 } 179 fsync (fd); 180 return 0; 181 } 182 183 /* 184 * Erase `filename' if it exists, trying to remove the contents if 185 * it's `safe'. We always try to remove the file, it it exists. It's 186 * only overwritten if it's a regular file (not a symlink and not a 187 * hardlink) 188 */ 189 190 static krb5_error_code 191 erase_file(const char *filename) 192 { 193 int fd; 194 struct stat sb1, sb2; 195 int ret; 196 197 ret = lstat (filename, &sb1); 198 if (ret < 0) 199 return errno; 200 201 fd = open(filename, O_RDWR | O_BINARY); 202 if(fd < 0) { 203 if(errno == ENOENT) 204 return 0; 205 else 206 return errno; 207 } 208 if (unlink(filename) < 0) { 209 close (fd); 210 return errno; 211 } 212 ret = fstat (fd, &sb2); 213 if (ret < 0) { 214 close (fd); 215 return errno; 216 } 217 218 /* check if someone was playing with symlinks */ 219 220 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) { 221 close (fd); 222 return EPERM; 223 } 224 225 /* there are still hard links to this file */ 226 227 if (sb2.st_nlink != 0) { 228 close (fd); 229 return 0; 230 } 231 232 ret = scrub_file (fd); 233 close (fd); 234 return ret; 235 } 236 237 static krb5_error_code 238 fcc_gen_new(krb5_context context, krb5_ccache *id) 239 { 240 krb5_fcache *f; 241 int fd; 242 char *file; 243 244 f = malloc(sizeof(*f)); 245 if(f == NULL) { 246 krb5_set_error_string(context, "malloc: out of memory"); 247 return KRB5_CC_NOMEM; 248 } 249 asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT); 250 if(file == NULL) { 251 free(f); 252 krb5_set_error_string(context, "malloc: out of memory"); 253 return KRB5_CC_NOMEM; 254 } 255 fd = mkstemp(file); 256 if(fd < 0) { 257 free(f); 258 free(file); 259 krb5_set_error_string(context, "mkstemp %s", file); 260 return errno; 261 } 262 close(fd); 263 f->filename = file; 264 f->version = 0; 265 (*id)->data.data = f; 266 (*id)->data.length = sizeof(*f); 267 return 0; 268 } 269 270 static void 271 storage_set_flags(krb5_context context, krb5_storage *sp, int vno) 272 { 273 int flags = 0; 274 switch(vno) { 275 case KRB5_FCC_FVNO_1: 276 flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS; 277 flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE; 278 flags |= KRB5_STORAGE_HOST_BYTEORDER; 279 break; 280 case KRB5_FCC_FVNO_2: 281 flags |= KRB5_STORAGE_HOST_BYTEORDER; 282 break; 283 case KRB5_FCC_FVNO_3: 284 flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE; 285 break; 286 case KRB5_FCC_FVNO_4: 287 break; 288 default: 289 krb5_abortx(context, 290 "storage_set_flags called with bad vno (%x)", vno); 291 } 292 krb5_storage_set_flags(sp, flags); 293 } 294 295 static krb5_error_code 296 fcc_open(krb5_context context, 297 krb5_ccache id, 298 int *fd_ret, 299 int flags, 300 mode_t mode) 301 { 302 krb5_boolean exclusive = ((flags | O_WRONLY) == flags || 303 (flags | O_RDWR) == flags); 304 krb5_error_code ret; 305 const char *filename = FILENAME(id); 306 int fd; 307 fd = open(filename, flags, mode); 308 if(fd < 0) { 309 ret = errno; 310 krb5_set_error_string(context, "open(%s): %s", filename, 311 strerror(ret)); 312 return ret; 313 } 314 315 if((ret = fcc_lock(context, id, fd, exclusive)) != 0) { 316 close(fd); 317 return ret; 318 } 319 *fd_ret = fd; 320 return 0; 321 } 322 323 static krb5_error_code 324 fcc_initialize(krb5_context context, 325 krb5_ccache id, 326 krb5_principal primary_principal) 327 { 328 krb5_fcache *f = FCACHE(id); 329 int ret = 0; 330 int fd; 331 char *filename = f->filename; 332 333 unlink (filename); 334 335 ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600); 336 if(ret) 337 return ret; 338 { 339 krb5_storage *sp; 340 sp = krb5_storage_from_fd(fd); 341 krb5_storage_set_eof_code(sp, KRB5_CC_END); 342 if(context->fcache_vno != 0) 343 f->version = context->fcache_vno; 344 else 345 f->version = KRB5_FCC_FVNO_4; 346 ret |= krb5_store_int8(sp, 5); 347 ret |= krb5_store_int8(sp, f->version); 348 storage_set_flags(context, sp, f->version); 349 if(f->version == KRB5_FCC_FVNO_4 && ret == 0) { 350 /* V4 stuff */ 351 if (context->kdc_sec_offset) { 352 ret |= krb5_store_int16 (sp, 12); /* length */ 353 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */ 354 ret |= krb5_store_int16 (sp, 8); /* length of data */ 355 ret |= krb5_store_int32 (sp, context->kdc_sec_offset); 356 ret |= krb5_store_int32 (sp, context->kdc_usec_offset); 357 } else { 358 ret |= krb5_store_int16 (sp, 0); 359 } 360 } 361 ret |= krb5_store_principal(sp, primary_principal); 362 363 krb5_storage_free(sp); 364 } 365 fcc_unlock(context, fd); 366 if (close(fd) < 0) 367 if (ret == 0) { 368 ret = errno; 369 krb5_set_error_string (context, "close %s: %s", 370 FILENAME(id), strerror(ret)); 371 } 372 return ret; 373 } 374 375 static krb5_error_code 376 fcc_close(krb5_context context, 377 krb5_ccache id) 378 { 379 free (FILENAME(id)); 380 krb5_data_free(&id->data); 381 return 0; 382 } 383 384 static krb5_error_code 385 fcc_destroy(krb5_context context, 386 krb5_ccache id) 387 { 388 erase_file(FILENAME(id)); 389 return 0; 390 } 391 392 static krb5_error_code 393 fcc_store_cred(krb5_context context, 394 krb5_ccache id, 395 krb5_creds *creds) 396 { 397 int ret; 398 int fd; 399 400 ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY, 0); 401 if(ret) 402 return ret; 403 { 404 krb5_storage *sp; 405 sp = krb5_storage_from_fd(fd); 406 krb5_storage_set_eof_code(sp, KRB5_CC_END); 407 storage_set_flags(context, sp, FCACHE(id)->version); 408 if (krb5_config_get_bool_default(context, NULL, FALSE, 409 "libdefaults", 410 "fcc-mit-ticketflags", 411 NULL)) 412 ret = _krb5_store_creds_heimdal_0_7(sp, creds); 413 else 414 ret = _krb5_store_creds_heimdal_pre_0_7(sp, creds); 415 krb5_storage_free(sp); 416 } 417 fcc_unlock(context, fd); 418 if (close(fd) < 0) 419 if (ret == 0) { 420 ret = errno; 421 krb5_set_error_string (context, "close %s: %s", 422 FILENAME(id), strerror(ret)); 423 } 424 return ret; 425 } 426 427 static krb5_error_code 428 init_fcc (krb5_context context, 429 krb5_ccache id, 430 krb5_storage **ret_sp, 431 int *ret_fd) 432 { 433 int fd; 434 int8_t pvno, tag; 435 krb5_storage *sp; 436 krb5_error_code ret; 437 438 ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY, 0); 439 440 if(ret) 441 return ret; 442 443 sp = krb5_storage_from_fd(fd); 444 if(sp == NULL) { 445 ret = ENOMEM; 446 goto out; 447 } 448 krb5_storage_set_eof_code(sp, KRB5_CC_END); 449 ret = krb5_ret_int8(sp, &pvno); 450 if(ret != 0) { 451 if(ret == KRB5_CC_END) 452 ret = ENOENT; /* empty file */ 453 goto out; 454 } 455 if(pvno != 5) { 456 ret = KRB5_CCACHE_BADVNO; 457 goto out; 458 } 459 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */ 460 if(ret != 0) { 461 ret = KRB5_CC_FORMAT; 462 goto out; 463 } 464 FCACHE(id)->version = tag; 465 storage_set_flags(context, sp, FCACHE(id)->version); 466 switch (tag) { 467 case KRB5_FCC_FVNO_4: { 468 int16_t length; 469 470 ret = krb5_ret_int16 (sp, &length); 471 if(ret) { 472 ret = KRB5_CC_FORMAT; 473 goto out; 474 } 475 while(length > 0) { 476 int16_t tag, data_len; 477 int i; 478 int8_t dummy; 479 480 ret = krb5_ret_int16 (sp, &tag); 481 if(ret) { 482 ret = KRB5_CC_FORMAT; 483 goto out; 484 } 485 ret = krb5_ret_int16 (sp, &data_len); 486 if(ret) { 487 ret = KRB5_CC_FORMAT; 488 goto out; 489 } 490 switch (tag) { 491 case FCC_TAG_DELTATIME : 492 ret = krb5_ret_int32 (sp, &context->kdc_sec_offset); 493 if(ret) { 494 ret = KRB5_CC_FORMAT; 495 goto out; 496 } 497 ret = krb5_ret_int32 (sp, &context->kdc_usec_offset); 498 if(ret) { 499 ret = KRB5_CC_FORMAT; 500 goto out; 501 } 502 break; 503 default : 504 for (i = 0; i < data_len; ++i) { 505 ret = krb5_ret_int8 (sp, &dummy); 506 if(ret) { 507 ret = KRB5_CC_FORMAT; 508 goto out; 509 } 510 } 511 break; 512 } 513 length -= 4 + data_len; 514 } 515 break; 516 } 517 case KRB5_FCC_FVNO_3: 518 case KRB5_FCC_FVNO_2: 519 case KRB5_FCC_FVNO_1: 520 break; 521 default : 522 ret = KRB5_CCACHE_BADVNO; 523 goto out; 524 } 525 *ret_sp = sp; 526 *ret_fd = fd; 527 528 return 0; 529 out: 530 if(sp != NULL) 531 krb5_storage_free(sp); 532 fcc_unlock(context, fd); 533 close(fd); 534 return ret; 535 } 536 537 static krb5_error_code 538 fcc_get_principal(krb5_context context, 539 krb5_ccache id, 540 krb5_principal *principal) 541 { 542 krb5_error_code ret; 543 int fd; 544 krb5_storage *sp; 545 546 ret = init_fcc (context, id, &sp, &fd); 547 if (ret) 548 return ret; 549 ret = krb5_ret_principal(sp, principal); 550 krb5_storage_free(sp); 551 fcc_unlock(context, fd); 552 close(fd); 553 return ret; 554 } 555 556 static krb5_error_code 557 fcc_end_get (krb5_context context, 558 krb5_ccache id, 559 krb5_cc_cursor *cursor); 560 561 static krb5_error_code 562 fcc_get_first (krb5_context context, 563 krb5_ccache id, 564 krb5_cc_cursor *cursor) 565 { 566 krb5_error_code ret; 567 krb5_principal principal; 568 569 *cursor = malloc(sizeof(struct fcc_cursor)); 570 571 ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp, 572 &FCC_CURSOR(*cursor)->fd); 573 if (ret) { 574 free(*cursor); 575 return ret; 576 } 577 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal); 578 if(ret) { 579 fcc_end_get(context, id, cursor); 580 return ret; 581 } 582 krb5_free_principal (context, principal); 583 fcc_unlock(context, FCC_CURSOR(*cursor)->fd); 584 return 0; 585 } 586 587 static krb5_error_code 588 fcc_get_next (krb5_context context, 589 krb5_ccache id, 590 krb5_cc_cursor *cursor, 591 krb5_creds *creds) 592 { 593 krb5_error_code ret; 594 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0) 595 return ret; 596 597 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds); 598 599 fcc_unlock(context, FCC_CURSOR(*cursor)->fd); 600 return ret; 601 } 602 603 static krb5_error_code 604 fcc_end_get (krb5_context context, 605 krb5_ccache id, 606 krb5_cc_cursor *cursor) 607 { 608 krb5_storage_free(FCC_CURSOR(*cursor)->sp); 609 close (FCC_CURSOR(*cursor)->fd); 610 free(*cursor); 611 *cursor = NULL; 612 return 0; 613 } 614 615 static krb5_error_code 616 fcc_remove_cred(krb5_context context, 617 krb5_ccache id, 618 krb5_flags which, 619 krb5_creds *cred) 620 { 621 return 0; /* XXX */ 622 } 623 624 static krb5_error_code 625 fcc_set_flags(krb5_context context, 626 krb5_ccache id, 627 krb5_flags flags) 628 { 629 return 0; /* XXX */ 630 } 631 632 static krb5_error_code 633 fcc_get_version(krb5_context context, 634 krb5_ccache id) 635 { 636 return FCACHE(id)->version; 637 } 638 639 const krb5_cc_ops krb5_fcc_ops = { 640 "FILE", 641 fcc_get_name, 642 fcc_resolve, 643 fcc_gen_new, 644 fcc_initialize, 645 fcc_destroy, 646 fcc_close, 647 fcc_store_cred, 648 NULL, /* fcc_retrieve */ 649 fcc_get_principal, 650 fcc_get_first, 651 fcc_get_next, 652 fcc_end_get, 653 fcc_remove_cred, 654 fcc_set_flags, 655 fcc_get_version 656 }; 657