Fix racy file ccache corruption in cred_delete()
[heimdal.git] / lib / krb5 / fcache.c
blobebe760e62a478009b7f4067cda90bc33c6926ba4
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, sb3;
400 int strict_checking;
401 int fd;
402 size_t tries = 3;
404 *fd_ret = -1;
406 if (FCACHE(id) == NULL)
407 return krb5_einval(context, 2);
409 filename = FILENAME(id);
411 strict_checking = (flags & O_CREAT) == 0 &&
412 (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
414 again:
415 if (strict_checking) {
416 ret = lstat(filename, &sb1);
417 if (ret < 0) {
418 krb5_set_error_message(context, ret, N_("%s lstat(%s)", "file, error"),
419 operation, filename);
420 return errno;
424 if (!S_ISREG(sb1.st_mode)) {
425 krb5_set_error_message(context, EPERM
426 N_("Refuses to open symlinks for "
427 "caches FILE:%s", ""), filename);
428 return EPERM;
431 fd = open(filename, flags, mode);
432 if(fd < 0) {
433 char buf[128];
434 ret = errno;
435 rk_strerror_r(ret, buf, sizeof(buf));
436 krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
437 operation, filename, buf);
438 return ret;
440 rk_cloexec(fd);
442 if (strict_checking) {
444 ret = fstat(fd, &sb2);
445 if (ret < 0) {
446 krb5_clear_error_message(context);
447 return errno;
450 if (!S_ISREG(sb2.st_mode)) {
451 krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
452 close(fd);
453 return EPERM;
456 #ifndef _WIN32
457 /* All of the following could be #ifndef O_NOFOLLOW, FYI */
458 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
460 * Perhaps we raced with a rename(). To complain about
461 * symlinks in that case would cause unnecessary concern, so
462 * we check for that possibility and loop. This has no
463 * TOCTOU problems because we redo the open() (and if we
464 * have O_NOFOLLOW we could even avoid that too).
466 close(fd);
467 ret = lstat(filename, &sb3);
468 if (ret || sb1.st_dev != sb2.st_dev ||
469 sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
470 krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
471 return EPERM;
473 if (--tries == 0) {
474 krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
475 return EPERM;
477 goto again;
479 #endif
481 if (sb2.st_nlink != 1) {
482 krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
483 close(fd);
484 return EPERM;
486 #ifndef _WIN32
488 * XXX Should probably add options to improve control over this
489 * check. We might want strict checking of everything except
490 * this, and we might want st_uid == getuid() || st_uid == geteuid()
491 * to be OK.
493 if (sb2.st_uid != getuid()) {
494 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);
495 close(fd);
496 return EPERM;
498 if ((sb2.st_mode & 077) != 0) {
499 krb5_set_error_message(context, EPERM,
500 N_("Refuses to open group/other readable files FILE:%s", ""), filename);
501 close(fd);
502 return EPERM;
504 #endif
507 if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
508 close(fd);
509 return ret;
511 *fd_ret = fd;
512 return 0;
515 static krb5_error_code KRB5_CALLCONV
516 fcc_initialize(krb5_context context,
517 krb5_ccache id,
518 krb5_principal primary_principal)
520 krb5_fcache *f = FCACHE(id);
521 int ret = 0;
522 int fd;
524 if (f == NULL)
525 return krb5_einval(context, 2);
527 unlink (f->filename);
529 ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0600);
530 if(ret)
531 return ret;
533 krb5_storage *sp;
534 sp = krb5_storage_emem();
535 krb5_storage_set_eof_code(sp, KRB5_CC_END);
536 if(context->fcache_vno != 0)
537 f->version = context->fcache_vno;
538 else
539 f->version = KRB5_FCC_FVNO_4;
540 ret |= krb5_store_int8(sp, 5);
541 ret |= krb5_store_int8(sp, f->version);
542 storage_set_flags(context, sp, f->version);
543 if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
544 /* V4 stuff */
545 if (context->kdc_sec_offset) {
546 ret |= krb5_store_int16 (sp, 12); /* length */
547 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
548 ret |= krb5_store_int16 (sp, 8); /* length of data */
549 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
550 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
551 } else {
552 ret |= krb5_store_int16 (sp, 0);
555 ret |= krb5_store_principal(sp, primary_principal);
557 ret |= write_storage(context, sp, fd);
559 krb5_storage_free(sp);
561 fcc_unlock(context, fd);
562 if (close(fd) < 0)
563 if (ret == 0) {
564 char buf[128];
565 ret = errno;
566 rk_strerror_r(ret, buf, sizeof(buf));
567 krb5_set_error_message (context, ret, N_("close %s: %s", ""),
568 FILENAME(id), buf);
570 return ret;
573 static krb5_error_code KRB5_CALLCONV
574 fcc_close(krb5_context context,
575 krb5_ccache id)
577 if (FCACHE(id) == NULL)
578 return krb5_einval(context, 2);
580 free (FILENAME(id));
581 krb5_data_free(&id->data);
582 return 0;
585 static krb5_error_code KRB5_CALLCONV
586 fcc_destroy(krb5_context context,
587 krb5_ccache id)
589 if (FCACHE(id) == NULL)
590 return krb5_einval(context, 2);
592 _krb5_erase_file(context, FILENAME(id));
593 return 0;
596 static krb5_error_code KRB5_CALLCONV
597 fcc_store_cred(krb5_context context,
598 krb5_ccache id,
599 krb5_creds *creds)
601 int ret;
602 int fd;
604 ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
605 if(ret)
606 return ret;
608 krb5_storage *sp;
610 sp = krb5_storage_emem();
611 krb5_storage_set_eof_code(sp, KRB5_CC_END);
612 storage_set_flags(context, sp, FCACHE(id)->version);
613 if (!krb5_config_get_bool_default(context, NULL, TRUE,
614 "libdefaults",
615 "fcc-mit-ticketflags",
616 NULL))
617 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
618 ret = krb5_store_creds(sp, creds);
619 if (ret == 0)
620 ret = write_storage(context, sp, fd);
621 krb5_storage_free(sp);
623 fcc_unlock(context, fd);
624 if (close(fd) < 0) {
625 if (ret == 0) {
626 char buf[128];
627 rk_strerror_r(ret, buf, sizeof(buf));
628 ret = errno;
629 krb5_set_error_message (context, ret, N_("close %s: %s", ""),
630 FILENAME(id), buf);
633 return ret;
636 static krb5_error_code
637 init_fcc(krb5_context context,
638 krb5_ccache id,
639 const char *operation,
640 krb5_storage **ret_sp,
641 int *ret_fd,
642 krb5_deltat *kdc_offset)
644 int fd;
645 int8_t pvno, tag;
646 krb5_storage *sp;
647 krb5_error_code ret;
649 if (kdc_offset)
650 *kdc_offset = 0;
652 ret = fcc_open(context, id, operation, &fd, O_RDONLY | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
653 if(ret)
654 return ret;
656 sp = krb5_storage_from_fd(fd);
657 if(sp == NULL) {
658 krb5_clear_error_message(context);
659 ret = ENOMEM;
660 goto out;
662 krb5_storage_set_eof_code(sp, KRB5_CC_END);
663 ret = krb5_ret_int8(sp, &pvno);
664 if(ret != 0) {
665 if(ret == KRB5_CC_END) {
666 ret = ENOENT;
667 krb5_set_error_message(context, ret,
668 N_("Empty credential cache file: %s", ""),
669 FILENAME(id));
670 } else
671 krb5_set_error_message(context, ret, N_("Error reading pvno "
672 "in cache file: %s", ""),
673 FILENAME(id));
674 goto out;
676 if(pvno != 5) {
677 ret = KRB5_CCACHE_BADVNO;
678 krb5_set_error_message(context, ret, N_("Bad version number in credential "
679 "cache file: %s", ""),
680 FILENAME(id));
681 goto out;
683 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
684 if(ret != 0) {
685 ret = KRB5_CC_FORMAT;
686 krb5_set_error_message(context, ret, "Error reading tag in "
687 "cache file: %s", FILENAME(id));
688 goto out;
690 FCACHE(id)->version = tag;
691 storage_set_flags(context, sp, FCACHE(id)->version);
692 switch (tag) {
693 case KRB5_FCC_FVNO_4: {
694 int16_t length;
696 ret = krb5_ret_int16 (sp, &length);
697 if(ret) {
698 ret = KRB5_CC_FORMAT;
699 krb5_set_error_message(context, ret,
700 N_("Error reading tag length in "
701 "cache file: %s", ""), FILENAME(id));
702 goto out;
704 while(length > 0) {
705 int16_t dtag, data_len;
706 int i;
707 int8_t dummy;
709 ret = krb5_ret_int16 (sp, &dtag);
710 if(ret) {
711 ret = KRB5_CC_FORMAT;
712 krb5_set_error_message(context, ret, N_("Error reading dtag in "
713 "cache file: %s", ""),
714 FILENAME(id));
715 goto out;
717 ret = krb5_ret_int16 (sp, &data_len);
718 if(ret) {
719 ret = KRB5_CC_FORMAT;
720 krb5_set_error_message(context, ret,
721 N_("Error reading dlength "
722 "in cache file: %s",""),
723 FILENAME(id));
724 goto out;
726 switch (dtag) {
727 case FCC_TAG_DELTATIME : {
728 int32_t offset;
730 ret = krb5_ret_int32 (sp, &offset);
731 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
732 if(ret) {
733 ret = KRB5_CC_FORMAT;
734 krb5_set_error_message(context, ret,
735 N_("Error reading kdc_sec in "
736 "cache file: %s", ""),
737 FILENAME(id));
738 goto out;
740 context->kdc_sec_offset = offset;
741 if (kdc_offset)
742 *kdc_offset = offset;
743 break;
745 default :
746 for (i = 0; i < data_len; ++i) {
747 ret = krb5_ret_int8 (sp, &dummy);
748 if(ret) {
749 ret = KRB5_CC_FORMAT;
750 krb5_set_error_message(context, ret,
751 N_("Error reading unknown "
752 "tag in cache file: %s", ""),
753 FILENAME(id));
754 goto out;
757 break;
759 length -= 4 + data_len;
761 break;
763 case KRB5_FCC_FVNO_3:
764 case KRB5_FCC_FVNO_2:
765 case KRB5_FCC_FVNO_1:
766 break;
767 default :
768 ret = KRB5_CCACHE_BADVNO;
769 krb5_set_error_message(context, ret,
770 N_("Unknown version number (%d) in "
771 "credential cache file: %s", ""),
772 (int)tag, FILENAME(id));
773 goto out;
775 *ret_sp = sp;
776 *ret_fd = fd;
778 return 0;
779 out:
780 if(sp != NULL)
781 krb5_storage_free(sp);
782 fcc_unlock(context, fd);
783 close(fd);
784 return ret;
787 static krb5_error_code KRB5_CALLCONV
788 fcc_get_principal(krb5_context context,
789 krb5_ccache id,
790 krb5_principal *principal)
792 krb5_error_code ret;
793 int fd;
794 krb5_storage *sp;
796 ret = init_fcc (context, id, "get-pricipal", &sp, &fd, NULL);
797 if (ret)
798 return ret;
799 ret = krb5_ret_principal(sp, principal);
800 if (ret)
801 krb5_clear_error_message(context);
802 krb5_storage_free(sp);
803 fcc_unlock(context, fd);
804 close(fd);
805 return ret;
808 static krb5_error_code KRB5_CALLCONV
809 fcc_end_get (krb5_context context,
810 krb5_ccache id,
811 krb5_cc_cursor *cursor);
813 static krb5_error_code KRB5_CALLCONV
814 fcc_get_first (krb5_context context,
815 krb5_ccache id,
816 krb5_cc_cursor *cursor)
818 krb5_error_code ret;
819 krb5_principal principal;
821 if (FCACHE(id) == NULL)
822 return krb5_einval(context, 2);
824 *cursor = malloc(sizeof(struct fcc_cursor));
825 if (*cursor == NULL) {
826 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
827 return ENOMEM;
829 memset(*cursor, 0, sizeof(struct fcc_cursor));
831 ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp,
832 &FCC_CURSOR(*cursor)->fd, NULL);
833 if (ret) {
834 free(*cursor);
835 *cursor = NULL;
836 return ret;
838 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
839 if(ret) {
840 krb5_clear_error_message(context);
841 fcc_end_get(context, id, cursor);
842 return ret;
844 krb5_free_principal (context, principal);
845 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
846 return 0;
849 static krb5_error_code KRB5_CALLCONV
850 fcc_get_next (krb5_context context,
851 krb5_ccache id,
852 krb5_cc_cursor *cursor,
853 krb5_creds *creds)
855 krb5_error_code ret;
857 if (FCACHE(id) == NULL)
858 return krb5_einval(context, 2);
860 if (FCC_CURSOR(*cursor) == NULL)
861 return krb5_einval(context, 3);
863 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
864 return ret;
865 FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd,
866 0, SEEK_CUR);
868 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
869 if (ret)
870 krb5_clear_error_message(context);
872 FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd,
873 0, SEEK_CUR);
875 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
876 return ret;
879 static krb5_error_code KRB5_CALLCONV
880 fcc_end_get (krb5_context context,
881 krb5_ccache id,
882 krb5_cc_cursor *cursor)
885 if (FCACHE(id) == NULL)
886 return krb5_einval(context, 2);
888 if (FCC_CURSOR(*cursor) == NULL)
889 return krb5_einval(context, 3);
891 krb5_storage_free(FCC_CURSOR(*cursor)->sp);
892 close (FCC_CURSOR(*cursor)->fd);
893 free(*cursor);
894 *cursor = NULL;
895 return 0;
898 static void KRB5_CALLCONV
899 cred_delete(krb5_context context,
900 krb5_ccache id,
901 krb5_cc_cursor *cursor,
902 krb5_creds *cred)
904 krb5_error_code ret;
905 krb5_storage *sp;
906 krb5_data orig_cred_data;
907 unsigned char *cred_data_in_file = NULL;
908 off_t new_cred_sz;
909 struct stat sb1, sb2;
910 int fd = -1;
911 size_t bytes;
912 krb5_flags flags = 0;
913 krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
915 /* This is best-effort code; if we lose track of errors here it's OK */
917 heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
918 "fcache internal error");
920 krb5_data_zero(&orig_cred_data);
921 if (!krb5_config_get_bool_default(context, NULL, TRUE,
922 "libdefaults",
923 "fcc-mit-ticketflags",
924 NULL))
925 flags = KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER;
927 sp = krb5_storage_emem();
928 if (sp == NULL)
929 return;
930 krb5_storage_set_eof_code(sp, KRB5_CC_END);
931 storage_set_flags(context, sp, FCACHE(id)->version);
932 if (flags)
933 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
935 /* Get a copy of what the cred should look like in the file; see below */
936 ret = krb5_store_creds(sp, cred);
937 if (ret)
938 goto out;
940 ret = krb5_storage_to_data(sp, &orig_cred_data);
941 if (ret)
942 goto out;
943 krb5_storage_free(sp);
945 cred_data_in_file = malloc(orig_cred_data.length);
946 if (cred_data_in_file == NULL)
947 goto out;
950 * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
951 * KRB5_TC_MATCH_TIMES, so this should be good enough...
953 cred->times.endtime = 0;
955 /* ...except for config creds because we don't check their endtimes */
956 if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
957 ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
958 if (ret)
959 return;
962 sp = krb5_storage_emem();
963 if (sp == NULL)
964 return;
965 krb5_storage_set_eof_code(sp, KRB5_CC_END);
966 storage_set_flags(context, sp, FCACHE(id)->version);
967 if (flags)
968 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
970 ret = krb5_store_creds(sp, cred);
972 /* The new cred must be the same size as the old cred */
973 new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
974 if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
975 (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
976 /* XXX This really can't happen. Assert like above? */
977 krb5_set_error_message(context, EINVAL,
978 N_("Credential deletion failed on ccache "
979 "FILE:%s: new credential size did not "
980 "match old credential size", ""),
981 FILENAME(id));
982 goto out;
985 ret = fcc_open(context, id, "remove_cred", &fd,
986 O_RDWR | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
987 if (ret)
988 goto out;
991 * Check that we're updating the same file where we got the
992 * cred's offset, else we'd be corrupting a new ccache.
994 if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
995 fstat(fd, &sb2) == -1)
996 goto out;
997 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
998 goto out;
1001 * Make sure what we overwrite is what we expected.
1003 * FIXME: We *really* need the ccache v4 tag for ccache ID. This
1004 * check that we're only overwriting something that looks exactly
1005 * like what we want to is probably good enough in practice, but
1006 * it's not guaranteed to work.
1008 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1009 goto out;
1010 bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1011 if (bytes != orig_cred_data.length)
1012 goto out;
1013 if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1014 goto out;
1015 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1016 goto out;
1017 ret = write_storage(context, sp, fd);
1018 out:
1019 if (fd > -1) {
1020 fcc_unlock(context, fd);
1021 if (close(fd) < 0 && ret == 0) {
1022 char buf[128];
1024 /* This error might be useful */
1025 rk_strerror_r(ret, buf, sizeof(buf));
1026 ret = errno;
1027 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
1028 FILENAME(id), buf);
1031 krb5_data_free(&orig_cred_data);
1032 free(cred_data_in_file);
1033 krb5_storage_free(sp);
1034 return;
1037 static krb5_error_code KRB5_CALLCONV
1038 fcc_remove_cred(krb5_context context,
1039 krb5_ccache id,
1040 krb5_flags which,
1041 krb5_creds *mcred)
1043 krb5_error_code ret, ret2;
1044 krb5_cc_cursor cursor;
1045 krb5_creds found_cred;
1047 if (FCACHE(id) == NULL)
1048 return krb5_einval(context, 2);
1050 ret = krb5_cc_start_seq_get(context, id, &cursor);
1051 if (ret)
1052 return ret;
1053 while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
1054 if (!krb5_compare_creds(context, which, mcred, &found_cred))
1055 continue;
1056 cred_delete(context, id, &cursor, &found_cred);
1057 krb5_free_cred_contents(context, &found_cred);
1059 ret2 = krb5_cc_end_seq_get(context, id, &cursor);
1060 if (ret == 0)
1061 return ret2;
1062 if (ret == KRB5_CC_END)
1063 return 0;
1064 return ret;
1067 static krb5_error_code KRB5_CALLCONV
1068 fcc_set_flags(krb5_context context,
1069 krb5_ccache id,
1070 krb5_flags flags)
1072 if (FCACHE(id) == NULL)
1073 return krb5_einval(context, 2);
1075 return 0; /* XXX */
1078 static int KRB5_CALLCONV
1079 fcc_get_version(krb5_context context,
1080 krb5_ccache id)
1082 if (FCACHE(id) == NULL)
1083 return -1;
1085 return FCACHE(id)->version;
1088 struct fcache_iter {
1089 int first;
1092 static krb5_error_code KRB5_CALLCONV
1093 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1095 struct fcache_iter *iter;
1097 iter = calloc(1, sizeof(*iter));
1098 if (iter == NULL) {
1099 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
1100 return ENOMEM;
1102 iter->first = 1;
1103 *cursor = iter;
1104 return 0;
1107 static krb5_error_code KRB5_CALLCONV
1108 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
1110 struct fcache_iter *iter = cursor;
1111 krb5_error_code ret;
1112 const char *fn;
1113 char *expandedfn = NULL;
1115 if (iter == NULL)
1116 return krb5_einval(context, 2);
1118 if (!iter->first) {
1119 krb5_clear_error_message(context);
1120 return KRB5_CC_END;
1122 iter->first = 0;
1125 * Can't call krb5_cc_default_name here since it refers back to
1126 * krb5_cc_cache_match() which will call back into this function.
1128 * Just use the default value if its set, otherwise, use the
1129 * default hardcoded value.
1131 fn = context->default_cc_name;
1132 if (fn == NULL || strncasecmp(fn, "FILE:", 5) != 0) {
1133 ret = _krb5_expand_default_cc_name(context,
1134 KRB5_DEFAULT_CCNAME_FILE,
1135 &expandedfn);
1136 if (ret)
1137 return ret;
1138 fn = expandedfn;
1140 /* check if file exists, don't return a non existant "next" */
1141 if (strncasecmp(fn, "FILE:", 5) == 0) {
1142 struct stat sb;
1143 ret = stat(fn + 5, &sb);
1144 if (ret) {
1145 ret = KRB5_CC_END;
1146 goto out;
1149 ret = krb5_cc_resolve(context, fn, id);
1150 out:
1151 if (expandedfn)
1152 free(expandedfn);
1154 return ret;
1157 static krb5_error_code KRB5_CALLCONV
1158 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1160 struct fcache_iter *iter = cursor;
1162 if (iter == NULL)
1163 return krb5_einval(context, 2);
1165 free(iter);
1166 return 0;
1169 static krb5_error_code KRB5_CALLCONV
1170 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1172 krb5_error_code ret = 0;
1174 ret = rk_rename(FILENAME(from), FILENAME(to));
1176 if (ret && errno != EXDEV) {
1177 char buf[128];
1178 ret = errno;
1179 rk_strerror_r(ret, buf, sizeof(buf));
1180 krb5_set_error_message(context, ret,
1181 N_("Rename of file from %s "
1182 "to %s failed: %s", ""),
1183 FILENAME(from), FILENAME(to), buf);
1184 return ret;
1185 } else if (ret && errno == EXDEV) {
1186 /* make a copy and delete the orignal */
1187 krb5_ssize_t sz1, sz2;
1188 int fd1, fd2;
1189 char buf[BUFSIZ];
1191 ret = fcc_open(context, from, "move/from", &fd1, O_RDONLY | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
1192 if(ret)
1193 return ret;
1195 unlink(FILENAME(to));
1197 ret = fcc_open(context, to, "move/to", &fd2,
1198 O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0600);
1199 if(ret)
1200 goto out1;
1202 while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
1203 sz2 = write(fd2, buf, sz1);
1204 if (sz1 != sz2) {
1205 ret = EIO;
1206 krb5_set_error_message(context, ret,
1207 N_("Failed to write data from one file "
1208 "credential cache to the other", ""));
1209 goto out2;
1212 if (sz1 < 0) {
1213 ret = EIO;
1214 krb5_set_error_message(context, ret,
1215 N_("Failed to read data from one file "
1216 "credential cache to the other", ""));
1217 goto out2;
1219 out2:
1220 fcc_unlock(context, fd2);
1221 close(fd2);
1223 out1:
1224 fcc_unlock(context, fd1);
1225 close(fd1);
1227 _krb5_erase_file(context, FILENAME(from));
1229 if (ret) {
1230 _krb5_erase_file(context, FILENAME(to));
1231 return ret;
1235 /* make sure ->version is uptodate */
1237 krb5_storage *sp;
1238 int fd;
1239 if ((ret = init_fcc (context, to, "move", &sp, &fd, NULL)) == 0) {
1240 if (sp)
1241 krb5_storage_free(sp);
1242 fcc_unlock(context, fd);
1243 close(fd);
1247 fcc_close(context, from);
1249 return ret;
1252 static krb5_error_code KRB5_CALLCONV
1253 fcc_get_default_name(krb5_context context, char **str)
1255 return _krb5_expand_default_cc_name(context,
1256 KRB5_DEFAULT_CCNAME_FILE,
1257 str);
1260 static krb5_error_code KRB5_CALLCONV
1261 fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1263 krb5_error_code ret;
1264 struct stat sb;
1265 int fd;
1267 ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY | O_BINARY | O_CLOEXEC | O_NOFOLLOW, 0);
1268 if(ret)
1269 return ret;
1270 ret = fstat(fd, &sb);
1271 close(fd);
1272 if (ret) {
1273 ret = errno;
1274 krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1275 return ret;
1277 *mtime = sb.st_mtime;
1278 return 0;
1281 static krb5_error_code KRB5_CALLCONV
1282 fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1284 return 0;
1287 static krb5_error_code KRB5_CALLCONV
1288 fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1290 krb5_error_code ret;
1291 krb5_storage *sp = NULL;
1292 int fd;
1293 ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
1294 if (sp)
1295 krb5_storage_free(sp);
1296 fcc_unlock(context, fd);
1297 close(fd);
1299 return ret;
1304 * Variable containing the FILE based credential cache implemention.
1306 * @ingroup krb5_ccache
1309 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1310 KRB5_CC_OPS_VERSION,
1311 "FILE",
1312 fcc_get_name,
1313 fcc_resolve,
1314 fcc_gen_new,
1315 fcc_initialize,
1316 fcc_destroy,
1317 fcc_close,
1318 fcc_store_cred,
1319 NULL, /* fcc_retrieve */
1320 fcc_get_principal,
1321 fcc_get_first,
1322 fcc_get_next,
1323 fcc_end_get,
1324 fcc_remove_cred,
1325 fcc_set_flags,
1326 fcc_get_version,
1327 fcc_get_cache_first,
1328 fcc_get_cache_next,
1329 fcc_end_cache_get,
1330 fcc_move,
1331 fcc_get_default_name,
1332 NULL,
1333 fcc_lastchange,
1334 fcc_set_kdc_offset,
1335 fcc_get_kdc_offset