155682Smarkm/*
2233294Sstas * Copyright (c) 1997 - 2008 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
555682Smarkm *
6233294Sstas * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
755682Smarkm *
8233294Sstas * Redistribution and use in source and binary forms, with or without
9233294Sstas * modification, are permitted provided that the following conditions
10233294Sstas * are met:
1155682Smarkm *
12233294Sstas * 1. Redistributions of source code must retain the above copyright
13233294Sstas *    notice, this list of conditions and the following disclaimer.
1455682Smarkm *
15233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
16233294Sstas *    notice, this list of conditions and the following disclaimer in the
17233294Sstas *    documentation and/or other materials provided with the distribution.
1855682Smarkm *
19233294Sstas * 3. Neither the name of the Institute nor the names of its contributors
20233294Sstas *    may be used to endorse or promote products derived from this software
21233294Sstas *    without specific prior written permission.
22233294Sstas *
23233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33233294Sstas * SUCH DAMAGE.
3455682Smarkm */
3555682Smarkm
3655682Smarkm#include "krb5_locl.h"
3755682Smarkm
3855682Smarkmtypedef struct krb5_fcache{
3955682Smarkm    char *filename;
4055682Smarkm    int version;
4155682Smarkm}krb5_fcache;
4255682Smarkm
4355682Smarkmstruct fcc_cursor {
4455682Smarkm    int fd;
4555682Smarkm    krb5_storage *sp;
4655682Smarkm};
4755682Smarkm
4855682Smarkm#define KRB5_FCC_FVNO_1 1
4955682Smarkm#define KRB5_FCC_FVNO_2 2
5055682Smarkm#define KRB5_FCC_FVNO_3 3
5155682Smarkm#define KRB5_FCC_FVNO_4 4
5255682Smarkm
5355682Smarkm#define FCC_TAG_DELTATIME 1
5455682Smarkm
5555682Smarkm#define FCACHE(X) ((krb5_fcache*)(X)->data.data)
5655682Smarkm
5755682Smarkm#define FILENAME(X) (FCACHE(X)->filename)
5855682Smarkm
5955682Smarkm#define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
6055682Smarkm
61233294Sstasstatic const char* KRB5_CALLCONV
6255682Smarkmfcc_get_name(krb5_context context,
6355682Smarkm	     krb5_ccache id)
6455682Smarkm{
65233294Sstas    if (FCACHE(id) == NULL)
66233294Sstas        return NULL;
67233294Sstas
6855682Smarkm    return FILENAME(id);
6955682Smarkm}
7055682Smarkm
71127808Snectarint
72127808Snectar_krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
73127808Snectar	    const char *filename)
74127808Snectar{
75127808Snectar    int ret;
76127808Snectar#ifdef HAVE_FCNTL
77127808Snectar    struct flock l;
78127808Snectar
79127808Snectar    l.l_start = 0;
80127808Snectar    l.l_len = 0;
81127808Snectar    l.l_type = exclusive ? F_WRLCK : F_RDLCK;
82127808Snectar    l.l_whence = SEEK_SET;
83127808Snectar    ret = fcntl(fd, F_SETLKW, &l);
84127808Snectar#else
85127808Snectar    ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
86127808Snectar#endif
87127808Snectar    if(ret < 0)
88127808Snectar	ret = errno;
89127808Snectar    if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
90127808Snectar	ret = EAGAIN;
91127808Snectar
92127808Snectar    switch (ret) {
93127808Snectar    case 0:
94127808Snectar	break;
95127808Snectar    case EINVAL: /* filesystem doesn't support locking, let the user have it */
96233294Sstas	ret = 0;
97127808Snectar	break;
98127808Snectar    case EAGAIN:
99233294Sstas	krb5_set_error_message(context, ret,
100233294Sstas			       N_("timed out locking cache file %s", "file"),
101233294Sstas			       filename);
102127808Snectar	break;
103233294Sstas    default: {
104233294Sstas	char buf[128];
105233294Sstas	rk_strerror_r(ret, buf, sizeof(buf));
106233294Sstas	krb5_set_error_message(context, ret,
107233294Sstas			       N_("error locking cache file %s: %s",
108233294Sstas				  "file, error"), filename, buf);
109127808Snectar	break;
110127808Snectar    }
111233294Sstas    }
112127808Snectar    return ret;
113127808Snectar}
114127808Snectar
115127808Snectarint
116178825Sdfr_krb5_xunlock(krb5_context context, int fd)
117127808Snectar{
118178825Sdfr    int ret;
119178825Sdfr#ifdef HAVE_FCNTL
120127808Snectar    struct flock l;
121127808Snectar    l.l_start = 0;
122127808Snectar    l.l_len = 0;
123127808Snectar    l.l_type = F_UNLCK;
124127808Snectar    l.l_whence = SEEK_SET;
125178825Sdfr    ret = fcntl(fd, F_SETLKW, &l);
126127808Snectar#else
127178825Sdfr    ret = flock(fd, LOCK_UN);
128127808Snectar#endif
129178825Sdfr    if (ret < 0)
130178825Sdfr	ret = errno;
131178825Sdfr    switch (ret) {
132178825Sdfr    case 0:
133178825Sdfr	break;
134178825Sdfr    case EINVAL: /* filesystem doesn't support locking, let the user have it */
135233294Sstas	ret = 0;
136178825Sdfr	break;
137233294Sstas    default: {
138233294Sstas	char buf[128];
139233294Sstas	rk_strerror_r(ret, buf, sizeof(buf));
140233294Sstas	krb5_set_error_message(context, ret,
141233294Sstas			       N_("Failed to unlock file: %s", ""), buf);
142178825Sdfr	break;
143178825Sdfr    }
144233294Sstas    }
145178825Sdfr    return ret;
146127808Snectar}
147127808Snectar
14855682Smarkmstatic krb5_error_code
149233294Sstaswrite_storage(krb5_context context, krb5_storage *sp, int fd)
150233294Sstas{
151233294Sstas    krb5_error_code ret;
152233294Sstas    krb5_data data;
153233294Sstas    ssize_t sret;
154233294Sstas
155233294Sstas    ret = krb5_storage_to_data(sp, &data);
156233294Sstas    if (ret) {
157233294Sstas	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
158233294Sstas	return ret;
159233294Sstas    }
160233294Sstas    sret = write(fd, data.data, data.length);
161233294Sstas    ret = (sret != (ssize_t)data.length);
162233294Sstas    krb5_data_free(&data);
163233294Sstas    if (ret) {
164233294Sstas	ret = errno;
165233294Sstas	krb5_set_error_message(context, ret,
166233294Sstas			       N_("Failed to write FILE credential data", ""));
167233294Sstas	return ret;
168233294Sstas    }
169233294Sstas    return 0;
170233294Sstas}
171233294Sstas
172233294Sstas
173233294Sstasstatic krb5_error_code KRB5_CALLCONV
174127808Snectarfcc_lock(krb5_context context, krb5_ccache id,
175127808Snectar	 int fd, krb5_boolean exclusive)
176127808Snectar{
177127808Snectar    return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
178127808Snectar}
179127808Snectar
180233294Sstasstatic krb5_error_code KRB5_CALLCONV
181127808Snectarfcc_unlock(krb5_context context, int fd)
182127808Snectar{
183178825Sdfr    return _krb5_xunlock(context, fd);
184127808Snectar}
185127808Snectar
186233294Sstasstatic krb5_error_code KRB5_CALLCONV
18755682Smarkmfcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
18855682Smarkm{
18955682Smarkm    krb5_fcache *f;
19055682Smarkm    f = malloc(sizeof(*f));
19178527Sassar    if(f == NULL) {
192233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
193233294Sstas			       N_("malloc: out of memory", ""));
19455682Smarkm	return KRB5_CC_NOMEM;
19578527Sassar    }
19655682Smarkm    f->filename = strdup(res);
19755682Smarkm    if(f->filename == NULL){
19855682Smarkm	free(f);
199233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
200233294Sstas			       N_("malloc: out of memory", ""));
20155682Smarkm	return KRB5_CC_NOMEM;
20255682Smarkm    }
20355682Smarkm    f->version = 0;
20455682Smarkm    (*id)->data.data = f;
20555682Smarkm    (*id)->data.length = sizeof(*f);
20655682Smarkm    return 0;
20755682Smarkm}
20855682Smarkm
20972445Sassar/*
21072445Sassar * Try to scrub the contents of `filename' safely.
21172445Sassar */
21272445Sassar
21372445Sassarstatic int
21472445Sassarscrub_file (int fd)
21572445Sassar{
21672445Sassar    off_t pos;
21772445Sassar    char buf[128];
21872445Sassar
21972445Sassar    pos = lseek(fd, 0, SEEK_END);
22072445Sassar    if (pos < 0)
22172445Sassar        return errno;
22272445Sassar    if (lseek(fd, 0, SEEK_SET) < 0)
22372445Sassar        return errno;
22472445Sassar    memset(buf, 0, sizeof(buf));
22572445Sassar    while(pos > 0) {
226233294Sstas        ssize_t tmp = write(fd, buf, min((off_t)sizeof(buf), pos));
22772445Sassar
22872445Sassar	if (tmp < 0)
22972445Sassar	    return errno;
23072445Sassar	pos -= tmp;
23172445Sassar    }
232233294Sstas#ifdef _MSC_VER
233233294Sstas    _commit (fd);
234233294Sstas#else
23572445Sassar    fsync (fd);
236233294Sstas#endif
23772445Sassar    return 0;
23872445Sassar}
23972445Sassar
24072445Sassar/*
24172445Sassar * Erase `filename' if it exists, trying to remove the contents if
24272445Sassar * it's `safe'.  We always try to remove the file, it it exists.  It's
24372445Sassar * only overwritten if it's a regular file (not a symlink and not a
24472445Sassar * hardlink)
24572445Sassar */
24672445Sassar
247233294Sstaskrb5_error_code
248233294Sstas_krb5_erase_file(krb5_context context, const char *filename)
24955682Smarkm{
25055682Smarkm    int fd;
25172445Sassar    struct stat sb1, sb2;
25272445Sassar    int ret;
25355682Smarkm
25472445Sassar    ret = lstat (filename, &sb1);
25572445Sassar    if (ret < 0)
25672445Sassar	return errno;
25772445Sassar
25855682Smarkm    fd = open(filename, O_RDWR | O_BINARY);
25972445Sassar    if(fd < 0) {
26055682Smarkm	if(errno == ENOENT)
26155682Smarkm	    return 0;
26255682Smarkm	else
26355682Smarkm	    return errno;
26455682Smarkm    }
265233294Sstas    rk_cloexec(fd);
266233294Sstas    ret = _krb5_xlock(context, fd, 1, filename);
267233294Sstas    if (ret) {
268233294Sstas	close(fd);
269233294Sstas	return ret;
270233294Sstas    }
27172445Sassar    if (unlink(filename) < 0) {
272233294Sstas	_krb5_xunlock(context, fd);
27372445Sassar        close (fd);
27472445Sassar        return errno;
27572445Sassar    }
27672445Sassar    ret = fstat (fd, &sb2);
27772445Sassar    if (ret < 0) {
278233294Sstas	_krb5_xunlock(context, fd);
27972445Sassar	close (fd);
28072445Sassar	return errno;
28172445Sassar    }
28272445Sassar
28372445Sassar    /* check if someone was playing with symlinks */
28472445Sassar
28572445Sassar    if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
286233294Sstas	_krb5_xunlock(context, fd);
28772445Sassar	close (fd);
28872445Sassar	return EPERM;
28972445Sassar    }
29072445Sassar
29172445Sassar    /* there are still hard links to this file */
29272445Sassar
29372445Sassar    if (sb2.st_nlink != 0) {
294233294Sstas	_krb5_xunlock(context, fd);
29572445Sassar        close (fd);
29672445Sassar        return 0;
29772445Sassar    }
29872445Sassar
29972445Sassar    ret = scrub_file (fd);
300233294Sstas    if (ret) {
301233294Sstas	_krb5_xunlock(context, fd);
302233294Sstas	close(fd);
303233294Sstas	return ret;
304233294Sstas    }
305233294Sstas    ret = _krb5_xunlock(context, fd);
30672445Sassar    close (fd);
30772445Sassar    return ret;
30855682Smarkm}
30955682Smarkm
310233294Sstasstatic krb5_error_code KRB5_CALLCONV
31155682Smarkmfcc_gen_new(krb5_context context, krb5_ccache *id)
31255682Smarkm{
313233294Sstas    char *file = NULL, *exp_file = NULL;
314233294Sstas    krb5_error_code ret;
31555682Smarkm    krb5_fcache *f;
31655682Smarkm    int fd;
31778527Sassar
31855682Smarkm    f = malloc(sizeof(*f));
31978527Sassar    if(f == NULL) {
320233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
321233294Sstas			       N_("malloc: out of memory", ""));
32255682Smarkm	return KRB5_CC_NOMEM;
32378527Sassar    }
324233294Sstas    ret = asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
325233294Sstas    if(ret < 0 || file == NULL) {
32655682Smarkm	free(f);
327233294Sstas	krb5_set_error_message(context, KRB5_CC_NOMEM,
328233294Sstas			       N_("malloc: out of memory", ""));
32955682Smarkm	return KRB5_CC_NOMEM;
33055682Smarkm    }
331233294Sstas    ret = _krb5_expand_path_tokens(context, file, &exp_file);
332233294Sstas    free(file);
333233294Sstas    if (ret)
334233294Sstas	return ret;
335233294Sstas
336233294Sstas    file = exp_file;
337233294Sstas
338233294Sstas    fd = mkstemp(exp_file);
33955682Smarkm    if(fd < 0) {
340233294Sstas	int xret = errno;
341233294Sstas	krb5_set_error_message(context, xret, N_("mkstemp %s failed", ""), exp_file);
34255682Smarkm	free(f);
343233294Sstas	free(exp_file);
344233294Sstas	return xret;
34555682Smarkm    }
34655682Smarkm    close(fd);
347233294Sstas    f->filename = exp_file;
34855682Smarkm    f->version = 0;
34955682Smarkm    (*id)->data.data = f;
35055682Smarkm    (*id)->data.length = sizeof(*f);
35155682Smarkm    return 0;
35255682Smarkm}
35355682Smarkm
35455682Smarkmstatic void
35555682Smarkmstorage_set_flags(krb5_context context, krb5_storage *sp, int vno)
35655682Smarkm{
35755682Smarkm    int flags = 0;
35855682Smarkm    switch(vno) {
35955682Smarkm    case KRB5_FCC_FVNO_1:
36055682Smarkm	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
36155682Smarkm	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
36255682Smarkm	flags |= KRB5_STORAGE_HOST_BYTEORDER;
36355682Smarkm	break;
36455682Smarkm    case KRB5_FCC_FVNO_2:
36555682Smarkm	flags |= KRB5_STORAGE_HOST_BYTEORDER;
36655682Smarkm	break;
36755682Smarkm    case KRB5_FCC_FVNO_3:
36855682Smarkm	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
36955682Smarkm	break;
37055682Smarkm    case KRB5_FCC_FVNO_4:
37155682Smarkm	break;
37255682Smarkm    default:
373233294Sstas	krb5_abortx(context,
37455682Smarkm		    "storage_set_flags called with bad vno (%x)", vno);
37555682Smarkm    }
37655682Smarkm    krb5_storage_set_flags(sp, flags);
37755682Smarkm}
37855682Smarkm
379233294Sstasstatic krb5_error_code KRB5_CALLCONV
380127808Snectarfcc_open(krb5_context context,
381127808Snectar	 krb5_ccache id,
382127808Snectar	 int *fd_ret,
383127808Snectar	 int flags,
384127808Snectar	 mode_t mode)
385127808Snectar{
386127808Snectar    krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
387127808Snectar			      (flags | O_RDWR) == flags);
388127808Snectar    krb5_error_code ret;
389233294Sstas    const char *filename;
390127808Snectar    int fd;
391233294Sstas
392233294Sstas    if (FCACHE(id) == NULL)
393233294Sstas        return krb5_einval(context, 2);
394233294Sstas
395233294Sstas    filename = FILENAME(id);
396233294Sstas
397127808Snectar    fd = open(filename, flags, mode);
398127808Snectar    if(fd < 0) {
399233294Sstas	char buf[128];
400127808Snectar	ret = errno;
401233294Sstas	rk_strerror_r(ret, buf, sizeof(buf));
402233294Sstas	krb5_set_error_message(context, ret, N_("open(%s): %s", "file, error"),
403233294Sstas			       filename, buf);
404127808Snectar	return ret;
405127808Snectar    }
406233294Sstas    rk_cloexec(fd);
407233294Sstas
408127808Snectar    if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
409127808Snectar	close(fd);
410127808Snectar	return ret;
411127808Snectar    }
412127808Snectar    *fd_ret = fd;
413127808Snectar    return 0;
414127808Snectar}
415127808Snectar
416233294Sstasstatic krb5_error_code KRB5_CALLCONV
41755682Smarkmfcc_initialize(krb5_context context,
41855682Smarkm	       krb5_ccache id,
41955682Smarkm	       krb5_principal primary_principal)
42055682Smarkm{
42155682Smarkm    krb5_fcache *f = FCACHE(id);
42272445Sassar    int ret = 0;
42355682Smarkm    int fd;
42455682Smarkm
425233294Sstas    if (f == NULL)
426233294Sstas        return krb5_einval(context, 2);
427233294Sstas
428233294Sstas    unlink (f->filename);
429233294Sstas
430233294Sstas    ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
431127808Snectar    if(ret)
43278527Sassar	return ret;
43355682Smarkm    {
434233294Sstas	krb5_storage *sp;
435233294Sstas	sp = krb5_storage_emem();
436102644Snectar	krb5_storage_set_eof_code(sp, KRB5_CC_END);
43755682Smarkm	if(context->fcache_vno != 0)
43855682Smarkm	    f->version = context->fcache_vno;
43955682Smarkm	else
44055682Smarkm	    f->version = KRB5_FCC_FVNO_4;
44172445Sassar	ret |= krb5_store_int8(sp, 5);
44272445Sassar	ret |= krb5_store_int8(sp, f->version);
44355682Smarkm	storage_set_flags(context, sp, f->version);
44472445Sassar	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
44555682Smarkm	    /* V4 stuff */
44655682Smarkm	    if (context->kdc_sec_offset) {
44772445Sassar		ret |= krb5_store_int16 (sp, 12); /* length */
44872445Sassar		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
44972445Sassar		ret |= krb5_store_int16 (sp, 8); /* length of data */
45072445Sassar		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
45172445Sassar		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
45255682Smarkm	    } else {
45372445Sassar		ret |= krb5_store_int16 (sp, 0);
45455682Smarkm	    }
45555682Smarkm	}
45672445Sassar	ret |= krb5_store_principal(sp, primary_principal);
457233294Sstas
458233294Sstas	ret |= write_storage(context, sp, fd);
459233294Sstas
46055682Smarkm	krb5_storage_free(sp);
46155682Smarkm    }
462127808Snectar    fcc_unlock(context, fd);
463127808Snectar    if (close(fd) < 0)
46478527Sassar	if (ret == 0) {
465233294Sstas	    char buf[128];
46672445Sassar	    ret = errno;
467233294Sstas	    rk_strerror_r(ret, buf, sizeof(buf));
468233294Sstas	    krb5_set_error_message (context, ret, N_("close %s: %s", ""),
469233294Sstas				    FILENAME(id), buf);
47078527Sassar	}
47172445Sassar    return ret;
47255682Smarkm}
47355682Smarkm
474233294Sstasstatic krb5_error_code KRB5_CALLCONV
47555682Smarkmfcc_close(krb5_context context,
47655682Smarkm	  krb5_ccache id)
47755682Smarkm{
478233294Sstas    if (FCACHE(id) == NULL)
479233294Sstas        return krb5_einval(context, 2);
480233294Sstas
48155682Smarkm    free (FILENAME(id));
48255682Smarkm    krb5_data_free(&id->data);
48355682Smarkm    return 0;
48455682Smarkm}
48555682Smarkm
486233294Sstasstatic krb5_error_code KRB5_CALLCONV
48755682Smarkmfcc_destroy(krb5_context context,
48855682Smarkm	    krb5_ccache id)
48955682Smarkm{
490233294Sstas    if (FCACHE(id) == NULL)
491233294Sstas        return krb5_einval(context, 2);
492233294Sstas
493233294Sstas    _krb5_erase_file(context, FILENAME(id));
49455682Smarkm    return 0;
49555682Smarkm}
49655682Smarkm
497233294Sstasstatic krb5_error_code KRB5_CALLCONV
49855682Smarkmfcc_store_cred(krb5_context context,
49955682Smarkm	       krb5_ccache id,
50055682Smarkm	       krb5_creds *creds)
50155682Smarkm{
50272445Sassar    int ret;
50355682Smarkm    int fd;
50455682Smarkm
505233294Sstas    ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY | O_CLOEXEC, 0);
506127808Snectar    if(ret)
50778527Sassar	return ret;
50855682Smarkm    {
50955682Smarkm	krb5_storage *sp;
510233294Sstas
511233294Sstas	sp = krb5_storage_emem();
512102644Snectar	krb5_storage_set_eof_code(sp, KRB5_CC_END);
51355682Smarkm	storage_set_flags(context, sp, FCACHE(id)->version);
514178825Sdfr	if (!krb5_config_get_bool_default(context, NULL, TRUE,
515178825Sdfr					  "libdefaults",
516178825Sdfr					  "fcc-mit-ticketflags",
517178825Sdfr					  NULL))
518178825Sdfr	    krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
519178825Sdfr	ret = krb5_store_creds(sp, creds);
520233294Sstas	if (ret == 0)
521233294Sstas	    ret = write_storage(context, sp, fd);
52255682Smarkm	krb5_storage_free(sp);
52355682Smarkm    }
524127808Snectar    fcc_unlock(context, fd);
525233294Sstas    if (close(fd) < 0) {
52678527Sassar	if (ret == 0) {
527233294Sstas	    char buf[128];
528233294Sstas	    rk_strerror_r(ret, buf, sizeof(buf));
52972445Sassar	    ret = errno;
530233294Sstas	    krb5_set_error_message (context, ret, N_("close %s: %s", ""),
531233294Sstas				    FILENAME(id), buf);
53278527Sassar	}
533233294Sstas    }
53472445Sassar    return ret;
53555682Smarkm}
53655682Smarkm
53755682Smarkmstatic krb5_error_code
53855682Smarkminit_fcc (krb5_context context,
539127808Snectar	  krb5_ccache id,
54055682Smarkm	  krb5_storage **ret_sp,
541233294Sstas	  int *ret_fd,
542233294Sstas	  krb5_deltat *kdc_offset)
54355682Smarkm{
54455682Smarkm    int fd;
54555682Smarkm    int8_t pvno, tag;
54655682Smarkm    krb5_storage *sp;
54772445Sassar    krb5_error_code ret;
54855682Smarkm
549233294Sstas    if (kdc_offset)
550233294Sstas	*kdc_offset = 0;
551233294Sstas
552233294Sstas    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
553127808Snectar    if(ret)
55478527Sassar	return ret;
555233294Sstas
556127808Snectar    sp = krb5_storage_from_fd(fd);
557127808Snectar    if(sp == NULL) {
558233294Sstas	krb5_clear_error_message(context);
559127808Snectar	ret = ENOMEM;
560127808Snectar	goto out;
56178527Sassar    }
562102644Snectar    krb5_storage_set_eof_code(sp, KRB5_CC_END);
56372445Sassar    ret = krb5_ret_int8(sp, &pvno);
564127808Snectar    if(ret != 0) {
565178825Sdfr	if(ret == KRB5_CC_END) {
566178825Sdfr	    ret = ENOENT;
567233294Sstas	    krb5_set_error_message(context, ret,
568233294Sstas				   N_("Empty credential cache file: %s", ""),
569233294Sstas				   FILENAME(id));
570178825Sdfr	} else
571233294Sstas	    krb5_set_error_message(context, ret, N_("Error reading pvno "
572233294Sstas						    "in cache file: %s", ""),
573233294Sstas				   FILENAME(id));
574127808Snectar	goto out;
575127808Snectar    }
57655682Smarkm    if(pvno != 5) {
577127808Snectar	ret = KRB5_CCACHE_BADVNO;
578233294Sstas	krb5_set_error_message(context, ret, N_("Bad version number in credential "
579233294Sstas						"cache file: %s", ""),
580233294Sstas			       FILENAME(id));
581127808Snectar	goto out;
58255682Smarkm    }
583127808Snectar    ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
584127808Snectar    if(ret != 0) {
585233294Sstas	ret = KRB5_CC_FORMAT;
586233294Sstas	krb5_set_error_message(context, ret, "Error reading tag in "
587178825Sdfr			      "cache file: %s", FILENAME(id));
588127808Snectar	goto out;
589127808Snectar    }
590127808Snectar    FCACHE(id)->version = tag;
591127808Snectar    storage_set_flags(context, sp, FCACHE(id)->version);
59255682Smarkm    switch (tag) {
59355682Smarkm    case KRB5_FCC_FVNO_4: {
59455682Smarkm	int16_t length;
59555682Smarkm
596127808Snectar	ret = krb5_ret_int16 (sp, &length);
597127808Snectar	if(ret) {
598127808Snectar	    ret = KRB5_CC_FORMAT;
599233294Sstas	    krb5_set_error_message(context, ret,
600233294Sstas				   N_("Error reading tag length in "
601233294Sstas				      "cache file: %s", ""), FILENAME(id));
602127808Snectar	    goto out;
603127808Snectar	}
60455682Smarkm	while(length > 0) {
605178825Sdfr	    int16_t dtag, data_len;
60655682Smarkm	    int i;
60755682Smarkm	    int8_t dummy;
60855682Smarkm
609178825Sdfr	    ret = krb5_ret_int16 (sp, &dtag);
610127808Snectar	    if(ret) {
611127808Snectar		ret = KRB5_CC_FORMAT;
612233294Sstas		krb5_set_error_message(context, ret, N_("Error reading dtag in "
613233294Sstas							"cache file: %s", ""),
614233294Sstas				       FILENAME(id));
615127808Snectar		goto out;
616127808Snectar	    }
617127808Snectar	    ret = krb5_ret_int16 (sp, &data_len);
618127808Snectar	    if(ret) {
619127808Snectar		ret = KRB5_CC_FORMAT;
620233294Sstas		krb5_set_error_message(context, ret,
621233294Sstas				       N_("Error reading dlength "
622233294Sstas					  "in cache file: %s",""),
623233294Sstas				       FILENAME(id));
624127808Snectar		goto out;
625127808Snectar	    }
626178825Sdfr	    switch (dtag) {
627233294Sstas	    case FCC_TAG_DELTATIME : {
628233294Sstas		int32_t offset;
629233294Sstas
630233294Sstas		ret = krb5_ret_int32 (sp, &offset);
631233294Sstas		ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
632127808Snectar		if(ret) {
633127808Snectar		    ret = KRB5_CC_FORMAT;
634233294Sstas		    krb5_set_error_message(context, ret,
635233294Sstas					   N_("Error reading kdc_sec in "
636233294Sstas					      "cache file: %s", ""),
637233294Sstas					   FILENAME(id));
638127808Snectar		    goto out;
639127808Snectar		}
640233294Sstas		context->kdc_sec_offset = offset;
641233294Sstas		if (kdc_offset)
642233294Sstas		    *kdc_offset = offset;
64355682Smarkm		break;
644233294Sstas	    }
64555682Smarkm	    default :
646127808Snectar		for (i = 0; i < data_len; ++i) {
647127808Snectar		    ret = krb5_ret_int8 (sp, &dummy);
648127808Snectar		    if(ret) {
649127808Snectar			ret = KRB5_CC_FORMAT;
650233294Sstas			krb5_set_error_message(context, ret,
651233294Sstas					       N_("Error reading unknown "
652233294Sstas						  "tag in cache file: %s", ""),
653233294Sstas					       FILENAME(id));
654127808Snectar			goto out;
655127808Snectar		    }
656127808Snectar		}
65755682Smarkm		break;
65855682Smarkm	    }
65955682Smarkm	    length -= 4 + data_len;
66055682Smarkm	}
66155682Smarkm	break;
66255682Smarkm    }
66355682Smarkm    case KRB5_FCC_FVNO_3:
66455682Smarkm    case KRB5_FCC_FVNO_2:
66555682Smarkm    case KRB5_FCC_FVNO_1:
66655682Smarkm	break;
66755682Smarkm    default :
668127808Snectar	ret = KRB5_CCACHE_BADVNO;
669233294Sstas	krb5_set_error_message(context, ret,
670233294Sstas			       N_("Unknown version number (%d) in "
671233294Sstas				  "credential cache file: %s", ""),
672233294Sstas			       (int)tag, FILENAME(id));
673127808Snectar	goto out;
67455682Smarkm    }
67555682Smarkm    *ret_sp = sp;
67655682Smarkm    *ret_fd = fd;
677233294Sstas
67855682Smarkm    return 0;
679127808Snectar  out:
680127808Snectar    if(sp != NULL)
681127808Snectar	krb5_storage_free(sp);
682127808Snectar    fcc_unlock(context, fd);
683127808Snectar    close(fd);
684127808Snectar    return ret;
68555682Smarkm}
68655682Smarkm
687233294Sstasstatic krb5_error_code KRB5_CALLCONV
68855682Smarkmfcc_get_principal(krb5_context context,
68955682Smarkm		  krb5_ccache id,
69055682Smarkm		  krb5_principal *principal)
69155682Smarkm{
69255682Smarkm    krb5_error_code ret;
69355682Smarkm    int fd;
69455682Smarkm    krb5_storage *sp;
69555682Smarkm
696233294Sstas    ret = init_fcc (context, id, &sp, &fd, NULL);
69755682Smarkm    if (ret)
69855682Smarkm	return ret;
69972445Sassar    ret = krb5_ret_principal(sp, principal);
700178825Sdfr    if (ret)
701233294Sstas	krb5_clear_error_message(context);
70255682Smarkm    krb5_storage_free(sp);
703127808Snectar    fcc_unlock(context, fd);
70455682Smarkm    close(fd);
70572445Sassar    return ret;
70655682Smarkm}
70755682Smarkm
708233294Sstasstatic krb5_error_code KRB5_CALLCONV
709127808Snectarfcc_end_get (krb5_context context,
710127808Snectar	     krb5_ccache id,
711127808Snectar	     krb5_cc_cursor *cursor);
712127808Snectar
713233294Sstasstatic krb5_error_code KRB5_CALLCONV
71455682Smarkmfcc_get_first (krb5_context context,
71555682Smarkm	       krb5_ccache id,
71655682Smarkm	       krb5_cc_cursor *cursor)
71755682Smarkm{
71855682Smarkm    krb5_error_code ret;
71955682Smarkm    krb5_principal principal;
72055682Smarkm
721233294Sstas    if (FCACHE(id) == NULL)
722233294Sstas        return krb5_einval(context, 2);
723233294Sstas
72455682Smarkm    *cursor = malloc(sizeof(struct fcc_cursor));
725178825Sdfr    if (*cursor == NULL) {
726233294Sstas        krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
727178825Sdfr	return ENOMEM;
728178825Sdfr    }
729178825Sdfr    memset(*cursor, 0, sizeof(struct fcc_cursor));
73055682Smarkm
731233294Sstas    ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp,
732233294Sstas		    &FCC_CURSOR(*cursor)->fd, NULL);
733127808Snectar    if (ret) {
734127808Snectar	free(*cursor);
735178825Sdfr	*cursor = NULL;
73655682Smarkm	return ret;
737127808Snectar    }
738127808Snectar    ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
739127808Snectar    if(ret) {
740233294Sstas	krb5_clear_error_message(context);
741127808Snectar	fcc_end_get(context, id, cursor);
742127808Snectar	return ret;
743127808Snectar    }
74455682Smarkm    krb5_free_principal (context, principal);
745127808Snectar    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
74655682Smarkm    return 0;
74755682Smarkm}
74855682Smarkm
749233294Sstasstatic krb5_error_code KRB5_CALLCONV
75055682Smarkmfcc_get_next (krb5_context context,
75155682Smarkm	      krb5_ccache id,
75255682Smarkm	      krb5_cc_cursor *cursor,
75355682Smarkm	      krb5_creds *creds)
75455682Smarkm{
755127808Snectar    krb5_error_code ret;
756233294Sstas
757233294Sstas    if (FCACHE(id) == NULL)
758233294Sstas        return krb5_einval(context, 2);
759233294Sstas
760233294Sstas    if (FCC_CURSOR(*cursor) == NULL)
761233294Sstas        return krb5_einval(context, 3);
762233294Sstas
763127808Snectar    if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
764127808Snectar	return ret;
765127808Snectar
766127808Snectar    ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
767178825Sdfr    if (ret)
768233294Sstas	krb5_clear_error_message(context);
769127808Snectar
770127808Snectar    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
771127808Snectar    return ret;
77255682Smarkm}
77355682Smarkm
774233294Sstasstatic krb5_error_code KRB5_CALLCONV
77555682Smarkmfcc_end_get (krb5_context context,
77655682Smarkm	     krb5_ccache id,
77755682Smarkm	     krb5_cc_cursor *cursor)
77855682Smarkm{
779233294Sstas
780233294Sstas    if (FCACHE(id) == NULL)
781233294Sstas        return krb5_einval(context, 2);
782233294Sstas
783233294Sstas    if (FCC_CURSOR(*cursor) == NULL)
784233294Sstas        return krb5_einval(context, 3);
785233294Sstas
78655682Smarkm    krb5_storage_free(FCC_CURSOR(*cursor)->sp);
78755682Smarkm    close (FCC_CURSOR(*cursor)->fd);
78855682Smarkm    free(*cursor);
789127808Snectar    *cursor = NULL;
79055682Smarkm    return 0;
79155682Smarkm}
79255682Smarkm
793233294Sstasstatic krb5_error_code KRB5_CALLCONV
79455682Smarkmfcc_remove_cred(krb5_context context,
79555682Smarkm		 krb5_ccache id,
79655682Smarkm		 krb5_flags which,
79755682Smarkm		 krb5_creds *cred)
79855682Smarkm{
799178825Sdfr    krb5_error_code ret;
800233294Sstas    krb5_ccache copy, newfile;
801233294Sstas    char *newname = NULL;
802233294Sstas    int fd;
803178825Sdfr
804233294Sstas    if (FCACHE(id) == NULL)
805233294Sstas        return krb5_einval(context, 2);
806233294Sstas
807233294Sstas    ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &copy);
808178825Sdfr    if (ret)
809178825Sdfr	return ret;
810178825Sdfr
811178825Sdfr    ret = krb5_cc_copy_cache(context, id, copy);
812178825Sdfr    if (ret) {
813178825Sdfr	krb5_cc_destroy(context, copy);
814178825Sdfr	return ret;
815178825Sdfr    }
816178825Sdfr
817178825Sdfr    ret = krb5_cc_remove_cred(context, copy, which, cred);
818178825Sdfr    if (ret) {
819178825Sdfr	krb5_cc_destroy(context, copy);
820178825Sdfr	return ret;
821178825Sdfr    }
822178825Sdfr
823233294Sstas    ret = asprintf(&newname, "FILE:%s.XXXXXX", FILENAME(id));
824233294Sstas    if (ret < 0 || newname == NULL) {
825233294Sstas	krb5_cc_destroy(context, copy);
826233294Sstas	return ENOMEM;
827233294Sstas    }
828178825Sdfr
829233294Sstas    fd = mkstemp(&newname[5]);
830233294Sstas    if (fd < 0) {
831233294Sstas	ret = errno;
832233294Sstas	krb5_cc_destroy(context, copy);
833233294Sstas	return ret;
834233294Sstas    }
835233294Sstas    close(fd);
836233294Sstas
837233294Sstas    ret = krb5_cc_resolve(context, newname, &newfile);
838233294Sstas    if (ret) {
839233294Sstas	unlink(&newname[5]);
840233294Sstas	free(newname);
841233294Sstas	krb5_cc_destroy(context, copy);
842233294Sstas	return ret;
843233294Sstas    }
844233294Sstas
845233294Sstas    ret = krb5_cc_copy_cache(context, copy, newfile);
846178825Sdfr    krb5_cc_destroy(context, copy);
847233294Sstas    if (ret) {
848233294Sstas	free(newname);
849233294Sstas	krb5_cc_destroy(context, newfile);
850233294Sstas	return ret;
851233294Sstas    }
852178825Sdfr
853233294Sstas    ret = rk_rename(&newname[5], FILENAME(id));
854233294Sstas    if (ret)
855233294Sstas	ret = errno;
856233294Sstas    free(newname);
857233294Sstas    krb5_cc_close(context, newfile);
858233294Sstas
859178825Sdfr    return ret;
86055682Smarkm}
86155682Smarkm
862233294Sstasstatic krb5_error_code KRB5_CALLCONV
86355682Smarkmfcc_set_flags(krb5_context context,
86455682Smarkm	      krb5_ccache id,
86555682Smarkm	      krb5_flags flags)
86655682Smarkm{
867233294Sstas    if (FCACHE(id) == NULL)
868233294Sstas        return krb5_einval(context, 2);
869233294Sstas
87055682Smarkm    return 0; /* XXX */
87155682Smarkm}
87255682Smarkm
873233294Sstasstatic int KRB5_CALLCONV
87455682Smarkmfcc_get_version(krb5_context context,
87555682Smarkm		krb5_ccache id)
87655682Smarkm{
877233294Sstas    if (FCACHE(id) == NULL)
878233294Sstas        return -1;
879233294Sstas
88055682Smarkm    return FCACHE(id)->version;
88155682Smarkm}
882233294Sstas
883178825Sdfrstruct fcache_iter {
884178825Sdfr    int first;
885178825Sdfr};
886178825Sdfr
887233294Sstasstatic krb5_error_code KRB5_CALLCONV
888178825Sdfrfcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
889178825Sdfr{
890178825Sdfr    struct fcache_iter *iter;
891178825Sdfr
892178825Sdfr    iter = calloc(1, sizeof(*iter));
893178825Sdfr    if (iter == NULL) {
894233294Sstas	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
895178825Sdfr	return ENOMEM;
896233294Sstas    }
897178825Sdfr    iter->first = 1;
898178825Sdfr    *cursor = iter;
899178825Sdfr    return 0;
900178825Sdfr}
901178825Sdfr
902233294Sstasstatic krb5_error_code KRB5_CALLCONV
903178825Sdfrfcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
904178825Sdfr{
905178825Sdfr    struct fcache_iter *iter = cursor;
906178825Sdfr    krb5_error_code ret;
907178825Sdfr    const char *fn;
908178825Sdfr    char *expandedfn = NULL;
909178825Sdfr
910233294Sstas    if (iter == NULL)
911233294Sstas        return krb5_einval(context, 2);
912233294Sstas
913178825Sdfr    if (!iter->first) {
914233294Sstas	krb5_clear_error_message(context);
915178825Sdfr	return KRB5_CC_END;
916178825Sdfr    }
917178825Sdfr    iter->first = 0;
918178825Sdfr
919178825Sdfr    fn = krb5_cc_default_name(context);
920233294Sstas    if (fn == NULL || strncasecmp(fn, "FILE:", 5) != 0) {
921233294Sstas	ret = _krb5_expand_default_cc_name(context,
922178825Sdfr					   KRB5_DEFAULT_CCNAME_FILE,
923178825Sdfr					   &expandedfn);
924178825Sdfr	if (ret)
925178825Sdfr	    return ret;
926233294Sstas	fn = expandedfn;
927178825Sdfr    }
928233294Sstas    /* check if file exists, don't return a non existant "next" */
929233294Sstas    if (strncasecmp(fn, "FILE:", 5) == 0) {
930233294Sstas	struct stat sb;
931233294Sstas	ret = stat(fn + 5, &sb);
932233294Sstas	if (ret) {
933233294Sstas	    ret = KRB5_CC_END;
934233294Sstas	    goto out;
935233294Sstas	}
936233294Sstas    }
937178825Sdfr    ret = krb5_cc_resolve(context, fn, id);
938233294Sstas out:
939178825Sdfr    if (expandedfn)
940178825Sdfr	free(expandedfn);
941233294Sstas
942178825Sdfr    return ret;
943178825Sdfr}
944178825Sdfr
945233294Sstasstatic krb5_error_code KRB5_CALLCONV
946178825Sdfrfcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
947178825Sdfr{
948178825Sdfr    struct fcache_iter *iter = cursor;
949233294Sstas
950233294Sstas    if (iter == NULL)
951233294Sstas        return krb5_einval(context, 2);
952233294Sstas
953178825Sdfr    free(iter);
954178825Sdfr    return 0;
955178825Sdfr}
956178825Sdfr
957233294Sstasstatic krb5_error_code KRB5_CALLCONV
958178825Sdfrfcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
959178825Sdfr{
960178825Sdfr    krb5_error_code ret = 0;
961178825Sdfr
962233294Sstas    ret = rk_rename(FILENAME(from), FILENAME(to));
963233294Sstas
964178825Sdfr    if (ret && errno != EXDEV) {
965233294Sstas	char buf[128];
966178825Sdfr	ret = errno;
967233294Sstas	rk_strerror_r(ret, buf, sizeof(buf));
968233294Sstas	krb5_set_error_message(context, ret,
969233294Sstas			       N_("Rename of file from %s "
970233294Sstas				  "to %s failed: %s", ""),
971233294Sstas			       FILENAME(from), FILENAME(to), buf);
972178825Sdfr	return ret;
973178825Sdfr    } else if (ret && errno == EXDEV) {
974178825Sdfr	/* make a copy and delete the orignal */
975178825Sdfr	krb5_ssize_t sz1, sz2;
976178825Sdfr	int fd1, fd2;
977178825Sdfr	char buf[BUFSIZ];
978178825Sdfr
979233294Sstas	ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
980178825Sdfr	if(ret)
981178825Sdfr	    return ret;
982178825Sdfr
983178825Sdfr	unlink(FILENAME(to));
984178825Sdfr
985233294Sstas	ret = fcc_open(context, to, &fd2,
986233294Sstas		       O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, 0600);
987178825Sdfr	if(ret)
988178825Sdfr	    goto out1;
989178825Sdfr
990178825Sdfr	while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
991178825Sdfr	    sz2 = write(fd2, buf, sz1);
992178825Sdfr	    if (sz1 != sz2) {
993178825Sdfr		ret = EIO;
994233294Sstas		krb5_set_error_message(context, ret,
995233294Sstas				       N_("Failed to write data from one file "
996233294Sstas					  "credential cache to the other", ""));
997178825Sdfr		goto out2;
998178825Sdfr	    }
999178825Sdfr	}
1000178825Sdfr	if (sz1 < 0) {
1001178825Sdfr	    ret = EIO;
1002233294Sstas	    krb5_set_error_message(context, ret,
1003233294Sstas				   N_("Failed to read data from one file "
1004233294Sstas				      "credential cache to the other", ""));
1005178825Sdfr	    goto out2;
1006178825Sdfr	}
1007178825Sdfr    out2:
1008178825Sdfr	fcc_unlock(context, fd2);
1009178825Sdfr	close(fd2);
1010178825Sdfr
1011178825Sdfr    out1:
1012178825Sdfr	fcc_unlock(context, fd1);
1013178825Sdfr	close(fd1);
1014178825Sdfr
1015233294Sstas	_krb5_erase_file(context, FILENAME(from));
1016233294Sstas
1017178825Sdfr	if (ret) {
1018233294Sstas	    _krb5_erase_file(context, FILENAME(to));
1019178825Sdfr	    return ret;
1020178825Sdfr	}
1021178825Sdfr    }
1022178825Sdfr
1023178825Sdfr    /* make sure ->version is uptodate */
1024178825Sdfr    {
1025178825Sdfr	krb5_storage *sp;
1026178825Sdfr	int fd;
1027233294Sstas	if ((ret = init_fcc (context, to, &sp, &fd, NULL)) == 0) {
1028233294Sstas	    if (sp)
1029233294Sstas		krb5_storage_free(sp);
1030233294Sstas	    fcc_unlock(context, fd);
1031233294Sstas	    close(fd);
1032233294Sstas	}
1033233294Sstas    }
1034233294Sstas
1035233294Sstas    fcc_close(context, from);
1036233294Sstas
1037178825Sdfr    return ret;
1038178825Sdfr}
1039178825Sdfr
1040233294Sstasstatic krb5_error_code KRB5_CALLCONV
1041233294Sstasfcc_get_default_name(krb5_context context, char **str)
1042178825Sdfr{
1043233294Sstas    return _krb5_expand_default_cc_name(context,
1044178825Sdfr					KRB5_DEFAULT_CCNAME_FILE,
1045178825Sdfr					str);
1046178825Sdfr}
1047178825Sdfr
1048233294Sstasstatic krb5_error_code KRB5_CALLCONV
1049233294Sstasfcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1050233294Sstas{
1051233294Sstas    krb5_error_code ret;
1052233294Sstas    struct stat sb;
1053233294Sstas    int fd;
1054233294Sstas
1055233294Sstas    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
1056233294Sstas    if(ret)
1057233294Sstas	return ret;
1058233294Sstas    ret = fstat(fd, &sb);
1059233294Sstas    close(fd);
1060233294Sstas    if (ret) {
1061233294Sstas	ret = errno;
1062233294Sstas	krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1063233294Sstas	return ret;
1064233294Sstas    }
1065233294Sstas    *mtime = sb.st_mtime;
1066233294Sstas    return 0;
1067233294Sstas}
1068233294Sstas
1069233294Sstasstatic krb5_error_code KRB5_CALLCONV
1070233294Sstasfcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1071233294Sstas{
1072233294Sstas    return 0;
1073233294Sstas}
1074233294Sstas
1075233294Sstasstatic krb5_error_code KRB5_CALLCONV
1076233294Sstasfcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1077233294Sstas{
1078233294Sstas    krb5_error_code ret;
1079233294Sstas    krb5_storage *sp = NULL;
1080233294Sstas    int fd;
1081233294Sstas    ret = init_fcc(context, id, &sp, &fd, kdc_offset);
1082233294Sstas    if (sp)
1083233294Sstas	krb5_storage_free(sp);
1084233294Sstas    fcc_unlock(context, fd);
1085233294Sstas    close(fd);
1086233294Sstas
1087233294Sstas    return ret;
1088233294Sstas}
1089233294Sstas
1090233294Sstas
1091178825Sdfr/**
1092178825Sdfr * Variable containing the FILE based credential cache implemention.
1093178825Sdfr *
1094178825Sdfr * @ingroup krb5_ccache
1095178825Sdfr */
1096178825Sdfr
1097233294SstasKRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1098233294Sstas    KRB5_CC_OPS_VERSION,
109955682Smarkm    "FILE",
110055682Smarkm    fcc_get_name,
110155682Smarkm    fcc_resolve,
110255682Smarkm    fcc_gen_new,
110355682Smarkm    fcc_initialize,
110455682Smarkm    fcc_destroy,
110555682Smarkm    fcc_close,
110655682Smarkm    fcc_store_cred,
110755682Smarkm    NULL, /* fcc_retrieve */
110855682Smarkm    fcc_get_principal,
110955682Smarkm    fcc_get_first,
111055682Smarkm    fcc_get_next,
111155682Smarkm    fcc_end_get,
111255682Smarkm    fcc_remove_cred,
111355682Smarkm    fcc_set_flags,
1114178825Sdfr    fcc_get_version,
1115178825Sdfr    fcc_get_cache_first,
1116178825Sdfr    fcc_get_cache_next,
1117178825Sdfr    fcc_end_cache_get,
1118178825Sdfr    fcc_move,
1119233294Sstas    fcc_get_default_name,
1120233294Sstas    NULL,
1121233294Sstas    fcc_lastchange,
1122233294Sstas    fcc_set_kdc_offset,
1123233294Sstas    fcc_get_kdc_offset
112455682Smarkm};
1125