1 /* $NetBSD: dcache.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 40 typedef struct krb5_dcache{ 41 krb5_ccache fcache; 42 char *dir; 43 char *name; 44 } krb5_dcache; 45 46 #define DCACHE(X) ((krb5_dcache*)(X)->data.data) 47 #define D2FCACHE(X) ((X)->fcache) 48 49 static krb5_error_code KRB5_CALLCONV dcc_close(krb5_context, krb5_ccache); 50 static krb5_error_code KRB5_CALLCONV dcc_get_default_name(krb5_context, char **); 51 52 53 static char * 54 primary_create(krb5_dcache *dc) 55 { 56 char *primary = NULL; 57 58 asprintf(&primary, "%s/primary", dc->dir); 59 if (primary == NULL) 60 return NULL; 61 62 return primary; 63 } 64 65 static int 66 is_filename_cacheish(const char *name) 67 { 68 return strncmp(name, "tkt", 3) == 0; 69 70 } 71 72 static krb5_error_code 73 set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual) 74 { 75 char *path = NULL, *primary = NULL; 76 krb5_error_code ret; 77 struct iovec iov[2]; 78 size_t len; 79 int fd = -1; 80 81 if (!is_filename_cacheish(residual)) { 82 krb5_set_error_message(context, KRB5_CC_FORMAT, 83 "name %s is not a cache (doesn't start with tkt)", residual); 84 return KRB5_CC_FORMAT; 85 } 86 87 asprintf(&path, "%s/primary-XXXXXX", dc->dir); 88 if (path == NULL) 89 return krb5_enomem(context); 90 91 fd = mkstemp(path); 92 if (fd < 0) { 93 ret = errno; 94 goto out; 95 } 96 rk_cloexec(fd); 97 #ifndef _WIN32 98 if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) { 99 ret = errno; 100 goto out; 101 } 102 #endif 103 len = strlen(residual); 104 105 iov[0].iov_base = rk_UNCONST(residual); 106 iov[0].iov_len = len; 107 iov[1].iov_base = "\n"; 108 iov[1].iov_len = 1; 109 110 if (writev(fd, iov, sizeof(iov)/sizeof(iov[0])) != len + 1) { 111 ret = errno; 112 goto out; 113 } 114 115 primary = primary_create(dc); 116 if (primary == NULL) { 117 ret = krb5_enomem(context); 118 goto out; 119 } 120 121 if (rename(path, primary) < 0) { 122 ret = errno; 123 goto out; 124 } 125 126 close(fd); 127 fd = -1; 128 129 ret = 0; 130 out: 131 if (fd >= 0) { 132 (void)unlink(path); 133 close(fd); 134 } 135 if (path) 136 free(path); 137 if (primary) 138 free(primary); 139 140 return ret; 141 } 142 143 static krb5_error_code 144 get_default_cache(krb5_context context, krb5_dcache *dc, char **residual) 145 { 146 krb5_error_code ret; 147 char buf[MAXPATHLEN]; 148 char *primary; 149 FILE *f; 150 151 *residual = NULL; 152 primary = primary_create(dc); 153 if (primary == NULL) 154 return krb5_enomem(context); 155 156 f = fopen(primary, "r"); 157 if (f == NULL) { 158 if (errno == ENOENT) { 159 free(primary); 160 *residual = strdup("tkt"); 161 if (*residual == NULL) 162 return krb5_enomem(context); 163 return 0; 164 } 165 ret = errno; 166 krb5_set_error_message(context, ret, "failed to open %s", primary); 167 free(primary); 168 return ret; 169 } 170 171 if (fgets(buf, sizeof(buf), f) == NULL) { 172 ret = ferror(f); 173 fclose(f); 174 krb5_set_error_message(context, ret, "read file %s", primary); 175 free(primary); 176 return ret; 177 } 178 fclose(f); 179 180 buf[strcspn(buf, "\r\n")] = '\0'; 181 182 if (!is_filename_cacheish(buf)) { 183 krb5_set_error_message(context, KRB5_CC_FORMAT, 184 "name in %s is not a cache (doesn't start with tkt)", primary); 185 free(primary); 186 return KRB5_CC_FORMAT; 187 } 188 189 free(primary); 190 191 *residual = strdup(buf); 192 if (*residual == NULL) 193 return krb5_enomem(context); 194 195 return 0; 196 } 197 198 199 200 static const char* KRB5_CALLCONV 201 dcc_get_name(krb5_context context, 202 krb5_ccache id) 203 { 204 krb5_dcache *dc = DCACHE(id); 205 return dc->name; 206 } 207 208 209 static krb5_error_code 210 verify_directory(krb5_context context, const char *path) 211 { 212 struct stat sb; 213 214 if (stat(path, &sb) != 0) { 215 if (errno == ENOENT) { 216 /* XXX should use mkdirx_np() */ 217 if (rk_mkdir(path, S_IRWXU) == 0) 218 return 0; 219 220 krb5_set_error_message(context, ENOENT, 221 N_("DIR directory %s doesn't exists", ""), path); 222 return ENOENT; 223 } else { 224 int ret = errno; 225 krb5_set_error_message(context, ret, 226 N_("DIR directory %s is bad: %s", ""), path, strerror(ret)); 227 return errno; 228 } 229 } 230 if (!S_ISDIR(sb.st_mode)) { 231 krb5_set_error_message(context, KRB5_CC_BADNAME, 232 N_("DIR directory %s is not a directory", ""), path); 233 return KRB5_CC_BADNAME; 234 } 235 236 return 0; 237 } 238 239 static void 240 dcc_release(krb5_context context, krb5_dcache *dc) 241 { 242 if (dc->fcache) 243 krb5_cc_close(context, dc->fcache); 244 if (dc->dir) 245 free(dc->dir); 246 if (dc->name) 247 free(dc->name); 248 memset(dc, 0, sizeof(*dc)); 249 free(dc); 250 } 251 252 static krb5_error_code KRB5_CALLCONV 253 dcc_resolve(krb5_context context, krb5_ccache *id, const char *res) 254 { 255 char *filename = NULL; 256 krb5_error_code ret; 257 krb5_dcache *dc; 258 const char *p; 259 260 p = res; 261 do { 262 p = strstr(p, ".."); 263 if (p && (p == res || ISPATHSEP(p[-1])) && (ISPATHSEP(p[2]) || p[2] == '\0')) { 264 krb5_set_error_message(context, KRB5_CC_FORMAT, 265 N_("Path contains a .. component", "")); 266 return KRB5_CC_FORMAT; 267 } 268 if (p) 269 p += 3; 270 } while (p); 271 272 dc = calloc(1, sizeof(*dc)); 273 if (dc == NULL) { 274 krb5_set_error_message(context, KRB5_CC_NOMEM, 275 N_("malloc: out of memory", "")); 276 return KRB5_CC_NOMEM; 277 } 278 279 /* check for explicit component */ 280 if (res[0] == ':') { 281 char *q; 282 283 dc->dir = strdup(&res[1]); 284 #ifdef _WIN32 285 q = strrchr(dc->dir, '\\'); 286 if (q == NULL) 287 #endif 288 q = strrchr(dc->dir, '/'); 289 if (q) { 290 *q++ = '\0'; 291 } else { 292 krb5_set_error_message(context, KRB5_CC_FORMAT, N_("Cache not an absolute path: %s", ""), dc->dir); 293 dcc_release(context, dc); 294 return KRB5_CC_FORMAT; 295 } 296 297 if (!is_filename_cacheish(q)) { 298 krb5_set_error_message(context, KRB5_CC_FORMAT, 299 N_("Name %s is not a cache (doesn't start with tkt)", ""), q); 300 dcc_release(context, dc); 301 return KRB5_CC_FORMAT; 302 } 303 304 ret = verify_directory(context, dc->dir); 305 if (ret) { 306 dcc_release(context, dc); 307 return ret; 308 } 309 310 dc->name = strdup(res); 311 if (dc->name == NULL) { 312 dcc_release(context, dc); 313 return krb5_enomem(context); 314 } 315 316 } else { 317 char *residual; 318 size_t len; 319 320 dc->dir = strdup(res); 321 if (dc->dir == NULL) { 322 dcc_release(context, dc); 323 return krb5_enomem(context); 324 } 325 326 len = strlen(dc->dir); 327 328 if (ISPATHSEP(dc->dir[len - 1])) 329 dc->dir[len - 1] = '\0'; 330 331 ret = verify_directory(context, dc->dir); 332 if (ret) { 333 dcc_release(context, dc); 334 return ret; 335 } 336 337 ret = get_default_cache(context, dc, &residual); 338 if (ret) { 339 dcc_release(context, dc); 340 return ret; 341 } 342 asprintf(&dc->name, ":%s/%s", dc->dir, residual); 343 free(residual); 344 if (dc->name == NULL) { 345 dcc_release(context, dc); 346 return krb5_enomem(context); 347 } 348 } 349 350 asprintf(&filename, "FILE%s", dc->name); 351 if (filename == NULL) { 352 dcc_release(context, dc); 353 return krb5_enomem(context); 354 } 355 356 ret = krb5_cc_resolve(context, filename, &dc->fcache); 357 free(filename); 358 if (ret) { 359 dcc_release(context, dc); 360 return ret; 361 } 362 363 364 (*id)->data.data = dc; 365 (*id)->data.length = sizeof(*dc); 366 return 0; 367 } 368 369 static char * 370 copy_default_dcc_cache(krb5_context context) 371 { 372 const char *defname; 373 krb5_error_code ret; 374 char *name = NULL; 375 size_t len; 376 377 len = strlen(krb5_dcc_ops.prefix); 378 379 defname = krb5_cc_default_name(context); 380 if (defname == NULL || 381 strncmp(defname, krb5_dcc_ops.prefix, len) != 0 || 382 defname[len] != ':') 383 { 384 ret = dcc_get_default_name(context, &name); 385 if (ret) 386 return NULL; 387 388 return name; 389 } else { 390 return strdup(&defname[len + 1]); 391 } 392 } 393 394 395 static krb5_error_code KRB5_CALLCONV 396 dcc_gen_new(krb5_context context, krb5_ccache *id) 397 { 398 krb5_error_code ret; 399 char *name = NULL; 400 krb5_dcache *dc; 401 int fd; 402 size_t len; 403 404 name = copy_default_dcc_cache(context); 405 if (name == NULL) { 406 krb5_set_error_message(context, KRB5_CC_FORMAT, 407 N_("Can't generate DIR caches unless its the default type", "")); 408 return KRB5_CC_FORMAT; 409 } 410 411 len = strlen(krb5_dcc_ops.prefix); 412 if (strncmp(name, krb5_dcc_ops.prefix, len) == 0 && name[len] == ':') 413 ++len; 414 else 415 len = 0; 416 417 ret = dcc_resolve(context, id, name + len); 418 free(name); 419 name = NULL; 420 if (ret) 421 return ret; 422 423 dc = DCACHE((*id)); 424 425 asprintf(&name, ":%s/tktXXXXXX", dc->dir); 426 if (name == NULL) { 427 dcc_close(context, *id); 428 return krb5_enomem(context); 429 } 430 431 fd = mkstemp(&name[1]); 432 if (fd < 0) { 433 dcc_close(context, *id); 434 return krb5_enomem(context); 435 } 436 close(fd); 437 438 free(dc->name); 439 dc->name = name; 440 441 return 0; 442 } 443 444 static krb5_error_code KRB5_CALLCONV 445 dcc_initialize(krb5_context context, 446 krb5_ccache id, 447 krb5_principal primary_principal) 448 { 449 krb5_dcache *dc = DCACHE(id); 450 return krb5_cc_initialize(context, D2FCACHE(dc), primary_principal); 451 } 452 453 static krb5_error_code KRB5_CALLCONV 454 dcc_close(krb5_context context, 455 krb5_ccache id) 456 { 457 dcc_release(context, DCACHE(id)); 458 return 0; 459 } 460 461 static krb5_error_code KRB5_CALLCONV 462 dcc_destroy(krb5_context context, 463 krb5_ccache id) 464 { 465 krb5_dcache *dc = DCACHE(id); 466 krb5_ccache fcache = D2FCACHE(dc); 467 dc->fcache = NULL; 468 return krb5_cc_destroy(context, fcache); 469 } 470 471 static krb5_error_code KRB5_CALLCONV 472 dcc_store_cred(krb5_context context, 473 krb5_ccache id, 474 krb5_creds *creds) 475 { 476 krb5_dcache *dc = DCACHE(id); 477 return krb5_cc_store_cred(context, D2FCACHE(dc), creds); 478 } 479 480 static krb5_error_code KRB5_CALLCONV 481 dcc_get_principal(krb5_context context, 482 krb5_ccache id, 483 krb5_principal *principal) 484 { 485 krb5_dcache *dc = DCACHE(id); 486 return krb5_cc_get_principal(context, D2FCACHE(dc), principal); 487 } 488 489 static krb5_error_code KRB5_CALLCONV 490 dcc_get_first (krb5_context context, 491 krb5_ccache id, 492 krb5_cc_cursor *cursor) 493 { 494 krb5_dcache *dc = DCACHE(id); 495 return krb5_cc_start_seq_get(context, D2FCACHE(dc), cursor); 496 } 497 498 static krb5_error_code KRB5_CALLCONV 499 dcc_get_next (krb5_context context, 500 krb5_ccache id, 501 krb5_cc_cursor *cursor, 502 krb5_creds *creds) 503 { 504 krb5_dcache *dc = DCACHE(id); 505 return krb5_cc_next_cred(context, D2FCACHE(dc), cursor, creds); 506 } 507 508 static krb5_error_code KRB5_CALLCONV 509 dcc_end_get (krb5_context context, 510 krb5_ccache id, 511 krb5_cc_cursor *cursor) 512 { 513 krb5_dcache *dc = DCACHE(id); 514 return krb5_cc_end_seq_get(context, D2FCACHE(dc), cursor); 515 } 516 517 static krb5_error_code KRB5_CALLCONV 518 dcc_remove_cred(krb5_context context, 519 krb5_ccache id, 520 krb5_flags which, 521 krb5_creds *cred) 522 { 523 krb5_dcache *dc = DCACHE(id); 524 return krb5_cc_remove_cred(context, D2FCACHE(dc), which, cred); 525 } 526 527 static krb5_error_code KRB5_CALLCONV 528 dcc_set_flags(krb5_context context, 529 krb5_ccache id, 530 krb5_flags flags) 531 { 532 krb5_dcache *dc = DCACHE(id); 533 return krb5_cc_set_flags(context, D2FCACHE(dc), flags); 534 } 535 536 static int KRB5_CALLCONV 537 dcc_get_version(krb5_context context, 538 krb5_ccache id) 539 { 540 krb5_dcache *dc = DCACHE(id); 541 return krb5_cc_get_version(context, D2FCACHE(dc)); 542 } 543 544 struct dcache_iter { 545 int first; 546 krb5_dcache *dc; 547 }; 548 549 static krb5_error_code KRB5_CALLCONV 550 dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor) 551 { 552 struct dcache_iter *iter; 553 krb5_error_code ret; 554 char *name; 555 556 *cursor = NULL; 557 iter = calloc(1, sizeof(*iter)); 558 if (iter == NULL) 559 return krb5_enomem(context); 560 iter->first = 1; 561 562 name = copy_default_dcc_cache(context); 563 if (name == NULL) { 564 free(iter); 565 krb5_set_error_message(context, KRB5_CC_FORMAT, 566 N_("Can't generate DIR caches unless its the default type", "")); 567 return KRB5_CC_FORMAT; 568 } 569 570 ret = dcc_resolve(context, NULL, name); 571 free(name); 572 if (ret) { 573 free(iter); 574 return ret; 575 } 576 577 /* XXX We need to opendir() here */ 578 579 *cursor = iter; 580 return 0; 581 } 582 583 static krb5_error_code KRB5_CALLCONV 584 dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id) 585 { 586 struct dcache_iter *iter = cursor; 587 588 if (iter == NULL) 589 return krb5_einval(context, 2); 590 591 if (!iter->first) { 592 krb5_clear_error_message(context); 593 return KRB5_CC_END; 594 } 595 596 /* XXX We need to readdir() here */ 597 iter->first = 0; 598 599 return KRB5_CC_END; 600 } 601 602 static krb5_error_code KRB5_CALLCONV 603 dcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor) 604 { 605 struct dcache_iter *iter = cursor; 606 607 if (iter == NULL) 608 return krb5_einval(context, 2); 609 610 /* XXX We need to closedir() here */ 611 if (iter->dc) 612 dcc_release(context, iter->dc); 613 free(iter); 614 return 0; 615 } 616 617 static krb5_error_code KRB5_CALLCONV 618 dcc_move(krb5_context context, krb5_ccache from, krb5_ccache to) 619 { 620 krb5_dcache *dcfrom = DCACHE(from); 621 krb5_dcache *dcto = DCACHE(to); 622 return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto)); 623 } 624 625 static krb5_error_code KRB5_CALLCONV 626 dcc_get_default_name(krb5_context context, char **str) 627 { 628 return _krb5_expand_default_cc_name(context, 629 KRB5_DEFAULT_CCNAME_DIR, 630 str); 631 } 632 633 static krb5_error_code KRB5_CALLCONV 634 dcc_set_default(krb5_context context, krb5_ccache id) 635 { 636 krb5_dcache *dc = DCACHE(id); 637 const char *name; 638 639 name = krb5_cc_get_name(context, D2FCACHE(dc)); 640 if (name == NULL) 641 return ENOENT; 642 643 return set_default_cache(context, dc, name); 644 } 645 646 static krb5_error_code KRB5_CALLCONV 647 dcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime) 648 { 649 krb5_dcache *dc = DCACHE(id); 650 return krb5_cc_last_change_time(context, D2FCACHE(dc), mtime); 651 } 652 653 static krb5_error_code KRB5_CALLCONV 654 dcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset) 655 { 656 krb5_dcache *dc = DCACHE(id); 657 return krb5_cc_set_kdc_offset(context, D2FCACHE(dc), kdc_offset); 658 } 659 660 static krb5_error_code KRB5_CALLCONV 661 dcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset) 662 { 663 krb5_dcache *dc = DCACHE(id); 664 return krb5_cc_get_kdc_offset(context, D2FCACHE(dc), kdc_offset); 665 } 666 667 668 /** 669 * Variable containing the DIR based credential cache implemention. 670 * 671 * @ingroup krb5_ccache 672 */ 673 674 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_dcc_ops = { 675 KRB5_CC_OPS_VERSION, 676 "DIR", 677 dcc_get_name, 678 dcc_resolve, 679 dcc_gen_new, 680 dcc_initialize, 681 dcc_destroy, 682 dcc_close, 683 dcc_store_cred, 684 NULL, /* dcc_retrieve */ 685 dcc_get_principal, 686 dcc_get_first, 687 dcc_get_next, 688 dcc_end_get, 689 dcc_remove_cred, 690 dcc_set_flags, 691 dcc_get_version, 692 dcc_get_cache_first, 693 dcc_get_cache_next, 694 dcc_end_cache_get, 695 dcc_move, 696 dcc_get_default_name, 697 dcc_set_default, 698 dcc_lastchange, 699 dcc_set_kdc_offset, 700 dcc_get_kdc_offset 701 }; 702