1e6397427569df30e5909c90d1e04411bd5b9375
[abartlet/lorikeet-heimdal.git/.git] / lib / krb5 / fcache.c
1 /*
2  * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
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.
18  *
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.
22  *
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.
34  */
35
36 #include "krb5_locl.h"
37
38 typedef struct krb5_fcache{
39     char *filename;
40     int version;
41 }krb5_fcache;
42
43 struct fcc_cursor {
44     int fd;
45     krb5_storage *sp;
46 };
47
48 #define KRB5_FCC_FVNO_1 1
49 #define KRB5_FCC_FVNO_2 2
50 #define KRB5_FCC_FVNO_3 3
51 #define KRB5_FCC_FVNO_4 4
52
53 #define FCC_TAG_DELTATIME 1
54
55 #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
56
57 #define FILENAME(X) (FCACHE(X)->filename)
58
59 #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
60
61 static const char* KRB5_CALLCONV
62 fcc_get_name(krb5_context context,
63              krb5_ccache id)
64 {
65     return FILENAME(id);
66 }
67
68 int
69 _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
70             const char *filename)
71 {
72     int ret;
73 #ifdef HAVE_FCNTL
74     struct flock l;
75
76     l.l_start = 0;
77     l.l_len = 0;
78     l.l_type = exclusive ? F_WRLCK : F_RDLCK;
79     l.l_whence = SEEK_SET;
80     ret = fcntl(fd, F_SETLKW, &l);
81 #else
82     ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
83 #endif
84     if(ret < 0)
85         ret = errno;
86     if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
87         ret = EAGAIN;
88
89     switch (ret) {
90     case 0:
91         break;
92     case EINVAL: /* filesystem doesn't support locking, let the user have it */
93         ret = 0;
94         break;
95     case EAGAIN:
96         krb5_set_error_message(context, ret,
97                                N_("timed out locking cache file %s", "file"),
98                                filename);
99         break;
100     default: {
101         char buf[128];
102         rk_strerror_r(ret, buf, sizeof(buf));
103         krb5_set_error_message(context, ret,
104                                N_("error locking cache file %s: %s",
105                                   "file, error"), filename, buf);
106         break;
107     }
108     }
109     return ret;
110 }
111
112 int
113 _krb5_xunlock(krb5_context context, int fd)
114 {
115     int ret;
116 #ifdef HAVE_FCNTL
117     struct flock l;
118     l.l_start = 0;
119     l.l_len = 0;
120     l.l_type = F_UNLCK;
121     l.l_whence = SEEK_SET;
122     ret = fcntl(fd, F_SETLKW, &l);
123 #else
124     ret = flock(fd, LOCK_UN);
125 #endif
126     if (ret < 0)
127         ret = errno;
128     switch (ret) {
129     case 0:
130         break;
131     case EINVAL: /* filesystem doesn't support locking, let the user have it */
132         ret = 0;
133         break;
134     default: {
135         char buf[128];
136         rk_strerror_r(ret, buf, sizeof(buf));
137         krb5_set_error_message(context, ret,
138                                N_("Failed to unlock file: %s", ""), buf);
139         break;
140     }
141     }
142     return ret;
143 }
144
145 static krb5_error_code
146 write_storage(krb5_context context, krb5_storage *sp, int fd)
147 {
148     krb5_error_code ret;
149     krb5_data data;
150     ssize_t sret;
151
152     ret = krb5_storage_to_data(sp, &data);
153     if (ret) {
154         krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
155         return ret;
156     }
157     sret = write(fd, data.data, data.length);
158     ret = (sret != data.length);
159     krb5_data_free(&data);
160     if (ret) {
161         ret = errno;
162         krb5_set_error_message(context, ret,
163                                N_("Failed to write FILE credential data", ""));
164         return ret;
165     }
166     return 0;
167 }
168
169
170 static krb5_error_code KRB5_CALLCONV
171 fcc_lock(krb5_context context, krb5_ccache id,
172          int fd, krb5_boolean exclusive)
173 {
174     return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
175 }
176
177 static krb5_error_code KRB5_CALLCONV
178 fcc_unlock(krb5_context context, int fd)
179 {
180     return _krb5_xunlock(context, fd);
181 }
182
183 static krb5_error_code KRB5_CALLCONV
184 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
185 {
186     krb5_fcache *f;
187     f = malloc(sizeof(*f));
188     if(f == NULL) {
189         krb5_set_error_message(context, KRB5_CC_NOMEM,
190                                N_("malloc: out of memory", ""));
191         return KRB5_CC_NOMEM;
192     }
193     f->filename = strdup(res);
194     if(f->filename == NULL){
195         free(f);
196         krb5_set_error_message(context, KRB5_CC_NOMEM,
197                                N_("malloc: out of memory", ""));
198         return KRB5_CC_NOMEM;
199     }
200     f->version = 0;
201     (*id)->data.data = f;
202     (*id)->data.length = sizeof(*f);
203     return 0;
204 }
205
206 /*
207  * Try to scrub the contents of `filename' safely.
208  */
209
210 static int
211 scrub_file (int fd)
212 {
213     off_t pos;
214     char buf[128];
215
216     pos = lseek(fd, 0, SEEK_END);
217     if (pos < 0)
218         return errno;
219     if (lseek(fd, 0, SEEK_SET) < 0)
220         return errno;
221     memset(buf, 0, sizeof(buf));
222     while(pos > 0) {
223         ssize_t tmp = write(fd, buf, min(sizeof(buf), pos));
224
225         if (tmp < 0)
226             return errno;
227         pos -= tmp;
228     }
229 #ifdef _MSC_VER
230     _commit (fd);
231 #else
232     fsync (fd);
233 #endif
234     return 0;
235 }
236
237 /*
238  * Erase `filename' if it exists, trying to remove the contents if
239  * it's `safe'.  We always try to remove the file, it it exists.  It's
240  * only overwritten if it's a regular file (not a symlink and not a
241  * hardlink)
242  */
243
244 krb5_error_code
245 _krb5_erase_file(krb5_context context, const char *filename)
246 {
247     int fd;
248     struct stat sb1, sb2;
249     int ret;
250
251     ret = lstat (filename, &sb1);
252     if (ret < 0)
253         return errno;
254
255     fd = open(filename, O_RDWR | O_BINARY);
256     if(fd < 0) {
257         if(errno == ENOENT)
258             return 0;
259         else
260             return errno;
261     }
262     rk_cloexec(fd);
263     ret = _krb5_xlock(context, fd, 1, filename);
264     if (ret) {
265         close(fd);
266         return ret;
267     }
268     if (unlink(filename) < 0) {
269         _krb5_xunlock(context, fd);
270         close (fd);
271         return errno;
272     }
273     ret = fstat (fd, &sb2);
274     if (ret < 0) {
275         _krb5_xunlock(context, fd);
276         close (fd);
277         return errno;
278     }
279
280     /* check if someone was playing with symlinks */
281
282     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
283         _krb5_xunlock(context, fd);
284         close (fd);
285         return EPERM;
286     }
287
288     /* there are still hard links to this file */
289
290     if (sb2.st_nlink != 0) {
291         _krb5_xunlock(context, fd);
292         close (fd);
293         return 0;
294     }
295
296     ret = scrub_file (fd);
297     if (ret) {
298         _krb5_xunlock(context, fd);
299         close(fd);
300         return ret;
301     }
302     ret = _krb5_xunlock(context, fd);
303     close (fd);
304     return ret;
305 }
306
307 static krb5_error_code KRB5_CALLCONV
308 fcc_gen_new(krb5_context context, krb5_ccache *id)
309 {
310     char *file = NULL, *exp_file = NULL;
311     krb5_error_code ret;
312     krb5_fcache *f;
313     int fd;
314
315     f = malloc(sizeof(*f));
316     if(f == NULL) {
317         krb5_set_error_message(context, KRB5_CC_NOMEM,
318                                N_("malloc: out of memory", ""));
319         return KRB5_CC_NOMEM;
320     }
321     ret = asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
322     if(ret < 0 || file == NULL) {
323         free(f);
324         krb5_set_error_message(context, KRB5_CC_NOMEM,
325                                N_("malloc: out of memory", ""));
326         return KRB5_CC_NOMEM;
327     }
328     ret = _krb5_expand_path_tokens(context, file, &exp_file);
329     free(file);
330     if (ret)
331         return ret;
332
333     file = exp_file;
334
335     fd = mkstemp(exp_file);
336     if(fd < 0) {
337         int ret = errno;
338         krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
339         free(f);
340         free(exp_file);
341         return ret;
342     }
343     close(fd);
344     f->filename = exp_file;
345     f->version = 0;
346     (*id)->data.data = f;
347     (*id)->data.length = sizeof(*f);
348     return 0;
349 }
350
351 static void
352 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
353 {
354     int flags = 0;
355     switch(vno) {
356     case KRB5_FCC_FVNO_1:
357         flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
358         flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
359         flags |= KRB5_STORAGE_HOST_BYTEORDER;
360         break;
361     case KRB5_FCC_FVNO_2:
362         flags |= KRB5_STORAGE_HOST_BYTEORDER;
363         break;
364     case KRB5_FCC_FVNO_3:
365         flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
366         break;
367     case KRB5_FCC_FVNO_4:
368         break;
369     default:
370         krb5_abortx(context,
371                     "storage_set_flags called with bad vno (%x)", vno);
372     }
373     krb5_storage_set_flags(sp, flags);
374 }
375
376 static krb5_error_code KRB5_CALLCONV
377 fcc_open(krb5_context context,
378          krb5_ccache id,
379          int *fd_ret,
380          int flags,
381          mode_t mode)
382 {
383     krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
384                               (flags | O_RDWR) == flags);
385     krb5_error_code ret;
386     const char *filename = FILENAME(id);
387     int fd;
388     fd = open(filename, flags, mode);
389     if(fd < 0) {
390         char buf[128];
391         ret = errno;
392         rk_strerror_r(ret, buf, sizeof(buf));
393         krb5_set_error_message(context, ret, N_("open(%s): %s", "file, error"),
394                                filename, buf);
395         return ret;
396     }
397     rk_cloexec(fd);
398
399     if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
400         close(fd);
401         return ret;
402     }
403     *fd_ret = fd;
404     return 0;
405 }
406
407 static krb5_error_code KRB5_CALLCONV
408 fcc_initialize(krb5_context context,
409                krb5_ccache id,
410                krb5_principal primary_principal)
411 {
412     krb5_fcache *f = FCACHE(id);
413     int ret = 0;
414     int fd;
415     char *filename = f->filename;
416
417     unlink (filename);
418
419     ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
420     if(ret)
421         return ret;
422     {
423         krb5_storage *sp;
424         sp = krb5_storage_emem();
425         krb5_storage_set_eof_code(sp, KRB5_CC_END);
426         if(context->fcache_vno != 0)
427             f->version = context->fcache_vno;
428         else
429             f->version = KRB5_FCC_FVNO_4;
430         ret |= krb5_store_int8(sp, 5);
431         ret |= krb5_store_int8(sp, f->version);
432         storage_set_flags(context, sp, f->version);
433         if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
434             /* V4 stuff */
435             if (context->kdc_sec_offset) {
436                 ret |= krb5_store_int16 (sp, 12); /* length */
437                 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
438                 ret |= krb5_store_int16 (sp, 8); /* length of data */
439                 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
440                 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
441             } else {
442                 ret |= krb5_store_int16 (sp, 0);
443             }
444         }
445         ret |= krb5_store_principal(sp, primary_principal);
446         
447         ret |= write_storage(context, sp, fd);
448
449         krb5_storage_free(sp);
450     }
451     fcc_unlock(context, fd);
452     if (close(fd) < 0)
453         if (ret == 0) {
454             char buf[128];
455             ret = errno;
456             rk_strerror_r(ret, buf, sizeof(buf));
457             krb5_set_error_message (context, ret, N_("close %s: %s", ""),
458                                     FILENAME(id), buf);
459         }
460     return ret;
461 }
462
463 static krb5_error_code KRB5_CALLCONV
464 fcc_close(krb5_context context,
465           krb5_ccache id)
466 {
467     free (FILENAME(id));
468     krb5_data_free(&id->data);
469     return 0;
470 }
471
472 static krb5_error_code KRB5_CALLCONV
473 fcc_destroy(krb5_context context,
474             krb5_ccache id)
475 {
476     _krb5_erase_file(context, FILENAME(id));
477     return 0;
478 }
479
480 static krb5_error_code KRB5_CALLCONV
481 fcc_store_cred(krb5_context context,
482                krb5_ccache id,
483                krb5_creds *creds)
484 {
485     int ret;
486     int fd;
487
488     ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY | O_CLOEXEC, 0);
489     if(ret)
490         return ret;
491     {
492         krb5_storage *sp;
493
494         sp = krb5_storage_emem();
495         krb5_storage_set_eof_code(sp, KRB5_CC_END);
496         storage_set_flags(context, sp, FCACHE(id)->version);
497         if (!krb5_config_get_bool_default(context, NULL, TRUE,
498                                           "libdefaults",
499                                           "fcc-mit-ticketflags",
500                                           NULL))
501             krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
502         ret = krb5_store_creds(sp, creds);
503         if (ret == 0)
504             ret = write_storage(context, sp, fd);
505         krb5_storage_free(sp);
506     }
507     fcc_unlock(context, fd);
508     if (close(fd) < 0) {
509         if (ret == 0) {
510             char buf[128];
511             rk_strerror_r(ret, buf, sizeof(buf));
512             ret = errno;
513             krb5_set_error_message (context, ret, N_("close %s: %s", ""),
514                                     FILENAME(id), buf);
515         }
516     }
517     return ret;
518 }
519
520 static krb5_error_code
521 init_fcc (krb5_context context,
522           krb5_ccache id,
523           krb5_storage **ret_sp,
524           int *ret_fd,
525           krb5_deltat *kdc_offset)
526 {
527     int fd;
528     int8_t pvno, tag;
529     krb5_storage *sp;
530     krb5_error_code ret;
531
532     if (kdc_offset)
533         *kdc_offset = 0;
534
535     ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
536     if(ret)
537         return ret;
538
539     sp = krb5_storage_from_fd(fd);
540     if(sp == NULL) {
541         krb5_clear_error_message(context);
542         ret = ENOMEM;
543         goto out;
544     }
545     krb5_storage_set_eof_code(sp, KRB5_CC_END);
546     ret = krb5_ret_int8(sp, &pvno);
547     if(ret != 0) {
548         if(ret == KRB5_CC_END) {
549             ret = ENOENT;
550             krb5_set_error_message(context, ret,
551                                    N_("Empty credential cache file: %s", ""),
552                                    FILENAME(id));
553         } else
554             krb5_set_error_message(context, ret, N_("Error reading pvno "
555                                                     "in cache file: %s", ""),
556                                    FILENAME(id));
557         goto out;
558     }
559     if(pvno != 5) {
560         ret = KRB5_CCACHE_BADVNO;
561         krb5_set_error_message(context, ret, N_("Bad version number in credential "
562                                                 "cache file: %s", ""),
563                                FILENAME(id));
564         goto out;
565     }
566     ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
567     if(ret != 0) {
568         ret = KRB5_CC_FORMAT;
569         krb5_set_error_message(context, ret, "Error reading tag in "
570                               "cache file: %s", FILENAME(id));
571         goto out;
572     }
573     FCACHE(id)->version = tag;
574     storage_set_flags(context, sp, FCACHE(id)->version);
575     switch (tag) {
576     case KRB5_FCC_FVNO_4: {
577         int16_t length;
578
579         ret = krb5_ret_int16 (sp, &length);
580         if(ret) {
581             ret = KRB5_CC_FORMAT;
582             krb5_set_error_message(context, ret,
583                                    N_("Error reading tag length in "
584                                       "cache file: %s", ""), FILENAME(id));
585             goto out;
586         }
587         while(length > 0) {
588             int16_t dtag, data_len;
589             int i;
590             int8_t dummy;
591
592             ret = krb5_ret_int16 (sp, &dtag);
593             if(ret) {
594                 ret = KRB5_CC_FORMAT;
595                 krb5_set_error_message(context, ret, N_("Error reading dtag in "
596                                                         "cache file: %s", ""),
597                                        FILENAME(id));
598                 goto out;
599             }
600             ret = krb5_ret_int16 (sp, &data_len);
601             if(ret) {
602                 ret = KRB5_CC_FORMAT;
603                 krb5_set_error_message(context, ret,
604                                        N_("Error reading dlength "
605                                           "in cache file: %s",""),
606                                        FILENAME(id));
607                 goto out;
608             }
609             switch (dtag) {
610             case FCC_TAG_DELTATIME : {
611                 int32_t offset;
612
613                 ret = krb5_ret_int32 (sp, &offset);
614                 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
615                 if(ret) {
616                     ret = KRB5_CC_FORMAT;
617                     krb5_set_error_message(context, ret,
618                                            N_("Error reading kdc_sec in "
619                                               "cache file: %s", ""),
620                                            FILENAME(id));
621                     goto out;
622                 }
623                 context->kdc_sec_offset = offset;
624                 if (kdc_offset)
625                     *kdc_offset = offset;
626                 break;
627             }
628             default :
629                 for (i = 0; i < data_len; ++i) {
630                     ret = krb5_ret_int8 (sp, &dummy);
631                     if(ret) {
632                         ret = KRB5_CC_FORMAT;
633                         krb5_set_error_message(context, ret,
634                                                N_("Error reading unknown "
635                                                   "tag in cache file: %s", ""),
636                                                FILENAME(id));
637                         goto out;
638                     }
639                 }
640                 break;
641             }
642             length -= 4 + data_len;
643         }
644         break;
645     }
646     case KRB5_FCC_FVNO_3:
647     case KRB5_FCC_FVNO_2:
648     case KRB5_FCC_FVNO_1:
649         break;
650     default :
651         ret = KRB5_CCACHE_BADVNO;
652         krb5_set_error_message(context, ret,
653                                N_("Unknown version number (%d) in "
654                                   "credential cache file: %s", ""),
655                                (int)tag, FILENAME(id));
656         goto out;
657     }
658     *ret_sp = sp;
659     *ret_fd = fd;
660
661     return 0;
662   out:
663     if(sp != NULL)
664         krb5_storage_free(sp);
665     fcc_unlock(context, fd);
666     close(fd);
667     return ret;
668 }
669
670 static krb5_error_code KRB5_CALLCONV
671 fcc_get_principal(krb5_context context,
672                   krb5_ccache id,
673                   krb5_principal *principal)
674 {
675     krb5_error_code ret;
676     int fd;
677     krb5_storage *sp;
678
679     ret = init_fcc (context, id, &sp, &fd, NULL);
680     if (ret)
681         return ret;
682     ret = krb5_ret_principal(sp, principal);
683     if (ret)
684         krb5_clear_error_message(context);
685     krb5_storage_free(sp);
686     fcc_unlock(context, fd);
687     close(fd);
688     return ret;
689 }
690
691 static krb5_error_code KRB5_CALLCONV
692 fcc_end_get (krb5_context context,
693              krb5_ccache id,
694              krb5_cc_cursor *cursor);
695
696 static krb5_error_code KRB5_CALLCONV
697 fcc_get_first (krb5_context context,
698                krb5_ccache id,
699                krb5_cc_cursor *cursor)
700 {
701     krb5_error_code ret;
702     krb5_principal principal;
703
704     *cursor = malloc(sizeof(struct fcc_cursor));
705     if (*cursor == NULL) {
706         krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
707         return ENOMEM;
708     }
709     memset(*cursor, 0, sizeof(struct fcc_cursor));
710
711     ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp,
712                     &FCC_CURSOR(*cursor)->fd, NULL);
713     if (ret) {
714         free(*cursor);
715         *cursor = NULL;
716         return ret;
717     }
718     ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
719     if(ret) {
720         krb5_clear_error_message(context);
721         fcc_end_get(context, id, cursor);
722         return ret;
723     }
724     krb5_free_principal (context, principal);
725     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
726     return 0;
727 }
728
729 static krb5_error_code KRB5_CALLCONV
730 fcc_get_next (krb5_context context,
731               krb5_ccache id,
732               krb5_cc_cursor *cursor,
733               krb5_creds *creds)
734 {
735     krb5_error_code ret;
736     if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
737         return ret;
738
739     ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
740     if (ret)
741         krb5_clear_error_message(context);
742
743     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
744     return ret;
745 }
746
747 static krb5_error_code KRB5_CALLCONV
748 fcc_end_get (krb5_context context,
749              krb5_ccache id,
750              krb5_cc_cursor *cursor)
751 {
752     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
753     close (FCC_CURSOR(*cursor)->fd);
754     free(*cursor);
755     *cursor = NULL;
756     return 0;
757 }
758
759 static krb5_error_code KRB5_CALLCONV
760 fcc_remove_cred(krb5_context context,
761                  krb5_ccache id,
762                  krb5_flags which,
763                  krb5_creds *cred)
764 {
765     krb5_error_code ret;
766     krb5_ccache copy, newfile;
767     char *newname = NULL;
768     int fd;
769
770     ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &copy);
771     if (ret)
772         return ret;
773
774     ret = krb5_cc_copy_cache(context, id, copy);
775     if (ret) {
776         krb5_cc_destroy(context, copy);
777         return ret;
778     }
779
780     ret = krb5_cc_remove_cred(context, copy, which, cred);
781     if (ret) {
782         krb5_cc_destroy(context, copy);
783         return ret;
784     }
785
786     ret = asprintf(&newname, "FILE:%s.XXXXXX", FILENAME(id));
787     if (ret < 0 || newname == NULL) {
788         krb5_cc_destroy(context, copy);
789         return ENOMEM;
790     }
791
792     fd = mkstemp(&newname[5]);
793     if (fd < 0) {
794         ret = errno;
795         krb5_cc_destroy(context, copy);
796         return ret;
797     }
798     close(fd);
799
800     ret = krb5_cc_resolve(context, newname, &newfile);
801     if (ret) {
802         unlink(&newname[5]);
803         free(newname);
804         krb5_cc_destroy(context, copy);
805         return ret;
806     }
807
808     ret = krb5_cc_copy_cache(context, copy, newfile);
809     krb5_cc_destroy(context, copy);
810     if (ret) {
811         free(newname);
812         krb5_cc_destroy(context, newfile);
813         return ret;
814     }
815
816     ret = rename(&newname[5], FILENAME(id));
817 #ifdef RENAME_DOES_NOT_UNLINK
818     if (ret && (errno == EEXIST || errno == EACCES)) {
819         ret = unlink(FILENAME(id));
820         if (ret == 0) {
821             ret = rename(&newname[5], FILENAME(id));
822         }
823     }
824 #endif
825     if (ret)
826         ret = errno;
827     free(newname);
828     krb5_cc_close(context, newfile);
829
830     return ret;
831 }
832
833 static krb5_error_code KRB5_CALLCONV
834 fcc_set_flags(krb5_context context,
835               krb5_ccache id,
836               krb5_flags flags)
837 {
838     return 0; /* XXX */
839 }
840
841 static int KRB5_CALLCONV
842 fcc_get_version(krb5_context context,
843                 krb5_ccache id)
844 {
845     return FCACHE(id)->version;
846 }
847                 
848 struct fcache_iter {
849     int first;
850 };
851
852 static krb5_error_code KRB5_CALLCONV
853 fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
854 {
855     struct fcache_iter *iter;
856
857     iter = calloc(1, sizeof(*iter));
858     if (iter == NULL) {
859         krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
860         return ENOMEM;
861     }
862     iter->first = 1;
863     *cursor = iter;
864     return 0;
865 }
866
867 static krb5_error_code KRB5_CALLCONV
868 fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
869 {
870     struct fcache_iter *iter = cursor;
871     krb5_error_code ret;
872     const char *fn;
873     char *expandedfn = NULL;
874
875     if (!iter->first) {
876         krb5_clear_error_message(context);
877         return KRB5_CC_END;
878     }
879     iter->first = 0;
880
881     fn = krb5_cc_default_name(context);
882     if (fn == NULL || strncasecmp(fn, "FILE:", 5) != 0) {
883         ret = _krb5_expand_default_cc_name(context,
884                                            KRB5_DEFAULT_CCNAME_FILE,
885                                            &expandedfn);
886         if (ret)
887             return ret;
888         fn = expandedfn;
889     }
890     /* check if file exists, don't return a non existant "next" */
891     if (strncasecmp(fn, "FILE:", 5) == 0) {
892         struct stat sb;
893         ret = stat(fn + 5, &sb);
894         if (ret) {
895             ret = KRB5_CC_END;
896             goto out;
897         }
898     }
899     ret = krb5_cc_resolve(context, fn, id);
900  out:
901     if (expandedfn)
902         free(expandedfn);
903
904     return ret;
905 }
906
907 static krb5_error_code KRB5_CALLCONV
908 fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
909 {
910     struct fcache_iter *iter = cursor;
911     free(iter);
912     return 0;
913 }
914
915 static krb5_error_code KRB5_CALLCONV
916 fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
917 {
918     krb5_error_code ret = 0;
919
920     ret = rename(FILENAME(from), FILENAME(to));
921 #ifdef RENAME_DOES_NOT_UNLINK
922     if (ret && (errno == EEXIST || errno == EACCES)) {
923         ret = unlink(FILENAME(to));
924         if (ret == 0) {
925             ret = rename(FILENAME(from), FILENAME(to));
926         }
927     }
928 #endif
929
930     if (ret && errno != EXDEV) {
931         char buf[128];
932         ret = errno;
933         rk_strerror_r(ret, buf, sizeof(buf));
934         krb5_set_error_message(context, ret,
935                                N_("Rename of file from %s "
936                                   "to %s failed: %s", ""),
937                                FILENAME(from), FILENAME(to), buf);
938         return ret;
939     } else if (ret && errno == EXDEV) {
940         /* make a copy and delete the orignal */
941         krb5_ssize_t sz1, sz2;
942         int fd1, fd2;
943         char buf[BUFSIZ];
944
945         ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
946         if(ret)
947             return ret;
948
949         unlink(FILENAME(to));
950
951         ret = fcc_open(context, to, &fd2,
952                        O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
953         if(ret)
954             goto out1;
955
956         while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
957             sz2 = write(fd2, buf, sz1);
958             if (sz1 != sz2) {
959                 ret = EIO;
960                 krb5_set_error_message(context, ret,
961                                        N_("Failed to write data from one file "
962                                           "credential cache to the other", ""));
963                 goto out2;
964             }
965         }
966         if (sz1 < 0) {
967             ret = EIO;
968             krb5_set_error_message(context, ret,
969                                    N_("Failed to read data from one file "
970                                       "credential cache to the other", ""));
971             goto out2;
972         }
973     out2:
974         fcc_unlock(context, fd2);
975         close(fd2);
976
977     out1:
978         fcc_unlock(context, fd1);
979         close(fd1);
980
981         _krb5_erase_file(context, FILENAME(from));
982
983         if (ret) {
984             _krb5_erase_file(context, FILENAME(to));
985             return ret;
986         }
987     }
988
989     /* make sure ->version is uptodate */
990     {
991         krb5_storage *sp;
992         int fd;
993         if ((ret = init_fcc (context, to, &sp, &fd, NULL)) == 0) {
994             if (sp)
995                 krb5_storage_free(sp);
996             fcc_unlock(context, fd);
997             close(fd);
998         }
999     }
1000
1001     fcc_close(context, from);
1002
1003     return ret;
1004 }
1005
1006 static krb5_error_code KRB5_CALLCONV
1007 fcc_get_default_name(krb5_context context, char **str)
1008 {
1009     return _krb5_expand_default_cc_name(context,
1010                                         KRB5_DEFAULT_CCNAME_FILE,
1011                                         str);
1012 }
1013
1014 static krb5_error_code KRB5_CALLCONV
1015 fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1016 {
1017     krb5_error_code ret;
1018     struct stat sb;
1019     int fd;
1020
1021     ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
1022     if(ret)
1023         return ret;
1024     ret = fstat(fd, &sb);
1025     close(fd);
1026     if (ret) {
1027         ret = errno;
1028         krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1029         return ret;
1030     }
1031     *mtime = sb.st_mtime;
1032     return 0;
1033 }
1034
1035 static krb5_error_code KRB5_CALLCONV
1036 fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1037 {
1038     return 0;
1039 }
1040
1041 static krb5_error_code KRB5_CALLCONV
1042 fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1043 {
1044     krb5_error_code ret;
1045     krb5_storage *sp = NULL;
1046     int fd;
1047     ret = init_fcc(context, id, &sp, &fd, kdc_offset);
1048     if (sp)
1049         krb5_storage_free(sp);
1050     fcc_unlock(context, fd);
1051     close(fd);
1052
1053     return ret;
1054 }
1055
1056
1057 /**
1058  * Variable containing the FILE based credential cache implemention.
1059  *
1060  * @ingroup krb5_ccache
1061  */
1062
1063 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1064     KRB5_CC_OPS_VERSION,
1065     "FILE",
1066     fcc_get_name,
1067     fcc_resolve,
1068     fcc_gen_new,
1069     fcc_initialize,
1070     fcc_destroy,
1071     fcc_close,
1072     fcc_store_cred,
1073     NULL, /* fcc_retrieve */
1074     fcc_get_principal,
1075     fcc_get_first,
1076     fcc_get_next,
1077     fcc_end_get,
1078     fcc_remove_cred,
1079     fcc_set_flags,
1080     fcc_get_version,
1081     fcc_get_cache_first,
1082     fcc_get_cache_next,
1083     fcc_end_cache_get,
1084     fcc_move,
1085     fcc_get_default_name,
1086     NULL,
1087     fcc_lastchange,
1088     fcc_set_kdc_offset,
1089     fcc_get_kdc_offset
1090 };