env KRB5CCNAME=/tmp/foocc kinit ignores the env
[heimdal.git] / lib / krb5 / fcache.c
blob4a16ff9f51c708168ad204ef9fcbd272d1bb60f9
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, &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, ret, 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 if (!krb5_config_get_bool_default(context, NULL, TRUE,
620 "libdefaults",
621 "fcc-mit-ticketflags",
622 NULL))
623 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
624 ret = krb5_store_creds(sp, creds);
625 if (ret == 0)
626 ret = write_storage(context, sp, fd);
627 krb5_storage_free(sp);
629 fcc_unlock(context, fd);
630 if (close(fd) < 0) {
631 if (ret == 0) {
632 char buf[128];
633 rk_strerror_r(ret, buf, sizeof(buf));
634 ret = errno;
635 krb5_set_error_message (context, ret, N_("close %s: %s", ""),
636 FILENAME(id), buf);
639 return ret;
642 static krb5_error_code
643 init_fcc(krb5_context context,
644 krb5_ccache id,
645 const char *operation,
646 krb5_storage **ret_sp,
647 int *ret_fd,
648 krb5_deltat *kdc_offset)
650 int fd;
651 int8_t pvno, tag;
652 krb5_storage *sp;
653 krb5_error_code ret;
655 if (kdc_offset)
656 *kdc_offset = 0;
658 ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0);
659 if(ret)
660 return ret;
662 sp = krb5_storage_from_fd(fd);
663 if(sp == NULL) {
664 krb5_clear_error_message(context);
665 ret = ENOMEM;
666 goto out;
668 krb5_storage_set_eof_code(sp, KRB5_CC_END);
669 ret = krb5_ret_int8(sp, &pvno);
670 if(ret != 0) {
671 if(ret == KRB5_CC_END) {
672 ret = ENOENT;
673 krb5_set_error_message(context, ret,
674 N_("Empty credential cache file: %s", ""),
675 FILENAME(id));
676 } else
677 krb5_set_error_message(context, ret, N_("Error reading pvno "
678 "in cache file: %s", ""),
679 FILENAME(id));
680 goto out;
682 if(pvno != 5) {
683 ret = KRB5_CCACHE_BADVNO;
684 krb5_set_error_message(context, ret, N_("Bad version number in credential "
685 "cache file: %s", ""),
686 FILENAME(id));
687 goto out;
689 ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
690 if(ret != 0) {
691 ret = KRB5_CC_FORMAT;
692 krb5_set_error_message(context, ret, "Error reading tag in "
693 "cache file: %s", FILENAME(id));
694 goto out;
696 FCACHE(id)->version = tag;
697 storage_set_flags(context, sp, FCACHE(id)->version);
698 switch (tag) {
699 case KRB5_FCC_FVNO_4: {
700 int16_t length;
702 ret = krb5_ret_int16 (sp, &length);
703 if(ret) {
704 ret = KRB5_CC_FORMAT;
705 krb5_set_error_message(context, ret,
706 N_("Error reading tag length in "
707 "cache file: %s", ""), FILENAME(id));
708 goto out;
710 while(length > 0) {
711 int16_t dtag, data_len;
712 int i;
713 int8_t dummy;
715 ret = krb5_ret_int16 (sp, &dtag);
716 if(ret) {
717 ret = KRB5_CC_FORMAT;
718 krb5_set_error_message(context, ret, N_("Error reading dtag in "
719 "cache file: %s", ""),
720 FILENAME(id));
721 goto out;
723 ret = krb5_ret_int16 (sp, &data_len);
724 if(ret) {
725 ret = KRB5_CC_FORMAT;
726 krb5_set_error_message(context, ret,
727 N_("Error reading dlength "
728 "in cache file: %s",""),
729 FILENAME(id));
730 goto out;
732 switch (dtag) {
733 case FCC_TAG_DELTATIME : {
734 int32_t offset;
736 ret = krb5_ret_int32 (sp, &offset);
737 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
738 if(ret) {
739 ret = KRB5_CC_FORMAT;
740 krb5_set_error_message(context, ret,
741 N_("Error reading kdc_sec in "
742 "cache file: %s", ""),
743 FILENAME(id));
744 goto out;
746 context->kdc_sec_offset = offset;
747 if (kdc_offset)
748 *kdc_offset = offset;
749 break;
751 default :
752 for (i = 0; i < data_len; ++i) {
753 ret = krb5_ret_int8 (sp, &dummy);
754 if(ret) {
755 ret = KRB5_CC_FORMAT;
756 krb5_set_error_message(context, ret,
757 N_("Error reading unknown "
758 "tag in cache file: %s", ""),
759 FILENAME(id));
760 goto out;
763 break;
765 length -= 4 + data_len;
767 break;
769 case KRB5_FCC_FVNO_3:
770 case KRB5_FCC_FVNO_2:
771 case KRB5_FCC_FVNO_1:
772 break;
773 default :
774 ret = KRB5_CCACHE_BADVNO;
775 krb5_set_error_message(context, ret,
776 N_("Unknown version number (%d) in "
777 "credential cache file: %s", ""),
778 (int)tag, FILENAME(id));
779 goto out;
781 *ret_sp = sp;
782 *ret_fd = fd;
784 return 0;
785 out:
786 if(sp != NULL)
787 krb5_storage_free(sp);
788 fcc_unlock(context, fd);
789 close(fd);
790 return ret;
793 static krb5_error_code KRB5_CALLCONV
794 fcc_get_principal(krb5_context context,
795 krb5_ccache id,
796 krb5_principal *principal)
798 krb5_error_code ret;
799 int fd;
800 krb5_storage *sp;
802 ret = init_fcc (context, id, "get-pricipal", &sp, &fd, NULL);
803 if (ret)
804 return ret;
805 ret = krb5_ret_principal(sp, principal);
806 if (ret)
807 krb5_clear_error_message(context);
808 krb5_storage_free(sp);
809 fcc_unlock(context, fd);
810 close(fd);
811 return ret;
814 static krb5_error_code KRB5_CALLCONV
815 fcc_end_get (krb5_context context,
816 krb5_ccache id,
817 krb5_cc_cursor *cursor);
819 static krb5_error_code KRB5_CALLCONV
820 fcc_get_first (krb5_context context,
821 krb5_ccache id,
822 krb5_cc_cursor *cursor)
824 krb5_error_code ret;
825 krb5_principal principal;
827 if (FCACHE(id) == NULL)
828 return krb5_einval(context, 2);
830 *cursor = malloc(sizeof(struct fcc_cursor));
831 if (*cursor == NULL) {
832 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
833 return ENOMEM;
835 memset(*cursor, 0, sizeof(struct fcc_cursor));
837 ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp,
838 &FCC_CURSOR(*cursor)->fd, NULL);
839 if (ret) {
840 free(*cursor);
841 *cursor = NULL;
842 return ret;
844 ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
845 if(ret) {
846 krb5_clear_error_message(context);
847 fcc_end_get(context, id, cursor);
848 return ret;
850 krb5_free_principal (context, principal);
851 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
852 return 0;
855 static krb5_error_code KRB5_CALLCONV
856 fcc_get_next (krb5_context context,
857 krb5_ccache id,
858 krb5_cc_cursor *cursor,
859 krb5_creds *creds)
861 krb5_error_code ret;
863 if (FCACHE(id) == NULL)
864 return krb5_einval(context, 2);
866 if (FCC_CURSOR(*cursor) == NULL)
867 return krb5_einval(context, 3);
869 if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
870 return ret;
871 FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd,
872 0, SEEK_CUR);
874 ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
875 if (ret)
876 krb5_clear_error_message(context);
878 FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd,
879 0, SEEK_CUR);
881 fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
882 return ret;
885 static krb5_error_code KRB5_CALLCONV
886 fcc_end_get (krb5_context context,
887 krb5_ccache id,
888 krb5_cc_cursor *cursor)
891 if (FCACHE(id) == NULL)
892 return krb5_einval(context, 2);
894 if (FCC_CURSOR(*cursor) == NULL)
895 return krb5_einval(context, 3);
897 krb5_storage_free(FCC_CURSOR(*cursor)->sp);
898 close (FCC_CURSOR(*cursor)->fd);
899 free(*cursor);
900 *cursor = NULL;
901 return 0;
904 static void KRB5_CALLCONV
905 cred_delete(krb5_context context,
906 krb5_ccache id,
907 krb5_cc_cursor *cursor,
908 krb5_creds *cred)
910 krb5_error_code ret;
911 krb5_storage *sp;
912 krb5_data orig_cred_data;
913 unsigned char *cred_data_in_file = NULL;
914 off_t new_cred_sz;
915 struct stat sb1, sb2;
916 int fd = -1;
917 ssize_t bytes;
918 krb5_flags flags = 0;
919 krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
921 /* This is best-effort code; if we lose track of errors here it's OK */
923 heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
924 "fcache internal error");
926 krb5_data_zero(&orig_cred_data);
927 if (!krb5_config_get_bool_default(context, NULL, TRUE,
928 "libdefaults",
929 "fcc-mit-ticketflags",
930 NULL))
931 flags = KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER;
933 sp = krb5_storage_emem();
934 if (sp == NULL)
935 return;
936 krb5_storage_set_eof_code(sp, KRB5_CC_END);
937 storage_set_flags(context, sp, FCACHE(id)->version);
938 if (flags)
939 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
941 /* Get a copy of what the cred should look like in the file; see below */
942 ret = krb5_store_creds(sp, cred);
943 if (ret)
944 goto out;
946 ret = krb5_storage_to_data(sp, &orig_cred_data);
947 if (ret)
948 goto out;
949 krb5_storage_free(sp);
951 cred_data_in_file = malloc(orig_cred_data.length);
952 if (cred_data_in_file == NULL)
953 goto out;
956 * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
957 * KRB5_TC_MATCH_TIMES, so this should be good enough...
959 cred->times.endtime = 0;
961 /* ...except for config creds because we don't check their endtimes */
962 if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
963 ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
964 if (ret)
965 return;
968 sp = krb5_storage_emem();
969 if (sp == NULL)
970 return;
971 krb5_storage_set_eof_code(sp, KRB5_CC_END);
972 storage_set_flags(context, sp, FCACHE(id)->version);
973 if (flags)
974 krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
976 ret = krb5_store_creds(sp, cred);
978 /* The new cred must be the same size as the old cred */
979 new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
980 if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
981 (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
982 /* XXX This really can't happen. Assert like above? */
983 krb5_set_error_message(context, EINVAL,
984 N_("Credential deletion failed on ccache "
985 "FILE:%s: new credential size did not "
986 "match old credential size", ""),
987 FILENAME(id));
988 goto out;
991 ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0);
992 if (ret)
993 goto out;
996 * Check that we're updating the same file where we got the
997 * cred's offset, else we'd be corrupting a new ccache.
999 if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
1000 fstat(fd, &sb2) == -1)
1001 goto out;
1002 if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
1003 goto out;
1006 * Make sure what we overwrite is what we expected.
1008 * FIXME: We *really* need the ccache v4 tag for ccache ID. This
1009 * check that we're only overwriting something that looks exactly
1010 * like what we want to is probably good enough in practice, but
1011 * it's not guaranteed to work.
1013 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1014 goto out;
1015 bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1016 if (bytes != orig_cred_data.length)
1017 goto out;
1018 if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1019 goto out;
1020 if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1021 goto out;
1022 ret = write_storage(context, sp, fd);
1023 out:
1024 if (fd > -1) {
1025 fcc_unlock(context, fd);
1026 if (close(fd) < 0 && ret == 0) {
1027 char buf[128];
1029 /* This error might be useful */
1030 rk_strerror_r(ret, buf, sizeof(buf));
1031 ret = errno;
1032 krb5_set_error_message(context, ret, N_("close %s: %s", ""),
1033 FILENAME(id), buf);
1036 krb5_data_free(&orig_cred_data);
1037 free(cred_data_in_file);
1038 krb5_storage_free(sp);
1039 return;
1042 static krb5_error_code KRB5_CALLCONV
1043 fcc_remove_cred(krb5_context context,
1044 krb5_ccache id,
1045 krb5_flags which,
1046 krb5_creds *mcred)
1048 krb5_error_code ret, ret2;
1049 krb5_cc_cursor cursor;
1050 krb5_creds found_cred;
1052 if (FCACHE(id) == NULL)
1053 return krb5_einval(context, 2);
1055 ret = krb5_cc_start_seq_get(context, id, &cursor);
1056 if (ret)
1057 return ret;
1058 while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
1059 if (!krb5_compare_creds(context, which, mcred, &found_cred))
1060 continue;
1061 cred_delete(context, id, &cursor, &found_cred);
1062 krb5_free_cred_contents(context, &found_cred);
1064 ret2 = krb5_cc_end_seq_get(context, id, &cursor);
1065 if (ret == 0)
1066 return ret2;
1067 if (ret == KRB5_CC_END)
1068 return 0;
1069 return ret;
1072 static krb5_error_code KRB5_CALLCONV
1073 fcc_set_flags(krb5_context context,
1074 krb5_ccache id,
1075 krb5_flags flags)
1077 if (FCACHE(id) == NULL)
1078 return krb5_einval(context, 2);
1080 return 0; /* XXX */
1083 static int KRB5_CALLCONV
1084 fcc_get_version(krb5_context context,
1085 krb5_ccache id)
1087 if (FCACHE(id) == NULL)
1088 return -1;
1090 return FCACHE(id)->version;
1093 struct fcache_iter {
1094 int first;
1097 static krb5_error_code KRB5_CALLCONV
1098 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1100 struct fcache_iter *iter;
1102 iter = calloc(1, sizeof(*iter));
1103 if (iter == NULL) {
1104 krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
1105 return ENOMEM;
1107 iter->first = 1;
1108 *cursor = iter;
1109 return 0;
1112 static krb5_error_code KRB5_CALLCONV
1113 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
1115 struct fcache_iter *iter = cursor;
1116 krb5_error_code ret;
1117 const char *fn, *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