LCOV - code coverage report
Current view: top level - third_party/heimdal/lib/krb5 - fcache.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 437 929 47.0 %
Date: 2024-04-21 15:09:00 Functions: 29 40 72.5 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 1997 - 2017 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             : 
      38             : typedef struct krb5_fcache{
      39             :     char *filename;
      40             :     char *res;
      41             :     char *sub;
      42             :     char *tmpfn;
      43             :     int version;
      44             : }krb5_fcache;
      45             : 
      46             : struct fcc_cursor {
      47             :     int fd;
      48             :     off_t cred_start;
      49             :     off_t cred_end;
      50             :     krb5_storage *sp;
      51             : };
      52             : 
      53             : #define KRB5_FCC_FVNO_1 1
      54             : #define KRB5_FCC_FVNO_2 2
      55             : #define KRB5_FCC_FVNO_3 3
      56             : #define KRB5_FCC_FVNO_4 4
      57             : 
      58             : #define FCC_TAG_DELTATIME 1
      59             : 
      60             : #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
      61             : 
      62             : #define FILENAME(X) (FCACHE(X)->filename)
      63             : #define TMPFILENAME(X) (FCACHE(X)->tmpfn)
      64             : #define RESFILENAME(X) (FCACHE(X)->res)
      65             : #define SUBFILENAME(X) (FCACHE(X)->sub)
      66             : 
      67             : #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
      68             : 
      69             : static krb5_error_code KRB5_CALLCONV
      70        1918 : fcc_get_name_2(krb5_context context,
      71             :                krb5_ccache id,
      72             :                const char **name,
      73             :                const char **colname,
      74             :                const char **sub)
      75             : {
      76        1918 :     if (FCACHE(id) == NULL)
      77           0 :         return KRB5_CC_NOTFOUND;
      78             : 
      79        1918 :     if (name)
      80        1918 :         *name = FILENAME(id);
      81        1918 :     if (colname)
      82           0 :         *colname = FILENAME(id);
      83        1918 :     if (sub)
      84           0 :         *sub = NULL;
      85        1918 :     return 0;
      86             : }
      87             : 
      88             : KRB5_LIB_FUNCTION int KRB5_LIB_CALL
      89       59847 : _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
      90             :             const char *filename)
      91             : {
      92        1379 :     int ret;
      93             : #ifdef HAVE_FCNTL
      94             :     struct flock l;
      95             : 
      96             :     l.l_start = 0;
      97             :     l.l_len = 0;
      98             :     l.l_type = exclusive ? F_WRLCK : F_RDLCK;
      99             :     l.l_whence = SEEK_SET;
     100             :     ret = fcntl(fd, F_SETLKW, &l);
     101             : #else
     102       60968 :     ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
     103             : #endif
     104       59847 :     if(ret < 0)
     105           0 :         ret = errno;
     106       59847 :     if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
     107           0 :         ret = EAGAIN;
     108             : 
     109       59847 :     switch (ret) {
     110       58468 :     case 0:
     111       58468 :         break;
     112           0 :     case EINVAL: /* filesystem doesn't support locking, let the user have it */
     113           0 :         ret = 0;
     114           0 :         break;
     115           0 :     case EAGAIN:
     116           0 :         krb5_set_error_message(context, ret,
     117           0 :                                N_("timed out locking cache file %s", "file"),
     118             :                                filename);
     119           0 :         break;
     120           0 :     default: {
     121           0 :         char buf[128];
     122           0 :         rk_strerror_r(ret, buf, sizeof(buf));
     123           0 :         krb5_set_error_message(context, ret,
     124           0 :                                N_("error locking cache file %s: %s",
     125             :                                   "file, error"), filename, buf);
     126           0 :         break;
     127             :     }
     128             :     }
     129       59847 :     return ret;
     130             : }
     131             : 
     132             : KRB5_LIB_FUNCTION int KRB5_LIB_CALL
     133           0 : _krb5_xunlock(krb5_context context, int fd)
     134             : {
     135           0 :     int ret;
     136             : #ifdef HAVE_FCNTL
     137             :     struct flock l;
     138             :     l.l_start = 0;
     139             :     l.l_len = 0;
     140             :     l.l_type = F_UNLCK;
     141             :     l.l_whence = SEEK_SET;
     142             :     ret = fcntl(fd, F_SETLKW, &l);
     143             : #else
     144           0 :     ret = flock(fd, LOCK_UN);
     145             : #endif
     146           0 :     if (ret < 0)
     147           0 :         ret = errno;
     148           0 :     switch (ret) {
     149           0 :     case 0:
     150           0 :         break;
     151           0 :     case EINVAL: /* filesystem doesn't support locking, let the user have it */
     152           0 :         ret = 0;
     153           0 :         break;
     154           0 :     default: {
     155           0 :         char buf[128];
     156           0 :         rk_strerror_r(ret, buf, sizeof(buf));
     157           0 :         krb5_set_error_message(context, ret,
     158           0 :                                N_("Failed to unlock file: %s", ""), buf);
     159           0 :         break;
     160             :     }
     161             :     }
     162           0 :     return ret;
     163             : }
     164             : 
     165             : static krb5_error_code
     166        1571 : write_storage(krb5_context context, krb5_storage *sp, int fd)
     167             : {
     168           0 :     krb5_error_code ret;
     169           0 :     krb5_data data;
     170           0 :     ssize_t sret;
     171             : 
     172        1571 :     ret = krb5_storage_to_data(sp, &data);
     173        1571 :     if (ret) {
     174           0 :         krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
     175           0 :         return ret;
     176             :     }
     177        1571 :     sret = write(fd, data.data, data.length);
     178        1571 :     ret = (sret != (ssize_t)data.length);
     179        1571 :     krb5_data_free(&data);
     180        1571 :     if (ret) {
     181           0 :         ret = errno;
     182           0 :         krb5_set_error_message(context, ret,
     183           0 :                                N_("Failed to write FILE credential data", ""));
     184           0 :         return ret;
     185             :     }
     186        1571 :     return 0;
     187             : }
     188             : 
     189             : 
     190             : static krb5_error_code KRB5_CALLCONV
     191        8421 : fcc_lock(krb5_context context, krb5_ccache id,
     192             :          int fd, krb5_boolean exclusive)
     193             : {
     194           0 :     krb5_error_code ret;
     195           0 :     const char *name;
     196             : 
     197        8421 :     if (exclusive == FALSE)
     198        6850 :         return 0;
     199        1571 :     ret = fcc_get_name_2(context, id, &name, NULL, NULL);
     200        1571 :     if (ret == 0)
     201        1571 :         ret = _krb5_xlock(context, fd, exclusive, name);
     202        1571 :     return ret;
     203             : }
     204             : 
     205             : static krb5_error_code KRB5_CALLCONV
     206             : fcc_get_default_name(krb5_context, char **);
     207             : 
     208             : /*
     209             :  * This is the character used to separate the residual from the subsidiary name
     210             :  * when both are given.  It's tempting to use ':' just as we do in the ccache
     211             :  * names, but we can't on Windows.
     212             :  */
     213             : #define FILESUBSEP "+"
     214             : #define FILESUBSEPCHR ((FILESUBSEP)[0])
     215             : 
     216             : static krb5_error_code KRB5_CALLCONV
     217       54530 : fcc_resolve_2(krb5_context context,
     218             :               krb5_ccache *id,
     219             :               const char *res,
     220             :               const char *sub)
     221             : {
     222         437 :     krb5_fcache *f;
     223       54530 :     char *freeme = NULL;
     224             : 
     225       54530 :     if (res == NULL && sub == NULL)
     226           0 :         return krb5_einval(context, 3);
     227       54530 :     if (res == NULL) {
     228           0 :         krb5_error_code ret;
     229             : 
     230           0 :         if ((ret = fcc_get_default_name(context, &freeme)))
     231           0 :             return ret;
     232           0 :         res = freeme + sizeof("FILE:") - 1;
     233       54530 :     } else if (!sub && (sub = strchr(res, FILESUBSEPCHR))) {
     234           0 :         if (sub[1] == '\0') {
     235           0 :             sub = NULL;
     236             :         } else {
     237             :             /* `res' has a subsidiary component, so split on it */
     238           0 :             if ((freeme = strndup(res, sub - res)) == NULL)
     239           0 :                 return krb5_enomem(context);
     240           0 :             res = freeme;
     241           0 :             sub++;
     242             :         }
     243             :     }
     244             : 
     245       54530 :     if ((f = calloc(1, sizeof(*f))) == NULL ||
     246      109060 :         (f->res = strdup(res)) == NULL ||
     247      108623 :         (f->sub = sub ? strdup(sub) : NULL) == (sub ? NULL : "") ||
     248       55404 :         asprintf(&f->filename, "%s%s%s",
     249       54530 :                  res, sub ? FILESUBSEP : "", sub ? sub : "") == -1 ||
     250       54530 :         f->filename == NULL) {
     251           0 :         if (f) {
     252           0 :             free(f->filename);
     253           0 :             free(f->res);
     254           0 :             free(f->sub);
     255             :         }
     256           0 :         free(f);
     257           0 :         free(freeme);
     258           0 :         return krb5_enomem(context);
     259             :     }
     260       54530 :     f->tmpfn = NULL;
     261       54530 :     f->version = 0;
     262       54530 :     (*id)->data.data = f;
     263       54530 :     (*id)->data.length = sizeof(*f);
     264             : 
     265       54530 :     free(freeme);
     266       54530 :     return 0;
     267             : }
     268             : 
     269             : /*
     270             :  * Try to scrub the contents of `filename' safely.
     271             :  */
     272             : 
     273             : static int
     274         172 : scrub_file (int fd)
     275             : {
     276           0 :     off_t pos;
     277           0 :     char buf[128];
     278             : 
     279         172 :     pos = lseek(fd, 0, SEEK_END);
     280         172 :     if (pos < 0)
     281           0 :         return errno;
     282         172 :     if (lseek(fd, 0, SEEK_SET) < 0)
     283           0 :         return errno;
     284         172 :     memset(buf, 0, sizeof(buf));
     285        3497 :     while(pos > 0) {
     286           0 :         ssize_t tmp;
     287        3325 :         size_t wr = sizeof(buf);
     288        3325 :         if (wr > pos)
     289         172 :             wr = (size_t)pos;
     290        3325 :         tmp = write(fd, buf, wr);
     291             : 
     292        3325 :         if (tmp < 0)
     293           0 :             return errno;
     294        3325 :         pos -= tmp;
     295             :     }
     296             : #ifdef _MSC_VER
     297             :     _commit (fd);
     298             : #else
     299         172 :     fsync (fd);
     300             : #endif
     301         172 :     return 0;
     302             : }
     303             : 
     304             : /*
     305             :  * Erase `filename' if it exists, trying to remove the contents if
     306             :  * it's `safe'.  We always try to remove the file, it it exists.  It's
     307             :  * only overwritten if it's a regular file (not a symlink and not a
     308             :  * hardlink)
     309             :  */
     310             : 
     311             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     312         172 : _krb5_erase_file(krb5_context context, const char *filename)
     313             : {
     314           0 :     int fd;
     315           0 :     struct stat sb1, sb2;
     316           0 :     int ret;
     317             : 
     318         172 :     ret = lstat (filename, &sb1);
     319         172 :     if (ret < 0) {
     320           0 :         if(errno == ENOENT)
     321           0 :             return 0;
     322             :         else
     323           0 :             return errno;
     324             :     }
     325             : 
     326         172 :     fd = open(filename, O_RDWR | O_BINARY | O_CLOEXEC | O_NOFOLLOW);
     327         172 :     if(fd < 0) {
     328           0 :         if(errno == ENOENT)
     329           0 :             return 0;
     330             :         else
     331           0 :             return errno;
     332             :     }
     333         172 :     rk_cloexec(fd);
     334         172 :     ret = _krb5_xlock(context, fd, 1, filename);
     335         172 :     if (ret) {
     336           0 :         close(fd);
     337           0 :         return ret;
     338             :     }
     339         172 :     if (unlink(filename) < 0) {
     340           0 :         ret = errno;
     341           0 :         close (fd);
     342           0 :         krb5_set_error_message(context, errno,
     343           0 :             N_("krb5_cc_destroy: unlinking \"%s\": %s", ""),
     344             :             filename, strerror(ret));
     345           0 :         return ret;
     346             :     }
     347         172 :     ret = fstat(fd, &sb2);
     348         172 :     if (ret < 0) {
     349           0 :         ret = errno;
     350           0 :         close (fd);
     351           0 :         return ret;
     352             :     }
     353             : 
     354             :     /* check if someone was playing with symlinks */
     355             : 
     356         172 :     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
     357           0 :         close(fd);
     358           0 :         return EPERM;
     359             :     }
     360             : 
     361             :     /* there are still hard links to this file */
     362             : 
     363         172 :     if (sb2.st_nlink != 0) {
     364           0 :         close(fd);
     365           0 :         return 0;
     366             :     }
     367             : 
     368         172 :     ret = scrub_file(fd);
     369         172 :     close(fd);
     370         172 :     return ret;
     371             : }
     372             : 
     373             : static krb5_error_code KRB5_CALLCONV
     374         172 : fcc_gen_new(krb5_context context, krb5_ccache *id)
     375             : {
     376         172 :     char *file = NULL, *exp_file = NULL;
     377           0 :     krb5_error_code ret;
     378           0 :     krb5_fcache *f;
     379           0 :     int fd;
     380             : 
     381         172 :     f = calloc(1, sizeof(*f));
     382         172 :     if(f == NULL) {
     383           0 :         krb5_set_error_message(context, KRB5_CC_NOMEM,
     384           0 :                                N_("malloc: out of memory", ""));
     385           0 :         return KRB5_CC_NOMEM;
     386             :     }
     387         172 :     f->tmpfn = NULL;
     388             :     /*
     389             :      * XXX We should asprintf(&file, "%s:XXXXXX", KRB5_DEFAULT_CCNAME_FILE)
     390             :      * instead so that new unique FILE ccaches can be found in the user's
     391             :      * default collection.
     392             :      * */
     393         172 :     ret = asprintf(&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
     394         172 :     if(ret < 0 || file == NULL) {
     395           0 :         free(f);
     396           0 :         krb5_set_error_message(context, KRB5_CC_NOMEM,
     397           0 :                                N_("malloc: out of memory", ""));
     398           0 :         return KRB5_CC_NOMEM;
     399             :     }
     400         172 :     ret = _krb5_expand_path_tokens(context, file, 1, &exp_file);
     401         172 :     free(file);
     402         172 :     if (ret) {
     403           0 :         free(f);
     404           0 :         return ret;
     405             :     }
     406             : 
     407         172 :     file = exp_file;
     408             : 
     409         172 :     fd = mkostemp(exp_file, O_CLOEXEC);
     410         172 :     if(fd < 0) {
     411           0 :         ret = (krb5_error_code)errno;
     412           0 :         krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
     413           0 :         free(f);
     414           0 :         free(exp_file);
     415           0 :         return ret;
     416             :     }
     417         172 :     close(fd);
     418         172 :     f->filename = exp_file;
     419         172 :     f->res = strdup(exp_file); /* XXX See above commentary about collection */
     420         172 :     f->sub = NULL;
     421         172 :     f->version = 0;
     422         172 :     (*id)->data.data = f;
     423         172 :     (*id)->data.length = sizeof(*f);
     424         172 :     return 0;
     425             : }
     426             : 
     427             : static void
     428        8423 : storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
     429             : {
     430        8423 :     int flags = 0;
     431        8423 :     switch(vno) {
     432           0 :     case KRB5_FCC_FVNO_1:
     433           0 :         flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
     434           0 :         flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
     435           0 :         flags |= KRB5_STORAGE_HOST_BYTEORDER;
     436           0 :         break;
     437           0 :     case KRB5_FCC_FVNO_2:
     438           0 :         flags |= KRB5_STORAGE_HOST_BYTEORDER;
     439           0 :         break;
     440           0 :     case KRB5_FCC_FVNO_3:
     441           0 :         flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
     442           0 :         break;
     443        8423 :     case KRB5_FCC_FVNO_4:
     444        8423 :         break;
     445           0 :     default:
     446           0 :         krb5_abortx(context,
     447             :                     "storage_set_flags called with bad vno (%x)", vno);
     448             :     }
     449        8423 :     krb5_storage_set_flags(sp, flags);
     450        8423 : }
     451             : 
     452             : static krb5_error_code KRB5_CALLCONV
     453       60180 : fcc_open(krb5_context context,
     454             :          krb5_ccache id,
     455             :          const char *operation,
     456             :          int *fd_ret,
     457             :          int flags,
     458             :          mode_t mode)
     459             : {
     460      118818 :     krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
     461       59075 :                               (flags | O_RDWR) == flags);
     462         437 :     krb5_error_code ret;
     463         437 :     const char *filename;
     464         437 :     struct stat sb1, sb2;
     465             : #ifndef _WIN32
     466         437 :     struct stat sb3;
     467       60180 :     size_t tries = 3;
     468             : #endif
     469         437 :     int strict_checking;
     470         437 :     int fd;
     471             : 
     472       60180 :     flags |= O_BINARY | O_CLOEXEC | O_NOFOLLOW;
     473             : 
     474       60180 :     *fd_ret = -1;
     475             : 
     476       60180 :     if (FCACHE(id) == NULL)
     477           0 :         return krb5_einval(context, 2);
     478             : 
     479       60180 :     if ((flags & O_EXCL)) {
     480             :         /*
     481             :          * FIXME Instead of mkostemp()... we could instead try to use a .new
     482             :          * file... with care.  Or the O_TMPFILE / linkat() extensions.  We need
     483             :          * a roken / heimbase abstraction for that.
     484             :          */
     485         464 :         if (TMPFILENAME(id))
     486         137 :             (void) unlink(TMPFILENAME(id));
     487         464 :         free(TMPFILENAME(id));
     488         464 :         TMPFILENAME(id) = NULL;
     489         464 :         if (asprintf(&TMPFILENAME(id), "%s-XXXXXX", FILENAME(id)) < 0 ||
     490         464 :             TMPFILENAME(id) == NULL)
     491           0 :             return krb5_enomem(context);
     492         464 :         if ((fd = mkostemp(TMPFILENAME(id), O_CLOEXEC)) == -1) {
     493           0 :             krb5_set_error_message(context, ret = errno,
     494           0 :                                    N_("Could not make temp ccache FILE:%s", ""),
     495           0 :                                    TMPFILENAME(id));
     496           0 :             free(TMPFILENAME(id));
     497           0 :             TMPFILENAME(id) = NULL;
     498           0 :             return ret;
     499             :         }
     500         464 :         goto out;
     501             :     }
     502             : 
     503       59716 :     filename = TMPFILENAME(id) ? TMPFILENAME(id) : FILENAME(id);
     504      119432 :     strict_checking = (flags & O_CREAT) == 0 &&
     505       59716 :         (context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
     506             : 
     507             : #ifndef WIN32
     508       59716 : again:
     509             : #endif
     510       59716 :     memset(&sb1, 0, sizeof(sb1));
     511       59716 :     ret = lstat(filename, &sb1);
     512       59716 :     if (ret == 0) {
     513        7957 :         if (!S_ISREG(sb1.st_mode)) {
     514           0 :             krb5_set_error_message(context, EPERM,
     515           0 :                                    N_("Refuses to open symlinks for caches FILE:%s", ""), filename);
     516           0 :             return EPERM;
     517             :         }
     518       51759 :     } else if (errno != ENOENT || !(flags & O_CREAT)) {
     519       51759 :         krb5_set_error_message(context, errno, N_("%s lstat(%s)", "file, error"),
     520             :                                operation, filename);
     521       51759 :         return errno;
     522             :     }
     523             : 
     524        7957 :     fd = open(filename, flags, mode);
     525        7957 :     if(fd < 0) {
     526           0 :         char buf[128];
     527           0 :         ret = errno;
     528           0 :         rk_strerror_r(ret, buf, sizeof(buf));
     529           0 :         krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
     530             :                                operation, filename, buf);
     531           0 :         return ret;
     532             :     }
     533        7957 :     rk_cloexec(fd);
     534             : 
     535        7957 :     ret = fstat(fd, &sb2);
     536        7957 :     if (ret < 0) {
     537           0 :         krb5_clear_error_message(context);
     538           0 :         close(fd);
     539           0 :         return errno;
     540             :     }
     541             : 
     542        7957 :     if (!S_ISREG(sb2.st_mode)) {
     543           0 :         krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
     544           0 :         close(fd);
     545           0 :         return EPERM;
     546             :     }
     547             : 
     548             : #ifndef _WIN32
     549        7957 :     if (sb1.st_dev && sb1.st_ino &&
     550        7957 :         (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) {
     551             :         /*
     552             :          * Perhaps we raced with a rename().  To complain about
     553             :          * symlinks in that case would cause unnecessary concern, so
     554             :          * we check for that possibility and loop.  This has no
     555             :          * TOCTOU problems because we redo the open().  We could also
     556             :          * not do any of this checking if O_NOFOLLOW != 0...
     557             :          */
     558           0 :         close(fd);
     559           0 :         ret = lstat(filename, &sb3);
     560           0 :         if (ret || sb1.st_dev != sb2.st_dev ||
     561           0 :             sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
     562           0 :             krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
     563           0 :             return EPERM;
     564             :         }
     565           0 :         if (--tries == 0) {
     566           0 :             krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
     567           0 :             return EPERM;
     568             :         }
     569           0 :         goto again;
     570             :     }
     571             : #endif
     572             : 
     573             :     /*
     574             :      * /tmp (or wherever default ccaches go) might not be on its own
     575             :      * filesystem, or on a filesystem different /etc, say, and even if
     576             :      * it were, suppose a user hard-links another's ccache to her
     577             :      * default ccache, then runs a set-uid program that will user her
     578             :      * default ccache (even if it ignores KRB5CCNAME)...
     579             :      *
     580             :      * Default ccache locations should really be on per-user non-tmp
     581             :      * locations on tmpfs "run" directories.  But we don't know here
     582             :      * that this is the case.  Thus: no hard-links, no symlinks.
     583             :      */
     584        7957 :     if (sb2.st_nlink > 1) {
     585           0 :         krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
     586           0 :         close(fd);
     587           0 :         return EPERM;
     588             :     }
     589             : 
     590        7957 :     if (strict_checking) {
     591             : #ifndef _WIN32
     592             :         /*
     593             :          * XXX WIN32: Needs to have ACL checking code!
     594             :          * st_mode comes out as 100666, and st_uid is no use.
     595             :          */
     596             :         /*
     597             :          * XXX Should probably add options to improve control over this
     598             :          * check.  We might want strict checking of everything except
     599             :          * this.
     600             :          */
     601           0 :         if (sb2.st_uid != geteuid()) {
     602           0 :             krb5_set_error_message(context, EPERM, N_("Refuses to open cache files not own by myself FILE:%s (owned by %d)", ""), filename, (int)sb2.st_uid);
     603           0 :             close(fd);
     604           0 :             return EPERM;
     605             :         }
     606           0 :         if ((sb2.st_mode & 077) != 0) {
     607           0 :             krb5_set_error_message(context, EPERM,
     608           0 :                                    N_("Refuses to open group/other readable files FILE:%s", ""), filename);
     609           0 :             close(fd);
     610           0 :             return EPERM;
     611             :         }
     612             : #endif
     613             :     }
     614             : 
     615        7957 : out:
     616        8421 :     if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
     617           0 :         close(fd);
     618           0 :         return ret;
     619             :     }
     620        8421 :     *fd_ret = fd;
     621        8421 :     return 0;
     622             : }
     623             : 
     624             : static krb5_error_code KRB5_CALLCONV
     625         464 : fcc_initialize(krb5_context context,
     626             :                krb5_ccache id,
     627             :                krb5_principal primary_principal)
     628             : {
     629         464 :     krb5_fcache *f = FCACHE(id);
     630         464 :     int ret = 0;
     631           0 :     int fd;
     632             : 
     633         464 :     if (f == NULL)
     634           0 :         return krb5_einval(context, 2);
     635             : 
     636             :     /*
     637             :      * fcc_open() will notice the O_EXCL and will make a temporary file that
     638             :      * will later be renamed into place.
     639             :      */
     640         464 :     ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL, 0600);
     641         464 :     if(ret)
     642           0 :         return ret;
     643             :     {
     644           0 :         krb5_storage *sp;
     645         464 :         sp = krb5_storage_emem();
     646         464 :         if (sp == NULL)
     647           0 :             return krb5_enomem(context);
     648         464 :         krb5_storage_set_eof_code(sp, KRB5_CC_END);
     649         464 :         if(context->fcache_vno != 0)
     650           0 :             f->version = context->fcache_vno;
     651             :         else
     652         464 :             f->version = KRB5_FCC_FVNO_4;
     653         464 :         if (ret == 0)
     654         464 :             ret = krb5_store_int8(sp, 5);
     655         464 :         if (ret == 0)
     656         464 :             ret = krb5_store_int8(sp, f->version);
     657         464 :         storage_set_flags(context, sp, f->version);
     658         464 :         if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
     659             :             /* V4 stuff */
     660         464 :             if (context->kdc_sec_offset) {
     661           0 :                 if (ret == 0)
     662           0 :                     ret = krb5_store_int16 (sp, 12); /* length */
     663           0 :                 if (ret == 0)
     664           0 :                     ret = krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
     665           0 :                 if (ret == 0)
     666           0 :                     ret = krb5_store_int16 (sp, 8); /* length of data */
     667           0 :                 if (ret == 0)
     668           0 :                     ret = krb5_store_int32 (sp, context->kdc_sec_offset);
     669           0 :                 if (ret == 0)
     670           0 :                     ret = krb5_store_int32 (sp, context->kdc_usec_offset);
     671             :             } else {
     672         464 :                 if (ret == 0)
     673         464 :                     ret = krb5_store_int16 (sp, 0);
     674             :             }
     675             :         }
     676         464 :         if (ret == 0)
     677         464 :             ret = krb5_store_principal(sp, primary_principal);
     678             : 
     679         464 :         if (ret == 0)
     680         464 :             ret = write_storage(context, sp, fd);
     681             : 
     682         464 :         krb5_storage_free(sp);
     683             :     }
     684         464 :     if (close(fd) < 0)
     685           0 :         if (ret == 0) {
     686           0 :             char buf[128];
     687           0 :             ret = errno;
     688           0 :             rk_strerror_r(ret, buf, sizeof(buf));
     689           0 :             krb5_set_error_message(context, ret, N_("close %s: %s", ""),
     690           0 :                                    FILENAME(id), buf);
     691             :         }
     692         464 :     return ret;
     693             : }
     694             : 
     695             : static krb5_error_code KRB5_CALLCONV
     696       54618 : fcc_close(krb5_context context,
     697             :           krb5_ccache id)
     698             : {
     699       54618 :     if (FCACHE(id) == NULL)
     700           0 :         return krb5_einval(context, 2);
     701             : 
     702       54618 :     if (TMPFILENAME(id))
     703           0 :         (void) unlink(TMPFILENAME(id));
     704       54618 :     free(TMPFILENAME(id));
     705       54618 :     free(RESFILENAME(id));
     706       54618 :     free(SUBFILENAME(id));
     707       54618 :     free(FILENAME(id));
     708       54618 :     krb5_data_free(&id->data);
     709       54618 :     return 0;
     710             : }
     711             : 
     712             : static krb5_error_code KRB5_CALLCONV
     713         172 : fcc_destroy(krb5_context context,
     714             :             krb5_ccache id)
     715             : {
     716         172 :     if (FCACHE(id) == NULL)
     717           0 :         return krb5_einval(context, 2);
     718             : 
     719         172 :     if (TMPFILENAME(id))
     720           0 :         (void) _krb5_erase_file(context, TMPFILENAME(id));
     721         172 :     return _krb5_erase_file(context, FILENAME(id));
     722             : }
     723             : 
     724             : static krb5_error_code KRB5_CALLCONV
     725        1105 : fcc_store_cred(krb5_context context,
     726             :                krb5_ccache id,
     727             :                krb5_creds *creds)
     728             : {
     729           0 :     int ret;
     730           0 :     int fd;
     731             : 
     732        1105 :     ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND, 0);
     733        1105 :     if(ret)
     734           0 :         return ret;
     735             :     {
     736           0 :         krb5_storage *sp;
     737             : 
     738        1105 :         sp = krb5_storage_emem();
     739        1105 :         if (sp == NULL)
     740           0 :             return krb5_enomem(context);
     741        1105 :         krb5_storage_set_eof_code(sp, KRB5_CC_END);
     742        1105 :         storage_set_flags(context, sp, FCACHE(id)->version);
     743        1105 :         ret = krb5_store_creds(sp, creds);
     744        1105 :         if (ret == 0)
     745        1105 :             ret = write_storage(context, sp, fd);
     746        1105 :         krb5_storage_free(sp);
     747             :     }
     748        1105 :     if (close(fd) < 0) {
     749           0 :         if (ret == 0) {
     750           0 :             char buf[128];
     751           0 :             ret = errno;
     752           0 :             rk_strerror_r(ret, buf, sizeof(buf));
     753           0 :             krb5_set_error_message(context, ret, N_("close %s: %s", ""),
     754           0 :                                    FILENAME(id), buf);
     755             :         }
     756             :     }
     757        1432 :     if (ret == 0 && TMPFILENAME(id) &&
     758         327 :         !krb5_is_config_principal(context, creds->server)) {
     759             : 
     760             :         /*
     761             :          * Portability note: there's no need to have WIN32 or other code here
     762             :          * for odd rename cases because rk_rename() is meant to handle that.
     763             :          */
     764         327 :         ret = rk_rename(TMPFILENAME(id), FILENAME(id));
     765         327 :         if (ret == 0) {
     766         327 :             free(TMPFILENAME(id));
     767         327 :             TMPFILENAME(id) = NULL;
     768             :         } else {
     769           0 :             ret = errno;
     770             :         }
     771             :     }
     772        1105 :     return ret;
     773             : }
     774             : 
     775             : static krb5_error_code
     776       58609 : init_fcc(krb5_context context,
     777             :          krb5_ccache id,
     778             :          const char *operation,
     779             :          krb5_storage **ret_sp,
     780             :          int *ret_fd,
     781             :          krb5_deltat *kdc_offset)
     782             : {
     783         437 :     int fd;
     784         437 :     int8_t pvno, tag;
     785         437 :     krb5_storage *sp;
     786         437 :     krb5_error_code ret;
     787             : 
     788       58609 :     *ret_fd = -1;
     789       58609 :     *ret_sp = NULL;
     790       58609 :     if (kdc_offset)
     791         197 :         *kdc_offset = 0;
     792             : 
     793       58609 :     ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0);
     794       58609 :     if(ret)
     795       51322 :         return ret;
     796             : 
     797        6850 :     sp = krb5_storage_stdio_from_fd(fd, "r");
     798        6850 :     if(sp == NULL) {
     799           0 :         krb5_clear_error_message(context);
     800           0 :         ret = ENOMEM;
     801           0 :         goto out;
     802             :     }
     803        6850 :     krb5_storage_set_eof_code(sp, KRB5_CC_END);
     804        6850 :     ret = krb5_ret_int8(sp, &pvno);
     805        6850 :     if (ret != 0) {
     806           0 :         if(ret == KRB5_CC_END) {
     807           0 :             ret = ENOENT;
     808           0 :             krb5_set_error_message(context, ret,
     809           0 :                                    N_("Empty credential cache file: %s", ""),
     810           0 :                                    FILENAME(id));
     811             :         } else
     812           0 :             krb5_set_error_message(context, ret, N_("Error reading pvno "
     813             :                                                     "in cache file: %s", ""),
     814           0 :                                    FILENAME(id));
     815           0 :         goto out;
     816             :     }
     817        6850 :     if (pvno != 5) {
     818           0 :         ret = KRB5_CCACHE_BADVNO;
     819           0 :         krb5_set_error_message(context, ret, N_("Bad version number in credential "
     820             :                                                 "cache file: %s", ""),
     821           0 :                                FILENAME(id));
     822           0 :         goto out;
     823             :     }
     824        6850 :     ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
     825        6850 :     if (ret != 0) {
     826           0 :         ret = KRB5_CC_FORMAT;
     827           0 :         krb5_set_error_message(context, ret, "Error reading tag in "
     828           0 :                               "cache file: %s", FILENAME(id));
     829           0 :         goto out;
     830             :     }
     831        6850 :     FCACHE(id)->version = tag;
     832        6850 :     storage_set_flags(context, sp, FCACHE(id)->version);
     833        6850 :     switch (tag) {
     834        6850 :     case KRB5_FCC_FVNO_4: {
     835           0 :         int16_t length;
     836             : 
     837        6850 :         ret = krb5_ret_int16 (sp, &length);
     838        6850 :         if(ret) {
     839           0 :             ret = KRB5_CC_FORMAT;
     840           0 :             krb5_set_error_message(context, ret,
     841           0 :                                    N_("Error reading tag length in "
     842           0 :                                       "cache file: %s", ""), FILENAME(id));
     843           0 :             goto out;
     844             :         }
     845        7980 :         while(length > 0) {
     846           0 :             int16_t dtag, data_len;
     847           0 :             int i;
     848           0 :             int8_t dummy;
     849             : 
     850        1130 :             ret = krb5_ret_int16 (sp, &dtag);
     851        1130 :             if(ret) {
     852           0 :                 ret = KRB5_CC_FORMAT;
     853           0 :                 krb5_set_error_message(context, ret, N_("Error reading dtag in "
     854             :                                                         "cache file: %s", ""),
     855           0 :                                        FILENAME(id));
     856           0 :                 goto out;
     857             :             }
     858        1130 :             ret = krb5_ret_int16 (sp, &data_len);
     859        1130 :             if(ret) {
     860           0 :                 ret = KRB5_CC_FORMAT;
     861           0 :                 krb5_set_error_message(context, ret,
     862           0 :                                        N_("Error reading dlength "
     863             :                                           "in cache file: %s",""),
     864           0 :                                        FILENAME(id));
     865           0 :                 goto out;
     866             :             }
     867        1130 :             switch (dtag) {
     868        1130 :             case FCC_TAG_DELTATIME : {
     869           0 :                 int32_t offset;
     870             : 
     871        1130 :                 ret = krb5_ret_int32 (sp, &offset);
     872        1130 :                 ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
     873        1130 :                 if(ret) {
     874           0 :                     ret = KRB5_CC_FORMAT;
     875           0 :                     krb5_set_error_message(context, ret,
     876           0 :                                            N_("Error reading kdc_sec in "
     877             :                                               "cache file: %s", ""),
     878           0 :                                            FILENAME(id));
     879           0 :                     goto out;
     880             :                 }
     881        1130 :                 context->kdc_sec_offset = offset;
     882        1130 :                 if (kdc_offset)
     883          18 :                     *kdc_offset = offset;
     884        1130 :                 break;
     885             :             }
     886           0 :             default :
     887           0 :                 for (i = 0; i < data_len; ++i) {
     888           0 :                     ret = krb5_ret_int8 (sp, &dummy);
     889           0 :                     if(ret) {
     890           0 :                         ret = KRB5_CC_FORMAT;
     891           0 :                         krb5_set_error_message(context, ret,
     892           0 :                                                N_("Error reading unknown "
     893             :                                                   "tag in cache file: %s", ""),
     894           0 :                                                FILENAME(id));
     895           0 :                         goto out;
     896             :                     }
     897             :                 }
     898           0 :                 break;
     899             :             }
     900        1130 :             length -= 4 + data_len;
     901             :         }
     902        6850 :         break;
     903             :     }
     904           0 :     case KRB5_FCC_FVNO_3:
     905             :     case KRB5_FCC_FVNO_2:
     906             :     case KRB5_FCC_FVNO_1:
     907           0 :         break;
     908           0 :     default :
     909           0 :         ret = KRB5_CCACHE_BADVNO;
     910           0 :         krb5_set_error_message(context, ret,
     911           0 :                                N_("Unknown version number (%d) in "
     912             :                                   "credential cache file: %s", ""),
     913           0 :                                (int)tag, FILENAME(id));
     914           0 :         goto out;
     915             :     }
     916        6850 :     *ret_sp = sp;
     917        6850 :     *ret_fd = fd;
     918             : 
     919        6850 :     return 0;
     920           0 :   out:
     921           0 :     if(sp != NULL)
     922           0 :         krb5_storage_free(sp);
     923           0 :     close(fd);
     924           0 :     return ret;
     925             : }
     926             : 
     927             : static krb5_error_code KRB5_CALLCONV
     928       55435 : fcc_get_principal(krb5_context context,
     929             :                   krb5_ccache id,
     930             :                   krb5_principal *principal)
     931             : {
     932         437 :     krb5_error_code ret;
     933         437 :     int fd;
     934         437 :     krb5_storage *sp;
     935             : 
     936       55435 :     ret = init_fcc (context, id, "get-principal", &sp, &fd, NULL);
     937       55435 :     if (ret)
     938       51322 :         return ret;
     939        3676 :     ret = krb5_ret_principal(sp, principal);
     940        3676 :     if (ret)
     941           0 :         krb5_clear_error_message(context);
     942        3676 :     krb5_storage_free(sp);
     943        3676 :     close(fd);
     944        3676 :     return ret;
     945             : }
     946             : 
     947             : static krb5_error_code KRB5_CALLCONV
     948             : fcc_end_get(krb5_context context,
     949             :             krb5_ccache id,
     950             :             krb5_cc_cursor *cursor);
     951             : 
     952             : static krb5_error_code KRB5_CALLCONV
     953        2977 : fcc_get_first(krb5_context context,
     954             :               krb5_ccache id,
     955             :               krb5_cc_cursor *cursor)
     956             : {
     957           0 :     krb5_error_code ret;
     958           0 :     krb5_principal principal;
     959             : 
     960        2977 :     if (FCACHE(id) == NULL)
     961           0 :         return krb5_einval(context, 2);
     962             : 
     963        2977 :     *cursor = calloc(1, sizeof(struct fcc_cursor));
     964        2977 :     if (*cursor == NULL) {
     965           0 :         krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
     966           0 :         return ENOMEM;
     967             :     }
     968             : 
     969        2977 :     ret = init_fcc(context, id, "get-first", &FCC_CURSOR(*cursor)->sp,
     970        2977 :                    &FCC_CURSOR(*cursor)->fd, NULL);
     971        2977 :     if (ret) {
     972           0 :         free(*cursor);
     973           0 :         *cursor = NULL;
     974           0 :         return ret;
     975             :     }
     976        2977 :     ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
     977        2977 :     if(ret) {
     978           0 :         krb5_clear_error_message(context);
     979           0 :         fcc_end_get(context, id, cursor);
     980           0 :         return ret;
     981             :     }
     982        2977 :     krb5_free_principal (context, principal);
     983        2977 :     return 0;
     984             : }
     985             : 
     986             : static krb5_error_code KRB5_CALLCONV
     987        8622 : fcc_get_next (krb5_context context,
     988             :               krb5_ccache id,
     989             :               krb5_cc_cursor *cursor,
     990             :               krb5_creds *creds)
     991             : {
     992           0 :     krb5_error_code ret;
     993             : 
     994        8622 :     if (FCACHE(id) == NULL)
     995           0 :         return krb5_einval(context, 2);
     996             : 
     997        8622 :     if (FCC_CURSOR(*cursor) == NULL)
     998           0 :         return krb5_einval(context, 3);
     999             : 
    1000       17244 :     FCC_CURSOR(*cursor)->cred_start =
    1001        8622 :         krb5_storage_seek(FCC_CURSOR(*cursor)->sp, 0, SEEK_CUR);
    1002             : 
    1003        8622 :     ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
    1004        8622 :     if (ret)
    1005        1915 :         krb5_clear_error_message(context);
    1006             : 
    1007       17244 :     FCC_CURSOR(*cursor)->cred_end =
    1008        8622 :         krb5_storage_seek(FCC_CURSOR(*cursor)->sp, 0, SEEK_CUR);
    1009             : 
    1010        8622 :     return ret;
    1011             : }
    1012             : 
    1013             : static krb5_error_code KRB5_CALLCONV
    1014        2977 : fcc_end_get (krb5_context context,
    1015             :              krb5_ccache id,
    1016             :              krb5_cc_cursor *cursor)
    1017             : {
    1018             : 
    1019        2977 :     if (FCACHE(id) == NULL)
    1020           0 :         return krb5_einval(context, 2);
    1021             : 
    1022        2977 :     if (FCC_CURSOR(*cursor) == NULL)
    1023           0 :         return krb5_einval(context, 3);
    1024             : 
    1025        2977 :     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
    1026        2977 :     close (FCC_CURSOR(*cursor)->fd);
    1027        2977 :     free(*cursor);
    1028        2977 :     *cursor = NULL;
    1029        2977 :     return 0;
    1030             : }
    1031             : 
    1032             : static void KRB5_CALLCONV
    1033           2 : cred_delete(krb5_context context,
    1034             :             krb5_ccache id,
    1035             :             krb5_cc_cursor *cursor,
    1036             :             krb5_creds *cred)
    1037             : {
    1038           0 :     krb5_error_code ret;
    1039           0 :     krb5_storage *sp;
    1040           0 :     krb5_data orig_cred_data;
    1041           2 :     unsigned char *cred_data_in_file = NULL;
    1042           0 :     off_t new_cred_sz;
    1043           0 :     struct stat sb1, sb2;
    1044           2 :     int fd = -1;
    1045           0 :     ssize_t bytes;
    1046           2 :     krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
    1047             : 
    1048             :     /* This is best-effort code; if we lose track of errors here it's OK */
    1049             : 
    1050           2 :     heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
    1051             :                 "fcache internal error");
    1052             : 
    1053           2 :     krb5_data_zero(&orig_cred_data);
    1054             : 
    1055           2 :     sp = krb5_storage_emem();
    1056           2 :     if (sp == NULL)
    1057           0 :         return;
    1058           2 :     krb5_storage_set_eof_code(sp, KRB5_CC_END);
    1059           2 :     storage_set_flags(context, sp, FCACHE(id)->version);
    1060             : 
    1061             :     /* Get a copy of what the cred should look like in the file; see below */
    1062           2 :     ret = krb5_store_creds(sp, cred);
    1063           2 :     if (ret)
    1064           0 :         goto out;
    1065             : 
    1066           2 :     ret = krb5_storage_to_data(sp, &orig_cred_data);
    1067           2 :     if (ret)
    1068           0 :         goto out;
    1069           2 :     krb5_storage_free(sp);
    1070             : 
    1071           2 :     cred_data_in_file = malloc(orig_cred_data.length);
    1072           2 :     if (cred_data_in_file == NULL)
    1073           0 :         goto out;
    1074             : 
    1075             :     /*
    1076             :      * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
    1077             :      * KRB5_TC_MATCH_TIMES, so this should be good enough...
    1078             :      */
    1079           2 :     cred->times.endtime = 0;
    1080             : 
    1081             :     /* ...except for config creds because we don't check their endtimes */
    1082           2 :     if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
    1083           0 :         ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
    1084           0 :         if (ret)
    1085           0 :             goto out;
    1086             :     }
    1087             : 
    1088           2 :     sp = krb5_storage_emem();
    1089           2 :     if (sp == NULL)
    1090           0 :         goto out;
    1091           2 :     krb5_storage_set_eof_code(sp, KRB5_CC_END);
    1092           2 :     storage_set_flags(context, sp, FCACHE(id)->version);
    1093             : 
    1094           2 :     ret = krb5_store_creds(sp, cred);
    1095             : 
    1096             :     /* The new cred must be the same size as the old cred */
    1097           2 :     new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
    1098           2 :     if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
    1099           2 :         (FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
    1100             :         /* XXX This really can't happen.  Assert like above? */
    1101           0 :         krb5_set_error_message(context, EINVAL,
    1102           0 :                                N_("Credential deletion failed on ccache "
    1103             :                                   "FILE:%s: new credential size did not "
    1104             :                                   "match old credential size", ""),
    1105           0 :                                FILENAME(id));
    1106           0 :         goto out;
    1107             :     }
    1108             : 
    1109           2 :     ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0);
    1110           2 :     if (ret)
    1111           0 :         goto out;
    1112             : 
    1113             :     /*
    1114             :      * Check that we're updating the same file where we got the
    1115             :      * cred's offset, else we'd be corrupting a new ccache.
    1116             :      */
    1117           4 :     if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
    1118           2 :         fstat(fd, &sb2) == -1)
    1119           0 :         goto out;
    1120           2 :     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
    1121           0 :         goto out;
    1122             : 
    1123             :     /*
    1124             :      * Make sure what we overwrite is what we expected.
    1125             :      *
    1126             :      * FIXME: We *really* need the ccache v4 tag for ccache ID.  This
    1127             :      * check that we're only overwriting something that looks exactly
    1128             :      * like what we want to is probably good enough in practice, but
    1129             :      * it's not guaranteed to work.
    1130             :      */
    1131           2 :     if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
    1132           0 :         goto out;
    1133           2 :     bytes = read(fd, cred_data_in_file, orig_cred_data.length);
    1134           2 :     if (bytes != orig_cred_data.length)
    1135           0 :         goto out;
    1136           2 :     if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
    1137           0 :         goto out;
    1138           2 :     if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
    1139           0 :         goto out;
    1140           2 :     ret = write_storage(context, sp, fd);
    1141           2 : out:
    1142           2 :     if (fd > -1) {
    1143           2 :         if (close(fd) < 0 && ret == 0) {
    1144           0 :             krb5_set_error_message(context, errno, N_("close %s", ""),
    1145           0 :                                    FILENAME(id));
    1146             :         }
    1147             :     }
    1148           2 :     krb5_data_free(&orig_cred_data);
    1149           2 :     free(cred_data_in_file);
    1150           2 :     krb5_storage_free(sp);
    1151           2 :     return;
    1152             : }
    1153             : 
    1154             : static krb5_error_code KRB5_CALLCONV
    1155         456 : fcc_remove_cred(krb5_context context,
    1156             :                 krb5_ccache id,
    1157             :                 krb5_flags which,
    1158             :                 krb5_creds *mcred)
    1159             : {
    1160           0 :     krb5_error_code ret, ret2;
    1161           0 :     krb5_cc_cursor cursor;
    1162           0 :     krb5_creds found_cred;
    1163             : 
    1164         456 :     if (FCACHE(id) == NULL)
    1165           0 :         return krb5_einval(context, 2);
    1166             : 
    1167         456 :     ret = krb5_cc_start_seq_get(context, id, &cursor);
    1168         456 :     if (ret)
    1169           0 :         return ret;
    1170        1072 :     while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
    1171         616 :         if (!krb5_compare_creds(context, which, mcred, &found_cred)) {
    1172         614 :             krb5_free_cred_contents(context, &found_cred);
    1173         614 :             continue;
    1174             :         }
    1175           2 :         cred_delete(context, id, &cursor, &found_cred);
    1176           2 :         krb5_free_cred_contents(context, &found_cred);
    1177             :     }
    1178         456 :     ret2 = krb5_cc_end_seq_get(context, id, &cursor);
    1179         456 :     if (ret2)   /* not expected to fail */
    1180           0 :         return ret2;
    1181         456 :     if (ret == KRB5_CC_END)
    1182         456 :         return 0;
    1183           0 :     return ret;
    1184             : }
    1185             : 
    1186             : static krb5_error_code KRB5_CALLCONV
    1187           0 : fcc_set_flags(krb5_context context,
    1188             :               krb5_ccache id,
    1189             :               krb5_flags flags)
    1190             : {
    1191           0 :     if (FCACHE(id) == NULL)
    1192           0 :         return krb5_einval(context, 2);
    1193             : 
    1194           0 :     return 0; /* XXX */
    1195             : }
    1196             : 
    1197             : static int KRB5_CALLCONV
    1198           0 : fcc_get_version(krb5_context context,
    1199             :                 krb5_ccache id)
    1200             : {
    1201           0 :     if (FCACHE(id) == NULL)
    1202           0 :         return -1;
    1203             : 
    1204           0 :     return FCACHE(id)->version;
    1205             : }
    1206             : 
    1207             : static const char *
    1208           0 : my_basename(const char *fn)
    1209             : {
    1210           0 :     const char *base, *p;
    1211             : 
    1212           0 :     if (strncmp(fn, "FILE:", sizeof("FILE:") - 1) == 0)
    1213           0 :         fn += sizeof("FILE:") - 1;
    1214           0 :     for (p = base = fn; *p; p++) {
    1215             : #ifdef WIN32
    1216             :         if (*p == '/' || *p == '\\')
    1217             :             base = p + 1;
    1218             : #else
    1219           0 :         if (*p == '/')
    1220           0 :             base = p + 1;
    1221             : #endif
    1222             :     }
    1223           0 :     return base;
    1224             : }
    1225             : 
    1226             : /* We could use an rk_dirname()... */
    1227             : static char *
    1228           0 : my_dirname(const char *fn)
    1229             : {
    1230           0 :     size_t len, i;
    1231           0 :     char *dname;
    1232             : 
    1233           0 :     if (strncmp(fn, "FILE:", sizeof("FILE:") - 1) == 0)
    1234           0 :         fn += sizeof("FILE:") - 1;
    1235             : 
    1236           0 :     if ((dname = strdup(fn)) == NULL)
    1237           0 :         return NULL;
    1238           0 :     len = strlen(dname);
    1239           0 :     for (i = 0; i < len; i++) {
    1240             : #ifdef WIN32
    1241             :         if (dname[len - i] == '\\' ||
    1242             :             dname[len - i] == '/') {
    1243             :             dname[len - i] = '\0';
    1244             :             break;
    1245             :         }
    1246             : #else
    1247           0 :         if (dname[len - i] == '/') {
    1248           0 :             dname[len - i] = '\0';
    1249           0 :             break;
    1250             :         }
    1251             : #endif
    1252             :     }
    1253           0 :     if (i < len)
    1254           0 :         return dname;
    1255           0 :     free(dname);
    1256           0 :     return strdup(".");
    1257             : }
    1258             : 
    1259             : /*
    1260             :  * This checks that a directory entry matches a required basename and has a
    1261             :  * non-empty subsidiary component.
    1262             :  */
    1263             : static int
    1264           0 : matchbase(const char *fn, const char *base, size_t baselen)
    1265             : {
    1266           0 :     return strncmp(fn, base, baselen) == 0 &&
    1267           0 :         (fn[baselen] == FILESUBSEPCHR && fn[baselen + 1] != '\0');
    1268             : }
    1269             : 
    1270             : /*
    1271             :  * Check if `def_locs' contains `name' (which must be the default ccache name),
    1272             :  * in which case the caller may look for subsidiaries of all of `def_locs'.
    1273             :  *
    1274             :  * This is needed because the collection iterators don't take a base location
    1275             :  * as an argument, so we can only search default locations, but only if the
    1276             :  * current default ccache name is indeed a default (as opposed to from
    1277             :  * KRB5CCNAME being set in the environment pointing to a non-default name).
    1278             :  */
    1279             : static krb5_error_code
    1280          52 : is_default_collection(krb5_context context, const char *name,
    1281             :                       const char * const *def_locs, int *res)
    1282             : {
    1283           0 :     krb5_error_code ret;
    1284          52 :     const char *def_loc[2] = { KRB5_DEFAULT_CCNAME_FILE, NULL };
    1285           0 :     const char *sep;
    1286           0 :     size_t namelen;
    1287           0 :     size_t i;
    1288             : 
    1289          52 :     *res = 0;
    1290          52 :     if (name == NULL) {
    1291          52 :         *res = 1;
    1292          52 :         return 0;
    1293             :     }
    1294           0 :     if ((sep = strchr(name, FILESUBSEPCHR)))
    1295           0 :         namelen = (size_t)(sep - name);
    1296             :     else
    1297           0 :         namelen = strlen(name);
    1298           0 :     if (def_locs == NULL)
    1299           0 :         def_locs = def_loc;
    1300           0 :     for (i = 0; !(*res) && def_locs[i]; i++) {
    1301           0 :         char *e = NULL;
    1302             : 
    1303           0 :         if ((ret = _krb5_expand_default_cc_name(context, def_locs[i], &e)))
    1304           0 :             return ret;
    1305           0 :         *res = strncmp(e, name, namelen) == 0 &&
    1306           0 :         (sep == NULL || e[namelen] == FILESUBSEPCHR || e[namelen] == '\0');
    1307           0 :         free(e);
    1308             :     }
    1309           0 :     return 0;
    1310             : }
    1311             : 
    1312             : /*
    1313             :  * Collection iterator cursor.
    1314             :  *
    1315             :  * There may be an array of locations, and for each location we'll try
    1316             :  * resolving it, as well as doing a readdir() of the dirname of it and output
    1317             :  * all ccache names in that directory that begin with the current location and
    1318             :  * end in "+${subsidiary}".
    1319             :  */
    1320             : struct fcache_iter {
    1321             :     const char *curr_location;
    1322             :     char *def_ccname;   /* The default ccname */
    1323             :     char **locations;   /* All the other places we'll look for a ccache */
    1324             :     char *dname;        /* dirname() of curr_location */
    1325             :     DIR *d;
    1326             :     struct dirent *dentry;
    1327             :     int location;       /* Index of `locations' */
    1328             :     unsigned int first:1;
    1329             :     unsigned int dead:1;
    1330             : };
    1331             : 
    1332             : /* Initiate FILE collection iteration */
    1333             : static krb5_error_code KRB5_CALLCONV
    1334          52 : fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
    1335             : {
    1336          52 :     struct fcache_iter *iter = NULL;
    1337           0 :     krb5_error_code ret;
    1338          52 :     const char *def_ccname = NULL;
    1339          52 :     char **def_locs = NULL;
    1340          52 :     int is_def_coll = 0;
    1341             : 
    1342          52 :     if (krb5_config_get_bool_default(context, NULL, FALSE, "libdefaults",
    1343             :                                      "enable_file_cache_iteration", NULL)) {
    1344           0 :         def_ccname = krb5_cc_default_name(context);
    1345           0 :         def_locs = krb5_config_get_strings(context, NULL, "libdefaults",
    1346             :                                            "default_file_cache_collections",
    1347             :                                            NULL);
    1348             :     }
    1349             : 
    1350             :     /*
    1351             :      * Note: do not allow krb5_cc_default_name() to recurse via
    1352             :      * krb5_cc_cache_match().
    1353             :      * Note that context->default_cc_name will be NULL even though
    1354             :      * KRB5CCNAME is set in the environment if neither krb5_cc_default_name()
    1355             :      * nor krb5_cc_set_default_name() have been called.
    1356             :      */
    1357             : 
    1358             :     /*
    1359             :      * Figure out if the current default ccache name is a really a default one
    1360             :      * so we know whether to search any other default FILE collection
    1361             :      * locations.
    1362             :      */
    1363          52 :     if ((ret = is_default_collection(context, def_ccname,
    1364             :                                      (const char **)def_locs,
    1365             :                                      &is_def_coll)))
    1366           0 :         goto out;
    1367             : 
    1368             :     /* Setup the cursor */
    1369          52 :     if ((iter = calloc(1, sizeof(*iter))) == NULL ||
    1370           0 :         (def_ccname && (iter->def_ccname = strdup(def_ccname)) == NULL)) {
    1371           0 :         ret = krb5_enomem(context);
    1372           0 :         goto out;
    1373             :     }
    1374             : 
    1375          52 :     if (is_def_coll) {
    1376             :         /* Since def_ccname is in the `def_locs', we'll include those */
    1377          52 :         iter->locations = def_locs;
    1378          52 :         free(iter->def_ccname);
    1379          52 :         iter->def_ccname = NULL;
    1380          52 :         def_locs = NULL;
    1381             :     } else {
    1382             :         /* Since def_ccname is NOT in the `def_locs', we'll exclude those */
    1383           0 :         iter->locations = NULL;
    1384             :     }
    1385          52 :     iter->curr_location = NULL;
    1386          52 :     iter->location = -1; /* Pre-incremented */
    1387          52 :     iter->first = 1;
    1388          52 :     iter->dname = NULL;
    1389          52 :     iter->d = NULL;
    1390          52 :     *cursor = iter;
    1391          52 :     iter = NULL;
    1392          52 :     ret = 0;
    1393             : 
    1394          52 : out:
    1395          52 :     krb5_config_free_strings(def_locs);
    1396          52 :     free(iter);
    1397          52 :     return ret;
    1398             : }
    1399             : 
    1400             : /* Pick the next location as the `iter->curr_location' */
    1401             : static krb5_error_code
    1402          52 : next_location(krb5_context context, struct fcache_iter *iter)
    1403             : {
    1404          52 :     if (iter->first && iter->def_ccname) {
    1405           0 :         iter->curr_location = iter->def_ccname;
    1406           0 :         iter->first = 0;
    1407           0 :         return 0;
    1408             :     }
    1409          52 :     iter->first = 0;
    1410             : 
    1411          52 :     if (iter->d)
    1412           0 :         closedir(iter->d);
    1413          52 :     iter->d = NULL;
    1414          52 :     iter->curr_location = NULL;
    1415          52 :     if (iter->locations &&
    1416           0 :         (iter->curr_location = iter->locations[++(iter->location)]))
    1417           0 :         return 0;
    1418             : 
    1419          52 :     iter->dead = 1; /* Do not run off the end of iter->locations */
    1420          52 :     return KRB5_CC_END;
    1421             : }
    1422             : 
    1423             : /* Output the next match for `iter->curr_location' from readdir() */
    1424             : static krb5_error_code
    1425           0 : next_dir_match(krb5_context context, struct fcache_iter *iter, char **fn)
    1426             : {
    1427           0 :     struct stat st;
    1428           0 :     const char *base = my_basename(iter->curr_location);
    1429           0 :     size_t baselen = strlen(base);
    1430           0 :     char *s;
    1431             : 
    1432           0 :     *fn = NULL;
    1433           0 :     if (iter->d == NULL)
    1434           0 :         return 0;
    1435           0 :     for (iter->dentry = readdir(iter->d);
    1436           0 :          iter->dentry;
    1437           0 :          iter->dentry = readdir(iter->d)) {
    1438           0 :         if (!matchbase(iter->dentry->d_name, base, baselen))
    1439           0 :             continue;
    1440           0 :         if (asprintf(&s, "FILE:%s/%s", iter->dname, iter->dentry->d_name) == -1 ||
    1441           0 :             s == NULL)
    1442           0 :             return krb5_enomem(context);
    1443           0 :         if (stat(s + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode)) {
    1444           0 :             *fn = s;
    1445           0 :             return 0;
    1446             :         }
    1447           0 :         free(s);
    1448             :     }
    1449           0 :     iter->curr_location = NULL;
    1450           0 :     closedir(iter->d);
    1451           0 :     iter->d = NULL;
    1452           0 :     return 0;
    1453             : }
    1454             : 
    1455             : /* See if the given `ccname' is a FILE ccache we can resolve */
    1456             : static krb5_error_code
    1457           0 : try1(krb5_context context, const char *ccname, krb5_ccache *id)
    1458             : {
    1459           0 :     krb5_error_code ret;
    1460           0 :     krb5_ccache cc;
    1461             : 
    1462           0 :     ret = krb5_cc_resolve(context, ccname, &cc);
    1463           0 :     if (ret == ENOMEM)
    1464           0 :         return ret;
    1465           0 :     if (ret == 0) {
    1466           0 :         if (strcmp(krb5_cc_get_type(context, cc), "FILE") == 0) {
    1467           0 :             *id = cc;
    1468           0 :             cc = NULL;
    1469             :         }
    1470           0 :         krb5_cc_close(context, cc);
    1471             :     }
    1472           0 :     return 0;
    1473             : }
    1474             : 
    1475             : /* Output the next FILE ccache in the FILE ccache collection */
    1476             : static krb5_error_code KRB5_CALLCONV
    1477          52 : fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
    1478             : {
    1479          52 :     struct fcache_iter *iter = cursor;
    1480           0 :     krb5_error_code ret;
    1481          52 :     char *name = NULL;
    1482             : 
    1483          52 :     *id = NULL;
    1484          52 :     if (iter == NULL)
    1485           0 :         return krb5_einval(context, 2);
    1486             : 
    1487             :     /* Do not run off the end of iter->locations */
    1488          52 :     if (iter->dead)
    1489           0 :         return KRB5_CC_END;
    1490             : 
    1491          52 :     if (!iter->curr_location) {
    1492             :         /* Next base location */
    1493          52 :         if ((ret = next_location(context, iter)))
    1494          52 :             return ret;
    1495             :         /* Output the current base location */
    1496           0 :         if ((ret = try1(context, iter->curr_location, id)) || *id)
    1497           0 :             return ret;
    1498             :     }
    1499             : 
    1500             :     /* Look for subsidiaries of iter->curr_location */
    1501           0 :     if (!iter->d) {
    1502           0 :         free(iter->dname);
    1503           0 :         if ((iter->dname = my_dirname(iter->curr_location)) == NULL)
    1504           0 :             return krb5_enomem(context);
    1505           0 :         if ((iter->d = opendir(iter->dname)) == NULL) {
    1506             :             /* Dirname ENOENT -> next location */
    1507           0 :             if ((ret = next_location(context, iter)))
    1508           0 :                 return ret;
    1509             :             /* Tail-recurse */
    1510           0 :             return fcc_get_cache_next(context, cursor, id);
    1511             :         }
    1512             :     }
    1513           0 :     for (ret = next_dir_match(context, iter, &name);
    1514           0 :          ret == 0 && name != NULL;
    1515           0 :          ret = next_dir_match(context, iter, &name)) {
    1516           0 :         if ((ret = try1(context, name, id)) || *id) {
    1517           0 :             free(name);
    1518           0 :             return ret;
    1519             :         }
    1520           0 :         free(name);
    1521             :     }
    1522             : 
    1523             :     /* Directory listing exhausted -> go to next location, tail-recurse */
    1524           0 :     if ((ret = next_location(context, iter)))
    1525           0 :         return ret;
    1526           0 :     return fcc_get_cache_next(context, cursor, id);
    1527             : }
    1528             : 
    1529             : static krb5_error_code KRB5_CALLCONV
    1530          52 : fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
    1531             : {
    1532          52 :     struct fcache_iter *iter = cursor;
    1533             : 
    1534          52 :     if (iter == NULL)
    1535           0 :         return krb5_einval(context, 2);
    1536             : 
    1537          52 :     krb5_config_free_strings(iter->locations);
    1538          52 :     if (iter->d)
    1539           0 :         closedir(iter->d);
    1540          52 :     free(iter->def_ccname);
    1541          52 :     free(iter->dname);
    1542          52 :     free(iter);
    1543          52 :     return 0;
    1544             : }
    1545             : 
    1546             : static krb5_error_code KRB5_CALLCONV
    1547         137 : fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
    1548             : {
    1549         137 :     krb5_error_code ret = 0;
    1550         137 :     krb5_fcache *f = FCACHE(from);
    1551         137 :     krb5_fcache *t = FCACHE(to);
    1552             : 
    1553         137 :     if (f->tmpfn) {
    1554             :         /*
    1555             :          * If `from' has a temp file and we haven't renamed it into place yet,
    1556             :          * then we should rename TMPFILENAME(from) to FILENAME(to).
    1557             :          *
    1558             :          * This can only happen if we're moving a ccache where only cc config
    1559             :          * entries, or no entries, have been written.  That's not likely.
    1560             :          */
    1561           0 :         if (rk_rename(f->tmpfn, t->filename)) {
    1562           0 :             ret = errno;
    1563             :         } else {
    1564           0 :             free(f->tmpfn);
    1565           0 :             f->tmpfn = NULL;
    1566             :         }
    1567         137 :     } else if (rk_rename(f->filename, t->filename)) {
    1568         137 :         ret = errno;
    1569             :     }
    1570             :     /*
    1571             :      * We need only close from -- we can't destroy it since the rename
    1572             :      * succeeded, which "destroyed" it at its old name.
    1573             :      */
    1574         137 :     if (ret == 0)
    1575           0 :         krb5_cc_close(context, from);
    1576         137 :     return ret;
    1577             : }
    1578             : 
    1579             : static krb5_error_code KRB5_CALLCONV
    1580           0 : fcc_get_default_name(krb5_context context, char **str)
    1581             : {
    1582           0 :     return _krb5_expand_default_cc_name(context,
    1583             :                                         KRB5_DEFAULT_CCNAME_FILE,
    1584             :                                         str);
    1585             : }
    1586             : 
    1587             : static krb5_error_code KRB5_CALLCONV
    1588         113 : fcc_set_default_cache(krb5_context context, krb5_ccache id)
    1589             : {
    1590           0 :     krb5_error_code ret;
    1591           0 :     krb5_ccache dest;
    1592         113 :     char *s = NULL;
    1593             : 
    1594         113 :     if (SUBFILENAME(id) == NULL)
    1595         113 :         return 0; /* Already a primary */
    1596           0 :     if (asprintf(&s, "FILE:%s", RESFILENAME(id)) == -1 || s == NULL)
    1597           0 :         return krb5_enomem(context);
    1598             : 
    1599             :     /*
    1600             :      * We can't hard-link, since we refuse to open ccaches with st_nlink > 1,
    1601             :      * and we can't rename() the ccache because the old name should remain
    1602             :      * available.  Ergo, we copy the ccache.
    1603             :      */
    1604           0 :     ret = krb5_cc_resolve(context, s, &dest);
    1605           0 :     if (ret == 0)
    1606           0 :         ret = krb5_cc_copy_cache(context, id, dest);
    1607           0 :     free(s);
    1608           0 :     if (ret)
    1609           0 :         krb5_set_error_message(context, ret,
    1610           0 :                                N_("Failed to copy subsidiary cache file %s to "
    1611           0 :                                   "default %s", ""), FILENAME(id),
    1612           0 :                                RESFILENAME(id));
    1613           0 :     return ret;
    1614             : }
    1615             : 
    1616             : static krb5_error_code KRB5_CALLCONV
    1617           0 : fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
    1618             : {
    1619           0 :     krb5_error_code ret;
    1620           0 :     struct stat sb;
    1621           0 :     int fd;
    1622             : 
    1623           0 :     ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY, 0);
    1624           0 :     if(ret)
    1625           0 :         return ret;
    1626           0 :     ret = fstat(fd, &sb);
    1627           0 :     close(fd);
    1628           0 :     if (ret) {
    1629           0 :         ret = errno;
    1630           0 :         krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
    1631           0 :         return ret;
    1632             :     }
    1633           0 :     *mtime = sb.st_mtime;
    1634           0 :     return 0;
    1635             : }
    1636             : 
    1637             : static krb5_error_code KRB5_CALLCONV
    1638           0 : fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
    1639             : {
    1640           0 :     return 0;
    1641             : }
    1642             : 
    1643             : static krb5_error_code KRB5_CALLCONV
    1644         197 : fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
    1645             : {
    1646           0 :     krb5_error_code ret;
    1647         197 :     krb5_storage *sp = NULL;
    1648           0 :     int fd;
    1649         197 :     ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
    1650         197 :     if (sp)
    1651         197 :         krb5_storage_free(sp);
    1652         197 :     close(fd);
    1653             : 
    1654         197 :     return ret;
    1655             : }
    1656             : 
    1657             : 
    1658             : /**
    1659             :  * Variable containing the FILE based credential cache implemention.
    1660             :  *
    1661             :  * @ingroup krb5_ccache
    1662             :  */
    1663             : 
    1664             : KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
    1665             :     KRB5_CC_OPS_VERSION_5,
    1666             :     "FILE",
    1667             :     NULL,
    1668             :     NULL,
    1669             :     fcc_gen_new,
    1670             :     fcc_initialize,
    1671             :     fcc_destroy,
    1672             :     fcc_close,
    1673             :     fcc_store_cred,
    1674             :     NULL, /* fcc_retrieve */
    1675             :     fcc_get_principal,
    1676             :     fcc_get_first,
    1677             :     fcc_get_next,
    1678             :     fcc_end_get,
    1679             :     fcc_remove_cred,
    1680             :     fcc_set_flags,
    1681             :     fcc_get_version,
    1682             :     fcc_get_cache_first,
    1683             :     fcc_get_cache_next,
    1684             :     fcc_end_cache_get,
    1685             :     fcc_move,
    1686             :     fcc_get_default_name,
    1687             :     fcc_set_default_cache,
    1688             :     fcc_lastchange,
    1689             :     fcc_set_kdc_offset,
    1690             :     fcc_get_kdc_offset,
    1691             :     fcc_get_name_2,
    1692             :     fcc_resolve_2
    1693             : };

Generated by: LCOV version 1.14