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