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