Move external libdeps after our own
[heimdal.git] / lib / krb5 / fcache.c
blobab5d1c137f741c86d1ef28c8db118273bd872905
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 | O_CLOEXEC | O_NOFOLLOW);
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 ret = errno;
279 _krb5_xunlock(context, fd);
280 close (fd);
281 krb5_set_error_message(context, errno,
282 N_("krb5_cc_destroy: unlinking \"%s\": %s", ""),
283 filename, strerror(ret));
284 return ret;
286 ret = fstat(fd, &sb2);
287 if (ret < 0) {
288 ret = errno;
289 _krb5_xunlock(context, fd);
290 close (fd);
291 return ret;
294 /* check if someone was playing with symlinks */
296 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
297 _krb5_xunlock(context, fd);
298 close(fd);
299 return EPERM;
302 /* there are still hard links to this file */
304 if (sb2.st_nlink != 0) {
305 _krb5_xunlock(context, fd);
306 close(fd);
307 return 0;
310 ret = scrub_file(fd);
311 if (ret) {
312 _krb5_xunlock(context, fd);
313 close(fd);
314 return ret;
316 ret = _krb5_xunlock(context, fd);
317 close(fd);
318 return ret;
321 static krb5_error_code KRB5_CALLCONV
322 fcc_gen_new(krb5_context context, krb5_ccache *id)
324 char *file = NULL, *exp_file = NULL;
325 krb5_error_code ret;
326 krb5_fcache *f;
327 int fd;
329 f = malloc(sizeof(*f));
330 if(f == NULL) {
331 krb5_set_error_message(context, KRB5_CC_NOMEM,
332 N_("malloc: out of memory", ""));
333 return KRB5_CC_NOMEM;
335 ret = asprintf(&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
336 if(ret < 0 || file == NULL) {
337 free(f);
338 krb5_set_error_message(context, KRB5_CC_NOMEM,
339 N_("malloc: out of memory", ""));
340 return KRB5_CC_NOMEM;
342 ret = _krb5_expand_path_tokens(context, file, 1, &exp_file);
343 free(file);
344 if (ret) {
345 free(f);
346 return ret;
349 file = exp_file;
351 fd = mkstemp(exp_file);
352 if(fd < 0) {
353 ret = (krb5_error_code)errno;
354 krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
355 free(f);
356 free(exp_file);
357 return ret;
359 close(fd);
360 f->filename = exp_file;
361 f->version = 0;
362 (*id)->data.data = f;
363 (*id)->data.length = sizeof(*f);
364 return 0;
367 static void
368 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
370 int flags = 0;
371 switch(vno) {
372 case KRB5_FCC_FVNO_1:
373 flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
374 flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
375 flags |= KRB5_STORAGE_HOST_BYTEORDER;
376 break;
377 case KRB5_FCC_FVNO_2:
378 flags |= KRB5_STORAGE_HOST_BYTEORDER;
379 break;
380 case KRB5_FCC_FVNO_3:
381 flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
382 break;
383 case KRB5_FCC_FVNO_4:
384 break;
385 default:
386 krb5_abortx(context,
387 "storage_set_flags called with bad vno (%x)", vno);
389 krb5_storage_set_flags(sp, flags);
392 static krb5_error_code KRB5_CALLCONV
393 fcc_open(krb5_context context,
394 krb5_ccache id,
395 const char *operation,
396 int *fd_ret,
397 int flags,
398 mode_t mode)
400 krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
401 (flags | O_RDWR) == flags);
402 krb5_error_code ret;
403 const char *filename;
404 struct stat sb1, sb2;
405 #ifndef _WIN32
406 struct stat sb3;
407 size_t tries = 3;
408 #endif
409 int strict_checking;
410 int fd;
412 flags |= O_BINARY | O_CLOEXEC | O_NOFOLLOW;
414 *fd_ret = -1;
416 if (FCACHE(id) == NULL)
417 return krb5_einval(context, 2);
419 filename = FILENAME(id);
421 strict_checking = (flags & O_CREAT) == 0 &&
422 (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
424 again:
425 memset(&sb1, 0, sizeof(sb1));
426 ret = lstat(filename, &sb1);
427 if (ret == 0) {
428 if (!S_ISREG(sb1.st_mode)) {
429 krb5_set_error_message(context, EPERM,
430 N_("Refuses to open symlinks for caches FILE:%s", ""), filename);
431 return EPERM;
433 } else if (errno != ENOENT || !(flags & O_CREAT)) {
434 krb5_set_error_message(context, errno, N_("%s lstat(%s)", "file, error"),
435 operation, filename);
436 return errno;
439 fd = open(filename, flags, mode);
440 if(fd < 0) {
441 char buf[128];
442 ret = errno;
443 rk_strerror_r(ret, buf, sizeof(buf));
444 krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
445 operation, filename, buf);
446 return ret;
448 rk_cloexec(fd);
450 ret = fstat(fd, &sb2);
451 if (ret < 0) {
452 krb5_clear_error_message(context);
453 close(fd);
454 return errno;
457 if (!S_ISREG(sb2.st_mode)) {
458 krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
459 close(fd);
460 return EPERM;
463 #ifndef _WIN32
464 if (sb1.st_dev && sb1.st_ino &&
465 (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) {
467 * Perhaps we raced with a rename(). To complain about
468 * symlinks in that case would cause unnecessary concern, so
469 * we check for that possibility and loop. This has no
470 * TOCTOU problems because we redo the open(). We could also
471 * not do any of this checking if O_NOFOLLOW != 0...
473 close(fd);
474 ret = lstat(filename, &sb3);
475 if (ret || sb1.st_dev != sb2.st_dev ||
476 sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
477 krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
478 return EPERM;
480 if (--tries == 0) {
481 krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
482 return EPERM;
484 goto again;
486 #endif
489 * /tmp (or wherever default ccaches go) might not be on its own
490 * filesystem, or on a filesystem different /etc, say, and even if
491 * it were, suppose a user hard-links another's ccache to her
492 * default ccache, then runs a set-uid program that will user her
493 * default ccache (even if it ignores KRB5CCNAME)...
495 * Default ccache locations should really be on per-user non-tmp
496 * locations on tmpfs "run" directories. But we don't know here
497 * that this is the case. Thus: no hard-links, no symlinks.
499 if (sb2.st_nlink != 1) {
500 krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
501 close(fd);
502 return EPERM;
505 if (strict_checking) {
506 #ifndef _WIN32
508 * XXX WIN32: Needs to have ACL checking code!
509 * st_mode comes out as 100666, and st_uid is no use.
512 * XXX Should probably add options to improve control over this
513 * check. We might want strict checking of everything except
514 * this.
516 if (sb2.st_uid != geteuid()) {
517 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);
518 close(fd);
519 return EPERM;
521 if ((sb2.st_mode & 077) != 0) {
522 krb5_set_error_message(context, EPERM,
523 N_("Refuses to open group/other readable files FILE:%s", ""), filename);
524 close(fd);
525 return EPERM;
527 #endif
530 if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
531 close(fd);
532 return ret;
534 *fd_ret = fd;
535 return 0;
538 static krb5_error_code KRB5_CALLCONV
539 fcc_initialize(krb5_context context,
540 krb5_ccache id,
541 krb5_principal primary_principal)
543 krb5_fcache *f = FCACHE(id);
544 int ret = 0;
545 int fd;
547 if (f == NULL)
548 return krb5_einval(context, 2);
550 unlink (f->filename);
552 ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL, 0600);
553 if(ret)
554 return ret;
556 krb5_storage *sp;
557 sp = krb5_storage_emem();
558 krb5_storage_set_eof_code(sp, KRB5_CC_END);
559 if(context->fcache_vno != 0)
560 f->version = context->fcache_vno;
561 else
562 f->version = KRB5_FCC_FVNO_4;
563 ret |= krb5_store_int8(sp, 5);
564 ret |= krb5_store_int8(sp, f->version);
565 storage_set_flags(context, sp, f->version);
566 if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
567 /* V4 stuff */
568 if (context->kdc_sec_offset) {
569 ret |= krb5_store_int16 (sp, 12); /* length */
570 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
571 ret |= krb5_store_int16 (sp, 8); /* length of data */
572 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
573 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
574 } else {
575 ret |= krb5_store_int16 (sp, 0);
578 ret |= krb5_store_principal(sp, primary_principal);
580 ret |= write_storage(context, sp, fd);
582 krb5_storage_free(sp);
584 fcc_unlock(context, fd);
585 if (close(fd) < 0)
586 if (ret == 0) {
587 char buf[128];
588 ret = errno;
589 rk_strerror_r(ret, buf, sizeof(buf));
590 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
591 FILENAME(id), buf);
593 return ret;
596 static krb5_error_code KRB5_CALLCONV
597 fcc_close(krb5_context context,
598 krb5_ccache id)
600 if (FCACHE(id) == NULL)
601 return krb5_einval(context, 2);
603 free (FILENAME(id));
604 krb5_data_free(&id->data);
605 return 0;
608 static krb5_error_code KRB5_CALLCONV
609 fcc_destroy(krb5_context context,
610 krb5_ccache id)
612 if (FCACHE(id) == NULL)
613 return krb5_einval(context, 2);
615 return _krb5_erase_file(context, FILENAME(id));
618 static krb5_error_code KRB5_CALLCONV
619 fcc_store_cred(krb5_context context,
620 krb5_ccache id,
621 krb5_creds *creds)
623 int ret;
624 int fd;
626 ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND, 0);
627 if(ret)
628 return ret;
630 krb5_storage *sp;
632 sp = krb5_storage_emem();
633 krb5_storage_set_eof_code(sp, KRB5_CC_END);
634 storage_set_flags(context, sp, FCACHE(id)->version);
635 ret = krb5_store_creds(sp, creds);
636 if (ret == 0)
637 ret = write_storage(context, sp, fd);
638 krb5_storage_free(sp);
640 fcc_unlock(context, fd);
641 if (close(fd) < 0) {
642 if (ret == 0) {
643 char buf[128];
644 ret = errno;
645 rk_strerror_r(ret, buf, sizeof(buf));
646 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
647 FILENAME(id), buf);
650 return ret;
653 static krb5_error_code
654 init_fcc(krb5_context context,
655 krb5_ccache id,
656 const char *operation,
657 krb5_storage **ret_sp,
658 int *ret_fd,
659 krb5_deltat *kdc_offset)
661 int fd;
662 int8_t pvno, tag;
663 krb5_storage *sp;
664 krb5_error_code ret;
666 *ret_fd = -1;
667 *ret_sp = NULL;
668 if (kdc_offset)
669 *kdc_offset = 0;
671 ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0);
672 if(ret)
673 return ret;
675 sp = krb5_storage_from_fd(fd);
676 if(sp == NULL) {
677 krb5_clear_error_message(context);
678 ret = ENOMEM;
679 goto out;
681 krb5_storage_set_eof_code(sp, KRB5_CC_END);
682 ret = krb5_ret_int8(sp, &pvno);
683 if (ret != 0) {
684 if(ret == KRB5_CC_END) {
685 ret = ENOENT;
686 krb5_set_error_message(context, ret,
687 N_("Empty credential cache file: %s", ""),
688 FILENAME(id));
689 } else
690 krb5_set_error_message(context, ret, N_("Error reading pvno "
691 "in cache file: %s", ""),
692 FILENAME(id));
693 goto out;
695 if (pvno != 5) {
696 ret = KRB5_CCACHE_BADVNO;
697 krb5_set_error_message(context, ret, N_("Bad version number in credential "
698 "cache file: %s", ""),
699 FILENAME(id));
700 goto out;
702 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
703 if (ret != 0) {
704 ret = KRB5_CC_FORMAT;
705 krb5_set_error_message(context, ret, "Error reading tag in "
706 "cache file: %s", FILENAME(id));
707 goto out;
709 FCACHE(id)->version = tag;
710 storage_set_flags(context, sp, FCACHE(id)->version);
711 switch (tag) {
712 case KRB5_FCC_FVNO_4: {
713 int16_t length;
715 ret = krb5_ret_int16 (sp, &length);
716 if(ret) {
717 ret = KRB5_CC_FORMAT;
718 krb5_set_error_message(context, ret,
719 N_("Error reading tag length in "
720 "cache file: %s", ""), FILENAME(id));
721 goto out;
723 while(length > 0) {
724 int16_t dtag, data_len;
725 int i;
726 int8_t dummy;
728 ret = krb5_ret_int16 (sp, &dtag);
729 if(ret) {
730 ret = KRB5_CC_FORMAT;
731 krb5_set_error_message(context, ret, N_("Error reading dtag in "
732 "cache file: %s", ""),
733 FILENAME(id));
734 goto out;
736 ret = krb5_ret_int16 (sp, &data_len);
737 if(ret) {
738 ret = KRB5_CC_FORMAT;
739 krb5_set_error_message(context, ret,
740 N_("Error reading dlength "
741 "in cache file: %s",""),
742 FILENAME(id));
743 goto out;
745 switch (dtag) {
746 case FCC_TAG_DELTATIME : {
747 int32_t offset;
749 ret = krb5_ret_int32 (sp, &offset);
750 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
751 if(ret) {
752 ret = KRB5_CC_FORMAT;
753 krb5_set_error_message(context, ret,
754 N_("Error reading kdc_sec in "
755 "cache file: %s", ""),
756 FILENAME(id));
757 goto out;
759 context->kdc_sec_offset = offset;
760 if (kdc_offset)
761 *kdc_offset = offset;
762 break;
764 default :
765 for (i = 0; i < data_len; ++i) {
766 ret = krb5_ret_int8 (sp, &dummy);
767 if(ret) {
768 ret = KRB5_CC_FORMAT;
769 krb5_set_error_message(context, ret,
770 N_("Error reading unknown "
771 "tag in cache file: %s", ""),
772 FILENAME(id));
773 goto out;
776 break;
778 length -= 4 + data_len;
780 break;
782 case KRB5_FCC_FVNO_3:
783 case KRB5_FCC_FVNO_2:
784 case KRB5_FCC_FVNO_1:
785 break;
786 default :
787 ret = KRB5_CCACHE_BADVNO;
788 krb5_set_error_message(context, ret,
789 N_("Unknown version number (%d) in "
790 "credential cache file: %s", ""),
791 (int)tag, FILENAME(id));
792 goto out;
794 *ret_sp = sp;
795 *ret_fd = fd;
797 return 0;
798 out:
799 if(sp != NULL)
800 krb5_storage_free(sp);
801 fcc_unlock(context, fd);
802 close(fd);
803 return ret;
806 static krb5_error_code KRB5_CALLCONV
807 fcc_get_principal(krb5_context context,
808 krb5_ccache id,
809 krb5_principal *principal)
811 krb5_error_code ret;
812 int fd;
813 krb5_storage *sp;
815 ret = init_fcc (context, id, "get-principal", &sp, &fd, NULL);
816 if (ret)
817 return ret;
818 ret = krb5_ret_principal(sp, principal);
819 if (ret)
820 krb5_clear_error_message(context);
821 krb5_storage_free(sp);
822 fcc_unlock(context, fd);
823 close(fd);
824 return ret;
827 static krb5_error_code KRB5_CALLCONV
828 fcc_end_get (krb5_context context,
829 krb5_ccache id,
830 krb5_cc_cursor *cursor);
832 static krb5_error_code KRB5_CALLCONV
833 fcc_get_first (krb5_context context,
834 krb5_ccache id,
835 krb5_cc_cursor *cursor)
837 krb5_error_code ret;
838 krb5_principal principal;
840 if (FCACHE(id) == NULL)
841 return krb5_einval(context, 2);
843 *cursor = malloc(sizeof(struct fcc_cursor));
844 if (*cursor == NULL) {
845 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
846 return ENOMEM;
848 memset(*cursor, 0, sizeof(struct fcc_cursor));
850 ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp,
851 &FCC_CURSOR(*cursor)->fd, NULL);
852 if (ret) {
853 free(*cursor);
854 *cursor = NULL;
855 return ret;
857 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
858 if(ret) {
859 krb5_clear_error_message(context);
860 fcc_end_get(context, id, cursor);
861 return ret;
863 krb5_free_principal (context, principal);
864 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
865 return 0;
868 static krb5_error_code KRB5_CALLCONV
869 fcc_get_next (krb5_context context,
870 krb5_ccache id,
871 krb5_cc_cursor *cursor,
872 krb5_creds *creds)
874 krb5_error_code ret;
876 if (FCACHE(id) == NULL)
877 return krb5_einval(context, 2);
879 if (FCC_CURSOR(*cursor) == NULL)
880 return krb5_einval(context, 3);
882 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
883 return ret;
884 FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd,
885 0, SEEK_CUR);
887 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
888 if (ret)
889 krb5_clear_error_message(context);
891 FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd,
892 0, SEEK_CUR);
894 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
895 return ret;
898 static krb5_error_code KRB5_CALLCONV
899 fcc_end_get (krb5_context context,
900 krb5_ccache id,
901 krb5_cc_cursor *cursor)
904 if (FCACHE(id) == NULL)
905 return krb5_einval(context, 2);
907 if (FCC_CURSOR(*cursor) == NULL)
908 return krb5_einval(context, 3);
910 krb5_storage_free(FCC_CURSOR(*cursor)->sp);
911 close (FCC_CURSOR(*cursor)->fd);
912 free(*cursor);
913 *cursor = NULL;
914 return 0;
917 static void KRB5_CALLCONV
918 cred_delete(krb5_context context,
919 krb5_ccache id,
920 krb5_cc_cursor *cursor,
921 krb5_creds *cred)
923 krb5_error_code ret;
924 krb5_storage *sp;
925 krb5_data orig_cred_data;
926 unsigned char *cred_data_in_file = NULL;
927 off_t new_cred_sz;
928 struct stat sb1, sb2;
929 int fd = -1;
930 ssize_t bytes;
931 krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
933 /* This is best-effort code; if we lose track of errors here it's OK */
935 heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
936 "fcache internal error");
938 krb5_data_zero(&orig_cred_data);
940 sp = krb5_storage_emem();
941 if (sp == NULL)
942 return;
943 krb5_storage_set_eof_code(sp, KRB5_CC_END);
944 storage_set_flags(context, sp, FCACHE(id)->version);
946 /* Get a copy of what the cred should look like in the file; see below */
947 ret = krb5_store_creds(sp, cred);
948 if (ret)
949 goto out;
951 ret = krb5_storage_to_data(sp, &orig_cred_data);
952 if (ret)
953 goto out;
954 krb5_storage_free(sp);
956 cred_data_in_file = malloc(orig_cred_data.length);
957 if (cred_data_in_file == NULL)
958 goto out;
961 * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
962 * KRB5_TC_MATCH_TIMES, so this should be good enough...
964 cred->times.endtime = 0;
966 /* ...except for config creds because we don't check their endtimes */
967 if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
968 ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
969 if (ret)
970 goto out;
973 sp = krb5_storage_emem();
974 if (sp == NULL)
975 goto out;
976 krb5_storage_set_eof_code(sp, KRB5_CC_END);
977 storage_set_flags(context, sp, FCACHE(id)->version);
979 ret = krb5_store_creds(sp, cred);
981 /* The new cred must be the same size as the old cred */
982 new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
983 if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
984 (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
985 /* XXX This really can't happen. Assert like above? */
986 krb5_set_error_message(context, EINVAL,
987 N_("Credential deletion failed on ccache "
988 "FILE:%s: new credential size did not "
989 "match old credential size", ""),
990 FILENAME(id));
991 goto out;
994 ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0);
995 if (ret)
996 goto out;
999 * Check that we're updating the same file where we got the
1000 * cred's offset, else we'd be corrupting a new ccache.
1002 if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
1003 fstat(fd, &sb2) == -1)
1004 goto out;
1005 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
1006 goto out;
1009 * Make sure what we overwrite is what we expected.
1011 * FIXME: We *really* need the ccache v4 tag for ccache ID. This
1012 * check that we're only overwriting something that looks exactly
1013 * like what we want to is probably good enough in practice, but
1014 * it's not guaranteed to work.
1016 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1017 goto out;
1018 bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1019 if (bytes != orig_cred_data.length)
1020 goto out;
1021 if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1022 goto out;
1023 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1024 goto out;
1025 ret = write_storage(context, sp, fd);
1026 out:
1027 if (fd > -1) {
1028 fcc_unlock(context, fd);
1029 if (close(fd) < 0 && ret == 0) {
1030 krb5_set_error_message(context, errno, N_("close %s", ""),
1031 FILENAME(id));
1034 krb5_data_free(&orig_cred_data);
1035 free(cred_data_in_file);
1036 krb5_storage_free(sp);
1037 return;
1040 static krb5_error_code KRB5_CALLCONV
1041 fcc_remove_cred(krb5_context context,
1042 krb5_ccache id,
1043 krb5_flags which,
1044 krb5_creds *mcred)
1046 krb5_error_code ret, ret2;
1047 krb5_cc_cursor cursor;
1048 krb5_creds found_cred;
1050 if (FCACHE(id) == NULL)
1051 return krb5_einval(context, 2);
1053 ret = krb5_cc_start_seq_get(context, id, &cursor);
1054 if (ret)
1055 return ret;
1056 while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
1057 if (!krb5_compare_creds(context, which, mcred, &found_cred)) {
1058 krb5_free_cred_contents(context, &found_cred);
1059 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, *cc_type;
1118 krb5_ccache cc;
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 * Note: do not allow krb5_cc_default_name() to recurse via
1131 * krb5_cc_cache_match().
1132 * Note that context->default_cc_name will be NULL even though
1133 * KRB5CCNAME is set in the environment if
1134 * krb5_cc_set_default_name() hasn't
1136 fn = krb5_cc_default_name(context);
1137 ret = krb5_cc_resolve(context, fn, &cc);
1138 if (ret != 0)
1139 return ret;
1140 cc_type = krb5_cc_get_type(context, cc);
1141 if (strcmp(cc_type, "FILE") != 0) {
1142 krb5_cc_close(context, cc);
1143 return KRB5_CC_END;
1146 *id = cc;
1148 return 0;
1151 static krb5_error_code KRB5_CALLCONV
1152 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1154 struct fcache_iter *iter = cursor;
1156 if (iter == NULL)
1157 return krb5_einval(context, 2);
1159 free(iter);
1160 return 0;
1163 static krb5_error_code KRB5_CALLCONV
1164 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1166 krb5_error_code ret = 0;
1168 ret = rk_rename(FILENAME(from), FILENAME(to));
1170 if (ret && errno != EXDEV) {
1171 char buf[128];
1172 ret = errno;
1173 rk_strerror_r(ret, buf, sizeof(buf));
1174 krb5_set_error_message(context, ret,
1175 N_("Rename of file from %s "
1176 "to %s failed: %s", ""),
1177 FILENAME(from), FILENAME(to), buf);
1178 return ret;
1179 } else if (ret && errno == EXDEV) {
1180 /* make a copy and delete the orignal */
1181 krb5_ssize_t sz1, sz2;
1182 int fd1, fd2;
1183 char buf[BUFSIZ];
1185 ret = fcc_open(context, from, "move/from", &fd1, O_RDONLY, 0);
1186 if(ret)
1187 return ret;
1189 unlink(FILENAME(to));
1191 ret = fcc_open(context, to, "move/to", &fd2,
1192 O_WRONLY | O_CREAT | O_EXCL, 0600);
1193 if(ret)
1194 goto out1;
1196 while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
1197 sz2 = write(fd2, buf, sz1);
1198 if (sz1 != sz2) {
1199 ret = EIO;
1200 krb5_set_error_message(context, ret,
1201 N_("Failed to write data from one file "
1202 "credential cache to the other", ""));
1203 goto out2;
1206 if (sz1 < 0) {
1207 ret = EIO;
1208 krb5_set_error_message(context, ret,
1209 N_("Failed to read data from one file "
1210 "credential cache to the other", ""));
1211 goto out2;
1213 out2:
1214 fcc_unlock(context, fd2);
1215 close(fd2);
1217 out1:
1218 fcc_unlock(context, fd1);
1219 close(fd1);
1221 _krb5_erase_file(context, FILENAME(from));
1223 if (ret) {
1224 _krb5_erase_file(context, FILENAME(to));
1225 return ret;
1229 /* make sure ->version is uptodate */
1231 krb5_storage *sp;
1232 int fd;
1233 if ((ret = init_fcc (context, to, "move", &sp, &fd, NULL)) == 0) {
1234 if (sp)
1235 krb5_storage_free(sp);
1236 fcc_unlock(context, fd);
1237 close(fd);
1241 fcc_close(context, from);
1243 return ret;
1246 static krb5_error_code KRB5_CALLCONV
1247 fcc_get_default_name(krb5_context context, char **str)
1249 return _krb5_expand_default_cc_name(context,
1250 KRB5_DEFAULT_CCNAME_FILE,
1251 str);
1254 static krb5_error_code KRB5_CALLCONV
1255 fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1257 krb5_error_code ret;
1258 struct stat sb;
1259 int fd;
1261 ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY, 0);
1262 if(ret)
1263 return ret;
1264 ret = fstat(fd, &sb);
1265 close(fd);
1266 if (ret) {
1267 ret = errno;
1268 krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1269 return ret;
1271 *mtime = sb.st_mtime;
1272 return 0;
1275 static krb5_error_code KRB5_CALLCONV
1276 fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1278 return 0;
1281 static krb5_error_code KRB5_CALLCONV
1282 fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1284 krb5_error_code ret;
1285 krb5_storage *sp = NULL;
1286 int fd;
1287 ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
1288 if (sp)
1289 krb5_storage_free(sp);
1290 fcc_unlock(context, fd);
1291 close(fd);
1293 return ret;
1298 * Variable containing the FILE based credential cache implemention.
1300 * @ingroup krb5_ccache
1303 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1304 KRB5_CC_OPS_VERSION,
1305 "FILE",
1306 fcc_get_name,
1307 fcc_resolve,
1308 fcc_gen_new,
1309 fcc_initialize,
1310 fcc_destroy,
1311 fcc_close,
1312 fcc_store_cred,
1313 NULL, /* fcc_retrieve */
1314 fcc_get_principal,
1315 fcc_get_first,
1316 fcc_get_next,
1317 fcc_end_get,
1318 fcc_remove_cred,
1319 fcc_set_flags,
1320 fcc_get_version,
1321 fcc_get_cache_first,
1322 fcc_get_cache_next,
1323 fcc_end_cache_get,
1324 fcc_move,
1325 fcc_get_default_name,
1326 NULL,
1327 fcc_lastchange,
1328 fcc_set_kdc_offset,
1329 fcc_get_kdc_offset