155682Smarkm/*
2178825Sdfr * Copyright (c) 1997 - 2008 Kungliga Tekniska H�gskolan
355682Smarkm * (Royal Institute of Technology, Stockholm, Sweden).
455682Smarkm * All rights reserved.
555682Smarkm *
655682Smarkm * Redistribution and use in source and binary forms, with or without
755682Smarkm * modification, are permitted provided that the following conditions
855682Smarkm * are met:
955682Smarkm *
1055682Smarkm * 1. Redistributions of source code must retain the above copyright
1155682Smarkm *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
1355682Smarkm * 2. Redistributions in binary form must reproduce the above copyright
1455682Smarkm *    notice, this list of conditions and the following disclaimer in the
1555682Smarkm *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
1755682Smarkm * 3. Neither the name of the Institute nor the names of its contributors
1855682Smarkm *    may be used to endorse or promote products derived from this software
1955682Smarkm *    without specific prior written permission.
2055682Smarkm *
2155682Smarkm * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
2255682Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455682Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
2555682Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2655682Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2755682Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2855682Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2955682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3055682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3155682Smarkm * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
3455682Smarkm#include "krb5_locl.h"
3555682Smarkm
36178825SdfrRCSID("$Id: fcache.c 22522 2008-01-24 11:56:25Z lha $");
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
61102644Snectarstatic const char*
6255682Smarkmfcc_get_name(krb5_context context,
6355682Smarkm	     krb5_ccache id)
6455682Smarkm{
6555682Smarkm    return FILENAME(id);
6655682Smarkm}
6755682Smarkm
68127808Snectarint
69127808Snectar_krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
70127808Snectar	    const char *filename)
71127808Snectar{
72127808Snectar    int ret;
73127808Snectar#ifdef HAVE_FCNTL
74127808Snectar    struct flock l;
75127808Snectar
76127808Snectar    l.l_start = 0;
77127808Snectar    l.l_len = 0;
78127808Snectar    l.l_type = exclusive ? F_WRLCK : F_RDLCK;
79127808Snectar    l.l_whence = SEEK_SET;
80127808Snectar    ret = fcntl(fd, F_SETLKW, &l);
81127808Snectar#else
82127808Snectar    ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
83127808Snectar#endif
84127808Snectar    if(ret < 0)
85127808Snectar	ret = errno;
86127808Snectar    if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
87127808Snectar	ret = EAGAIN;
88127808Snectar
89127808Snectar    switch (ret) {
90127808Snectar    case 0:
91127808Snectar	break;
92127808Snectar    case EINVAL: /* filesystem doesn't support locking, let the user have it */
93127808Snectar	ret = 0;
94127808Snectar	break;
95127808Snectar    case EAGAIN:
96127808Snectar	krb5_set_error_string(context, "timed out locking cache file %s",
97127808Snectar			      filename);
98127808Snectar	break;
99127808Snectar    default:
100127808Snectar	krb5_set_error_string(context, "error locking cache file %s: %s",
101127808Snectar			      filename, strerror(ret));
102127808Snectar	break;
103127808Snectar    }
104127808Snectar    return ret;
105127808Snectar}
106127808Snectar
107127808Snectarint
108178825Sdfr_krb5_xunlock(krb5_context context, int fd)
109127808Snectar{
110178825Sdfr    int ret;
111178825Sdfr#ifdef HAVE_FCNTL
112127808Snectar    struct flock l;
113127808Snectar    l.l_start = 0;
114127808Snectar    l.l_len = 0;
115127808Snectar    l.l_type = F_UNLCK;
116127808Snectar    l.l_whence = SEEK_SET;
117178825Sdfr    ret = fcntl(fd, F_SETLKW, &l);
118127808Snectar#else
119178825Sdfr    ret = flock(fd, LOCK_UN);
120127808Snectar#endif
121178825Sdfr    if (ret < 0)
122178825Sdfr	ret = errno;
123178825Sdfr    switch (ret) {
124178825Sdfr    case 0:
125178825Sdfr	break;
126178825Sdfr    case EINVAL: /* filesystem doesn't support locking, let the user have it */
127178825Sdfr	ret = 0;
128178825Sdfr	break;
129178825Sdfr    default:
130178825Sdfr	krb5_set_error_string(context,
131178825Sdfr			      "Failed to unlock file: %s", strerror(ret));
132178825Sdfr	break;
133178825Sdfr    }
134178825Sdfr    return ret;
135127808Snectar}
136127808Snectar
13755682Smarkmstatic krb5_error_code
138127808Snectarfcc_lock(krb5_context context, krb5_ccache id,
139127808Snectar	 int fd, krb5_boolean exclusive)
140127808Snectar{
141127808Snectar    return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
142127808Snectar}
143127808Snectar
144127808Snectarstatic krb5_error_code
145127808Snectarfcc_unlock(krb5_context context, int fd)
146127808Snectar{
147178825Sdfr    return _krb5_xunlock(context, fd);
148127808Snectar}
149127808Snectar
150127808Snectarstatic krb5_error_code
15155682Smarkmfcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
15255682Smarkm{
15355682Smarkm    krb5_fcache *f;
15455682Smarkm    f = malloc(sizeof(*f));
15578527Sassar    if(f == NULL) {
15678527Sassar	krb5_set_error_string(context, "malloc: out of memory");
15755682Smarkm	return KRB5_CC_NOMEM;
15878527Sassar    }
15955682Smarkm    f->filename = strdup(res);
16055682Smarkm    if(f->filename == NULL){
16155682Smarkm	free(f);
16278527Sassar	krb5_set_error_string(context, "malloc: out of memory");
16355682Smarkm	return KRB5_CC_NOMEM;
16455682Smarkm    }
16555682Smarkm    f->version = 0;
16655682Smarkm    (*id)->data.data = f;
16755682Smarkm    (*id)->data.length = sizeof(*f);
16855682Smarkm    return 0;
16955682Smarkm}
17055682Smarkm
17172445Sassar/*
17272445Sassar * Try to scrub the contents of `filename' safely.
17372445Sassar */
17472445Sassar
17572445Sassarstatic int
17672445Sassarscrub_file (int fd)
17772445Sassar{
17872445Sassar    off_t pos;
17972445Sassar    char buf[128];
18072445Sassar
18172445Sassar    pos = lseek(fd, 0, SEEK_END);
18272445Sassar    if (pos < 0)
18372445Sassar        return errno;
18472445Sassar    if (lseek(fd, 0, SEEK_SET) < 0)
18572445Sassar        return errno;
18672445Sassar    memset(buf, 0, sizeof(buf));
18772445Sassar    while(pos > 0) {
18872445Sassar        ssize_t tmp = write(fd, buf, min(sizeof(buf), pos));
18972445Sassar
19072445Sassar	if (tmp < 0)
19172445Sassar	    return errno;
19272445Sassar	pos -= tmp;
19372445Sassar    }
19472445Sassar    fsync (fd);
19572445Sassar    return 0;
19672445Sassar}
19772445Sassar
19872445Sassar/*
19972445Sassar * Erase `filename' if it exists, trying to remove the contents if
20072445Sassar * it's `safe'.  We always try to remove the file, it it exists.  It's
20172445Sassar * only overwritten if it's a regular file (not a symlink and not a
20272445Sassar * hardlink)
20372445Sassar */
20472445Sassar
20555682Smarkmstatic krb5_error_code
20655682Smarkmerase_file(const char *filename)
20755682Smarkm{
20855682Smarkm    int fd;
20972445Sassar    struct stat sb1, sb2;
21072445Sassar    int ret;
21155682Smarkm
21272445Sassar    ret = lstat (filename, &sb1);
21372445Sassar    if (ret < 0)
21472445Sassar	return errno;
21572445Sassar
21655682Smarkm    fd = open(filename, O_RDWR | O_BINARY);
21772445Sassar    if(fd < 0) {
21855682Smarkm	if(errno == ENOENT)
21955682Smarkm	    return 0;
22055682Smarkm	else
22155682Smarkm	    return errno;
22255682Smarkm    }
22372445Sassar    if (unlink(filename) < 0) {
22472445Sassar        close (fd);
22572445Sassar        return errno;
22672445Sassar    }
22772445Sassar    ret = fstat (fd, &sb2);
22872445Sassar    if (ret < 0) {
22972445Sassar	close (fd);
23072445Sassar	return errno;
23172445Sassar    }
23272445Sassar
23372445Sassar    /* check if someone was playing with symlinks */
23472445Sassar
23572445Sassar    if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
23672445Sassar	close (fd);
23772445Sassar	return EPERM;
23872445Sassar    }
23972445Sassar
24072445Sassar    /* there are still hard links to this file */
24172445Sassar
24272445Sassar    if (sb2.st_nlink != 0) {
24372445Sassar        close (fd);
24472445Sassar        return 0;
24572445Sassar    }
24672445Sassar
24772445Sassar    ret = scrub_file (fd);
24872445Sassar    close (fd);
24972445Sassar    return ret;
25055682Smarkm}
25155682Smarkm
25255682Smarkmstatic krb5_error_code
25355682Smarkmfcc_gen_new(krb5_context context, krb5_ccache *id)
25455682Smarkm{
25555682Smarkm    krb5_fcache *f;
25655682Smarkm    int fd;
25755682Smarkm    char *file;
25878527Sassar
25955682Smarkm    f = malloc(sizeof(*f));
26078527Sassar    if(f == NULL) {
26178527Sassar	krb5_set_error_string(context, "malloc: out of memory");
26255682Smarkm	return KRB5_CC_NOMEM;
26378527Sassar    }
26472445Sassar    asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
26555682Smarkm    if(file == NULL) {
26655682Smarkm	free(f);
26778527Sassar	krb5_set_error_string(context, "malloc: out of memory");
26855682Smarkm	return KRB5_CC_NOMEM;
26955682Smarkm    }
27055682Smarkm    fd = mkstemp(file);
27155682Smarkm    if(fd < 0) {
272178825Sdfr	int ret = errno;
273178825Sdfr	krb5_set_error_string(context, "mkstemp %s", file);
27455682Smarkm	free(f);
27555682Smarkm	free(file);
276178825Sdfr	return ret;
27755682Smarkm    }
27855682Smarkm    close(fd);
27955682Smarkm    f->filename = file;
28055682Smarkm    f->version = 0;
28155682Smarkm    (*id)->data.data = f;
28255682Smarkm    (*id)->data.length = sizeof(*f);
28355682Smarkm    return 0;
28455682Smarkm}
28555682Smarkm
28655682Smarkmstatic void
28755682Smarkmstorage_set_flags(krb5_context context, krb5_storage *sp, int vno)
28855682Smarkm{
28955682Smarkm    int flags = 0;
29055682Smarkm    switch(vno) {
29155682Smarkm    case KRB5_FCC_FVNO_1:
29255682Smarkm	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
29355682Smarkm	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
29455682Smarkm	flags |= KRB5_STORAGE_HOST_BYTEORDER;
29555682Smarkm	break;
29655682Smarkm    case KRB5_FCC_FVNO_2:
29755682Smarkm	flags |= KRB5_STORAGE_HOST_BYTEORDER;
29855682Smarkm	break;
29955682Smarkm    case KRB5_FCC_FVNO_3:
30055682Smarkm	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
30155682Smarkm	break;
30255682Smarkm    case KRB5_FCC_FVNO_4:
30355682Smarkm	break;
30455682Smarkm    default:
30555682Smarkm	krb5_abortx(context,
30655682Smarkm		    "storage_set_flags called with bad vno (%x)", vno);
30755682Smarkm    }
30855682Smarkm    krb5_storage_set_flags(sp, flags);
30955682Smarkm}
31055682Smarkm
31155682Smarkmstatic krb5_error_code
312127808Snectarfcc_open(krb5_context context,
313127808Snectar	 krb5_ccache id,
314127808Snectar	 int *fd_ret,
315127808Snectar	 int flags,
316127808Snectar	 mode_t mode)
317127808Snectar{
318127808Snectar    krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
319127808Snectar			      (flags | O_RDWR) == flags);
320127808Snectar    krb5_error_code ret;
321127808Snectar    const char *filename = FILENAME(id);
322127808Snectar    int fd;
323127808Snectar    fd = open(filename, flags, mode);
324127808Snectar    if(fd < 0) {
325127808Snectar	ret = errno;
326127808Snectar	krb5_set_error_string(context, "open(%s): %s", filename,
327127808Snectar			      strerror(ret));
328127808Snectar	return ret;
329127808Snectar    }
330127808Snectar
331127808Snectar    if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
332127808Snectar	close(fd);
333127808Snectar	return ret;
334127808Snectar    }
335127808Snectar    *fd_ret = fd;
336127808Snectar    return 0;
337127808Snectar}
338127808Snectar
339127808Snectarstatic krb5_error_code
34055682Smarkmfcc_initialize(krb5_context context,
34155682Smarkm	       krb5_ccache id,
34255682Smarkm	       krb5_principal primary_principal)
34355682Smarkm{
34455682Smarkm    krb5_fcache *f = FCACHE(id);
34572445Sassar    int ret = 0;
34655682Smarkm    int fd;
34755682Smarkm    char *filename = f->filename;
34855682Smarkm
34972445Sassar    unlink (filename);
35055682Smarkm
351127808Snectar    ret = fcc_open(context, id, &fd, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
352127808Snectar    if(ret)
35378527Sassar	return ret;
35455682Smarkm    {
35555682Smarkm	krb5_storage *sp;
35655682Smarkm	sp = krb5_storage_from_fd(fd);
357102644Snectar	krb5_storage_set_eof_code(sp, KRB5_CC_END);
35855682Smarkm	if(context->fcache_vno != 0)
35955682Smarkm	    f->version = context->fcache_vno;
36055682Smarkm	else
36155682Smarkm	    f->version = KRB5_FCC_FVNO_4;
36272445Sassar	ret |= krb5_store_int8(sp, 5);
36372445Sassar	ret |= krb5_store_int8(sp, f->version);
36455682Smarkm	storage_set_flags(context, sp, f->version);
36572445Sassar	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
36655682Smarkm	    /* V4 stuff */
36755682Smarkm	    if (context->kdc_sec_offset) {
36872445Sassar		ret |= krb5_store_int16 (sp, 12); /* length */
36972445Sassar		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
37072445Sassar		ret |= krb5_store_int16 (sp, 8); /* length of data */
37172445Sassar		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
37272445Sassar		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
37355682Smarkm	    } else {
37472445Sassar		ret |= krb5_store_int16 (sp, 0);
37555682Smarkm	    }
37655682Smarkm	}
37772445Sassar	ret |= krb5_store_principal(sp, primary_principal);
378127808Snectar
37955682Smarkm	krb5_storage_free(sp);
38055682Smarkm    }
381127808Snectar    fcc_unlock(context, fd);
382127808Snectar    if (close(fd) < 0)
38378527Sassar	if (ret == 0) {
38472445Sassar	    ret = errno;
385127808Snectar	    krb5_set_error_string (context, "close %s: %s",
386127808Snectar				   FILENAME(id), strerror(ret));
38778527Sassar	}
38872445Sassar    return ret;
38955682Smarkm}
39055682Smarkm
39155682Smarkmstatic krb5_error_code
39255682Smarkmfcc_close(krb5_context context,
39355682Smarkm	  krb5_ccache id)
39455682Smarkm{
39555682Smarkm    free (FILENAME(id));
39655682Smarkm    krb5_data_free(&id->data);
39755682Smarkm    return 0;
39855682Smarkm}
39955682Smarkm
40055682Smarkmstatic krb5_error_code
40155682Smarkmfcc_destroy(krb5_context context,
40255682Smarkm	    krb5_ccache id)
40355682Smarkm{
404127808Snectar    erase_file(FILENAME(id));
40555682Smarkm    return 0;
40655682Smarkm}
40755682Smarkm
40855682Smarkmstatic krb5_error_code
40955682Smarkmfcc_store_cred(krb5_context context,
41055682Smarkm	       krb5_ccache id,
41155682Smarkm	       krb5_creds *creds)
41255682Smarkm{
41372445Sassar    int ret;
41455682Smarkm    int fd;
41555682Smarkm
416127808Snectar    ret = fcc_open(context, id, &fd, O_WRONLY | O_APPEND | O_BINARY, 0);
417127808Snectar    if(ret)
41878527Sassar	return ret;
41955682Smarkm    {
42055682Smarkm	krb5_storage *sp;
42155682Smarkm	sp = krb5_storage_from_fd(fd);
422102644Snectar	krb5_storage_set_eof_code(sp, KRB5_CC_END);
42355682Smarkm	storage_set_flags(context, sp, FCACHE(id)->version);
424178825Sdfr	if (!krb5_config_get_bool_default(context, NULL, TRUE,
425178825Sdfr					  "libdefaults",
426178825Sdfr					  "fcc-mit-ticketflags",
427178825Sdfr					  NULL))
428178825Sdfr	    krb5_storage_set_flags(sp, KRB5_STORAGE_CREDS_FLAGS_WRONG_BITORDER);
429178825Sdfr	ret = krb5_store_creds(sp, creds);
43055682Smarkm	krb5_storage_free(sp);
43155682Smarkm    }
432127808Snectar    fcc_unlock(context, fd);
43372445Sassar    if (close(fd) < 0)
43478527Sassar	if (ret == 0) {
43572445Sassar	    ret = errno;
436127808Snectar	    krb5_set_error_string (context, "close %s: %s",
437127808Snectar				   FILENAME(id), strerror(ret));
43878527Sassar	}
43972445Sassar    return ret;
44055682Smarkm}
44155682Smarkm
44255682Smarkmstatic krb5_error_code
44355682Smarkminit_fcc (krb5_context context,
444127808Snectar	  krb5_ccache id,
44555682Smarkm	  krb5_storage **ret_sp,
44655682Smarkm	  int *ret_fd)
44755682Smarkm{
44855682Smarkm    int fd;
44955682Smarkm    int8_t pvno, tag;
45055682Smarkm    krb5_storage *sp;
45172445Sassar    krb5_error_code ret;
45255682Smarkm
453127808Snectar    ret = fcc_open(context, id, &fd, O_RDONLY | O_BINARY, 0);
454127808Snectar    if(ret)
45578527Sassar	return ret;
456127808Snectar
457127808Snectar    sp = krb5_storage_from_fd(fd);
458127808Snectar    if(sp == NULL) {
459178825Sdfr	krb5_clear_error_string(context);
460127808Snectar	ret = ENOMEM;
461127808Snectar	goto out;
46278527Sassar    }
463102644Snectar    krb5_storage_set_eof_code(sp, KRB5_CC_END);
46472445Sassar    ret = krb5_ret_int8(sp, &pvno);
465127808Snectar    if(ret != 0) {
466178825Sdfr	if(ret == KRB5_CC_END) {
467178825Sdfr	    krb5_set_error_string(context, "Empty credential cache file: %s",
468178825Sdfr				  FILENAME(id));
469178825Sdfr	    ret = ENOENT;
470178825Sdfr	} else
471178825Sdfr	    krb5_set_error_string(context, "Error reading pvno in "
472178825Sdfr				  "cache file: %s", FILENAME(id));
473127808Snectar	goto out;
474127808Snectar    }
47555682Smarkm    if(pvno != 5) {
476178825Sdfr	krb5_set_error_string(context, "Bad version number in credential "
477178825Sdfr			      "cache file: %s", FILENAME(id));
478127808Snectar	ret = KRB5_CCACHE_BADVNO;
479127808Snectar	goto out;
48055682Smarkm    }
481127808Snectar    ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
482127808Snectar    if(ret != 0) {
483178825Sdfr	krb5_set_error_string(context, "Error reading tag in "
484178825Sdfr			      "cache file: %s", FILENAME(id));
485127808Snectar	ret = KRB5_CC_FORMAT;
486127808Snectar	goto out;
487127808Snectar    }
488127808Snectar    FCACHE(id)->version = tag;
489127808Snectar    storage_set_flags(context, sp, FCACHE(id)->version);
49055682Smarkm    switch (tag) {
49155682Smarkm    case KRB5_FCC_FVNO_4: {
49255682Smarkm	int16_t length;
49355682Smarkm
494127808Snectar	ret = krb5_ret_int16 (sp, &length);
495127808Snectar	if(ret) {
496127808Snectar	    ret = KRB5_CC_FORMAT;
497178825Sdfr	    krb5_set_error_string(context, "Error reading tag length in "
498178825Sdfr			      "cache file: %s", FILENAME(id));
499127808Snectar	    goto out;
500127808Snectar	}
50155682Smarkm	while(length > 0) {
502178825Sdfr	    int16_t dtag, data_len;
50355682Smarkm	    int i;
50455682Smarkm	    int8_t dummy;
50555682Smarkm
506178825Sdfr	    ret = krb5_ret_int16 (sp, &dtag);
507127808Snectar	    if(ret) {
508178825Sdfr		krb5_set_error_string(context, "Error reading dtag in "
509178825Sdfr				      "cache file: %s", FILENAME(id));
510127808Snectar		ret = KRB5_CC_FORMAT;
511127808Snectar		goto out;
512127808Snectar	    }
513127808Snectar	    ret = krb5_ret_int16 (sp, &data_len);
514127808Snectar	    if(ret) {
515178825Sdfr		krb5_set_error_string(context, "Error reading dlength in "
516178825Sdfr				      "cache file: %s", FILENAME(id));
517127808Snectar		ret = KRB5_CC_FORMAT;
518127808Snectar		goto out;
519127808Snectar	    }
520178825Sdfr	    switch (dtag) {
52155682Smarkm	    case FCC_TAG_DELTATIME :
522127808Snectar		ret = krb5_ret_int32 (sp, &context->kdc_sec_offset);
523127808Snectar		if(ret) {
524178825Sdfr		    krb5_set_error_string(context, "Error reading kdc_sec in "
525178825Sdfr					  "cache file: %s", FILENAME(id));
526127808Snectar		    ret = KRB5_CC_FORMAT;
527127808Snectar		    goto out;
528127808Snectar		}
529127808Snectar		ret = krb5_ret_int32 (sp, &context->kdc_usec_offset);
530127808Snectar		if(ret) {
531178825Sdfr		    krb5_set_error_string(context, "Error reading kdc_usec in "
532178825Sdfr					  "cache file: %s", FILENAME(id));
533127808Snectar		    ret = KRB5_CC_FORMAT;
534127808Snectar		    goto out;
535127808Snectar		}
53655682Smarkm		break;
53755682Smarkm	    default :
538127808Snectar		for (i = 0; i < data_len; ++i) {
539127808Snectar		    ret = krb5_ret_int8 (sp, &dummy);
540127808Snectar		    if(ret) {
541178825Sdfr			krb5_set_error_string(context, "Error reading unknown "
542178825Sdfr					      "tag in cache file: %s",
543178825Sdfr					      FILENAME(id));
544127808Snectar			ret = KRB5_CC_FORMAT;
545127808Snectar			goto out;
546127808Snectar		    }
547127808Snectar		}
54855682Smarkm		break;
54955682Smarkm	    }
55055682Smarkm	    length -= 4 + data_len;
55155682Smarkm	}
55255682Smarkm	break;
55355682Smarkm    }
55455682Smarkm    case KRB5_FCC_FVNO_3:
55555682Smarkm    case KRB5_FCC_FVNO_2:
55655682Smarkm    case KRB5_FCC_FVNO_1:
55755682Smarkm	break;
55855682Smarkm    default :
559127808Snectar	ret = KRB5_CCACHE_BADVNO;
560178825Sdfr	krb5_set_error_string(context, "Unknown version number (%d) in "
561178825Sdfr			      "credential cache file: %s",
562178825Sdfr			      (int)tag, FILENAME(id));
563127808Snectar	goto out;
56455682Smarkm    }
56555682Smarkm    *ret_sp = sp;
56655682Smarkm    *ret_fd = fd;
567127808Snectar
56855682Smarkm    return 0;
569127808Snectar  out:
570127808Snectar    if(sp != NULL)
571127808Snectar	krb5_storage_free(sp);
572127808Snectar    fcc_unlock(context, fd);
573127808Snectar    close(fd);
574127808Snectar    return ret;
57555682Smarkm}
57655682Smarkm
57755682Smarkmstatic krb5_error_code
57855682Smarkmfcc_get_principal(krb5_context context,
57955682Smarkm		  krb5_ccache id,
58055682Smarkm		  krb5_principal *principal)
58155682Smarkm{
58255682Smarkm    krb5_error_code ret;
58355682Smarkm    int fd;
58455682Smarkm    krb5_storage *sp;
58555682Smarkm
586127808Snectar    ret = init_fcc (context, id, &sp, &fd);
58755682Smarkm    if (ret)
58855682Smarkm	return ret;
58972445Sassar    ret = krb5_ret_principal(sp, principal);
590178825Sdfr    if (ret)
591178825Sdfr	krb5_clear_error_string(context);
59255682Smarkm    krb5_storage_free(sp);
593127808Snectar    fcc_unlock(context, fd);
59455682Smarkm    close(fd);
59572445Sassar    return ret;
59655682Smarkm}
59755682Smarkm
59855682Smarkmstatic krb5_error_code
599127808Snectarfcc_end_get (krb5_context context,
600127808Snectar	     krb5_ccache id,
601127808Snectar	     krb5_cc_cursor *cursor);
602127808Snectar
603127808Snectarstatic krb5_error_code
60455682Smarkmfcc_get_first (krb5_context context,
60555682Smarkm	       krb5_ccache id,
60655682Smarkm	       krb5_cc_cursor *cursor)
60755682Smarkm{
60855682Smarkm    krb5_error_code ret;
60955682Smarkm    krb5_principal principal;
61055682Smarkm
61155682Smarkm    *cursor = malloc(sizeof(struct fcc_cursor));
612178825Sdfr    if (*cursor == NULL) {
613178825Sdfr        krb5_set_error_string (context, "malloc: out of memory");
614178825Sdfr	return ENOMEM;
615178825Sdfr    }
616178825Sdfr    memset(*cursor, 0, sizeof(struct fcc_cursor));
61755682Smarkm
618127808Snectar    ret = init_fcc (context, id, &FCC_CURSOR(*cursor)->sp,
61955682Smarkm		    &FCC_CURSOR(*cursor)->fd);
620127808Snectar    if (ret) {
621127808Snectar	free(*cursor);
622178825Sdfr	*cursor = NULL;
62355682Smarkm	return ret;
624127808Snectar    }
625127808Snectar    ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
626127808Snectar    if(ret) {
627178825Sdfr	krb5_clear_error_string(context);
628127808Snectar	fcc_end_get(context, id, cursor);
629127808Snectar	return ret;
630127808Snectar    }
63155682Smarkm    krb5_free_principal (context, principal);
632127808Snectar    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
63355682Smarkm    return 0;
63455682Smarkm}
63555682Smarkm
63655682Smarkmstatic krb5_error_code
63755682Smarkmfcc_get_next (krb5_context context,
63855682Smarkm	      krb5_ccache id,
63955682Smarkm	      krb5_cc_cursor *cursor,
64055682Smarkm	      krb5_creds *creds)
64155682Smarkm{
642127808Snectar    krb5_error_code ret;
643127808Snectar    if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
644127808Snectar	return ret;
645127808Snectar
646127808Snectar    ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
647178825Sdfr    if (ret)
648178825Sdfr	krb5_clear_error_string(context);
649127808Snectar
650127808Snectar    fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
651127808Snectar    return ret;
65255682Smarkm}
65355682Smarkm
65455682Smarkmstatic krb5_error_code
65555682Smarkmfcc_end_get (krb5_context context,
65655682Smarkm	     krb5_ccache id,
65755682Smarkm	     krb5_cc_cursor *cursor)
65855682Smarkm{
65955682Smarkm    krb5_storage_free(FCC_CURSOR(*cursor)->sp);
66055682Smarkm    close (FCC_CURSOR(*cursor)->fd);
66155682Smarkm    free(*cursor);
662127808Snectar    *cursor = NULL;
66355682Smarkm    return 0;
66455682Smarkm}
66555682Smarkm
66655682Smarkmstatic krb5_error_code
66755682Smarkmfcc_remove_cred(krb5_context context,
66855682Smarkm		 krb5_ccache id,
66955682Smarkm		 krb5_flags which,
67055682Smarkm		 krb5_creds *cred)
67155682Smarkm{
672178825Sdfr    krb5_error_code ret;
673178825Sdfr    krb5_ccache copy;
674178825Sdfr
675178825Sdfr    ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &copy);
676178825Sdfr    if (ret)
677178825Sdfr	return ret;
678178825Sdfr
679178825Sdfr    ret = krb5_cc_copy_cache(context, id, copy);
680178825Sdfr    if (ret) {
681178825Sdfr	krb5_cc_destroy(context, copy);
682178825Sdfr	return ret;
683178825Sdfr    }
684178825Sdfr
685178825Sdfr    ret = krb5_cc_remove_cred(context, copy, which, cred);
686178825Sdfr    if (ret) {
687178825Sdfr	krb5_cc_destroy(context, copy);
688178825Sdfr	return ret;
689178825Sdfr    }
690178825Sdfr
691178825Sdfr    fcc_destroy(context, id);
692178825Sdfr
693178825Sdfr    ret = krb5_cc_copy_cache(context, copy, id);
694178825Sdfr    krb5_cc_destroy(context, copy);
695178825Sdfr
696178825Sdfr    return ret;
69755682Smarkm}
69855682Smarkm
69955682Smarkmstatic krb5_error_code
70055682Smarkmfcc_set_flags(krb5_context context,
70155682Smarkm	      krb5_ccache id,
70255682Smarkm	      krb5_flags flags)
70355682Smarkm{
70455682Smarkm    return 0; /* XXX */
70555682Smarkm}
70655682Smarkm
70755682Smarkmstatic krb5_error_code
70855682Smarkmfcc_get_version(krb5_context context,
70955682Smarkm		krb5_ccache id)
71055682Smarkm{
71155682Smarkm    return FCACHE(id)->version;
71255682Smarkm}
71355682Smarkm
714178825Sdfrstruct fcache_iter {
715178825Sdfr    int first;
716178825Sdfr};
717178825Sdfr
718178825Sdfrstatic krb5_error_code
719178825Sdfrfcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
720178825Sdfr{
721178825Sdfr    struct fcache_iter *iter;
722178825Sdfr
723178825Sdfr    iter = calloc(1, sizeof(*iter));
724178825Sdfr    if (iter == NULL) {
725178825Sdfr	krb5_set_error_string(context, "malloc - out of memory");
726178825Sdfr	return ENOMEM;
727178825Sdfr    }
728178825Sdfr    iter->first = 1;
729178825Sdfr    *cursor = iter;
730178825Sdfr    return 0;
731178825Sdfr}
732178825Sdfr
733178825Sdfrstatic krb5_error_code
734178825Sdfrfcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
735178825Sdfr{
736178825Sdfr    struct fcache_iter *iter = cursor;
737178825Sdfr    krb5_error_code ret;
738178825Sdfr    const char *fn;
739178825Sdfr    char *expandedfn = NULL;
740178825Sdfr
741178825Sdfr    if (!iter->first) {
742178825Sdfr	krb5_clear_error_string(context);
743178825Sdfr	return KRB5_CC_END;
744178825Sdfr    }
745178825Sdfr    iter->first = 0;
746178825Sdfr
747178825Sdfr    fn = krb5_cc_default_name(context);
748178825Sdfr    if (strncasecmp(fn, "FILE:", 5) != 0) {
749178825Sdfr	ret = _krb5_expand_default_cc_name(context,
750178825Sdfr					   KRB5_DEFAULT_CCNAME_FILE,
751178825Sdfr					   &expandedfn);
752178825Sdfr	if (ret)
753178825Sdfr	    return ret;
754178825Sdfr    }
755178825Sdfr    ret = krb5_cc_resolve(context, fn, id);
756178825Sdfr    if (expandedfn)
757178825Sdfr	free(expandedfn);
758178825Sdfr
759178825Sdfr    return ret;
760178825Sdfr}
761178825Sdfr
762178825Sdfrstatic krb5_error_code
763178825Sdfrfcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
764178825Sdfr{
765178825Sdfr    struct fcache_iter *iter = cursor;
766178825Sdfr    free(iter);
767178825Sdfr    return 0;
768178825Sdfr}
769178825Sdfr
770178825Sdfrstatic krb5_error_code
771178825Sdfrfcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
772178825Sdfr{
773178825Sdfr    krb5_error_code ret = 0;
774178825Sdfr
775178825Sdfr    ret = rename(FILENAME(from), FILENAME(to));
776178825Sdfr    if (ret && errno != EXDEV) {
777178825Sdfr	ret = errno;
778178825Sdfr	krb5_set_error_string(context,
779178825Sdfr			      "Rename of file from %s to %s failed: %s",
780178825Sdfr			      FILENAME(from), FILENAME(to),
781178825Sdfr			      strerror(ret));
782178825Sdfr	return ret;
783178825Sdfr    } else if (ret && errno == EXDEV) {
784178825Sdfr	/* make a copy and delete the orignal */
785178825Sdfr	krb5_ssize_t sz1, sz2;
786178825Sdfr	int fd1, fd2;
787178825Sdfr	char buf[BUFSIZ];
788178825Sdfr
789178825Sdfr	ret = fcc_open(context, from, &fd1, O_RDONLY | O_BINARY, 0);
790178825Sdfr	if(ret)
791178825Sdfr	    return ret;
792178825Sdfr
793178825Sdfr	unlink(FILENAME(to));
794178825Sdfr
795178825Sdfr	ret = fcc_open(context, to, &fd2,
796178825Sdfr		       O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600);
797178825Sdfr	if(ret)
798178825Sdfr	    goto out1;
799178825Sdfr
800178825Sdfr	while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
801178825Sdfr	    sz2 = write(fd2, buf, sz1);
802178825Sdfr	    if (sz1 != sz2) {
803178825Sdfr		ret = EIO;
804178825Sdfr		krb5_set_error_string(context,
805178825Sdfr				      "Failed to write data from one file "
806178825Sdfr				      "credential cache to the other");
807178825Sdfr		goto out2;
808178825Sdfr	    }
809178825Sdfr	}
810178825Sdfr	if (sz1 < 0) {
811178825Sdfr	    ret = EIO;
812178825Sdfr	    krb5_set_error_string(context,
813178825Sdfr				  "Failed to read data from one file "
814178825Sdfr				  "credential cache to the other");
815178825Sdfr	    goto out2;
816178825Sdfr	}
817178825Sdfr	erase_file(FILENAME(from));
818178825Sdfr
819178825Sdfr    out2:
820178825Sdfr	fcc_unlock(context, fd2);
821178825Sdfr	close(fd2);
822178825Sdfr
823178825Sdfr    out1:
824178825Sdfr	fcc_unlock(context, fd1);
825178825Sdfr	close(fd1);
826178825Sdfr
827178825Sdfr	if (ret) {
828178825Sdfr	    erase_file(FILENAME(to));
829178825Sdfr	    return ret;
830178825Sdfr	}
831178825Sdfr    }
832178825Sdfr
833178825Sdfr    /* make sure ->version is uptodate */
834178825Sdfr    {
835178825Sdfr	krb5_storage *sp;
836178825Sdfr	int fd;
837178825Sdfr	ret = init_fcc (context, to, &sp, &fd);
838178825Sdfr	krb5_storage_free(sp);
839178825Sdfr	fcc_unlock(context, fd);
840178825Sdfr	close(fd);
841178825Sdfr    }
842178825Sdfr    return ret;
843178825Sdfr}
844178825Sdfr
845178825Sdfrstatic krb5_error_code
846178825Sdfrfcc_default_name(krb5_context context, char **str)
847178825Sdfr{
848178825Sdfr    return _krb5_expand_default_cc_name(context,
849178825Sdfr					KRB5_DEFAULT_CCNAME_FILE,
850178825Sdfr					str);
851178825Sdfr}
852178825Sdfr
853178825Sdfr/**
854178825Sdfr * Variable containing the FILE based credential cache implemention.
855178825Sdfr *
856178825Sdfr * @ingroup krb5_ccache
857178825Sdfr */
858178825Sdfr
85955682Smarkmconst krb5_cc_ops krb5_fcc_ops = {
86055682Smarkm    "FILE",
86155682Smarkm    fcc_get_name,
86255682Smarkm    fcc_resolve,
86355682Smarkm    fcc_gen_new,
86455682Smarkm    fcc_initialize,
86555682Smarkm    fcc_destroy,
86655682Smarkm    fcc_close,
86755682Smarkm    fcc_store_cred,
86855682Smarkm    NULL, /* fcc_retrieve */
86955682Smarkm    fcc_get_principal,
87055682Smarkm    fcc_get_first,
87155682Smarkm    fcc_get_next,
87255682Smarkm    fcc_end_get,
87355682Smarkm    fcc_remove_cred,
87455682Smarkm    fcc_set_flags,
875178825Sdfr    fcc_get_version,
876178825Sdfr    fcc_get_cache_first,
877178825Sdfr    fcc_get_cache_next,
878178825Sdfr    fcc_end_cache_get,
879178825Sdfr    fcc_move,
880178825Sdfr    fcc_default_name
88155682Smarkm};
882