Fix Windows build: no fork for kdc
[heimdal.git] / lib / krb5 / fcache.c
blob10c5de1f48f866532b76400b3a9ea3b0224c696b
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 _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, 1, &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 flags |= O_BINARY | O_CLOEXEC | O_NOFOLLOW;
409 *fd_ret = -1;
411 if (FCACHE(id) == NULL)
412 return krb5_einval(context, 2);
414 filename = FILENAME(id);
416 strict_checking = (flags & O_CREAT) == 0 &&
417 (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
419 again:
420 memset(&sb1, 0, sizeof(sb1));
421 ret = lstat(filename, &sb1);
422 if (ret == 0) {
423 if (!S_ISREG(sb1.st_mode)) {
424 krb5_set_error_message(context, EPERM,
425 N_("Refuses to open symlinks for caches FILE:%s", ""), filename);
426 return EPERM;
428 } else if (errno != ENOENT || !(flags & O_CREAT)) {
429 krb5_set_error_message(context, errno, N_("%s lstat(%s)", "file, error"),
430 operation, filename);
431 return errno;
434 fd = open(filename, flags, mode);
435 if(fd < 0) {
436 char buf[128];
437 ret = errno;
438 rk_strerror_r(ret, buf, sizeof(buf));
439 krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
440 operation, filename, buf);
441 return ret;
443 rk_cloexec(fd);
445 ret = fstat(fd, &sb2);
446 if (ret < 0) {
447 krb5_clear_error_message(context);
448 return errno;
451 if (!S_ISREG(sb2.st_mode)) {
452 krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
453 close(fd);
454 return EPERM;
457 #ifndef _WIN32
458 if (sb1.st_dev && sb1.st_ino &&
459 (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) {
461 * Perhaps we raced with a rename(). To complain about
462 * symlinks in that case would cause unnecessary concern, so
463 * we check for that possibility and loop. This has no
464 * TOCTOU problems because we redo the open(). We could also
465 * not do any of this checking if O_NOFOLLOW != 0...
467 close(fd);
468 ret = lstat(filename, &sb3);
469 if (ret || sb1.st_dev != sb2.st_dev ||
470 sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
471 krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
472 return EPERM;
474 if (--tries == 0) {
475 krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
476 return EPERM;
478 goto again;
480 #endif
482 if (sb2.st_nlink != 1) {
483 krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
484 close(fd);
485 return EPERM;
488 if (strict_checking) {
489 #ifndef _WIN32
491 * XXX WIN32: Needs to have ACL checking code!
492 * st_mode comes out as 100666, and st_uid is no use.
495 * XXX Should probably add options to improve control over this
496 * check. We might want strict checking of everything except
497 * this.
499 if (sb2.st_uid != geteuid()) {
500 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);
501 close(fd);
502 return EPERM;
504 if ((sb2.st_mode & 077) != 0) {
505 krb5_set_error_message(context, EPERM,
506 N_("Refuses to open group/other readable files FILE:%s", ""), filename);
507 close(fd);
508 return EPERM;
510 #endif
513 if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
514 close(fd);
515 return ret;
517 *fd_ret = fd;
518 return 0;
521 static krb5_error_code KRB5_CALLCONV
522 fcc_initialize(krb5_context context,
523 krb5_ccache id,
524 krb5_principal primary_principal)
526 krb5_fcache *f = FCACHE(id);
527 int ret = 0;
528 int fd;
530 if (f == NULL)
531 return krb5_einval(context, 2);
533 unlink (f->filename);
535 ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL, 0600);
536 if(ret)
537 return ret;
539 krb5_storage *sp;
540 sp = krb5_storage_emem();
541 krb5_storage_set_eof_code(sp, KRB5_CC_END);
542 if(context->fcache_vno != 0)
543 f->version = context->fcache_vno;
544 else
545 f->version = KRB5_FCC_FVNO_4;
546 ret |= krb5_store_int8(sp, 5);
547 ret |= krb5_store_int8(sp, f->version);
548 storage_set_flags(context, sp, f->version);
549 if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
550 /* V4 stuff */
551 if (context->kdc_sec_offset) {
552 ret |= krb5_store_int16 (sp, 12); /* length */
553 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
554 ret |= krb5_store_int16 (sp, 8); /* length of data */
555 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
556 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
557 } else {
558 ret |= krb5_store_int16 (sp, 0);
561 ret |= krb5_store_principal(sp, primary_principal);
563 ret |= write_storage(context, sp, fd);
565 krb5_storage_free(sp);
567 fcc_unlock(context, fd);
568 if (close(fd) < 0)
569 if (ret == 0) {
570 char buf[128];
571 ret = errno;
572 rk_strerror_r(ret, buf, sizeof(buf));
573 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
574 FILENAME(id), buf);
576 return ret;
579 static krb5_error_code KRB5_CALLCONV
580 fcc_close(krb5_context context,
581 krb5_ccache id)
583 if (FCACHE(id) == NULL)
584 return krb5_einval(context, 2);
586 free (FILENAME(id));
587 krb5_data_free(&id->data);
588 return 0;
591 static krb5_error_code KRB5_CALLCONV
592 fcc_destroy(krb5_context context,
593 krb5_ccache id)
595 if (FCACHE(id) == NULL)
596 return krb5_einval(context, 2);
598 _krb5_erase_file(context, FILENAME(id));
599 return 0;
602 static krb5_error_code KRB5_CALLCONV
603 fcc_store_cred(krb5_context context,
604 krb5_ccache id,
605 krb5_creds *creds)
607 int ret;
608 int fd;
610 ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND, 0);
611 if(ret)
612 return ret;
614 krb5_storage *sp;
616 sp = krb5_storage_emem();
617 krb5_storage_set_eof_code(sp, KRB5_CC_END);
618 storage_set_flags(context, sp, FCACHE(id)->version);
619 ret = krb5_store_creds(sp, creds);
620 if (ret == 0)
621 ret = write_storage(context, sp, fd);
622 krb5_storage_free(sp);
624 fcc_unlock(context, fd);
625 if (close(fd) < 0) {
626 if (ret == 0) {
627 char buf[128];
628 rk_strerror_r(ret, buf, sizeof(buf));
629 ret = errno;
630 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
631 FILENAME(id), buf);
634 return ret;
637 static krb5_error_code
638 init_fcc(krb5_context context,
639 krb5_ccache id,
640 const char *operation,
641 krb5_storage **ret_sp,
642 int *ret_fd,
643 krb5_deltat *kdc_offset)
645 int fd;
646 int8_t pvno, tag;
647 krb5_storage *sp;
648 krb5_error_code ret;
650 if (kdc_offset)
651 *kdc_offset = 0;
653 ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0);
654 if(ret)
655 return ret;
657 sp = krb5_storage_from_fd(fd);
658 if(sp == NULL) {
659 krb5_clear_error_message(context);
660 ret = ENOMEM;
661 goto out;
663 krb5_storage_set_eof_code(sp, KRB5_CC_END);
664 ret = krb5_ret_int8(sp, &pvno);
665 if (ret != 0) {
666 if(ret == KRB5_CC_END) {
667 ret = ENOENT;
668 krb5_set_error_message(context, ret,
669 N_("Empty credential cache file: %s", ""),
670 FILENAME(id));
671 } else
672 krb5_set_error_message(context, ret, N_("Error reading pvno "
673 "in cache file: %s", ""),
674 FILENAME(id));
675 goto out;
677 if (pvno != 5) {
678 ret = KRB5_CCACHE_BADVNO;
679 krb5_set_error_message(context, ret, N_("Bad version number in credential "
680 "cache file: %s", ""),
681 FILENAME(id));
682 goto out;
684 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
685 if (ret != 0) {
686 ret = KRB5_CC_FORMAT;
687 krb5_set_error_message(context, ret, "Error reading tag in "
688 "cache file: %s", FILENAME(id));
689 goto out;
691 FCACHE(id)->version = tag;
692 storage_set_flags(context, sp, FCACHE(id)->version);
693 switch (tag) {
694 case KRB5_FCC_FVNO_4: {
695 int16_t length;
697 ret = krb5_ret_int16 (sp, &length);
698 if(ret) {
699 ret = KRB5_CC_FORMAT;
700 krb5_set_error_message(context, ret,
701 N_("Error reading tag length in "
702 "cache file: %s", ""), FILENAME(id));
703 goto out;
705 while(length > 0) {
706 int16_t dtag, data_len;
707 int i;
708 int8_t dummy;
710 ret = krb5_ret_int16 (sp, &dtag);
711 if(ret) {
712 ret = KRB5_CC_FORMAT;
713 krb5_set_error_message(context, ret, N_("Error reading dtag in "
714 "cache file: %s", ""),
715 FILENAME(id));
716 goto out;
718 ret = krb5_ret_int16 (sp, &data_len);
719 if(ret) {
720 ret = KRB5_CC_FORMAT;
721 krb5_set_error_message(context, ret,
722 N_("Error reading dlength "
723 "in cache file: %s",""),
724 FILENAME(id));
725 goto out;
727 switch (dtag) {
728 case FCC_TAG_DELTATIME : {
729 int32_t offset;
731 ret = krb5_ret_int32 (sp, &offset);
732 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
733 if(ret) {
734 ret = KRB5_CC_FORMAT;
735 krb5_set_error_message(context, ret,
736 N_("Error reading kdc_sec in "
737 "cache file: %s", ""),
738 FILENAME(id));
739 goto out;
741 context->kdc_sec_offset = offset;
742 if (kdc_offset)
743 *kdc_offset = offset;
744 break;
746 default :
747 for (i = 0; i < data_len; ++i) {
748 ret = krb5_ret_int8 (sp, &dummy);
749 if(ret) {
750 ret = KRB5_CC_FORMAT;
751 krb5_set_error_message(context, ret,
752 N_("Error reading unknown "
753 "tag in cache file: %s", ""),
754 FILENAME(id));
755 goto out;
758 break;
760 length -= 4 + data_len;
762 break;
764 case KRB5_FCC_FVNO_3:
765 case KRB5_FCC_FVNO_2:
766 case KRB5_FCC_FVNO_1:
767 break;
768 default :
769 ret = KRB5_CCACHE_BADVNO;
770 krb5_set_error_message(context, ret,
771 N_("Unknown version number (%d) in "
772 "credential cache file: %s", ""),
773 (int)tag, FILENAME(id));
774 goto out;
776 *ret_sp = sp;
777 *ret_fd = fd;
779 return 0;
780 out:
781 if(sp != NULL)
782 krb5_storage_free(sp);
783 fcc_unlock(context, fd);
784 close(fd);
785 return ret;
788 static krb5_error_code KRB5_CALLCONV
789 fcc_get_principal(krb5_context context,
790 krb5_ccache id,
791 krb5_principal *principal)
793 krb5_error_code ret;
794 int fd;
795 krb5_storage *sp;
797 ret = init_fcc (context, id, "get-pricipal", &sp, &fd, NULL);
798 if (ret)
799 return ret;
800 ret = krb5_ret_principal(sp, principal);
801 if (ret)
802 krb5_clear_error_message(context);
803 krb5_storage_free(sp);
804 fcc_unlock(context, fd);
805 close(fd);
806 return ret;
809 static krb5_error_code KRB5_CALLCONV
810 fcc_end_get (krb5_context context,
811 krb5_ccache id,
812 krb5_cc_cursor *cursor);
814 static krb5_error_code KRB5_CALLCONV
815 fcc_get_first (krb5_context context,
816 krb5_ccache id,
817 krb5_cc_cursor *cursor)
819 krb5_error_code ret;
820 krb5_principal principal;
822 if (FCACHE(id) == NULL)
823 return krb5_einval(context, 2);
825 *cursor = malloc(sizeof(struct fcc_cursor));
826 if (*cursor == NULL) {
827 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
828 return ENOMEM;
830 memset(*cursor, 0, sizeof(struct fcc_cursor));
832 ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp,
833 &FCC_CURSOR(*cursor)->fd, NULL);
834 if (ret) {
835 free(*cursor);
836 *cursor = NULL;
837 return ret;
839 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
840 if(ret) {
841 krb5_clear_error_message(context);
842 fcc_end_get(context, id, cursor);
843 return ret;
845 krb5_free_principal (context, principal);
846 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
847 return 0;
850 static krb5_error_code KRB5_CALLCONV
851 fcc_get_next (krb5_context context,
852 krb5_ccache id,
853 krb5_cc_cursor *cursor,
854 krb5_creds *creds)
856 krb5_error_code ret;
858 if (FCACHE(id) == NULL)
859 return krb5_einval(context, 2);
861 if (FCC_CURSOR(*cursor) == NULL)
862 return krb5_einval(context, 3);
864 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
865 return ret;
866 FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd,
867 0, SEEK_CUR);
869 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
870 if (ret)
871 krb5_clear_error_message(context);
873 FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd,
874 0, SEEK_CUR);
876 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
877 return ret;
880 static krb5_error_code KRB5_CALLCONV
881 fcc_end_get (krb5_context context,
882 krb5_ccache id,
883 krb5_cc_cursor *cursor)
886 if (FCACHE(id) == NULL)
887 return krb5_einval(context, 2);
889 if (FCC_CURSOR(*cursor) == NULL)
890 return krb5_einval(context, 3);
892 krb5_storage_free(FCC_CURSOR(*cursor)->sp);
893 close (FCC_CURSOR(*cursor)->fd);
894 free(*cursor);
895 *cursor = NULL;
896 return 0;
899 static void KRB5_CALLCONV
900 cred_delete(krb5_context context,
901 krb5_ccache id,
902 krb5_cc_cursor *cursor,
903 krb5_creds *cred)
905 krb5_error_code ret;
906 krb5_storage *sp;
907 krb5_data orig_cred_data;
908 unsigned char *cred_data_in_file = NULL;
909 off_t new_cred_sz;
910 struct stat sb1, sb2;
911 int fd = -1;
912 ssize_t bytes;
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);
922 sp = krb5_storage_emem();
923 if (sp == NULL)
924 return;
925 krb5_storage_set_eof_code(sp, KRB5_CC_END);
926 storage_set_flags(context, sp, FCACHE(id)->version);
928 /* Get a copy of what the cred should look like in the file; see below */
929 ret = krb5_store_creds(sp, cred);
930 if (ret)
931 goto out;
933 ret = krb5_storage_to_data(sp, &orig_cred_data);
934 if (ret)
935 goto out;
936 krb5_storage_free(sp);
938 cred_data_in_file = malloc(orig_cred_data.length);
939 if (cred_data_in_file == NULL)
940 goto out;
943 * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
944 * KRB5_TC_MATCH_TIMES, so this should be good enough...
946 cred->times.endtime = 0;
948 /* ...except for config creds because we don't check their endtimes */
949 if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
950 ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
951 if (ret)
952 return;
955 sp = krb5_storage_emem();
956 if (sp == NULL)
957 return;
958 krb5_storage_set_eof_code(sp, KRB5_CC_END);
959 storage_set_flags(context, sp, FCACHE(id)->version);
961 ret = krb5_store_creds(sp, cred);
963 /* The new cred must be the same size as the old cred */
964 new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
965 if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
966 (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
967 /* XXX This really can't happen. Assert like above? */
968 krb5_set_error_message(context, EINVAL,
969 N_("Credential deletion failed on ccache "
970 "FILE:%s: new credential size did not "
971 "match old credential size", ""),
972 FILENAME(id));
973 goto out;
976 ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0);
977 if (ret)
978 goto out;
981 * Check that we're updating the same file where we got the
982 * cred's offset, else we'd be corrupting a new ccache.
984 if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
985 fstat(fd, &sb2) == -1)
986 goto out;
987 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
988 goto out;
991 * Make sure what we overwrite is what we expected.
993 * FIXME: We *really* need the ccache v4 tag for ccache ID. This
994 * check that we're only overwriting something that looks exactly
995 * like what we want to is probably good enough in practice, but
996 * it's not guaranteed to work.
998 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
999 goto out;
1000 bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1001 if (bytes != orig_cred_data.length)
1002 goto out;
1003 if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1004 goto out;
1005 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1006 goto out;
1007 ret = write_storage(context, sp, fd);
1008 out:
1009 if (fd > -1) {
1010 fcc_unlock(context, fd);
1011 if (close(fd) < 0 && ret == 0) {
1012 krb5_set_error_message(context, errno, N_("close %s", ""),
1013 FILENAME(id));
1016 krb5_data_free(&orig_cred_data);
1017 free(cred_data_in_file);
1018 krb5_storage_free(sp);
1019 return;
1022 static krb5_error_code KRB5_CALLCONV
1023 fcc_remove_cred(krb5_context context,
1024 krb5_ccache id,
1025 krb5_flags which,
1026 krb5_creds *mcred)
1028 krb5_error_code ret, ret2;
1029 krb5_cc_cursor cursor;
1030 krb5_creds found_cred;
1032 if (FCACHE(id) == NULL)
1033 return krb5_einval(context, 2);
1035 ret = krb5_cc_start_seq_get(context, id, &cursor);
1036 if (ret)
1037 return ret;
1038 while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
1039 if (!krb5_compare_creds(context, which, mcred, &found_cred)) {
1040 krb5_free_cred_contents(context, &found_cred);
1041 continue;
1043 cred_delete(context, id, &cursor, &found_cred);
1044 krb5_free_cred_contents(context, &found_cred);
1046 ret2 = krb5_cc_end_seq_get(context, id, &cursor);
1047 if (ret == 0)
1048 return ret2;
1049 if (ret == KRB5_CC_END)
1050 return 0;
1051 return ret;
1054 static krb5_error_code KRB5_CALLCONV
1055 fcc_set_flags(krb5_context context,
1056 krb5_ccache id,
1057 krb5_flags flags)
1059 if (FCACHE(id) == NULL)
1060 return krb5_einval(context, 2);
1062 return 0; /* XXX */
1065 static int KRB5_CALLCONV
1066 fcc_get_version(krb5_context context,
1067 krb5_ccache id)
1069 if (FCACHE(id) == NULL)
1070 return -1;
1072 return FCACHE(id)->version;
1075 struct fcache_iter {
1076 int first;
1079 static krb5_error_code KRB5_CALLCONV
1080 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1082 struct fcache_iter *iter;
1084 iter = calloc(1, sizeof(*iter));
1085 if (iter == NULL) {
1086 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
1087 return ENOMEM;
1089 iter->first = 1;
1090 *cursor = iter;
1091 return 0;
1094 static krb5_error_code KRB5_CALLCONV
1095 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
1097 struct fcache_iter *iter = cursor;
1098 krb5_error_code ret;
1099 const char *fn, *cc_type;
1100 krb5_ccache cc;
1102 if (iter == NULL)
1103 return krb5_einval(context, 2);
1105 if (!iter->first) {
1106 krb5_clear_error_message(context);
1107 return KRB5_CC_END;
1109 iter->first = 0;
1112 * Note: do not allow krb5_cc_default_name() to recurse via
1113 * krb5_cc_cache_match().
1114 * Note that context->default_cc_name will be NULL even though
1115 * KRB5CCNAME is set in the environment if
1116 * krb5_cc_set_default_name() hasn't
1118 fn = krb5_cc_default_name(context);
1119 ret = krb5_cc_resolve(context, fn, &cc);
1120 if (ret != 0)
1121 return ret;
1122 cc_type = krb5_cc_get_type(context, cc);
1123 if (strcmp(cc_type, "FILE") != 0) {
1124 krb5_cc_close(context, cc);
1125 return KRB5_CC_END;
1128 *id = cc;
1130 return 0;
1133 static krb5_error_code KRB5_CALLCONV
1134 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1136 struct fcache_iter *iter = cursor;
1138 if (iter == NULL)
1139 return krb5_einval(context, 2);
1141 free(iter);
1142 return 0;
1145 static krb5_error_code KRB5_CALLCONV
1146 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1148 krb5_error_code ret = 0;
1150 ret = rk_rename(FILENAME(from), FILENAME(to));
1152 if (ret && errno != EXDEV) {
1153 char buf[128];
1154 ret = errno;
1155 rk_strerror_r(ret, buf, sizeof(buf));
1156 krb5_set_error_message(context, ret,
1157 N_("Rename of file from %s "
1158 "to %s failed: %s", ""),
1159 FILENAME(from), FILENAME(to), buf);
1160 return ret;
1161 } else if (ret && errno == EXDEV) {
1162 /* make a copy and delete the orignal */
1163 krb5_ssize_t sz1, sz2;
1164 int fd1, fd2;
1165 char buf[BUFSIZ];
1167 ret = fcc_open(context, from, "move/from", &fd1, O_RDONLY, 0);
1168 if(ret)
1169 return ret;
1171 unlink(FILENAME(to));
1173 ret = fcc_open(context, to, "move/to", &fd2,
1174 O_WRONLY | O_CREAT | O_EXCL, 0600);
1175 if(ret)
1176 goto out1;
1178 while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
1179 sz2 = write(fd2, buf, sz1);
1180 if (sz1 != sz2) {
1181 ret = EIO;
1182 krb5_set_error_message(context, ret,
1183 N_("Failed to write data from one file "
1184 "credential cache to the other", ""));
1185 goto out2;
1188 if (sz1 < 0) {
1189 ret = EIO;
1190 krb5_set_error_message(context, ret,
1191 N_("Failed to read data from one file "
1192 "credential cache to the other", ""));
1193 goto out2;
1195 out2:
1196 fcc_unlock(context, fd2);
1197 close(fd2);
1199 out1:
1200 fcc_unlock(context, fd1);
1201 close(fd1);
1203 _krb5_erase_file(context, FILENAME(from));
1205 if (ret) {
1206 _krb5_erase_file(context, FILENAME(to));
1207 return ret;
1211 /* make sure ->version is uptodate */
1213 krb5_storage *sp;
1214 int fd;
1215 if ((ret = init_fcc (context, to, "move", &sp, &fd, NULL)) == 0) {
1216 if (sp)
1217 krb5_storage_free(sp);
1218 fcc_unlock(context, fd);
1219 close(fd);
1223 fcc_close(context, from);
1225 return ret;
1228 static krb5_error_code KRB5_CALLCONV
1229 fcc_get_default_name(krb5_context context, char **str)
1231 return _krb5_expand_default_cc_name(context,
1232 KRB5_DEFAULT_CCNAME_FILE,
1233 str);
1236 static krb5_error_code KRB5_CALLCONV
1237 fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1239 krb5_error_code ret;
1240 struct stat sb;
1241 int fd;
1243 ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY, 0);
1244 if(ret)
1245 return ret;
1246 ret = fstat(fd, &sb);
1247 close(fd);
1248 if (ret) {
1249 ret = errno;
1250 krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1251 return ret;
1253 *mtime = sb.st_mtime;
1254 return 0;
1257 static krb5_error_code KRB5_CALLCONV
1258 fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1260 return 0;
1263 static krb5_error_code KRB5_CALLCONV
1264 fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1266 krb5_error_code ret;
1267 krb5_storage *sp = NULL;
1268 int fd;
1269 ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
1270 if (sp)
1271 krb5_storage_free(sp);
1272 fcc_unlock(context, fd);
1273 close(fd);
1275 return ret;
1280 * Variable containing the FILE based credential cache implemention.
1282 * @ingroup krb5_ccache
1285 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1286 KRB5_CC_OPS_VERSION,
1287 "FILE",
1288 fcc_get_name,
1289 fcc_resolve,
1290 fcc_gen_new,
1291 fcc_initialize,
1292 fcc_destroy,
1293 fcc_close,
1294 fcc_store_cred,
1295 NULL, /* fcc_retrieve */
1296 fcc_get_principal,
1297 fcc_get_first,
1298 fcc_get_next,
1299 fcc_end_get,
1300 fcc_remove_cred,
1301 fcc_set_flags,
1302 fcc_get_version,
1303 fcc_get_cache_first,
1304 fcc_get_cache_next,
1305 fcc_end_cache_get,
1306 fcc_move,
1307 fcc_get_default_name,
1308 NULL,
1309 fcc_lastchange,
1310 fcc_set_kdc_offset,
1311 fcc_get_kdc_offset