r8302: import mini HEIMDAL into the tree
[samba.git] / source4 / heimdal / lib / krb5 / fcache.c
1 /*
2  * Copyright (c) 1997 - 2004 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden). 
4  * All rights reserved. 
5  *
6  * Redistribution and use in source and binary forms, with or without 
7  * modification, are permitted provided that the following conditions 
8  * are met: 
9  *
10  * 1. Redistributions of source code must retain the above copyright 
11  *    notice, this list of conditions and the following disclaimer. 
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright 
14  *    notice, this list of conditions and the following disclaimer in the 
15  *    documentation and/or other materials provided with the distribution. 
16  *
17  * 3. Neither the name of the Institute nor the names of its contributors 
18  *    may be used to endorse or promote products derived from this software 
19  *    without specific prior written permission. 
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
31  * SUCH DAMAGE. 
32  */
33
34 #include "krb5_locl.h"
35
36 RCSID("$Id: fcache.c,v 1.49 2005/06/16 20:25:20 lha Exp $");
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*
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_string(context, "timed out locking cache file %s", 
97                               filename);
98         break;
99     default:
100         krb5_set_error_string(context, "error locking cache file %s: %s",
101                               filename, strerror(ret));
102         break;
103     }
104     return ret;
105 }
106
107 int
108 _krb5_xunlock(krb5_context context, int fd)
109 {
110     int ret;
111 #ifdef HAVE_FCNTL_LOCK
112     struct flock l;
113     l.l_start = 0;
114     l.l_len = 0;
115     l.l_type = F_UNLCK;
116     l.l_whence = SEEK_SET;
117     ret = fcntl(fd, F_SETLKW, &l);
118 #else
119     ret = flock(fd, LOCK_UN);
120 #endif
121     if (ret < 0)
122         ret = errno;
123     switch (ret) {
124     case 0:
125         break;
126     case EINVAL: /* filesystem doesn't support locking, let the user have it */
127         ret = 0; 
128         break;
129     default:
130         krb5_set_error_string(context, 
131                               "Failed to unlock file: %s", strerror(ret));
132         break;
133     }
134     return ret;
135 }
136
137 static krb5_error_code
138 fcc_lock(krb5_context context, krb5_ccache id,
139          int fd, krb5_boolean exclusive)
140 {
141     return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
142 }
143
144 static krb5_error_code
145 fcc_unlock(krb5_context context, int fd)
146 {
147     return _krb5_xunlock(context, fd);
148 }
149
150 static krb5_error_code
151 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
152 {
153     krb5_fcache *f;
154     f = malloc(sizeof(*f));
155     if(f == NULL) {
156         krb5_set_error_string(context, "malloc: out of memory");
157         return KRB5_CC_NOMEM;
158     }
159     f->filename = strdup(res);
160     if(f->filename == NULL){
161         free(f);
162         krb5_set_error_string(context, "malloc: out of memory");
163         return KRB5_CC_NOMEM;
164     }
165     f->version = 0;
166     (*id)->data.data = f;
167     (*id)->data.length = sizeof(*f);
168     return 0;
169 }
170
171 /*
172  * Try to scrub the contents of `filename' safely.
173  */
174
175 static int
176 scrub_file (int fd)
177 {
178     off_t pos;
179     char buf[128];
180
181     pos = lseek(fd, 0, SEEK_END);
182     if (pos < 0)
183         return errno;
184     if (lseek(fd, 0, SEEK_SET) < 0)
185         return errno;
186     memset(buf, 0, sizeof(buf));
187     while(pos > 0) {
188         ssize_t tmp = write(fd, buf, min(sizeof(buf), pos));
189
190         if (tmp < 0)
191             return errno;
192         pos -= tmp;
193     }
194     fsync (fd);
195     return 0;
196 }
197
198 /*
199  * Erase `filename' if it exists, trying to remove the contents if
200  * it's `safe'.  We always try to remove the file, it it exists.  It's
201  * only overwritten if it's a regular file (not a symlink and not a
202  * hardlink)
203  */
204
205 static krb5_error_code
206 erase_file(const char *filename)
207 {
208     int fd;
209     struct stat sb1, sb2;
210     int ret;
211
212     ret = lstat (filename, &sb1);
213     if (ret < 0)
214         return errno;
215
216     fd = open(filename, O_RDWR | O_BINARY);
217     if(fd < 0) {
218         if(errno == ENOENT)
219             return 0;
220         else
221             return errno;
222     }
223     if (unlink(filename) < 0) {
224         close (fd);
225         return errno;
226     }
227     ret = fstat (fd, &sb2);
228     if (ret < 0) {
229         close (fd);
230         return errno;
231     }
232
233     /* check if someone was playing with symlinks */
234
235     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
236         close (fd);
237         return EPERM;
238     }
239
240     /* there are still hard links to this file */
241
242     if (sb2.st_nlink != 0) {
243         close (fd);
244         return 0;
245     }
246
247     ret = scrub_file (fd);
248     close (fd);
249     return ret;
250 }
251
252 static krb5_error_code
253 fcc_gen_new(krb5_context context, krb5_ccache *id)
254 {
255     krb5_fcache *f;
256     int fd;
257     char *file;
258
259     f = malloc(sizeof(*f));
260     if(f == NULL) {
261         krb5_set_error_string(context, "malloc: out of memory");
262         return KRB5_CC_NOMEM;
263     }
264     asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
265     if(file == NULL) {
266         free(f);
267         krb5_set_error_string(context, "malloc: out of memory");
268         return KRB5_CC_NOMEM;
269     }
270     fd = mkstemp(file);
271     if(fd < 0) {
272         free(f);
273         free(file);
274         krb5_set_error_string(context, "mkstemp %s", file);
275         return errno;
276     }
277     close(fd);
278     f->filename = file;
279     f->version = 0;
280     (*id)->data.data = f;
281     (*id)->data.length = sizeof(*f);
282     return 0;
283 }
284
285 static void
286 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
287 {
288     int flags = 0;
289     switch(vno) {
290     case KRB5_FCC_FVNO_1:
291         flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
292         flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
293         flags |= KRB5_STORAGE_HOST_BYTEORDER;
294         break;
295     case KRB5_FCC_FVNO_2:
296         flags |= KRB5_STORAGE_HOST_BYTEORDER;
297         break;
298     case KRB5_FCC_FVNO_3:
299         flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
300         break;
301     case KRB5_FCC_FVNO_4:
302         break;
303     default:
304         krb5_abortx(context, 
305                     "storage_set_flags called with bad vno (%x)", vno);
306     }
307     krb5_storage_set_flags(sp, flags);
308 }
309
310 static krb5_error_code
311 fcc_open(krb5_context context,
312          krb5_ccache id,
313          int *fd_ret,
314          int flags,
315          mode_t mode)
316 {
317     krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
318                               (flags | O_RDWR) == flags);
319     krb5_error_code ret;
320     const char *filename = FILENAME(id);
321     int fd;
322     fd = open(filename, flags, mode);
323     if(fd < 0) {
324         ret = errno;
325         krb5_set_error_string(context, "open(%s): %s", filename,
326                               strerror(ret));
327         return ret;
328     }
329         
330     if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
331         close(fd);
332         return ret;
333     }
334     *fd_ret = fd;
335     return 0;
336 }
337
338 static krb5_error_code
339 fcc_initialize(krb5_context context,
340                krb5_ccache id,
341                krb5_principal primary_principal)
342 {
343     krb5_fcache *f = FCACHE(id);
344     int ret = 0;
345     int fd;
346     char *filename = f->filename;
347
348     unlink (filename);
349   
350     ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
351     if(ret)
352         return ret;
353     {
354         krb5_storage *sp;    
355         sp = krb5_storage_from_fd(fd);
356         krb5_storage_set_eof_code(sp, KRB5_CC_END);
357         if(context->fcache_vno != 0)
358             f->version = context->fcache_vno;
359         else
360             f->version = KRB5_FCC_FVNO_4;
361         ret |= krb5_store_int8(sp, 5);
362         ret |= krb5_store_int8(sp, f->version);
363         storage_set_flags(context, sp, f->version);
364         if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
365             /* V4 stuff */
366             if (context->kdc_sec_offset) {
367                 ret |= krb5_store_int16 (sp, 12); /* length */
368                 ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
369                 ret |= krb5_store_int16 (sp, 8); /* length of data */
370                 ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
371                 ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
372             } else {
373                 ret |= krb5_store_int16 (sp, 0);
374             }
375         }
376         ret |= krb5_store_principal(sp, primary_principal);
377         
378         krb5_storage_free(sp);
379     }
380     fcc_unlock(context, fd);
381     if (close(fd) < 0)
382         if (ret == 0) {
383             ret = errno;
384             krb5_set_error_string (context, "close %s: %s", 
385                                    FILENAME(id), strerror(ret));
386         }
387     return ret;
388 }
389
390 static krb5_error_code
391 fcc_close(krb5_context context,
392           krb5_ccache id)
393 {
394     free (FILENAME(id));
395     krb5_data_free(&id->data);
396     return 0;
397 }
398
399 static krb5_error_code
400 fcc_destroy(krb5_context context,
401             krb5_ccache id)
402 {
403     erase_file(FILENAME(id));
404     return 0;
405 }
406
407 static krb5_error_code
408 fcc_store_cred(krb5_context context,
409                krb5_ccache id,
410                krb5_creds *creds)
411 {
412     int ret;
413     int fd;
414
415     ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY, 0);
416     if(ret)
417         return ret;
418     {
419         krb5_storage *sp;
420         sp = krb5_storage_from_fd(fd);
421         krb5_storage_set_eof_code(sp, KRB5_CC_END);
422         storage_set_flags(context, sp, FCACHE(id)->version);
423         if (!krb5_config_get_bool_default(context, NULL, TRUE,
424                                           "libdefaults",
425                                           "fcc-mit-ticketflags",
426                                           NULL))
427             krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
428         ret = krb5_store_creds(sp, creds);
429         krb5_storage_free(sp);
430     }
431     fcc_unlock(context, fd);
432     if (close(fd) < 0)
433         if (ret == 0) {
434             ret = errno;
435             krb5_set_error_string (context, "close %s: %s", 
436                                    FILENAME(id), strerror(ret));
437         }
438     return ret;
439 }
440
441 static krb5_error_code
442 init_fcc (krb5_context context,
443           krb5_ccache id,
444           krb5_storage **ret_sp,
445           int *ret_fd)
446 {
447     int fd;
448     int8_t pvno, tag;
449     krb5_storage *sp;
450     krb5_error_code ret;
451
452     ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY, 0);
453     if(ret)
454         return ret;
455     
456     sp = krb5_storage_from_fd(fd);
457     if(sp == NULL) {
458         krb5_clear_error_string(context);
459         ret = ENOMEM;
460         goto out;
461     }
462     krb5_storage_set_eof_code(sp, KRB5_CC_END);
463     ret = krb5_ret_int8(sp, &pvno);
464     if(ret != 0) {
465         if(ret == KRB5_CC_END)
466             ret = ENOENT; /* empty file */
467         krb5_clear_error_string(context);
468         goto out;
469     }
470     if(pvno != 5) {
471         krb5_set_error_string(context, "Bad version number in credential "
472                               "cache file: %s", FILENAME(id));
473         ret = KRB5_CCACHE_BADVNO;
474         goto out;
475     }
476     ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
477     if(ret != 0) {
478         krb5_clear_error_string(context);
479         ret = KRB5_CC_FORMAT;
480         goto out;
481     }
482     FCACHE(id)->version = tag;
483     storage_set_flags(context, sp, FCACHE(id)->version);
484     switch (tag) {
485     case KRB5_FCC_FVNO_4: {
486         int16_t length;
487
488         ret = krb5_ret_int16 (sp, &length);
489         if(ret) {
490             ret = KRB5_CC_FORMAT;
491             krb5_clear_error_string(context);
492             goto out;
493         }
494         while(length > 0) {
495             int16_t dtag, data_len;
496             int i;
497             int8_t dummy;
498
499             ret = krb5_ret_int16 (sp, &dtag);
500             if(ret) {
501                 krb5_clear_error_string(context);
502                 ret = KRB5_CC_FORMAT;
503                 goto out;
504             }
505             ret = krb5_ret_int16 (sp, &data_len);
506             if(ret) {
507                 krb5_clear_error_string(context);
508                 ret = KRB5_CC_FORMAT;
509                 goto out;
510             }
511             switch (dtag) {
512             case FCC_TAG_DELTATIME :
513                 ret = krb5_ret_int32 (sp, &context->kdc_sec_offset);
514                 if(ret) {
515                     krb5_clear_error_string(context);
516                     ret = KRB5_CC_FORMAT;
517                     goto out;
518                 }
519                 ret = krb5_ret_int32 (sp, &context->kdc_usec_offset);
520                 if(ret) {
521                     krb5_clear_error_string(context);
522                     ret = KRB5_CC_FORMAT;
523                     goto out;
524                 }
525                 break;
526             default :
527                 for (i = 0; i < data_len; ++i) {
528                     ret = krb5_ret_int8 (sp, &dummy);
529                     if(ret) {
530                         krb5_clear_error_string(context);
531                         ret = KRB5_CC_FORMAT;
532                         goto out;
533                     }
534                 }
535                 break;
536             }
537             length -= 4 + data_len;
538         }
539         break;
540     }
541     case KRB5_FCC_FVNO_3:
542     case KRB5_FCC_FVNO_2:
543     case KRB5_FCC_FVNO_1:
544         break;
545     default :
546         ret = KRB5_CCACHE_BADVNO;
547         krb5_set_error_string(context, "Unknown version number (%d) in "
548                               "credential cache file: %s",
549                               (int)tag, FILENAME(id));
550         goto out;
551     }
552     *ret_sp = sp;
553     *ret_fd = fd;
554     
555     return 0;
556   out:
557     if(sp != NULL)
558         krb5_storage_free(sp);
559     fcc_unlock(context, fd);
560     close(fd);
561     return ret;
562 }
563
564 static krb5_error_code
565 fcc_get_principal(krb5_context context,
566                   krb5_ccache id,
567                   krb5_principal *principal)
568 {
569     krb5_error_code ret;
570     int fd;
571     krb5_storage *sp;
572
573     ret = init_fcc (context, id, &sp, &fd);
574     if (ret)
575         return ret;
576     ret = krb5_ret_principal(sp, principal);
577     if (ret)
578         krb5_clear_error_string(context);
579     krb5_storage_free(sp);
580     fcc_unlock(context, fd);
581     close(fd);
582     return ret;
583 }
584
585 static krb5_error_code
586 fcc_end_get (krb5_context context,
587              krb5_ccache id,
588              krb5_cc_cursor *cursor);
589
590 static krb5_error_code
591 fcc_get_first (krb5_context context,
592                krb5_ccache id,
593                krb5_cc_cursor *cursor)
594 {
595     krb5_error_code ret;
596     krb5_principal principal;
597
598     *cursor = malloc(sizeof(struct fcc_cursor));
599     if (*cursor == NULL) {
600         krb5_set_error_string (context, "malloc: out of memory");
601         return ENOMEM;
602     }
603     memset(*cursor, 0, sizeof(struct fcc_cursor));
604
605     ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp, 
606                     &FCC_CURSOR(*cursor)->fd);
607     if (ret) {
608         free(*cursor);
609         *cursor = NULL;
610         return ret;
611     }
612     ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
613     if(ret) {
614         krb5_clear_error_string(context);
615         fcc_end_get(context, id, cursor);
616         return ret;
617     }
618     krb5_free_principal (context, principal);
619     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
620     return 0;
621 }
622
623 static krb5_error_code
624 fcc_get_next (krb5_context context,
625               krb5_ccache id,
626               krb5_cc_cursor *cursor,
627               krb5_creds *creds)
628 {
629     krb5_error_code ret;
630     if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
631         return ret;
632
633     ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
634     if (ret)
635         krb5_clear_error_string(context);
636
637     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
638     return ret;
639 }
640
641 static krb5_error_code
642 fcc_end_get (krb5_context context,
643              krb5_ccache id,
644              krb5_cc_cursor *cursor)
645 {
646     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
647     close (FCC_CURSOR(*cursor)->fd);
648     free(*cursor);
649     *cursor = NULL;
650     return 0;
651 }
652
653 static krb5_error_code
654 fcc_remove_cred(krb5_context context,
655                  krb5_ccache id,
656                  krb5_flags which,
657                  krb5_creds *cred)
658 {
659     krb5_error_code ret;
660     krb5_ccache copy;
661
662     ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &copy);
663     if (ret)
664         return ret;
665
666     ret = krb5_cc_copy_cache(context, id, copy);
667     if (ret) {
668         krb5_cc_destroy(context, copy);
669         return ret;
670     }
671
672     ret = krb5_cc_remove_cred(context, copy, which, cred);
673     if (ret) {
674         krb5_cc_destroy(context, copy);
675         return ret;
676     }
677
678     fcc_destroy(context, id);
679
680     ret = krb5_cc_copy_cache(context, copy, id);
681     krb5_cc_destroy(context, copy);
682
683     return ret;
684 }
685
686 static krb5_error_code
687 fcc_set_flags(krb5_context context,
688               krb5_ccache id,
689               krb5_flags flags)
690 {
691     return 0; /* XXX */
692 }
693
694 static krb5_error_code
695 fcc_get_version(krb5_context context,
696                 krb5_ccache id)
697 {
698     return FCACHE(id)->version;
699 }
700                     
701 const krb5_cc_ops krb5_fcc_ops = {
702     "FILE",
703     fcc_get_name,
704     fcc_resolve,
705     fcc_gen_new,
706     fcc_initialize,
707     fcc_destroy,
708     fcc_close,
709     fcc_store_cred,
710     NULL, /* fcc_retrieve */
711     fcc_get_principal,
712     fcc_get_first,
713     fcc_get_next,
714     fcc_end_get,
715     fcc_remove_cred,
716     fcc_set_flags,
717     fcc_get_version
718 };