Only gate the st_uid fcache checks
[heimdal.git] / lib / krb5 / fcache.c
blob9017aa4a22cf5a50994cf61009376176aeb1a426
1 /*
2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
36 #include "krb5_locl.h"
38 typedef struct krb5_fcache{
39 char *filename;
40 int version;
41 }krb5_fcache;
43 struct fcc_cursor {
44 int fd;
45 off_t cred_start;
46 off_t cred_end;
47 krb5_storage *sp;
50 #define KRB5_FCC_FVNO_1 1
51 #define KRB5_FCC_FVNO_2 2
52 #define KRB5_FCC_FVNO_3 3
53 #define KRB5_FCC_FVNO_4 4
55 #define FCC_TAG_DELTATIME 1
57 #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
59 #define FILENAME(X) (FCACHE(X)->filename)
61 #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
63 static const char* KRB5_CALLCONV
64 fcc_get_name(krb5_context context,
65 krb5_ccache id)
67 if (FCACHE(id) == NULL)
68 return NULL;
70 return FILENAME(id);
73 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
74 _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
75 const char *filename)
77 int ret;
78 #ifdef HAVE_FCNTL
79 struct flock l;
81 l.l_start = 0;
82 l.l_len = 0;
83 l.l_type = exclusive ? F_WRLCK : F_RDLCK;
84 l.l_whence = SEEK_SET;
85 ret = fcntl(fd, F_SETLKW, &l);
86 #else
87 ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
88 #endif
89 if(ret < 0)
90 ret = errno;
91 if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
92 ret = EAGAIN;
94 switch (ret) {
95 case 0:
96 break;
97 case EINVAL: /* filesystem doesn't support locking, let the user have it */
98 ret = 0;
99 break;
100 case EAGAIN:
101 krb5_set_error_message(context, ret,
102 N_("timed out locking cache file %s", "file"),
103 filename);
104 break;
105 default: {
106 char buf[128];
107 rk_strerror_r(ret, buf, sizeof(buf));
108 krb5_set_error_message(context, ret,
109 N_("error locking cache file %s: %s",
110 "file, error"), filename, buf);
111 break;
114 return ret;
117 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
118 _krb5_xunlock(krb5_context context, int fd)
120 int ret;
121 #ifdef HAVE_FCNTL
122 struct flock l;
123 l.l_start = 0;
124 l.l_len = 0;
125 l.l_type = F_UNLCK;
126 l.l_whence = SEEK_SET;
127 ret = fcntl(fd, F_SETLKW, &l);
128 #else
129 ret = flock(fd, LOCK_UN);
130 #endif
131 if (ret < 0)
132 ret = errno;
133 switch (ret) {
134 case 0:
135 break;
136 case EINVAL: /* filesystem doesn't support locking, let the user have it */
137 ret = 0;
138 break;
139 default: {
140 char buf[128];
141 rk_strerror_r(ret, buf, sizeof(buf));
142 krb5_set_error_message(context, ret,
143 N_("Failed to unlock file: %s", ""), buf);
144 break;
147 return ret;
150 static krb5_error_code
151 write_storage(krb5_context context, krb5_storage *sp, int fd)
153 krb5_error_code ret;
154 krb5_data data;
155 ssize_t sret;
157 ret = krb5_storage_to_data(sp, &data);
158 if (ret) {
159 krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
160 return ret;
162 sret = write(fd, data.data, data.length);
163 ret = (sret != (ssize_t)data.length);
164 krb5_data_free(&data);
165 if (ret) {
166 ret = errno;
167 krb5_set_error_message(context, ret,
168 N_("Failed to write FILE credential data", ""));
169 return ret;
171 return 0;
175 static krb5_error_code KRB5_CALLCONV
176 fcc_lock(krb5_context context, krb5_ccache id,
177 int fd, krb5_boolean exclusive)
179 return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
182 static krb5_error_code KRB5_CALLCONV
183 fcc_unlock(krb5_context context, int fd)
185 return _krb5_xunlock(context, fd);
188 static krb5_error_code KRB5_CALLCONV
189 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
191 krb5_fcache *f;
192 f = malloc(sizeof(*f));
193 if(f == NULL) {
194 krb5_set_error_message(context, KRB5_CC_NOMEM,
195 N_("malloc: out of memory", ""));
196 return KRB5_CC_NOMEM;
198 f->filename = strdup(res);
199 if(f->filename == NULL){
200 free(f);
201 krb5_set_error_message(context, KRB5_CC_NOMEM,
202 N_("malloc: out of memory", ""));
203 return KRB5_CC_NOMEM;
205 f->version = 0;
206 (*id)->data.data = f;
207 (*id)->data.length = sizeof(*f);
208 return 0;
212 * Try to scrub the contents of `filename' safely.
215 static int
216 scrub_file (int fd)
218 off_t pos;
219 char buf[128];
221 pos = lseek(fd, 0, SEEK_END);
222 if (pos < 0)
223 return errno;
224 if (lseek(fd, 0, SEEK_SET) < 0)
225 return errno;
226 memset(buf, 0, sizeof(buf));
227 while(pos > 0) {
228 ssize_t tmp;
229 size_t wr = sizeof(buf);
230 if (wr > pos)
231 wr = (size_t)pos;
232 tmp = write(fd, buf, wr);
234 if (tmp < 0)
235 return errno;
236 pos -= tmp;
238 #ifdef _MSC_VER
239 _commit (fd);
240 #else
241 fsync (fd);
242 #endif
243 return 0;
247 * Erase `filename' if it exists, trying to remove the contents if
248 * it's `safe'. We always try to remove the file, it it exists. It's
249 * only overwritten if it's a regular file (not a symlink and not a
250 * hardlink)
253 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
254 _krb5_erase_file(krb5_context context, const char *filename)
256 int fd;
257 struct stat sb1, sb2;
258 int ret;
260 ret = lstat (filename, &sb1);
261 if (ret < 0)
262 return errno;
264 fd = open(filename, O_RDWR | O_BINARY);
265 if(fd < 0) {
266 if(errno == ENOENT)
267 return 0;
268 else
269 return errno;
271 rk_cloexec(fd);
272 ret = _krb5_xlock(context, fd, 1, filename);
273 if (ret) {
274 close(fd);
275 return ret;
277 if (unlink(filename) < 0) {
278 _krb5_xunlock(context, fd);
279 close (fd);
280 return errno;
282 ret = fstat(fd, &sb2);
283 if (ret < 0) {
284 _krb5_xunlock(context, fd);
285 close (fd);
286 return errno;
289 /* check if someone was playing with symlinks */
291 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
292 _krb5_xunlock(context, fd);
293 close(fd);
294 return EPERM;
297 /* there are still hard links to this file */
299 if (sb2.st_nlink != 0) {
300 _krb5_xunlock(context, fd);
301 close(fd);
302 return 0;
305 ret = scrub_file(fd);
306 if (ret) {
307 _krb5_xunlock(context, fd);
308 close(fd);
309 return ret;
311 ret = _krb5_xunlock(context, fd);
312 close(fd);
313 return ret;
316 static krb5_error_code KRB5_CALLCONV
317 fcc_gen_new(krb5_context context, krb5_ccache *id)
319 char *file = NULL, *exp_file = NULL;
320 krb5_error_code ret;
321 krb5_fcache *f;
322 int fd;
324 f = malloc(sizeof(*f));
325 if(f == NULL) {
326 krb5_set_error_message(context, KRB5_CC_NOMEM,
327 N_("malloc: out of memory", ""));
328 return KRB5_CC_NOMEM;
330 ret = asprintf(&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
331 if(ret < 0 || file == NULL) {
332 free(f);
333 krb5_set_error_message(context, KRB5_CC_NOMEM,
334 N_("malloc: out of memory", ""));
335 return KRB5_CC_NOMEM;
337 ret = _krb5_expand_path_tokens(context, file, &exp_file);
338 free(file);
339 if (ret) {
340 free(f);
341 return ret;
344 file = exp_file;
346 fd = mkstemp(exp_file);
347 if(fd < 0) {
348 ret = (krb5_error_code)errno;
349 krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
350 free(f);
351 free(exp_file);
352 return ret;
354 close(fd);
355 f->filename = exp_file;
356 f->version = 0;
357 (*id)->data.data = f;
358 (*id)->data.length = sizeof(*f);
359 return 0;
362 static void
363 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
365 int flags = 0;
366 switch(vno) {
367 case KRB5_FCC_FVNO_1:
368 flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
369 flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
370 flags |= KRB5_STORAGE_HOST_BYTEORDER;
371 break;
372 case KRB5_FCC_FVNO_2:
373 flags |= KRB5_STORAGE_HOST_BYTEORDER;
374 break;
375 case KRB5_FCC_FVNO_3:
376 flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
377 break;
378 case KRB5_FCC_FVNO_4:
379 break;
380 default:
381 krb5_abortx(context,
382 "storage_set_flags called with bad vno (%x)", vno);
384 krb5_storage_set_flags(sp, flags);
387 static krb5_error_code KRB5_CALLCONV
388 fcc_open(krb5_context context,
389 krb5_ccache id,
390 const char *operation,
391 int *fd_ret,
392 int flags,
393 mode_t mode)
395 krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
396 (flags | O_RDWR) == flags);
397 krb5_error_code ret;
398 const char *filename;
399 struct stat sb1, sb2;
400 #ifndef _WIN32
401 struct stat sb3;
402 size_t tries = 3;
403 #endif
404 int strict_checking;
405 int fd;
407 *fd_ret = -1;
409 if (FCACHE(id) == NULL)
410 return krb5_einval(context, 2);
412 filename = FILENAME(id);
414 strict_checking = (flags & O_CREAT) == 0 &&
415 (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
417 again:
418 memset(&sb1, 0, sizeof(sb1));
419 ret = lstat(filename, &sb1);
420 if (ret == 0) {
421 if (!S_ISREG(sb1.st_mode)) {
422 krb5_set_error_message(context, EPERM,
423 N_("Refuses to open symlinks for caches FILE:%s", ""), filename);
424 return EPERM;
426 } else if (errno != ENOENT || !(flags & O_CREAT)) {
427 krb5_set_error_message(context, ret, N_("%s lstat(%s)", "file, error"),
428 operation, filename);
429 return errno;
432 fd = open(filename, flags, mode);
433 if(fd < 0) {
434 char buf[128];
435 ret = errno;
436 rk_strerror_r(ret, buf, sizeof(buf));
437 krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
438 operation, filename, buf);
439 return ret;
441 rk_cloexec(fd);
443 ret = fstat(fd, &sb2);
444 if (ret < 0) {
445 krb5_clear_error_message(context);
446 return errno;
449 if (!S_ISREG(sb2.st_mode)) {
450 krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
451 close(fd);
452 return EPERM;
455 #ifndef _WIN32
456 if (sb1.st_dev && sb1.st_ino &&
457 (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) {
459 * Perhaps we raced with a rename(). To complain about
460 * symlinks in that case would cause unnecessary concern, so
461 * we check for that possibility and loop. This has no
462 * TOCTOU problems because we redo the open() (and if we
463 * have O_NOFOLLOW we could even avoid that too).
465 close(fd);
466 ret = lstat(filename, &sb3);
467 if (ret || sb1.st_dev != sb2.st_dev ||
468 sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
469 krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
470 return EPERM;
472 if (--tries == 0) {
473 krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
474 return EPERM;
476 goto again;
478 #endif
480 if (sb2.st_nlink != 1) {
481 krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
482 close(fd);
483 return EPERM;
486 if (strict_checking) {
487 #ifndef _WIN32
489 * XXX WIN32: Needs to have ACL checking code!
490 * st_mode comes out as 100666, and st_uid is no use.
493 * XXX Should probably add options to improve control over this
494 * check. We might want strict checking of everything except
495 * this, and we might want st_uid == getuid() || st_uid == geteuid()
496 * to be OK.
498 if (sb2.st_uid != getuid()) {
499 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);
500 close(fd);
501 return EPERM;
503 if ((sb2.st_mode & 077) != 0) {
504 krb5_set_error_message(context, EPERM,
505 N_("Refuses to open group/other readable files FILE:%s", ""), filename);
506 close(fd);
507 return EPERM;
509 #endif
512 if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
513 close(fd);
514 return ret;
516 *fd_ret = fd;
517 return 0;
520 static krb5_error_code KRB5_CALLCONV
521 fcc_initialize(krb5_context context,
522 krb5_ccache id,
523 krb5_principal primary_principal)
525 krb5_fcache *f = FCACHE(id);
526 int ret = 0;
527 int fd;
529 if (f == NULL)
530 return krb5_einval(context, 2);
532 unlink (f->filename);
534 ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0600);
535 if(ret)
536 return ret;
538 krb5_storage *sp;
539 sp = krb5_storage_emem();
540 krb5_storage_set_eof_code(sp, KRB5_CC_END);
541 if(context->fcache_vno != 0)
542 f->version = context->fcache_vno;
543 else
544 f->version = KRB5_FCC_FVNO_4;
545 ret |= krb5_store_int8(sp, 5);
546 ret |= krb5_store_int8(sp, f->version);
547 storage_set_flags(context, sp, f->version);
548 if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
549 /* V4 stuff */
550 if (context->kdc_sec_offset) {
551 ret |= krb5_store_int16 (sp, 12); /* length */
552 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
553 ret |= krb5_store_int16 (sp, 8); /* length of data */
554 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
555 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
556 } else {
557 ret |= krb5_store_int16 (sp, 0);
560 ret |= krb5_store_principal(sp, primary_principal);
562 ret |= write_storage(context, sp, fd);
564 krb5_storage_free(sp);
566 fcc_unlock(context, fd);
567 if (close(fd) < 0)
568 if (ret == 0) {
569 char buf[128];
570 ret = errno;
571 rk_strerror_r(ret, buf, sizeof(buf));
572 krb5_set_error_message (context, ret, N_("close %s: %s", ""),
573 FILENAME(id), buf);
575 return ret;
578 static krb5_error_code KRB5_CALLCONV
579 fcc_close(krb5_context context,
580 krb5_ccache id)
582 if (FCACHE(id) == NULL)
583 return krb5_einval(context, 2);
585 free (FILENAME(id));
586 krb5_data_free(&id->data);
587 return 0;
590 static krb5_error_code KRB5_CALLCONV
591 fcc_destroy(krb5_context context,
592 krb5_ccache id)
594 if (FCACHE(id) == NULL)
595 return krb5_einval(context, 2);
597 _krb5_erase_file(context, FILENAME(id));
598 return 0;
601 static krb5_error_code KRB5_CALLCONV
602 fcc_store_cred(krb5_context context,
603 krb5_ccache id,
604 krb5_creds *creds)
606 int ret;
607 int fd;
609 ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
610 if(ret)
611 return ret;
613 krb5_storage *sp;
615 sp = krb5_storage_emem();
616 krb5_storage_set_eof_code(sp, KRB5_CC_END);
617 storage_set_flags(context, sp, FCACHE(id)->version);
618 if (!krb5_config_get_bool_default(context, NULL, TRUE,
619 "libdefaults",
620 "fcc-mit-ticketflags",
621 NULL))
622 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
623 ret = krb5_store_creds(sp, creds);
624 if (ret == 0)
625 ret = write_storage(context, sp, fd);
626 krb5_storage_free(sp);
628 fcc_unlock(context, fd);
629 if (close(fd) < 0) {
630 if (ret == 0) {
631 char buf[128];
632 rk_strerror_r(ret, buf, sizeof(buf));
633 ret = errno;
634 krb5_set_error_message (context, ret, N_("close %s: %s", ""),
635 FILENAME(id), buf);
638 return ret;
641 static krb5_error_code
642 init_fcc(krb5_context context,
643 krb5_ccache id,
644 const char *operation,
645 krb5_storage **ret_sp,
646 int *ret_fd,
647 krb5_deltat *kdc_offset)
649 int fd;
650 int8_t pvno, tag;
651 krb5_storage *sp;
652 krb5_error_code ret;
654 if (kdc_offset)
655 *kdc_offset = 0;
657 ret = fcc_open(context, id, operation, &fd, O_RDONLY | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
658 if(ret)
659 return ret;
661 sp = krb5_storage_from_fd(fd);
662 if(sp == NULL) {
663 krb5_clear_error_message(context);
664 ret = ENOMEM;
665 goto out;
667 krb5_storage_set_eof_code(sp, KRB5_CC_END);
668 ret = krb5_ret_int8(sp, &pvno);
669 if(ret != 0) {
670 if(ret == KRB5_CC_END) {
671 ret = ENOENT;
672 krb5_set_error_message(context, ret,
673 N_("Empty credential cache file: %s", ""),
674 FILENAME(id));
675 } else
676 krb5_set_error_message(context, ret, N_("Error reading pvno "
677 "in cache file: %s", ""),
678 FILENAME(id));
679 goto out;
681 if(pvno != 5) {
682 ret = KRB5_CCACHE_BADVNO;
683 krb5_set_error_message(context, ret, N_("Bad version number in credential "
684 "cache file: %s", ""),
685 FILENAME(id));
686 goto out;
688 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
689 if(ret != 0) {
690 ret = KRB5_CC_FORMAT;
691 krb5_set_error_message(context, ret, "Error reading tag in "
692 "cache file: %s", FILENAME(id));
693 goto out;
695 FCACHE(id)->version = tag;
696 storage_set_flags(context, sp, FCACHE(id)->version);
697 switch (tag) {
698 case KRB5_FCC_FVNO_4: {
699 int16_t length;
701 ret = krb5_ret_int16 (sp, &length);
702 if(ret) {
703 ret = KRB5_CC_FORMAT;
704 krb5_set_error_message(context, ret,
705 N_("Error reading tag length in "
706 "cache file: %s", ""), FILENAME(id));
707 goto out;
709 while(length > 0) {
710 int16_t dtag, data_len;
711 int i;
712 int8_t dummy;
714 ret = krb5_ret_int16 (sp, &dtag);
715 if(ret) {
716 ret = KRB5_CC_FORMAT;
717 krb5_set_error_message(context, ret, N_("Error reading dtag in "
718 "cache file: %s", ""),
719 FILENAME(id));
720 goto out;
722 ret = krb5_ret_int16 (sp, &data_len);
723 if(ret) {
724 ret = KRB5_CC_FORMAT;
725 krb5_set_error_message(context, ret,
726 N_("Error reading dlength "
727 "in cache file: %s",""),
728 FILENAME(id));
729 goto out;
731 switch (dtag) {
732 case FCC_TAG_DELTATIME : {
733 int32_t offset;
735 ret = krb5_ret_int32 (sp, &offset);
736 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
737 if(ret) {
738 ret = KRB5_CC_FORMAT;
739 krb5_set_error_message(context, ret,
740 N_("Error reading kdc_sec in "
741 "cache file: %s", ""),
742 FILENAME(id));
743 goto out;
745 context->kdc_sec_offset = offset;
746 if (kdc_offset)
747 *kdc_offset = offset;
748 break;
750 default :
751 for (i = 0; i < data_len; ++i) {
752 ret = krb5_ret_int8 (sp, &dummy);
753 if(ret) {
754 ret = KRB5_CC_FORMAT;
755 krb5_set_error_message(context, ret,
756 N_("Error reading unknown "
757 "tag in cache file: %s", ""),
758 FILENAME(id));
759 goto out;
762 break;
764 length -= 4 + data_len;
766 break;
768 case KRB5_FCC_FVNO_3:
769 case KRB5_FCC_FVNO_2:
770 case KRB5_FCC_FVNO_1:
771 break;
772 default :
773 ret = KRB5_CCACHE_BADVNO;
774 krb5_set_error_message(context, ret,
775 N_("Unknown version number (%d) in "
776 "credential cache file: %s", ""),
777 (int)tag, FILENAME(id));
778 goto out;
780 *ret_sp = sp;
781 *ret_fd = fd;
783 return 0;
784 out:
785 if(sp != NULL)
786 krb5_storage_free(sp);
787 fcc_unlock(context, fd);
788 close(fd);
789 return ret;
792 static krb5_error_code KRB5_CALLCONV
793 fcc_get_principal(krb5_context context,
794 krb5_ccache id,
795 krb5_principal *principal)
797 krb5_error_code ret;
798 int fd;
799 krb5_storage *sp;
801 ret = init_fcc (context, id, "get-pricipal", &sp, &fd, NULL);
802 if (ret)
803 return ret;
804 ret = krb5_ret_principal(sp, principal);
805 if (ret)
806 krb5_clear_error_message(context);
807 krb5_storage_free(sp);
808 fcc_unlock(context, fd);
809 close(fd);
810 return ret;
813 static krb5_error_code KRB5_CALLCONV
814 fcc_end_get (krb5_context context,
815 krb5_ccache id,
816 krb5_cc_cursor *cursor);
818 static krb5_error_code KRB5_CALLCONV
819 fcc_get_first (krb5_context context,
820 krb5_ccache id,
821 krb5_cc_cursor *cursor)
823 krb5_error_code ret;
824 krb5_principal principal;
826 if (FCACHE(id) == NULL)
827 return krb5_einval(context, 2);
829 *cursor = malloc(sizeof(struct fcc_cursor));
830 if (*cursor == NULL) {
831 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
832 return ENOMEM;
834 memset(*cursor, 0, sizeof(struct fcc_cursor));
836 ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp,
837 &FCC_CURSOR(*cursor)->fd, NULL);
838 if (ret) {
839 free(*cursor);
840 *cursor = NULL;
841 return ret;
843 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
844 if(ret) {
845 krb5_clear_error_message(context);
846 fcc_end_get(context, id, cursor);
847 return ret;
849 krb5_free_principal (context, principal);
850 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
851 return 0;
854 static krb5_error_code KRB5_CALLCONV
855 fcc_get_next (krb5_context context,
856 krb5_ccache id,
857 krb5_cc_cursor *cursor,
858 krb5_creds *creds)
860 krb5_error_code ret;
862 if (FCACHE(id) == NULL)
863 return krb5_einval(context, 2);
865 if (FCC_CURSOR(*cursor) == NULL)
866 return krb5_einval(context, 3);
868 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
869 return ret;
870 FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd,
871 0, SEEK_CUR);
873 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
874 if (ret)
875 krb5_clear_error_message(context);
877 FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd,
878 0, SEEK_CUR);
880 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
881 return ret;
884 static krb5_error_code KRB5_CALLCONV
885 fcc_end_get (krb5_context context,
886 krb5_ccache id,
887 krb5_cc_cursor *cursor)
890 if (FCACHE(id) == NULL)
891 return krb5_einval(context, 2);
893 if (FCC_CURSOR(*cursor) == NULL)
894 return krb5_einval(context, 3);
896 krb5_storage_free(FCC_CURSOR(*cursor)->sp);
897 close (FCC_CURSOR(*cursor)->fd);
898 free(*cursor);
899 *cursor = NULL;
900 return 0;
903 static void KRB5_CALLCONV
904 cred_delete(krb5_context context,
905 krb5_ccache id,
906 krb5_cc_cursor *cursor,
907 krb5_creds *cred)
909 krb5_error_code ret;
910 krb5_storage *sp;
911 krb5_data orig_cred_data;
912 unsigned char *cred_data_in_file = NULL;
913 off_t new_cred_sz;
914 struct stat sb1, sb2;
915 int fd = -1;
916 ssize_t bytes;
917 krb5_flags flags = 0;
918 krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
920 /* This is best-effort code; if we lose track of errors here it's OK */
922 heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
923 "fcache internal error");
925 krb5_data_zero(&orig_cred_data);
926 if (!krb5_config_get_bool_default(context, NULL, TRUE,
927 "libdefaults",
928 "fcc-mit-ticketflags",
929 NULL))
930 flags = KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER;
932 sp = krb5_storage_emem();
933 if (sp == NULL)
934 return;
935 krb5_storage_set_eof_code(sp, KRB5_CC_END);
936 storage_set_flags(context, sp, FCACHE(id)->version);
937 if (flags)
938 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
940 /* Get a copy of what the cred should look like in the file; see below */
941 ret = krb5_store_creds(sp, cred);
942 if (ret)
943 goto out;
945 ret = krb5_storage_to_data(sp, &orig_cred_data);
946 if (ret)
947 goto out;
948 krb5_storage_free(sp);
950 cred_data_in_file = malloc(orig_cred_data.length);
951 if (cred_data_in_file == NULL)
952 goto out;
955 * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
956 * KRB5_TC_MATCH_TIMES, so this should be good enough...
958 cred->times.endtime = 0;
960 /* ...except for config creds because we don't check their endtimes */
961 if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
962 ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
963 if (ret)
964 return;
967 sp = krb5_storage_emem();
968 if (sp == NULL)
969 return;
970 krb5_storage_set_eof_code(sp, KRB5_CC_END);
971 storage_set_flags(context, sp, FCACHE(id)->version);
972 if (flags)
973 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
975 ret = krb5_store_creds(sp, cred);
977 /* The new cred must be the same size as the old cred */
978 new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
979 if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
980 (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
981 /* XXX This really can't happen. Assert like above? */
982 krb5_set_error_message(context, EINVAL,
983 N_("Credential deletion failed on ccache "
984 "FILE:%s: new credential size did not "
985 "match old credential size", ""),
986 FILENAME(id));
987 goto out;
990 ret = fcc_open(context, id, "remove_cred", &fd,
991 O_RDWR | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
992 if (ret)
993 goto out;
996 * Check that we're updating the same file where we got the
997 * cred's offset, else we'd be corrupting a new ccache.
999 if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
1000 fstat(fd, &sb2) == -1)
1001 goto out;
1002 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
1003 goto out;
1006 * Make sure what we overwrite is what we expected.
1008 * FIXME: We *really* need the ccache v4 tag for ccache ID. This
1009 * check that we're only overwriting something that looks exactly
1010 * like what we want to is probably good enough in practice, but
1011 * it's not guaranteed to work.
1013 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1014 goto out;
1015 bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1016 if (bytes != orig_cred_data.length)
1017 goto out;
1018 if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1019 goto out;
1020 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1021 goto out;
1022 ret = write_storage(context, sp, fd);
1023 out:
1024 if (fd > -1) {
1025 fcc_unlock(context, fd);
1026 if (close(fd) < 0 && ret == 0) {
1027 char buf[128];
1029 /* This error might be useful */
1030 rk_strerror_r(ret, buf, sizeof(buf));
1031 ret = errno;
1032 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
1033 FILENAME(id), buf);
1036 krb5_data_free(&orig_cred_data);
1037 free(cred_data_in_file);
1038 krb5_storage_free(sp);
1039 return;
1042 static krb5_error_code KRB5_CALLCONV
1043 fcc_remove_cred(krb5_context context,
1044 krb5_ccache id,
1045 krb5_flags which,
1046 krb5_creds *mcred)
1048 krb5_error_code ret, ret2;
1049 krb5_cc_cursor cursor;
1050 krb5_creds found_cred;
1052 if (FCACHE(id) == NULL)
1053 return krb5_einval(context, 2);
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 continue;
1061 cred_delete(context, id, &cursor, &found_cred);
1062 krb5_free_cred_contents(context, &found_cred);
1064 ret2 = krb5_cc_end_seq_get(context, id, &cursor);
1065 if (ret == 0)
1066 return ret2;
1067 if (ret == KRB5_CC_END)
1068 return 0;
1069 return ret;
1072 static krb5_error_code KRB5_CALLCONV
1073 fcc_set_flags(krb5_context context,
1074 krb5_ccache id,
1075 krb5_flags flags)
1077 if (FCACHE(id) == NULL)
1078 return krb5_einval(context, 2);
1080 return 0; /* XXX */
1083 static int KRB5_CALLCONV
1084 fcc_get_version(krb5_context context,
1085 krb5_ccache id)
1087 if (FCACHE(id) == NULL)
1088 return -1;
1090 return FCACHE(id)->version;
1093 struct fcache_iter {
1094 int first;
1097 static krb5_error_code KRB5_CALLCONV
1098 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1100 struct fcache_iter *iter;
1102 iter = calloc(1, sizeof(*iter));
1103 if (iter == NULL) {
1104 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
1105 return ENOMEM;
1107 iter->first = 1;
1108 *cursor = iter;
1109 return 0;
1112 static krb5_error_code KRB5_CALLCONV
1113 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
1115 struct fcache_iter *iter = cursor;
1116 krb5_error_code ret;
1117 const char *fn;
1118 char *expandedfn = NULL;
1120 if (iter == NULL)
1121 return krb5_einval(context, 2);
1123 if (!iter->first) {
1124 krb5_clear_error_message(context);
1125 return KRB5_CC_END;
1127 iter->first = 0;
1130 * Can't call krb5_cc_default_name here since it refers back to
1131 * krb5_cc_cache_match() which will call back into this function.
1133 * Just use the default value if its set, otherwise, use the
1134 * default hardcoded value.
1136 fn = context->default_cc_name;
1137 if (fn == NULL || strncasecmp(fn, "FILE:", 5) != 0) {
1138 ret = _krb5_expand_default_cc_name(context,
1139 KRB5_DEFAULT_CCNAME_FILE,
1140 &expandedfn);
1141 if (ret)
1142 return ret;
1143 fn = expandedfn;
1145 /* check if file exists, don't return a non existant "next" */
1146 if (strncasecmp(fn, "FILE:", 5) == 0) {
1147 struct stat sb;
1148 ret = stat(fn + 5, &sb);
1149 if (ret) {
1150 ret = KRB5_CC_END;
1151 goto out;
1154 ret = krb5_cc_resolve(context, fn, id);
1155 out:
1156 if (expandedfn)
1157 free(expandedfn);
1159 return ret;
1162 static krb5_error_code KRB5_CALLCONV
1163 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1165 struct fcache_iter *iter = cursor;
1167 if (iter == NULL)
1168 return krb5_einval(context, 2);
1170 free(iter);
1171 return 0;
1174 static krb5_error_code KRB5_CALLCONV
1175 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1177 krb5_error_code ret = 0;
1179 ret = rk_rename(FILENAME(from), FILENAME(to));
1181 if (ret && errno != EXDEV) {
1182 char buf[128];
1183 ret = errno;
1184 rk_strerror_r(ret, buf, sizeof(buf));
1185 krb5_set_error_message(context, ret,
1186 N_("Rename of file from %s "
1187 "to %s failed: %s", ""),
1188 FILENAME(from), FILENAME(to), buf);
1189 return ret;
1190 } else if (ret && errno == EXDEV) {
1191 /* make a copy and delete the orignal */
1192 krb5_ssize_t sz1, sz2;
1193 int fd1, fd2;
1194 char buf[BUFSIZ];
1196 ret = fcc_open(context, from, "move/from", &fd1, O_RDONLY | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
1197 if(ret)
1198 return ret;
1200 unlink(FILENAME(to));
1202 ret = fcc_open(context, to, "move/to", &fd2,
1203 O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0600);
1204 if(ret)
1205 goto out1;
1207 while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
1208 sz2 = write(fd2, buf, sz1);
1209 if (sz1 != sz2) {
1210 ret = EIO;
1211 krb5_set_error_message(context, ret,
1212 N_("Failed to write data from one file "
1213 "credential cache to the other", ""));
1214 goto out2;
1217 if (sz1 < 0) {
1218 ret = EIO;
1219 krb5_set_error_message(context, ret,
1220 N_("Failed to read data from one file "
1221 "credential cache to the other", ""));
1222 goto out2;
1224 out2:
1225 fcc_unlock(context, fd2);
1226 close(fd2);
1228 out1:
1229 fcc_unlock(context, fd1);
1230 close(fd1);
1232 _krb5_erase_file(context, FILENAME(from));
1234 if (ret) {
1235 _krb5_erase_file(context, FILENAME(to));
1236 return ret;
1240 /* make sure ->version is uptodate */
1242 krb5_storage *sp;
1243 int fd;
1244 if ((ret = init_fcc (context, to, "move", &sp, &fd, NULL)) == 0) {
1245 if (sp)
1246 krb5_storage_free(sp);
1247 fcc_unlock(context, fd);
1248 close(fd);
1252 fcc_close(context, from);
1254 return ret;
1257 static krb5_error_code KRB5_CALLCONV
1258 fcc_get_default_name(krb5_context context, char **str)
1260 return _krb5_expand_default_cc_name(context,
1261 KRB5_DEFAULT_CCNAME_FILE,
1262 str);
1265 static krb5_error_code KRB5_CALLCONV
1266 fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1268 krb5_error_code ret;
1269 struct stat sb;
1270 int fd;
1272 ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
1273 if(ret)
1274 return ret;
1275 ret = fstat(fd, &sb);
1276 close(fd);
1277 if (ret) {
1278 ret = errno;
1279 krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1280 return ret;
1282 *mtime = sb.st_mtime;
1283 return 0;
1286 static krb5_error_code KRB5_CALLCONV
1287 fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1289 return 0;
1292 static krb5_error_code KRB5_CALLCONV
1293 fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1295 krb5_error_code ret;
1296 krb5_storage *sp = NULL;
1297 int fd;
1298 ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
1299 if (sp)
1300 krb5_storage_free(sp);
1301 fcc_unlock(context, fd);
1302 close(fd);
1304 return ret;
1309 * Variable containing the FILE based credential cache implemention.
1311 * @ingroup krb5_ccache
1314 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1315 KRB5_CC_OPS_VERSION,
1316 "FILE",
1317 fcc_get_name,
1318 fcc_resolve,
1319 fcc_gen_new,
1320 fcc_initialize,
1321 fcc_destroy,
1322 fcc_close,
1323 fcc_store_cred,
1324 NULL, /* fcc_retrieve */
1325 fcc_get_principal,
1326 fcc_get_first,
1327 fcc_get_next,
1328 fcc_end_get,
1329 fcc_remove_cred,
1330 fcc_set_flags,
1331 fcc_get_version,
1332 fcc_get_cache_first,
1333 fcc_get_cache_next,
1334 fcc_end_cache_get,
1335 fcc_move,
1336 fcc_get_default_name,
1337 NULL,
1338 fcc_lastchange,
1339 fcc_set_kdc_offset,
1340 fcc_get_kdc_offset