summaryrefslogtreecommitdiff
path: root/src/lib/krb5/ccache
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/krb5/ccache')
-rw-r--r--src/lib/krb5/ccache/Makefile.in159
-rw-r--r--src/lib/krb5/ccache/cc-int.h210
-rw-r--r--src/lib/krb5/ccache/cc_dir.c772
-rw-r--r--src/lib/krb5/ccache/cc_file.c1296
-rw-r--r--src/lib/krb5/ccache/cc_kcm.c1074
-rw-r--r--src/lib/krb5/ccache/cc_keyring.c1755
-rw-r--r--src/lib/krb5/ccache/cc_memory.c772
-rw-r--r--src/lib/krb5/ccache/cc_mslsa.c2209
-rw-r--r--src/lib/krb5/ccache/cc_retr.c280
-rw-r--r--src/lib/krb5/ccache/ccapi/Makefile.in26
-rw-r--r--src/lib/krb5/ccache/ccapi/deps18
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc.c1730
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc.h180
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc_util.c1071
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc_util.h49
-rw-r--r--src/lib/krb5/ccache/ccapi/winccld.c101
-rw-r--r--src/lib/krb5/ccache/ccapi/winccld.h204
-rw-r--r--src/lib/krb5/ccache/ccbase.c579
-rw-r--r--src/lib/krb5/ccache/cccopy.c37
-rw-r--r--src/lib/krb5/ccache/cccursor.c295
-rw-r--r--src/lib/krb5/ccache/ccdefault.c97
-rw-r--r--src/lib/krb5/ccache/ccdefops.c58
-rw-r--r--src/lib/krb5/ccache/ccfns.c331
-rw-r--r--src/lib/krb5/ccache/ccmarshal.c517
-rw-r--r--src/lib/krb5/ccache/ccselect.c179
-rw-r--r--src/lib/krb5/ccache/ccselect_k5identity.c210
-rw-r--r--src/lib/krb5/ccache/ccselect_realm.c95
-rw-r--r--src/lib/krb5/ccache/deps221
-rw-r--r--src/lib/krb5/ccache/fcc.h41
-rw-r--r--src/lib/krb5/ccache/kcmrpc.defs56
-rw-r--r--src/lib/krb5/ccache/kcmrpc_types.h39
-rw-r--r--src/lib/krb5/ccache/scc.h88
-rw-r--r--src/lib/krb5/ccache/ser_cc.c215
-rw-r--r--src/lib/krb5/ccache/t_cc.c439
-rw-r--r--src/lib/krb5/ccache/t_cccol.c363
-rwxr-xr-xsrc/lib/krb5/ccache/t_cccol.py119
-rw-r--r--src/lib/krb5/ccache/t_cccursor.c81
-rw-r--r--src/lib/krb5/ccache/t_marshal.c407
-rw-r--r--src/lib/krb5/ccache/t_memory.c138
-rw-r--r--src/lib/krb5/ccache/t_stdio.c168
40 files changed, 16679 insertions, 0 deletions
diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in
new file mode 100644
index 0000000000000..5ac870728d9d5
--- /dev/null
+++ b/src/lib/krb5/ccache/Makefile.in
@@ -0,0 +1,159 @@
+mydir=lib$(S)krb5$(S)ccache
+BUILDTOP=$(REL)..$(S)..$(S)..
+SUBDIRS = # ccapi
+WINSUBDIRS = ccapi
+##WIN32##DEFINES = -DUSE_CCAPI -DUSE_CCAPI_V3
+
+LOCALINCLUDES = -I$(srcdir)$(S)ccapi -I$(srcdir) -I. $(WIN_INCLUDES)
+
+##DOS##WIN_INCLUDES = -I$(top_srcdir)\windows\lib
+
+##DOS##BUILDTOP = ..\..\..
+##DOS##PREFIXDIR=ccache
+##DOS##OBJFILE=..\$(OUTPRE)$(PREFIXDIR).lst
+
+##WIN32##MSLSA_OBJ = $(OUTPRE)cc_mslsa.$(OBJEXT)
+##WIN32##MSLSA_SRC = $(srcdir)/cc_mslsa.c
+
+##WIN32##!if 0
+KCMRPC_DEPS-osx = kcmrpc.h kcmrpc_types.h
+KCMRPC_OBJ-osx = kcmrpc.o
+KCMRPC_DEPS-no = # empty
+KCMRPC_OBJ-no = # empty
+
+KCMRPC_DEPS = $(KCMRPC_DEPS-@OSX@)
+KCMRPC_OBJ = $(KCMRPC_OBJ-@OSX@)
+##WIN32##!endif
+
+
+STLIBOBJS= \
+ ccbase.o \
+ cccopy.o \
+ cccursor.o \
+ ccdefault.o \
+ ccdefops.o \
+ ccmarshal.o \
+ ccselect.o \
+ ccselect_k5identity.o \
+ ccselect_realm.o \
+ cc_dir.o \
+ cc_retr.o \
+ cc_file.o \
+ cc_kcm.o \
+ cc_memory.o \
+ cc_keyring.o \
+ ccfns.o \
+ ser_cc.o $(KCMRPC_OBJ)
+
+OBJS= $(OUTPRE)ccbase.$(OBJEXT) \
+ $(OUTPRE)cccopy.$(OBJEXT) \
+ $(OUTPRE)cccursor.$(OBJEXT) \
+ $(OUTPRE)ccdefault.$(OBJEXT) \
+ $(OUTPRE)ccdefops.$(OBJEXT) \
+ $(OUTPRE)ccmarshal.$(OBJEXT) \
+ $(OUTPRE)ccselect.$(OBJEXT) \
+ $(OUTPRE)ccselect_k5identity.$(OBJEXT) \
+ $(OUTPRE)ccselect_realm.$(OBJEXT) \
+ $(OUTPRE)cc_dir.$(OBJEXT) \
+ $(OUTPRE)cc_retr.$(OBJEXT) \
+ $(OUTPRE)cc_file.$(OBJEXT) \
+ $(OUTPRE)cc_kcm.$(OBJEXT) \
+ $(OUTPRE)cc_memory.$(OBJEXT) \
+ $(OUTPRE)cc_keyring.$(OBJEXT) \
+ $(OUTPRE)ccfns.$(OBJEXT) \
+ $(OUTPRE)ser_cc.$(OBJEXT) $(MSLSA_OBJ)
+
+SRCS= $(srcdir)/ccbase.c \
+ $(srcdir)/cccopy.c \
+ $(srcdir)/cccursor.c \
+ $(srcdir)/ccdefault.c \
+ $(srcdir)/ccdefops.c \
+ $(srcdir)/ccmarshal.c \
+ $(srcdir)/ccselect.c \
+ $(srcdir)/ccselect_k5identity.c \
+ $(srcdir)/ccselect_realm.c \
+ $(srcdir)/cc_dir.c \
+ $(srcdir)/cc_retr.c \
+ $(srcdir)/cc_file.c \
+ $(srcdir)/cc_kcm.c \
+ $(srcdir)/cc_memory.c \
+ $(srcdir)/cc_keyring.c \
+ $(srcdir)/ccfns.c \
+ $(srcdir)/ser_cc.c $(MSLSA_SRC)
+
+EXTRADEPSRCS= \
+ $(srcdir)/t_cc.c \
+ $(srcdir)/t_cccol.c \
+ $(srcdir)/t_cccursor.c \
+ $(srcdir)/t_marshal.c
+
+##DOS##OBJS=$(OBJS) $(OUTPRE)ccfns.$(OBJEXT)
+
+all-unix: all-libobjs
+
+all-windows: subdirs $(OBJFILE)
+
+##DOS##subdirs: ccapi\$(OUTPRE)file.lst
+
+##DOS##ccapi\$(OUTPRE)file.lst:
+##DOS## cd ccapi
+##DOS## @echo Making in krb5\ccache\ccapi
+##DOS## $(MAKE) -$(MFLAGS)
+##DOS## cd ..
+
+##DOS##$(OBJFILE): $(OBJS) ccapi\$(OUTPRE)file.lst
+##DOS## $(RM) $(OBJFILE)
+##WIN32## $(LIBECHO) -p $(PREFIXDIR)\ $(OUTPRE)*.obj \
+##WIN32## ccapi\$(OUTPRE)*.obj > $(OBJFILE)
+
+kcmrpc.h kcmrpc.c: kcmrpc.defs
+ mig -header kcmrpc.h -user kcmrpc.c -sheader /dev/null \
+ -server /dev/null -I$(srcdir) $(srcdir)/kcmrpc.defs
+
+clean-unix:: clean-libobjs
+
+clean-windows::
+ cd ccapi
+ @echo Making clean in krb5\ccache\ccapi
+ $(MAKE) -$(MFLAGS) clean
+ cd ..
+ @echo Making clean in krb5\ccache
+ $(RM) $(OBJFILE)
+
+T_CC_OBJS=t_cc.o
+
+t_cc: $(T_CC_OBJS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o t_cc $(T_CC_OBJS) $(KRB5_BASE_LIBS)
+
+T_CCCOL_OBJS = t_cccol.o
+t_cccol: $(T_CCCOL_OBJS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ $(T_CCCOL_OBJS) $(KRB5_BASE_LIBS)
+
+T_CCCURSOR_OBJS = t_cccursor.o
+t_cccursor: $(T_CCCURSOR_OBJS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ $(T_CCCURSOR_OBJS) $(KRB5_BASE_LIBS)
+
+T_MARSHAL_OBJS = t_marshal.o
+t_marshal: $(T_MARSHAL_OBJS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ $(T_MARSHAL_OBJS) $(KRB5_BASE_LIBS)
+
+check-unix: t_cc t_marshal
+ $(RUN_TEST) ./t_cc
+ $(RUN_TEST) ./t_marshal testcache
+
+check-pytests: t_cccursor t_cccol
+ $(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS)
+
+clean-unix::
+ $(RM) t_cc t_cc.o t_cccursor t_cccursor.o t_cccol t_cccol.o
+ $(RM) t_marshal t_marshal.o testcache kcmrpc.c kcmrpc.h
+
+depend: $(KCMRPC_DEPS)
+
+##WIN32##$(OUTPRE)cc_mslsa.$(OBJEXT): cc_mslsa.c $(top_srcdir)/include/k5-int.h $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS)
+
+cc_kcm.so cc_kcm.o: $(KCMRPC_DEPS)
+kcmrpc.so kcmrpc.o: kcmrpc.h kcmrpc_types.h
+
+@libobj_frag@
+
diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h
new file mode 100644
index 0000000000000..ee9b5e0e97a1a
--- /dev/null
+++ b/src/lib/krb5/ccache/cc-int.h
@@ -0,0 +1,210 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc-int.h */
+/*
+ * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/* This file contains constant and function declarations used in the
+ * file-based credential cache routines. */
+
+#ifndef __KRB5_CCACHE_H__
+#define __KRB5_CCACHE_H__
+
+#include "k5-int.h"
+
+struct _krb5_ccache {
+ krb5_magic magic;
+ const struct _krb5_cc_ops *ops;
+ krb5_pointer data;
+};
+
+krb5_error_code
+k5_cc_retrieve_cred_default(krb5_context, krb5_ccache, krb5_flags,
+ krb5_creds *, krb5_creds *);
+
+krb5_boolean
+krb5int_cc_creds_match_request(krb5_context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds);
+
+int
+krb5int_cc_initialize(void);
+
+void
+krb5int_cc_finalize(void);
+
+/*
+ * Cursor for iterating over ccache types
+ */
+struct krb5_cc_typecursor;
+typedef struct krb5_cc_typecursor *krb5_cc_typecursor;
+
+krb5_error_code
+krb5int_cc_typecursor_new(krb5_context context, krb5_cc_typecursor *cursor);
+
+krb5_error_code
+krb5int_cc_typecursor_next(
+ krb5_context context,
+ krb5_cc_typecursor cursor,
+ const struct _krb5_cc_ops **ops);
+
+krb5_error_code
+krb5int_cc_typecursor_free(
+ krb5_context context,
+ krb5_cc_typecursor *cursor);
+
+/* reentrant mutex used by krb5_cc_* functions */
+typedef struct _k5_cc_mutex {
+ k5_mutex_t lock;
+ krb5_context owner;
+ krb5_int32 refcount;
+} k5_cc_mutex;
+
+#define K5_CC_MUTEX_PARTIAL_INITIALIZER \
+ { K5_MUTEX_PARTIAL_INITIALIZER, NULL, 0 }
+
+krb5_error_code
+k5_cc_mutex_init(k5_cc_mutex *m);
+
+krb5_error_code
+k5_cc_mutex_finish_init(k5_cc_mutex *m);
+
+#define k5_cc_mutex_destroy(M) \
+ k5_mutex_destroy(&(M)->lock);
+
+void
+k5_cc_mutex_assert_locked(krb5_context context, k5_cc_mutex *m);
+
+void
+k5_cc_mutex_assert_unlocked(krb5_context context, k5_cc_mutex *m);
+
+void
+k5_cc_mutex_lock(krb5_context context, k5_cc_mutex *m);
+
+void
+k5_cc_mutex_unlock(krb5_context context, k5_cc_mutex *m);
+
+extern k5_cc_mutex krb5int_mcc_mutex;
+extern k5_cc_mutex krb5int_krcc_mutex;
+extern k5_cc_mutex krb5int_cc_file_mutex;
+
+#ifdef USE_CCAPI_V3
+extern krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_lock
+(krb5_context context);
+
+extern krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_unlock
+(krb5_context context);
+#endif
+
+void
+k5_cc_mutex_force_unlock(k5_cc_mutex *m);
+
+void
+k5_cccol_force_unlock(void);
+
+krb5_error_code
+krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id);
+
+krb5_error_code
+ccselect_realm_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+krb5_error_code
+ccselect_k5identity_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable);
+
+krb5_error_code
+k5_unmarshal_cred(const unsigned char *data, size_t len, int version,
+ krb5_creds *creds);
+
+krb5_error_code
+k5_unmarshal_princ(const unsigned char *data, size_t len, int version,
+ krb5_principal *princ_out);
+
+void
+k5_marshal_cred(struct k5buf *buf, int version, krb5_creds *creds);
+
+void
+k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred);
+
+void
+k5_marshal_princ(struct k5buf *buf, int version, krb5_principal princ);
+
+/*
+ * Per-type ccache cursor.
+ */
+struct krb5_cc_ptcursor_s {
+ const struct _krb5_cc_ops *ops;
+ krb5_pointer data;
+};
+typedef struct krb5_cc_ptcursor_s *krb5_cc_ptcursor;
+
+struct _krb5_cc_ops {
+ krb5_magic magic;
+ char *prefix;
+ const char * (KRB5_CALLCONV *get_name)(krb5_context, krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *resolve)(krb5_context, krb5_ccache *,
+ const char *);
+ krb5_error_code (KRB5_CALLCONV *gen_new)(krb5_context, krb5_ccache *);
+ krb5_error_code (KRB5_CALLCONV *init)(krb5_context, krb5_ccache,
+ krb5_principal);
+ krb5_error_code (KRB5_CALLCONV *destroy)(krb5_context, krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *close)(krb5_context, krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *store)(krb5_context, krb5_ccache,
+ krb5_creds *);
+ krb5_error_code (KRB5_CALLCONV *retrieve)(krb5_context, krb5_ccache,
+ krb5_flags, krb5_creds *,
+ krb5_creds *);
+ krb5_error_code (KRB5_CALLCONV *get_princ)(krb5_context, krb5_ccache,
+ krb5_principal *);
+ krb5_error_code (KRB5_CALLCONV *get_first)(krb5_context, krb5_ccache,
+ krb5_cc_cursor *);
+ krb5_error_code (KRB5_CALLCONV *get_next)(krb5_context, krb5_ccache,
+ krb5_cc_cursor *, krb5_creds *);
+ krb5_error_code (KRB5_CALLCONV *end_get)(krb5_context, krb5_ccache,
+ krb5_cc_cursor *);
+ krb5_error_code (KRB5_CALLCONV *remove_cred)(krb5_context, krb5_ccache,
+ krb5_flags, krb5_creds *);
+ krb5_error_code (KRB5_CALLCONV *set_flags)(krb5_context, krb5_ccache,
+ krb5_flags);
+ krb5_error_code (KRB5_CALLCONV *get_flags)(krb5_context, krb5_ccache,
+ krb5_flags *);
+ krb5_error_code (KRB5_CALLCONV *ptcursor_new)(krb5_context,
+ krb5_cc_ptcursor *);
+ krb5_error_code (KRB5_CALLCONV *ptcursor_next)(krb5_context,
+ krb5_cc_ptcursor,
+ krb5_ccache *);
+ krb5_error_code (KRB5_CALLCONV *ptcursor_free)(krb5_context,
+ krb5_cc_ptcursor *);
+ krb5_error_code (KRB5_CALLCONV *move)(krb5_context, krb5_ccache,
+ krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *lastchange)(krb5_context,
+ krb5_ccache, krb5_timestamp *);
+ krb5_error_code (KRB5_CALLCONV *wasdefault)(krb5_context, krb5_ccache,
+ krb5_timestamp *);
+ krb5_error_code (KRB5_CALLCONV *lock)(krb5_context, krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *unlock)(krb5_context, krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *switch_to)(krb5_context, krb5_ccache);
+};
+
+extern const krb5_cc_ops *krb5_cc_dfl_ops;
+
+#endif /* __KRB5_CCACHE_H__ */
diff --git a/src/lib/krb5/ccache/cc_dir.c b/src/lib/krb5/ccache/cc_dir.c
new file mode 100644
index 0000000000000..bba64e516f969
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_dir.c
@@ -0,0 +1,772 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_dir.c - Directory-based credential cache collection */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * This credential cache type represents a set of file-based caches with a
+ * switchable primary cache. An alternate form of the type represents a
+ * subsidiary file cache within the directory.
+ *
+ * A cache name of the form DIR:dirname identifies a directory containing the
+ * cache set. Resolving a name of this form results in dirname's primary
+ * cache. If a context's default cache is of this form, the global cache
+ * collection will contain dirname's cache set, and new unique caches of type
+ * DIR will be created within dirname.
+ *
+ * A cache name of the form DIR::filepath represents a single cache within the
+ * directory. Switching to a ccache of this type causes the directory's
+ * primary cache to be set to the named cache.
+ *
+ * Within the directory, cache names begin with 'tkt'. The file "primary"
+ * contains a single line naming the primary cache. The directory must already
+ * exist when the DIR ccache is resolved, but the primary file will be created
+ * automatically if it does not exist.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+/* This is Unix-only for now. To work on Windows, we will need opendir/readdir
+ * replacements and possibly more flexible newline handling. */
+#ifndef _WIN32
+
+#include <dirent.h>
+
+extern const krb5_cc_ops krb5_dcc_ops;
+extern const krb5_cc_ops krb5_fcc_ops;
+
+/* Fields are not modified after creation, so no lock is necessary. */
+typedef struct dcc_data_st {
+ char *residual; /* dirname or :filename */
+ krb5_ccache fcc; /* File cache for actual cache ops */
+} dcc_data;
+
+static inline krb5_boolean
+filename_is_cache(const char *filename)
+{
+ return (strncmp(filename, "tkt", 3) == 0);
+}
+
+/* Compose the pathname of the primary file within a cache directory. */
+static inline krb5_error_code
+primary_pathname(const char *dirname, char **path_out)
+{
+ return k5_path_join(dirname, "primary", path_out);
+}
+
+/* Compose a residual string for a subsidiary path with the specified directory
+ * name and filename. */
+static krb5_error_code
+subsidiary_residual(const char *dirname, const char *filename, char **out)
+{
+ krb5_error_code ret;
+ char *path, *residual;
+
+ *out = NULL;
+ ret = k5_path_join(dirname, filename, &path);
+ if (ret)
+ return ret;
+ ret = asprintf(&residual, ":%s", path);
+ free(path);
+ if (ret < 0)
+ return ENOMEM;
+ *out = residual;
+ return 0;
+}
+
+static inline krb5_error_code
+split_path(krb5_context context, const char *path, char **dirname_out,
+ char **filename_out)
+{
+ krb5_error_code ret;
+ char *dirname, *filename;
+
+ *dirname_out = NULL;
+ *filename_out = NULL;
+ ret = k5_path_split(path, &dirname, &filename);
+ if (ret)
+ return ret;
+
+ if (*dirname == '\0') {
+ ret = KRB5_CC_BADNAME;
+ k5_setmsg(context, ret,
+ _("Subsidiary cache path %s has no parent directory"), path);
+ goto error;
+ }
+ if (!filename_is_cache(filename)) {
+ ret = KRB5_CC_BADNAME;
+ k5_setmsg(context, ret,
+ _("Subsidiary cache path %s filename does not begin with "
+ "\"tkt\""), path);
+ goto error;
+ }
+
+ *dirname_out = dirname;
+ *filename_out = filename;
+ return 0;
+
+error:
+ free(dirname);
+ free(filename);
+ return ret;
+}
+
+/* Read the primary file and compose the residual string for the primary
+ * subsidiary cache file. */
+static krb5_error_code
+read_primary_file(krb5_context context, const char *primary_path,
+ const char *dirname, char **residual_out)
+{
+ FILE *fp;
+ char buf[64], *ret;
+ size_t len;
+
+ *residual_out = NULL;
+
+ /* Open the file and read its first line. */
+ fp = fopen(primary_path, "r");
+ if (fp == NULL)
+ return ENOENT;
+ ret = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (ret == NULL)
+ return KRB5_CC_IO;
+ len = strlen(buf);
+
+ /* Check if line is too long, doesn't look like a subsidiary cache
+ * filename, or isn't a single-component filename. */
+ if (buf[len - 1] != '\n' || !filename_is_cache(buf) ||
+ strchr(buf, '/') || strchr(buf, '\\')) {
+ k5_setmsg(context, KRB5_CC_FORMAT, _("%s contains invalid filename"),
+ primary_path);
+ return KRB5_CC_FORMAT;
+ }
+ buf[len - 1] = '\0';
+
+ return subsidiary_residual(dirname, buf, residual_out);
+}
+
+/* Create or update the primary file with a line containing contents. */
+static krb5_error_code
+write_primary_file(const char *primary_path, const char *contents)
+{
+ krb5_error_code ret = KRB5_CC_IO;
+ char *newpath = NULL;
+ FILE *fp = NULL;
+ int fd = -1, status;
+
+ if (asprintf(&newpath, "%s.XXXXXX", primary_path) < 0)
+ return ENOMEM;
+ fd = mkstemp(newpath);
+ if (fd < 0)
+ goto cleanup;
+#ifdef HAVE_CHMOD
+ chmod(newpath, S_IRUSR | S_IWUSR);
+#endif
+ fp = fdopen(fd, "w");
+ if (fp == NULL)
+ goto cleanup;
+ fd = -1;
+ if (fprintf(fp, "%s\n", contents) < 0)
+ goto cleanup;
+ status = fclose(fp);
+ fp = NULL;
+ if (status == EOF)
+ goto cleanup;
+ fp = NULL;
+ if (rename(newpath, primary_path) != 0)
+ goto cleanup;
+ ret = 0;
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ if (fp != NULL)
+ fclose(fp);
+ free(newpath);
+ return ret;
+}
+
+/* Verify or create a cache directory path. */
+static krb5_error_code
+verify_dir(krb5_context context, const char *dirname)
+{
+ struct stat st;
+
+ if (stat(dirname, &st) < 0) {
+ if (errno == ENOENT && mkdir(dirname, S_IRWXU) == 0)
+ return 0;
+ k5_setmsg(context, KRB5_FCC_NOFILE,
+ _("Credential cache directory %s does not exist"),
+ dirname);
+ return KRB5_FCC_NOFILE;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ k5_setmsg(context, KRB5_CC_FORMAT,
+ _("Credential cache directory %s exists but is not a "
+ "directory"), dirname);
+ return KRB5_CC_FORMAT;
+ }
+ return 0;
+}
+
+/*
+ * If the default ccache name for context is a directory collection, set
+ * *dirname_out to the directory name for that collection. Otherwise set
+ * *dirname_out to NULL.
+ */
+static krb5_error_code
+get_context_default_dir(krb5_context context, char **dirname_out)
+{
+ const char *defname;
+ char *dirname;
+
+ *dirname_out = NULL;
+ defname = krb5_cc_default_name(context);
+ if (defname == NULL)
+ return 0;
+ if (strncmp(defname, "DIR:", 4) != 0 ||
+ defname[4] == ':' || defname[4] == '\0')
+ return 0;
+ dirname = strdup(defname + 4);
+ if (dirname == NULL)
+ return ENOMEM;
+ *dirname_out = dirname;
+ return 0;
+}
+
+/*
+ * If the default ccache name for context is a subsidiary file in a directory
+ * collection, set *subsidiary_out to the residual value. Otherwise set
+ * *subsidiary_out to NULL.
+ */
+static krb5_error_code
+get_context_subsidiary_file(krb5_context context, char **subsidiary_out)
+{
+ const char *defname;
+ char *residual;
+
+ *subsidiary_out = NULL;
+ defname = krb5_cc_default_name(context);
+ if (defname == NULL || strncmp(defname, "DIR::", 5) != 0)
+ return 0;
+ residual = strdup(defname + 4);
+ if (residual == NULL)
+ return ENOMEM;
+ *subsidiary_out = residual;
+ return 0;
+}
+
+static const char * KRB5_CALLCONV
+dcc_get_name(krb5_context context, krb5_ccache cache)
+{
+ dcc_data *data = cache->data;
+
+ return data->residual;
+}
+
+/* Construct a cache object given a residual string and file ccache. Take
+ * ownership of fcc on success. */
+static krb5_error_code
+make_cache(const char *residual, krb5_ccache fcc, krb5_ccache *cache_out)
+{
+ krb5_ccache cache = NULL;
+ dcc_data *data = NULL;
+ char *residual_copy = NULL;
+
+ cache = malloc(sizeof(*cache));
+ if (cache == NULL)
+ goto oom;
+ data = malloc(sizeof(*data));
+ if (data == NULL)
+ goto oom;
+ residual_copy = strdup(residual);
+ if (residual_copy == NULL)
+ goto oom;
+
+ data->residual = residual_copy;
+ data->fcc = fcc;
+ cache->ops = &krb5_dcc_ops;
+ cache->data = data;
+ cache->magic = KV5M_CCACHE;
+ *cache_out = cache;
+ return 0;
+
+oom:
+ free(cache);
+ free(data);
+ free(residual_copy);
+ return ENOMEM;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
+{
+ krb5_error_code ret;
+ krb5_ccache fcc;
+ char *primary_path = NULL, *sresidual = NULL, *dirname, *filename;
+
+ *cache_out = NULL;
+
+ if (*residual == ':') {
+ /* This is a subsidiary cache within the directory. */
+ ret = split_path(context, residual + 1, &dirname, &filename);
+ if (ret)
+ return ret;
+
+ ret = verify_dir(context, dirname);
+ free(dirname);
+ free(filename);
+ if (ret)
+ return ret;
+ } else {
+ /* This is the directory itself; resolve to the primary cache. */
+ ret = verify_dir(context, residual);
+ if (ret)
+ return ret;
+
+ ret = primary_pathname(residual, &primary_path);
+ if (ret)
+ goto cleanup;
+
+ ret = read_primary_file(context, primary_path, residual, &sresidual);
+ if (ret == ENOENT) {
+ /* Create an initial primary file. */
+ ret = write_primary_file(primary_path, "tkt");
+ if (ret)
+ goto cleanup;
+ ret = subsidiary_residual(residual, "tkt", &sresidual);
+ }
+ if (ret)
+ goto cleanup;
+ residual = sresidual;
+ }
+
+ ret = krb5_fcc_ops.resolve(context, &fcc, residual + 1);
+ if (ret)
+ goto cleanup;
+ ret = make_cache(residual, fcc, cache_out);
+ if (ret)
+ krb5_fcc_ops.close(context, fcc);
+
+cleanup:
+ free(primary_path);
+ free(sresidual);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_gen_new(krb5_context context, krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ char *dirname = NULL, *template = NULL, *residual = NULL;
+ krb5_ccache fcc = NULL;
+
+ *cache_out = NULL;
+ ret = get_context_default_dir(context, &dirname);
+ if (ret)
+ return ret;
+ if (dirname == NULL) {
+ k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
+ _("Can't create new subsidiary cache because default cache "
+ "is not a directory collection"));
+ return KRB5_DCC_CANNOT_CREATE;
+ }
+ ret = verify_dir(context, dirname);
+ if (ret)
+ goto cleanup;
+ ret = k5_path_join(dirname, "tktXXXXXX", &template);
+ if (ret)
+ goto cleanup;
+ ret = krb5int_fcc_new_unique(context, template, &fcc);
+ if (ret)
+ goto cleanup;
+ if (asprintf(&residual, ":%s", template) < 0) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ ret = make_cache(residual, fcc, cache_out);
+ if (ret)
+ goto cleanup;
+ fcc = NULL;
+
+cleanup:
+ if (fcc != NULL)
+ krb5_fcc_ops.destroy(context, fcc);
+ free(dirname);
+ free(template);
+ free(residual);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_init(krb5_context context, krb5_ccache cache, krb5_principal princ)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.init(context, data->fcc, princ);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_destroy(krb5_context context, krb5_ccache cache)
+{
+ dcc_data *data = cache->data;
+ krb5_error_code ret;
+
+ ret = krb5_fcc_ops.destroy(context, data->fcc);
+ free(data->residual);
+ free(data);
+ free(cache);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_close(krb5_context context, krb5_ccache cache)
+{
+ dcc_data *data = cache->data;
+ krb5_error_code ret;
+
+ ret = krb5_fcc_ops.close(context, data->fcc);
+ free(data->residual);
+ free(data);
+ free(cache);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_store(krb5_context context, krb5_ccache cache, krb5_creds *creds)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.store(context, data->fcc, creds);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *mcreds, krb5_creds *creds)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.retrieve(context, data->fcc, flags, mcreds,
+ creds);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_get_princ(krb5_context context, krb5_ccache cache,
+ krb5_principal *princ_out)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.get_princ(context, data->fcc, princ_out);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_get_first(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.get_first(context, data->fcc, cursor);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_get_next(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
+ krb5_creds *creds)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.get_next(context, data->fcc, cursor, creds);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_end_get(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.end_get(context, data->fcc, cursor);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *creds)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.remove_cred(context, data->fcc, flags, creds);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.set_flags(context, data->fcc, flags);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.get_flags(context, data->fcc, flags_out);
+}
+
+struct dcc_ptcursor_data {
+ char *primary;
+ char *dirname;
+ DIR *dir;
+ krb5_boolean first;
+};
+
+/* Construct a cursor, taking ownership of dirname, primary, and dir on
+ * success. */
+static krb5_error_code
+make_cursor(char *dirname, char *primary, DIR *dir,
+ krb5_cc_ptcursor *cursor_out)
+{
+ krb5_cc_ptcursor cursor;
+ struct dcc_ptcursor_data *data;
+
+ *cursor_out = NULL;
+
+ data = malloc(sizeof(*data));
+ if (data == NULL)
+ return ENOMEM;
+ cursor = malloc(sizeof(*cursor));
+ if (cursor == NULL) {
+ free(data);
+ return ENOMEM;
+ }
+
+ data->dirname = dirname;
+ data->primary = primary;
+ data->dir = dir;
+ data->first = TRUE;
+ cursor->ops = &krb5_dcc_ops;
+ cursor->data = data;
+ *cursor_out = cursor;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
+{
+ krb5_error_code ret;
+ char *dirname = NULL, *primary_path = NULL, *primary = NULL;
+ DIR *dir = NULL;
+
+ *cursor_out = NULL;
+
+ /* If the default cache is a subsidiary file, make a cursor with the
+ * specified file as the primary but with no directory collection. */
+ ret = get_context_subsidiary_file(context, &primary);
+ if (ret)
+ goto cleanup;
+ if (primary != NULL) {
+ ret = make_cursor(NULL, primary, NULL, cursor_out);
+ if (ret)
+ free(primary);
+ return ret;
+ }
+
+ /* Open the directory for the context's default cache. */
+ ret = get_context_default_dir(context, &dirname);
+ if (ret || dirname == NULL)
+ goto cleanup;
+ dir = opendir(dirname);
+ if (dir == NULL)
+ goto cleanup;
+
+ /* Fetch the primary cache name if possible. */
+ ret = primary_pathname(dirname, &primary_path);
+ if (ret)
+ goto cleanup;
+ ret = read_primary_file(context, primary_path, dirname, &primary);
+ if (ret)
+ krb5_clear_error_message(context);
+
+ ret = make_cursor(dirname, primary, dir, cursor_out);
+ if (ret)
+ goto cleanup;
+ dirname = primary = NULL;
+ dir = NULL;
+
+cleanup:
+ free(dirname);
+ free(primary_path);
+ free(primary);
+ if (dir)
+ closedir(dir);
+ /* Return an empty cursor if we fail for any reason. */
+ if (*cursor_out == NULL)
+ return make_cursor(NULL, NULL, NULL, cursor_out);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
+ krb5_ccache *cache_out)
+{
+ struct dcc_ptcursor_data *data = cursor->data;
+ struct dirent *ent;
+ char *residual;
+ krb5_error_code ret;
+ struct stat sb;
+
+ *cache_out = NULL;
+
+ /* Return the primary or specified subsidiary cache if we haven't yet. */
+ if (data->first) {
+ data->first = FALSE;
+ if (data->primary != NULL && stat(data->primary + 1, &sb) == 0)
+ return dcc_resolve(context, cache_out, data->primary);
+ }
+
+ if (data->dir == NULL) /* No directory collection */
+ return 0;
+
+ /* Look for the next filename of the correct form, without repeating the
+ * primary cache. */
+ while ((ent = readdir(data->dir)) != NULL) {
+ if (!filename_is_cache(ent->d_name))
+ continue;
+ ret = subsidiary_residual(data->dirname, ent->d_name, &residual);
+ if (ret)
+ return ret;
+ if (data->primary != NULL && strcmp(residual, data->primary) == 0) {
+ free(residual);
+ continue;
+ }
+ ret = dcc_resolve(context, cache_out, residual);
+ free(residual);
+ return ret;
+ }
+
+ /* We exhausted the directory without finding a cache to yield. */
+ closedir(data->dir);
+ data->dir = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ struct dcc_ptcursor_data *data = (*cursor)->data;
+
+ if (data->dir)
+ closedir(data->dir);
+ free(data->dirname);
+ free(data->primary);
+ free(data);
+ free(*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_lastchange(krb5_context context, krb5_ccache cache,
+ krb5_timestamp *time_out)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.lastchange(context, data->fcc, time_out);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_lock(krb5_context context, krb5_ccache cache)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.lock(context, data->fcc);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_unlock(krb5_context context, krb5_ccache cache)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.unlock(context, data->fcc);
+}
+
+static krb5_error_code KRB5_CALLCONV
+dcc_switch_to(krb5_context context, krb5_ccache cache)
+{
+ dcc_data *data = cache->data;
+ char *primary_path = NULL, *dirname = NULL, *filename = NULL;
+ krb5_error_code ret;
+
+ ret = split_path(context, data->residual + 1, &dirname, &filename);
+ if (ret)
+ return ret;
+
+ ret = primary_pathname(dirname, &primary_path);
+ if (ret)
+ goto cleanup;
+
+ ret = write_primary_file(primary_path, filename);
+
+cleanup:
+ free(primary_path);
+ free(dirname);
+ free(filename);
+ return ret;
+}
+
+const krb5_cc_ops krb5_dcc_ops = {
+ 0,
+ "DIR",
+ dcc_get_name,
+ dcc_resolve,
+ dcc_gen_new,
+ dcc_init,
+ dcc_destroy,
+ dcc_close,
+ dcc_store,
+ dcc_retrieve,
+ dcc_get_princ,
+ dcc_get_first,
+ dcc_get_next,
+ dcc_end_get,
+ dcc_remove_cred,
+ dcc_set_flags,
+ dcc_get_flags,
+ dcc_ptcursor_new,
+ dcc_ptcursor_next,
+ dcc_ptcursor_free,
+ NULL, /* move */
+ dcc_lastchange,
+ NULL, /* wasdefault */
+ dcc_lock,
+ dcc_unlock,
+ dcc_switch_to,
+};
+
+#endif /* not _WIN32 */
diff --git a/src/lib/krb5/ccache/cc_file.c b/src/lib/krb5/ccache/cc_file.c
new file mode 100644
index 0000000000000..6789c09e189c5
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_file.c
@@ -0,0 +1,1296 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_file.c - File-based credential cache */
+/*
+ * Copyright 1990,1991,1992,1993,1994,2000,2004,2007 Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Original stdio support copyright 1995 by Cygnus Support.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * A psuedo-BNF grammar for the FILE credential cache format is:
+ *
+ * file ::=
+ * version (2 bytes; 05 01 for version 1 through 05 04 for version 4)
+ * header [not present before version 4]
+ * principal
+ * credential1
+ * credential2
+ * ...
+ *
+ * header ::=
+ * headerlen (16 bits)
+ * header1tag (16 bits)
+ * header1len (16 bits)
+ * header1val (header1len bytes)
+ *
+ * See ccmarshal.c for the principal and credential formats. Although versions
+ * 1 and 2 of the FILE format use native byte order for integer representations
+ * within principals and credentials, the integer fields in the grammar above
+ * are always in big-endian byte order.
+ *
+ * Only one header tag is currently defined. The tag value is 1
+ * (FCC_TAG_DELTATIME), and its contents are two 32-bit integers giving the
+ * seconds and microseconds of the time offset of the KDC relative to the
+ * client.
+ *
+ * Each of the file ccache functions opens and closes the file whenever it
+ * needs to access it.
+ *
+ * This module depends on UNIX-like file descriptors, and UNIX-like behavior
+ * from the functions: open, close, read, write, lseek.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+extern const krb5_cc_ops krb5_cc_file_ops;
+
+krb5_error_code krb5_change_cache(void);
+
+static krb5_error_code interpret_errno(krb5_context, int);
+
+/* The cache format version is a positive integer, represented in the cache
+ * file as a two-byte big endian number with 0x0500 added to it. */
+#define FVNO_BASE 0x0500
+
+#define FCC_TAG_DELTATIME 1
+
+#ifndef TKT_ROOT
+#ifdef MSDOS_FILESYSTEM
+#define TKT_ROOT "\\tkt"
+#else
+#define TKT_ROOT "/tmp/tkt"
+#endif
+#endif
+
+typedef struct fcc_data_st {
+ k5_cc_mutex lock;
+ char *filename;
+} fcc_data;
+
+/* Iterator over file caches. */
+struct krb5_fcc_ptcursor_data {
+ krb5_boolean first;
+};
+
+/* Iterator over a cache. */
+typedef struct _krb5_fcc_cursor {
+ FILE *fp;
+ int version;
+} krb5_fcc_cursor;
+
+k5_cc_mutex krb5int_cc_file_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+
+/* Add fname to the standard error message for ret. */
+static krb5_error_code
+set_errmsg_filename(krb5_context context, krb5_error_code ret,
+ const char *fname)
+{
+ if (!ret)
+ return 0;
+ k5_setmsg(context, ret, "%s (filename: %s)", error_message(ret), fname);
+ return ret;
+}
+
+/* Get the size of the cache file as a size_t, or SIZE_MAX if it is too
+ * large to be represented as a size_t. */
+static krb5_error_code
+get_size(krb5_context context, FILE *fp, size_t *size_out)
+{
+ struct stat sb;
+
+ *size_out = 0;
+ if (fstat(fileno(fp), &sb) == -1)
+ return interpret_errno(context, errno);
+ if (sizeof(off_t) > sizeof(size_t) && sb.st_size > (off_t)SIZE_MAX)
+ *size_out = SIZE_MAX;
+ else
+ *size_out = sb.st_size;
+ return 0;
+}
+
+/* Read len bytes from fp, storing them in buf. Return KRB5_CC_END
+ * if not enough bytes are present. */
+static krb5_error_code
+read_bytes(krb5_context context, FILE *fp, void *buf, size_t len)
+{
+ size_t nread;
+
+ nread = fread(buf, 1, len, fp);
+ if (nread < len)
+ return ferror(fp) ? errno : KRB5_CC_END;
+ return 0;
+}
+
+/* Load four bytes from the cache file. Add them to buf (if set) and return
+ * their value as a 32-bit unsigned integer according to the file format. */
+static krb5_error_code
+read32(krb5_context context, FILE *fp, int version, struct k5buf *buf,
+ uint32_t *out)
+{
+ krb5_error_code ret;
+ char bytes[4];
+
+ ret = read_bytes(context, fp, bytes, 4);
+ if (ret)
+ return ret;
+ if (buf != NULL)
+ k5_buf_add_len(buf, bytes, 4);
+ *out = (version < 3) ? load_32_n(bytes) : load_32_be(bytes);
+ return 0;
+}
+
+/* Load two bytes from the cache file and return their value as a 16-bit
+ * unsigned integer according to the file format. */
+static krb5_error_code
+read16(krb5_context context, FILE *fp, int version, uint16_t *out)
+{
+ krb5_error_code ret;
+ char bytes[2];
+
+ ret = read_bytes(context, fp, bytes, 2);
+ if (ret)
+ return ret;
+ *out = (version < 3) ? load_16_n(bytes) : load_16_be(bytes);
+ return 0;
+}
+
+/* Read len bytes from the cache file and add them to buf. */
+static krb5_error_code
+load_bytes(krb5_context context, FILE *fp, size_t len, struct k5buf *buf)
+{
+ void *ptr;
+
+ ptr = k5_buf_get_space(buf, len);
+ return (ptr == NULL) ? KRB5_CC_NOMEM : read_bytes(context, fp, ptr, len);
+}
+
+/* Load a 32-bit length and data from the cache file into buf, but not more
+ * than maxsize bytes. */
+static krb5_error_code
+load_data(krb5_context context, FILE *fp, int version, size_t maxsize,
+ struct k5buf *buf)
+{
+ krb5_error_code ret;
+ uint32_t count;
+
+ ret = read32(context, fp, version, buf, &count);
+ if (ret)
+ return ret;
+ if (count > maxsize)
+ return KRB5_CC_FORMAT;
+ return load_bytes(context, fp, count, buf);
+}
+
+/* Load a marshalled principal from the cache file into buf, without
+ * unmarshalling it. */
+static krb5_error_code
+load_principal(krb5_context context, FILE *fp, int version, size_t maxsize,
+ struct k5buf *buf)
+{
+ krb5_error_code ret;
+ uint32_t count;
+
+ if (version > 1) {
+ ret = load_bytes(context, fp, 4, buf);
+ if (ret)
+ return ret;
+ }
+ ret = read32(context, fp, version, buf, &count);
+ if (ret)
+ return ret;
+ /* Add one for the realm (except in version 1 which already counts it). */
+ if (version != 1)
+ count++;
+ while (count-- > 0) {
+ ret = load_data(context, fp, version, maxsize, buf);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* Load a marshalled credential from the cache file into buf, without
+ * unmarshalling it. */
+static krb5_error_code
+load_cred(krb5_context context, FILE *fp, int version, size_t maxsize,
+ struct k5buf *buf)
+{
+ krb5_error_code ret;
+ uint32_t count, i;
+
+ /* client and server */
+ ret = load_principal(context, fp, version, maxsize, buf);
+ if (ret)
+ return ret;
+ ret = load_principal(context, fp, version, maxsize, buf);
+ if (ret)
+ return ret;
+
+ /* keyblock (enctype, enctype again for version 3, length, value) */
+ ret = load_bytes(context, fp, (version == 3) ? 4 : 2, buf);
+ if (ret)
+ return ret;
+ ret = load_data(context, fp, version, maxsize, buf);
+ if (ret)
+ return ret;
+
+ /* times (4*4 bytes), is_skey (1 byte), ticket flags (4 bytes) */
+ ret = load_bytes(context, fp, 4 * 4 + 1 + 4, buf);
+ if (ret)
+ return ret;
+
+ /* addresses and authdata, both lists of {type, length, data} */
+ for (i = 0; i < 2; i++) {
+ ret = read32(context, fp, version, buf, &count);
+ if (ret)
+ return ret;
+ while (count-- > 0) {
+ ret = load_bytes(context, fp, 2, buf);
+ if (ret)
+ return ret;
+ ret = load_data(context, fp, version, maxsize, buf);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /* ticket and second_ticket */
+ ret = load_data(context, fp, version, maxsize, buf);
+ if (ret)
+ return ret;
+ return load_data(context, fp, version, maxsize, buf);
+}
+
+static krb5_error_code
+read_principal(krb5_context context, FILE *fp, int version,
+ krb5_principal *princ)
+{
+ krb5_error_code ret;
+ struct k5buf buf;
+ size_t maxsize;
+
+ *princ = NULL;
+ k5_buf_init_dynamic(&buf);
+
+ /* Read the principal representation into memory. */
+ ret = get_size(context, fp, &maxsize);
+ if (ret)
+ goto cleanup;
+ ret = load_principal(context, fp, version, maxsize, &buf);
+ if (ret)
+ goto cleanup;
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto cleanup;
+
+ /* Unmarshal it from buf into princ. */
+ ret = k5_unmarshal_princ(buf.data, buf.len, version, princ);
+
+cleanup:
+ k5_buf_free(&buf);
+ return ret;
+}
+
+/*
+ * Open and lock an existing cache file. If writable is true, open it for
+ * writing (with O_APPEND) and get an exclusive lock; otherwise open it for
+ * reading and get a shared lock.
+ */
+static krb5_error_code
+open_cache_file(krb5_context context, const char *filename,
+ krb5_boolean writable, FILE **fp_out)
+{
+ krb5_error_code ret;
+ int fd, flags, lockmode;
+ FILE *fp;
+
+ *fp_out = NULL;
+
+ flags = writable ? (O_RDWR | O_APPEND) : O_RDONLY;
+ fd = open(filename, flags | O_BINARY | O_CLOEXEC, 0600);
+ if (fd == -1)
+ return interpret_errno(context, errno);
+ set_cloexec_fd(fd);
+
+ lockmode = writable ? KRB5_LOCKMODE_EXCLUSIVE : KRB5_LOCKMODE_SHARED;
+ ret = krb5_lock_file(context, fd, lockmode);
+ if (ret) {
+ (void)close(fd);
+ return ret;
+ }
+
+ fp = fdopen(fd, writable ? "r+b" : "rb");
+ if (fp == NULL) {
+ (void)krb5_unlock_file(context, fd);
+ (void)close(fd);
+ return KRB5_CC_NOMEM;
+ }
+
+ *fp_out = fp;
+ return 0;
+}
+
+/* Unlock and close the cache file. Do nothing if fp is NULL. */
+static krb5_error_code
+close_cache_file(krb5_context context, FILE *fp)
+{
+ int st;
+ krb5_error_code ret;
+
+ if (fp == NULL)
+ return 0;
+ ret = krb5_unlock_file(context, fileno(fp));
+ st = fclose(fp);
+ if (ret)
+ return ret;
+ return st ? interpret_errno(context, errno) : 0;
+}
+
+/* Read the cache file header. Set time offsets in context from the header if
+ * appropriate. Set *version_out to the cache file format version. */
+static krb5_error_code
+read_header(krb5_context context, FILE *fp, int *version_out)
+{
+ krb5_error_code ret;
+ krb5_os_context os_ctx = &context->os_context;
+ uint16_t fields_len, tag, flen;
+ uint32_t time_offset, usec_offset;
+ char i16buf[2];
+ int version;
+
+ *version_out = 0;
+
+ /* Get the file format version. */
+ ret = read_bytes(context, fp, i16buf, 2);
+ if (ret)
+ return KRB5_CC_FORMAT;
+ version = load_16_be(i16buf) - FVNO_BASE;
+ if (version < 1 || version > 4)
+ return KRB5_CCACHE_BADVNO;
+ *version_out = version;
+
+ /* Tagged header fields begin with version 4. */
+ if (version < 4)
+ return 0;
+
+ if (read16(context, fp, version, &fields_len))
+ return KRB5_CC_FORMAT;
+ while (fields_len) {
+ if (fields_len < 4 || read16(context, fp, version, &tag) ||
+ read16(context, fp, version, &flen) || flen > fields_len - 4)
+ return KRB5_CC_FORMAT;
+
+ switch (tag) {
+ case FCC_TAG_DELTATIME:
+ if (flen != 8 ||
+ read32(context, fp, version, NULL, &time_offset) ||
+ read32(context, fp, version, NULL, &usec_offset))
+ return KRB5_CC_FORMAT;
+
+ if (!(context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) ||
+ (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID))
+ break;
+
+ os_ctx->time_offset = time_offset;
+ os_ctx->usec_offset = usec_offset;
+ os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) |
+ KRB5_OS_TOFFSET_VALID);
+ break;
+
+ default:
+ if (flen && fseek(fp, flen, SEEK_CUR) != 0)
+ return KRB5_CC_FORMAT;
+ break;
+ }
+ fields_len -= (4 + flen);
+ }
+ return 0;
+}
+
+/* Create or overwrite the cache file with a header and default principal. */
+static krb5_error_code KRB5_CALLCONV
+fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krb5_error_code ret;
+ krb5_os_context os_ctx = &context->os_context;
+ fcc_data *data = id->data;
+ char i16buf[2], i32buf[4];
+ uint16_t fields_len;
+ ssize_t nwritten;
+ int st, flags, version, fd = -1;
+ struct k5buf buf = EMPTY_K5BUF;
+ krb5_boolean file_locked = FALSE;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ unlink(data->filename);
+ flags = O_CREAT | O_EXCL | O_RDWR | O_BINARY | O_CLOEXEC;
+ fd = open(data->filename, flags, 0600);
+ if (fd == -1) {
+ ret = interpret_errno(context, errno);
+ goto cleanup;
+ }
+ set_cloexec_fd(fd);
+
+#if defined(HAVE_FCHMOD) || defined(HAVE_CHMOD)
+#ifdef HAVE_FCHMOD
+ st = fchmod(fd, S_IRUSR | S_IWUSR);
+#else
+ st = chmod(data->filename, S_IRUSR | S_IWUSR);
+#endif
+ if (st == -1) {
+ ret = interpret_errno(context, errno);
+ goto cleanup;
+ }
+#endif
+
+ ret = krb5_lock_file(context, fd, KRB5_LOCKMODE_EXCLUSIVE);
+ if (ret)
+ goto cleanup;
+ file_locked = TRUE;
+
+ /* Prepare the header and principal in buf. */
+ k5_buf_init_dynamic(&buf);
+ version = context->fcc_default_format - FVNO_BASE;
+ store_16_be(FVNO_BASE + version, i16buf);
+ k5_buf_add_len(&buf, i16buf, 2);
+ if (version >= 4) {
+ /* Add tagged header fields. */
+ fields_len = 0;
+ if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)
+ fields_len += 12;
+ store_16_be(fields_len, i16buf);
+ k5_buf_add_len(&buf, i16buf, 2);
+ if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
+ /* Add time offset tag. */
+ store_16_be(FCC_TAG_DELTATIME, i16buf);
+ k5_buf_add_len(&buf, i16buf, 2);
+ store_16_be(8, i16buf);
+ k5_buf_add_len(&buf, i16buf, 2);
+ store_32_be(os_ctx->time_offset, i32buf);
+ k5_buf_add_len(&buf, i32buf, 4);
+ store_32_be(os_ctx->usec_offset, i32buf);
+ k5_buf_add_len(&buf, i32buf, 4);
+ }
+ }
+ k5_marshal_princ(&buf, version, princ);
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto cleanup;
+
+ /* Write the header and principal. */
+ nwritten = write(fd, buf.data, buf.len);
+ if (nwritten == -1)
+ ret = interpret_errno(context, errno);
+ if ((size_t)nwritten != buf.len)
+ ret = KRB5_CC_IO;
+
+cleanup:
+ k5_buf_free(&buf);
+ if (file_locked)
+ krb5_unlock_file(context, fd);
+ if (fd != -1)
+ close(fd);
+ k5_cc_mutex_unlock(context, &data->lock);
+ krb5_change_cache();
+ return set_errmsg_filename(context, ret, data->filename);
+}
+
+/* Release an fcc_data object. */
+static void
+free_fccdata(krb5_context context, fcc_data *data)
+{
+ k5_cc_mutex_assert_unlocked(context, &data->lock);
+ free(data->filename);
+ k5_cc_mutex_destroy(&data->lock);
+ free(data);
+}
+
+/* Release the ccache handle. */
+static krb5_error_code KRB5_CALLCONV
+fcc_close(krb5_context context, krb5_ccache id)
+{
+ free_fccdata(context, id->data);
+ free(id);
+ return 0;
+}
+
+/* Destroy the cache file and release the handle. */
+static krb5_error_code KRB5_CALLCONV
+fcc_destroy(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code ret = 0;
+ fcc_data *data = id->data;
+ int st, fd;
+ struct stat buf;
+ unsigned long i, size;
+ unsigned int wlen;
+ char zeros[BUFSIZ];
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC, 0);
+ if (fd < 0) {
+ ret = interpret_errno(context, errno);
+ goto cleanup;
+ }
+ set_cloexec_fd(fd);
+
+#ifdef MSDOS_FILESYSTEM
+ /*
+ * "Disgusting bit of UNIX trivia" - that's how the writers of NFS describe
+ * the ability of UNIX to still write to a file which has been unlinked.
+ * Naturally, the PC can't do this. As a result, we have to delete the
+ * file after we wipe it clean, but that throws off all the error handling
+ * code. So we have do the work ourselves.
+ */
+ st = fstat(fd, &buf);
+ if (st == -1) {
+ ret = interpret_errno(context, errno);
+ size = 0; /* Nothing to wipe clean */
+ } else {
+ size = (unsigned long)buf.st_size;
+ }
+
+ memset(zeros, 0, BUFSIZ);
+ while (size > 0) {
+ wlen = (int)((size > BUFSIZ) ? BUFSIZ : size); /* How much to write */
+ i = write(fd, zeros, wlen);
+ if (i < 0) {
+ ret = interpret_errno(context, errno);
+ /* Don't jump to cleanup--we still want to delete the file. */
+ break;
+ }
+ size -= i;
+ }
+
+ (void)close(fd);
+
+ st = unlink(data->filename);
+ if (st < 0) {
+ ret = interpret_errno(context, errno);
+ goto cleanup;
+ }
+
+#else /* MSDOS_FILESYSTEM */
+
+ st = unlink(data->filename);
+ if (st < 0) {
+ ret = interpret_errno(context, errno);
+ (void)close(fd);
+ goto cleanup;
+ }
+
+ st = fstat(fd, &buf);
+ if (st < 0) {
+ ret = interpret_errno(context, errno);
+ (void)close(fd);
+ goto cleanup;
+ }
+
+ /* XXX This may not be legal XXX */
+ size = (unsigned long)buf.st_size;
+ memset(zeros, 0, BUFSIZ);
+ for (i = 0; i < size / BUFSIZ; i++) {
+ if (write(fd, zeros, BUFSIZ) < 0) {
+ ret = interpret_errno(context, errno);
+ (void)close(fd);
+ goto cleanup;
+ }
+ }
+
+ wlen = size % BUFSIZ;
+ if (write(fd, zeros, wlen) < 0) {
+ ret = interpret_errno(context, errno);
+ (void)close(fd);
+ goto cleanup;
+ }
+
+ st = close(fd);
+
+ if (st)
+ ret = interpret_errno(context, errno);
+
+#endif /* MSDOS_FILESYSTEM */
+
+cleanup:
+ (void)set_errmsg_filename(context, ret, data->filename);
+ k5_cc_mutex_unlock(context, &data->lock);
+ free_fccdata(context, data);
+ free(id);
+
+ krb5_change_cache();
+ return ret;
+}
+
+extern const krb5_cc_ops krb5_fcc_ops;
+
+/* Create a file ccache handle for the pathname given by residual. */
+static krb5_error_code KRB5_CALLCONV
+fcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
+{
+ krb5_ccache lid;
+ krb5_error_code ret;
+ fcc_data *data;
+
+ data = malloc(sizeof(fcc_data));
+ if (data == NULL)
+ return KRB5_CC_NOMEM;
+ data->filename = strdup(residual);
+ if (data->filename == NULL) {
+ free(data);
+ return KRB5_CC_NOMEM;
+ }
+ ret = k5_cc_mutex_init(&data->lock);
+ if (ret) {
+ free(data->filename);
+ free(data);
+ return ret;
+ }
+
+ lid = malloc(sizeof(struct _krb5_ccache));
+ if (lid == NULL) {
+ free_fccdata(context, data);
+ return KRB5_CC_NOMEM;
+ }
+
+ lid->ops = &krb5_fcc_ops;
+ lid->data = data;
+ lid->magic = KV5M_CCACHE;
+
+ /* Other routines will get errors on open, and callers must expect them, if
+ * cache is non-existent/unusable. */
+ *id = lid;
+ return 0;
+}
+
+/* Prepare for a sequential iteration over the cache file. */
+static krb5_error_code KRB5_CALLCONV
+fcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ krb5_fcc_cursor *fcursor = NULL;
+ krb5_error_code ret;
+ krb5_principal princ = NULL;
+ fcc_data *data = id->data;
+ FILE *fp = NULL;
+ int version;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ fcursor = malloc(sizeof(krb5_fcc_cursor));
+ if (fcursor == NULL) {
+ ret = KRB5_CC_NOMEM;
+ goto cleanup;
+ }
+
+ /* Open the cache file and read the header. */
+ ret = open_cache_file(context, data->filename, FALSE, &fp);
+ if (ret)
+ goto cleanup;
+ ret = read_header(context, fp, &version);
+ if (ret)
+ goto cleanup;
+
+ /* Read past the default client principal name. */
+ ret = read_principal(context, fp, version, &princ);
+ if (ret)
+ goto cleanup;
+
+ /* Drop the shared file lock but retain the file handle. */
+ (void)krb5_unlock_file(context, fileno(fp));
+ fcursor->fp = fp;
+ fp = NULL;
+ fcursor->version = version;
+ *cursor = (krb5_cc_cursor)fcursor;
+ fcursor = NULL;
+
+cleanup:
+ (void)close_cache_file(context, fp);
+ free(fcursor);
+ krb5_free_principal(context, princ);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return set_errmsg_filename(context, ret, data->filename);
+}
+
+/* Get the next credential from the cache file. */
+static krb5_error_code KRB5_CALLCONV
+fcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds)
+{
+ krb5_error_code ret;
+ krb5_fcc_cursor *fcursor = *cursor;
+ fcc_data *data = id->data;
+ struct k5buf buf;
+ size_t maxsize;
+ krb5_boolean file_locked = FALSE;
+
+ memset(creds, 0, sizeof(*creds));
+ k5_cc_mutex_lock(context, &data->lock);
+ k5_buf_init_dynamic(&buf);
+
+ ret = krb5_lock_file(context, fileno(fcursor->fp), KRB5_LOCKMODE_SHARED);
+ if (ret)
+ goto cleanup;
+ file_locked = TRUE;
+
+ /* Load a marshalled cred into memory. */
+ ret = get_size(context, fcursor->fp, &maxsize);
+ if (ret)
+ goto cleanup;
+ ret = load_cred(context, fcursor->fp, fcursor->version, maxsize, &buf);
+ if (ret)
+ goto cleanup;
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto cleanup;
+
+ /* Unmarshal it from buf into creds. */
+ ret = k5_unmarshal_cred(buf.data, buf.len, fcursor->version, creds);
+
+cleanup:
+ if (file_locked)
+ (void)krb5_unlock_file(context, fileno(fcursor->fp));
+ k5_cc_mutex_unlock(context, &data->lock);
+ k5_buf_free(&buf);
+ return set_errmsg_filename(context, ret, data->filename);
+}
+
+/* Release an iteration cursor. */
+static krb5_error_code KRB5_CALLCONV
+fcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ krb5_fcc_cursor *fcursor = *cursor;
+
+ (void)fclose(fcursor->fp);
+ free(fcursor);
+ *cursor = NULL;
+ return 0;
+}
+
+/* Generate a unique file ccache using the given template (which will be
+ * modified to contain the actual name of the file). */
+krb5_error_code
+krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id)
+{
+ krb5_ccache lid;
+ int fd;
+ krb5_error_code ret;
+ fcc_data *data;
+ char fcc_fvno[2];
+ int16_t fcc_flen = 0;
+ int errsave, cnt;
+
+ fd = mkstemp(template);
+ if (fd == -1)
+ return interpret_errno(context, errno);
+ set_cloexec_fd(fd);
+
+ /* Allocate memory */
+ data = malloc(sizeof(fcc_data));
+ if (data == NULL) {
+ close(fd);
+ unlink(template);
+ return KRB5_CC_NOMEM;
+ }
+
+ data->filename = strdup(template);
+ if (data->filename == NULL) {
+ free(data);
+ close(fd);
+ unlink(template);
+ return KRB5_CC_NOMEM;
+ }
+
+ ret = k5_cc_mutex_init(&data->lock);
+ if (ret) {
+ free(data->filename);
+ free(data);
+ close(fd);
+ unlink(template);
+ return ret;
+ }
+ k5_cc_mutex_lock(context, &data->lock);
+
+ /* Ignore user's umask, set mode = 0600 */
+#ifndef HAVE_FCHMOD
+#ifdef HAVE_CHMOD
+ chmod(data->filename, S_IRUSR | S_IWUSR);
+#endif
+#else
+ fchmod(fd, S_IRUSR | S_IWUSR);
+#endif
+ store_16_be(context->fcc_default_format, fcc_fvno);
+ cnt = write(fd, &fcc_fvno, 2);
+ if (cnt != 2) {
+ errsave = errno;
+ (void)close(fd);
+ (void)unlink(data->filename);
+ ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
+ goto err_out;
+ }
+ /* For version 4 we save a length for the rest of the header */
+ if (context->fcc_default_format == FVNO_BASE + 4) {
+ cnt = write(fd, &fcc_flen, sizeof(fcc_flen));
+ if (cnt != sizeof(fcc_flen)) {
+ errsave = errno;
+ (void)close(fd);
+ (void)unlink(data->filename);
+ ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
+ goto err_out;
+ }
+ }
+ if (close(fd) == -1) {
+ errsave = errno;
+ (void)unlink(data->filename);
+ ret = interpret_errno(context, errsave);
+ goto err_out;
+ }
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+ k5_cc_mutex_unlock(context, &data->lock);
+ lid = malloc(sizeof(*lid));
+ if (lid == NULL) {
+ free_fccdata(context, data);
+ return KRB5_CC_NOMEM;
+ }
+
+ lid->ops = &krb5_fcc_ops;
+ lid->data = data;
+ lid->magic = KV5M_CCACHE;
+
+ *id = lid;
+
+ krb5_change_cache();
+ return 0;
+
+err_out:
+ (void)set_errmsg_filename(context, ret, data->filename);
+ k5_cc_mutex_unlock(context, &data->lock);
+ k5_cc_mutex_destroy(&data->lock);
+ free(data->filename);
+ free(data);
+ return ret;
+}
+
+/*
+ * Create a new file cred cache whose name is guaranteed to be unique. The
+ * name begins with the string TKT_ROOT (from fcc.h). The cache file is not
+ * opened, but the new filename is reserved.
+ */
+static krb5_error_code KRB5_CALLCONV
+fcc_generate_new(krb5_context context, krb5_ccache *id)
+{
+ char scratch[sizeof(TKT_ROOT) + 7]; /* Room for XXXXXX and terminator */
+
+ (void)snprintf(scratch, sizeof(scratch), "%sXXXXXX", TKT_ROOT);
+ return krb5int_fcc_new_unique(context, scratch, id);
+}
+
+/* Return an alias to the pathname of the cache file. */
+static const char * KRB5_CALLCONV
+fcc_get_name(krb5_context context, krb5_ccache id)
+{
+ return ((fcc_data *)id->data)->filename;
+}
+
+/* Retrieve a copy of the default principal, if the cache is initialized. */
+static krb5_error_code KRB5_CALLCONV
+fcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
+{
+ krb5_error_code ret;
+ fcc_data *data = id->data;
+ FILE *fp = NULL;
+ int version;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ ret = open_cache_file(context, data->filename, FALSE, &fp);
+ if (ret)
+ goto cleanup;
+ ret = read_header(context, fp, &version);
+ if (ret)
+ goto cleanup;
+ ret = read_principal(context, fp, version, princ);
+
+cleanup:
+ (void)close_cache_file(context, fp);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return set_errmsg_filename(context, ret, data->filename);
+}
+
+/* Search for a credential within the cache file. */
+static krb5_error_code KRB5_CALLCONV
+fcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
+ krb5_creds *mcreds, krb5_creds *creds)
+{
+ krb5_error_code ret;
+
+ ret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, creds);
+ return set_errmsg_filename(context, ret, ((fcc_data *)id->data)->filename);
+}
+
+/* Store a credential in the cache file. */
+static krb5_error_code KRB5_CALLCONV
+fcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
+{
+ krb5_error_code ret, ret2;
+ fcc_data *data = id->data;
+ FILE *fp = NULL;
+ int version;
+ struct k5buf buf = EMPTY_K5BUF;
+ ssize_t nwritten;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ /* Open the cache file for O_APPEND writing. */
+ ret = open_cache_file(context, data->filename, TRUE, &fp);
+ if (ret)
+ goto cleanup;
+ ret = read_header(context, fp, &version);
+ if (ret)
+ goto cleanup;
+
+ /* Marshal the cred and write it to the file with a single append write. */
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_cred(&buf, version, creds);
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto cleanup;
+ nwritten = write(fileno(fp), buf.data, buf.len);
+ if (nwritten == -1)
+ ret = interpret_errno(context, errno);
+ if ((size_t)nwritten != buf.len)
+ ret = KRB5_CC_IO;
+
+ krb5_change_cache();
+
+cleanup:
+ k5_buf_free(&buf);
+ ret2 = close_cache_file(context, fp);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return set_errmsg_filename(context, ret ? ret : ret2, data->filename);
+}
+
+/* Non-functional stub for removing a cred from the cache file. */
+static krb5_error_code KRB5_CALLCONV
+fcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *creds)
+{
+ return KRB5_CC_NOSUPP;
+}
+
+static krb5_error_code KRB5_CALLCONV
+fcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
+{
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+fcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
+{
+ *flags = 0;
+ return 0;
+}
+
+/* Prepare to iterate over the caches in the per-type collection. */
+static krb5_error_code KRB5_CALLCONV
+fcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ krb5_cc_ptcursor n = NULL;
+ struct krb5_fcc_ptcursor_data *cdata = NULL;
+
+ *cursor = NULL;
+
+ n = malloc(sizeof(*n));
+ if (n == NULL)
+ return ENOMEM;
+ n->ops = &krb5_fcc_ops;
+ cdata = malloc(sizeof(*cdata));
+ if (cdata == NULL) {
+ free(n);
+ return ENOMEM;
+ }
+ cdata->first = TRUE;
+ n->data = cdata;
+ *cursor = n;
+ return 0;
+}
+
+/* Get the next cache in the per-type collection. The FILE per-type collection
+ * contains only the context's default cache if it is a file cache. */
+static krb5_error_code KRB5_CALLCONV
+fcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ struct krb5_fcc_ptcursor_data *cdata = cursor->data;
+ const char *defname, *residual;
+ krb5_ccache cache;
+ struct stat sb;
+
+ *cache_out = NULL;
+ if (!cdata->first)
+ return 0;
+ cdata->first = FALSE;
+
+ defname = krb5_cc_default_name(context);
+ if (!defname)
+ return 0;
+
+ /* Check if the default has type FILE or no type; find the residual. */
+ if (strncmp(defname, "FILE:", 5) == 0)
+ residual = defname + 5;
+ else if (strchr(defname + 2, ':') == NULL) /* Skip drive prefix if any. */
+ residual = defname;
+ else
+ return 0;
+
+ /* Don't yield a nonexistent default file cache. */
+ if (stat(residual, &sb) != 0)
+ return 0;
+
+ ret = krb5_cc_resolve(context, defname, &cache);
+ if (ret)
+ return set_errmsg_filename(context, ret, defname);
+ *cache_out = cache;
+ return 0;
+}
+
+/* Release a per-type collection iteration cursor. */
+static krb5_error_code KRB5_CALLCONV
+fcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ if (*cursor == NULL)
+ return 0;
+ free((*cursor)->data);
+ free(*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+/* Get the cache file's last modification time. */
+static krb5_error_code KRB5_CALLCONV
+fcc_last_change_time(krb5_context context, krb5_ccache id,
+ krb5_timestamp *change_time)
+{
+ krb5_error_code ret = 0;
+ fcc_data *data = id->data;
+ struct stat buf;
+
+ *change_time = 0;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (stat(data->filename, &buf) == -1)
+ ret = interpret_errno(context, errno);
+ else
+ *change_time = (krb5_timestamp)buf.st_mtime;
+
+ k5_cc_mutex_unlock(context, &data->lock);
+
+ return set_errmsg_filename(context, ret, data->filename);
+}
+
+/* Lock the cache handle against other threads. (This does not lock the cache
+ * file against other processes.) */
+static krb5_error_code KRB5_CALLCONV
+fcc_lock(krb5_context context, krb5_ccache id)
+{
+ fcc_data *data = id->data;
+ k5_cc_mutex_lock(context, &data->lock);
+ return 0;
+}
+
+/* Unlock the cache handle. */
+static krb5_error_code KRB5_CALLCONV
+fcc_unlock(krb5_context context, krb5_ccache id)
+{
+ fcc_data *data = id->data;
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+/* Translate a system errno value to a Kerberos com_err code. */
+static krb5_error_code
+interpret_errno(krb5_context context, int errnum)
+{
+ krb5_error_code ret;
+
+ switch (errnum) {
+ case ENOENT:
+ case ENOTDIR:
+#ifdef ELOOP
+ case ELOOP:
+#endif
+#ifdef ENAMETOOLONG
+ case ENAMETOOLONG:
+#endif
+ ret = KRB5_FCC_NOFILE;
+ break;
+ case EPERM:
+ case EACCES:
+#ifdef EISDIR
+ case EISDIR: /* Mac doesn't have EISDIR */
+#endif
+ case EROFS:
+ ret = KRB5_FCC_PERM;
+ break;
+ case EINVAL:
+ case EEXIST:
+ case EFAULT:
+ case EBADF:
+#ifdef EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif
+ ret = KRB5_FCC_INTERNAL;
+ break;
+ /*
+ * The rest all map to KRB5_CC_IO. These errnos are listed to
+ * document that they've been considered explicitly:
+ *
+ * - EDQUOT
+ * - ENOSPC
+ * - EIO
+ * - ENFILE
+ * - EMFILE
+ * - ENXIO
+ * - EBUSY
+ * - ETXTBSY
+ */
+ default:
+ ret = KRB5_CC_IO;
+ break;
+ }
+ return ret;
+}
+
+const krb5_cc_ops krb5_fcc_ops = {
+ 0,
+ "FILE",
+ fcc_get_name,
+ fcc_resolve,
+ fcc_generate_new,
+ fcc_initialize,
+ fcc_destroy,
+ fcc_close,
+ fcc_store,
+ fcc_retrieve,
+ fcc_get_principal,
+ fcc_start_seq_get,
+ fcc_next_cred,
+ fcc_end_seq_get,
+ fcc_remove_cred,
+ fcc_set_flags,
+ fcc_get_flags,
+ fcc_ptcursor_new,
+ fcc_ptcursor_next,
+ fcc_ptcursor_free,
+ NULL, /* move */
+ fcc_last_change_time,
+ NULL, /* wasdefault */
+ fcc_lock,
+ fcc_unlock,
+ NULL, /* switch_to */
+};
+
+#if defined(_WIN32)
+/*
+ * krb5_change_cache should be called after the cache changes.
+ * A notification message is is posted out to all top level
+ * windows so that they may recheck the cache based on the
+ * changes made. We register a unique message type with which
+ * we'll communicate to all other processes.
+ */
+
+krb5_error_code
+krb5_change_cache(void)
+{
+ PostMessage(HWND_BROADCAST, krb5_get_notification_message(), 0, 0);
+ return 0;
+}
+
+unsigned int KRB5_CALLCONV
+krb5_get_notification_message(void)
+{
+ static unsigned int message = 0;
+
+ if (message == 0)
+ message = RegisterWindowMessage(WM_KERBEROS5_CHANGED);
+
+ return message;
+}
+#else /* _WIN32 */
+
+krb5_error_code
+krb5_change_cache(void)
+{
+ return 0;
+}
+
+unsigned int
+krb5_get_notification_message(void)
+{
+ return 0;
+}
+
+#endif /* _WIN32 */
+
+const krb5_cc_ops krb5_cc_file_ops = {
+ 0,
+ "FILE",
+ fcc_get_name,
+ fcc_resolve,
+ fcc_generate_new,
+ fcc_initialize,
+ fcc_destroy,
+ fcc_close,
+ fcc_store,
+ fcc_retrieve,
+ fcc_get_principal,
+ fcc_start_seq_get,
+ fcc_next_cred,
+ fcc_end_seq_get,
+ fcc_remove_cred,
+ fcc_set_flags,
+ fcc_get_flags,
+ fcc_ptcursor_new,
+ fcc_ptcursor_next,
+ fcc_ptcursor_free,
+ NULL, /* move */
+ fcc_last_change_time,
+ NULL, /* wasdefault */
+ fcc_lock,
+ fcc_unlock,
+ NULL, /* switch_to */
+};
diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c
new file mode 100644
index 0000000000000..a889e67b44926
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_kcm.c
@@ -0,0 +1,1074 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_kcm.c - KCM cache type (client side) */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This cache type contacts a daemon for each cache operation, using Heimdal's
+ * KCM protocol. On OS X, the preferred transport is Mach RPC; on other
+ * Unix-like platforms or if the daemon is not available via RPC, Unix domain
+ * sockets are used instead.
+ */
+
+#ifndef _WIN32
+#include "k5-int.h"
+#include "k5-input.h"
+#include "cc-int.h"
+#include "kcm.h"
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef __APPLE__
+#include <mach/mach.h>
+#include <servers/bootstrap.h>
+#include "kcmrpc.h"
+#endif
+
+#define MAX_REPLY_SIZE (10 * 1024 * 1024)
+
+const krb5_cc_ops krb5_kcm_ops;
+
+struct uuid_list {
+ unsigned char *uuidbytes; /* all of the uuids concatenated together */
+ size_t count;
+ size_t pos;
+};
+
+struct kcmio {
+ int fd;
+#ifdef __APPLE__
+ mach_port_t mport;
+#endif
+};
+
+/* This structure bundles together a KCM request and reply, to minimize how
+ * much we have to declare and clean up in each method. */
+struct kcmreq {
+ struct k5buf reqbuf;
+ struct k5input reply;
+ void *reply_mem;
+};
+#define EMPTY_KCMREQ { EMPTY_K5BUF }
+
+struct kcm_cache_data {
+ char *residual; /* immutable; may be accessed without lock */
+ k5_cc_mutex lock; /* protects io and changetime */
+ struct kcmio *io;
+ krb5_timestamp changetime;
+};
+
+struct kcm_ptcursor {
+ char *residual; /* primary or singleton subsidiary */
+ struct uuid_list *uuids; /* NULL for singleton subsidiary */
+ struct kcmio *io;
+ krb5_boolean first;
+};
+
+/* Map EINVAL or KRB5_CC_FORMAT to KRB5_KCM_MALFORMED_REPLY; pass through all
+ * other codes. */
+static inline krb5_error_code
+map_invalid(krb5_error_code code)
+{
+ return (code == EINVAL || code == KRB5_CC_FORMAT) ?
+ KRB5_KCM_MALFORMED_REPLY : code;
+}
+
+/* Begin a request for the given opcode. If cache is non-null, supply the
+ * cache name as a request parameter. */
+static void
+kcmreq_init(struct kcmreq *req, kcm_opcode opcode, krb5_ccache cache)
+{
+ unsigned char bytes[4];
+ const char *name;
+
+ memset(req, 0, sizeof(*req));
+
+ bytes[0] = KCM_PROTOCOL_VERSION_MAJOR;
+ bytes[1] = KCM_PROTOCOL_VERSION_MINOR;
+ store_16_be(opcode, bytes + 2);
+
+ k5_buf_init_dynamic(&req->reqbuf);
+ k5_buf_add_len(&req->reqbuf, bytes, 4);
+ if (cache != NULL) {
+ name = ((struct kcm_cache_data *)cache->data)->residual;
+ k5_buf_add_len(&req->reqbuf, name, strlen(name) + 1);
+ }
+}
+
+/* Add a 32-bit value to the request in big-endian byte order. */
+static void
+kcmreq_put32(struct kcmreq *req, uint32_t val)
+{
+ unsigned char bytes[4];
+
+ store_32_be(val, bytes);
+ k5_buf_add_len(&req->reqbuf, bytes, 4);
+}
+
+#ifdef __APPLE__
+
+/* The maximum length of an in-band request or reply as defined by the RPC
+ * protocol. */
+#define MAX_INBAND_SIZE 2048
+
+/* Connect or reconnect to the KCM daemon via Mach RPC, if possible. */
+static krb5_error_code
+kcmio_mach_connect(krb5_context context, struct kcmio *io)
+{
+ krb5_error_code ret;
+ kern_return_t st;
+ mach_port_t mport;
+ char *service;
+
+ ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+ KRB5_CONF_KCM_MACH_SERVICE, NULL,
+ DEFAULT_KCM_MACH_SERVICE, &service);
+ if (ret)
+ return ret;
+ if (strcmp(service, "-") == 0) {
+ profile_release_string(service);
+ return KRB5_KCM_NO_SERVER;
+ }
+
+ st = bootstrap_look_up(bootstrap_port, service, &mport);
+ profile_release_string(service);
+ if (st)
+ return KRB5_KCM_NO_SERVER;
+ if (io->mport != MACH_PORT_NULL)
+ mach_port_deallocate(mach_task_self(), io->mport);
+ io->mport = mport;
+ return 0;
+}
+
+/* Invoke the Mach RPC to get a reply from the KCM daemon. */
+static krb5_error_code
+kcmio_mach_call(krb5_context context, struct kcmio *io, void *data,
+ size_t len, void **reply_out, size_t *len_out)
+{
+ krb5_error_code ret;
+ size_t inband_req_len = 0, outband_req_len = 0, reply_len;
+ char *inband_req = NULL, *outband_req = NULL, *outband_reply, *copy;
+ char inband_reply[MAX_INBAND_SIZE];
+ mach_msg_type_number_t inband_reply_len, outband_reply_len;
+ const void *reply;
+ kern_return_t st;
+ int code;
+
+ *reply_out = NULL;
+ *len_out = 0;
+
+ /* Use the in-band or out-of-band request buffer depending on len. */
+ if (len <= MAX_INBAND_SIZE) {
+ inband_req = data;
+ inband_req_len = len;
+ } else {
+ outband_req = data;
+ outband_req_len = len;
+ }
+
+ st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
+ outband_req_len, &code, inband_reply,
+ &inband_reply_len, &outband_reply, &outband_reply_len);
+ if (st == MACH_SEND_INVALID_DEST) {
+ /* Get a new port and try again. */
+ st = kcmio_mach_connect(context, io);
+ if (st)
+ return KRB5_KCM_RPC_ERROR;
+ st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
+ outband_req_len, &code, inband_reply,
+ &inband_reply_len, &outband_reply,
+ &outband_reply_len);
+ }
+ if (st)
+ return KRB5_KCM_RPC_ERROR;
+
+ if (code) {
+ ret = code;
+ goto cleanup;
+ }
+
+ /* The reply could be in the in-band or out-of-band reply buffer. */
+ reply = outband_reply_len ? outband_reply : inband_reply;
+ reply_len = outband_reply_len ? outband_reply_len : inband_reply_len;
+ copy = k5memdup(reply, reply_len, &ret);
+ if (copy == NULL)
+ goto cleanup;
+
+ *reply_out = copy;
+ *len_out = reply_len;
+
+cleanup:
+ if (outband_reply_len) {
+ vm_deallocate(mach_task_self(), (vm_address_t)outband_reply,
+ outband_reply_len);
+ }
+ return ret;
+}
+
+/* Release any Mach RPC state within io. */
+static void
+kcmio_mach_close(struct kcmio *io)
+{
+ if (io->mport != MACH_PORT_NULL)
+ mach_port_deallocate(mach_task_self(), io->mport);
+}
+
+#else /* __APPLE__ */
+
+#define kcmio_mach_connect(context, io) EINVAL
+#define kcmio_mach_call(context, io, data, len, reply_out, len_out) EINVAL
+#define kcmio_mach_close(io)
+
+#endif
+
+/* Connect to the KCM daemon via a Unix domain socket. */
+static krb5_error_code
+kcmio_unix_socket_connect(krb5_context context, struct kcmio *io)
+{
+ krb5_error_code ret;
+ int fd = -1;
+ struct sockaddr_un addr;
+ char *path = NULL;
+
+ ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
+ KRB5_CONF_KCM_SOCKET, NULL,
+ DEFAULT_KCM_SOCKET_PATH, &path);
+ if (ret)
+ goto cleanup;
+ if (strcmp(path, "-") == 0) {
+ ret = KRB5_KCM_NO_SERVER;
+ goto cleanup;
+ }
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ ret = errno;
+ goto cleanup;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+ ret = (errno == ENOENT) ? KRB5_KCM_NO_SERVER : errno;
+ goto cleanup;
+ }
+
+ io->fd = fd;
+ fd = -1;
+
+cleanup:
+ if (fd != -1)
+ close(fd);
+ profile_release_string(path);
+ return ret;
+}
+
+/* Write a KCM request: 4-byte big-endian length, then the marshalled
+ * request. */
+static krb5_error_code
+kcmio_unix_socket_write(krb5_context context, struct kcmio *io, void *request,
+ size_t len)
+{
+ char lenbytes[4];
+
+ store_32_be(len, lenbytes);
+ if (krb5_net_write(context, io->fd, lenbytes, 4) < 0)
+ return errno;
+ if (krb5_net_write(context, io->fd, request, len) < 0)
+ return errno;
+ return 0;
+}
+
+/* Read a KCM reply: 4-byte big-endian length, 4-byte big-endian status code,
+ * then the marshalled reply. */
+static krb5_error_code
+kcmio_unix_socket_read(krb5_context context, struct kcmio *io,
+ void **reply_out, size_t *len_out)
+{
+ krb5_error_code code;
+ char lenbytes[4], codebytes[4], *reply;
+ size_t len;
+ int st;
+
+ *reply_out = NULL;
+ *len_out = 0;
+
+ st = krb5_net_read(context, io->fd, lenbytes, 4);
+ if (st != 4)
+ return (st == -1) ? errno : KRB5_CC_IO;
+ len = load_32_be(lenbytes);
+ if (len > MAX_REPLY_SIZE)
+ return KRB5_KCM_REPLY_TOO_BIG;
+
+ st = krb5_net_read(context, io->fd, codebytes, 4);
+ if (st != 4)
+ return (st == -1) ? errno : KRB5_CC_IO;
+ code = load_32_be(codebytes);
+ if (code != 0)
+ return code;
+
+ reply = malloc(len);
+ if (reply == NULL)
+ return ENOMEM;
+ st = krb5_net_read(context, io->fd, reply, len);
+ if (st == -1 || (size_t)st != len) {
+ free(reply);
+ return (st < 0) ? errno : KRB5_CC_IO;
+ }
+
+ *reply_out = reply;
+ *len_out = len;
+ return 0;
+}
+
+static krb5_error_code
+kcmio_connect(krb5_context context, struct kcmio **io_out)
+{
+ krb5_error_code ret;
+ struct kcmio *io;
+
+ *io_out = NULL;
+ io = calloc(1, sizeof(*io));
+ if (io == NULL)
+ return ENOMEM;
+ io->fd = -1;
+
+ /* Try Mach RPC (OS X only), then fall back to Unix domain sockets */
+ ret = kcmio_mach_connect(context, io);
+ if (ret)
+ ret = kcmio_unix_socket_connect(context, io);
+ if (ret) {
+ free(io);
+ return ret;
+ }
+
+ *io_out = io;
+ return 0;
+}
+
+/* Check req->reqbuf for an error condition and return it. Otherwise, send the
+ * request to the KCM daemon and get a response. */
+static krb5_error_code
+kcmio_call(krb5_context context, struct kcmio *io, struct kcmreq *req)
+{
+ krb5_error_code ret;
+ size_t reply_len = 0;
+
+ if (k5_buf_status(&req->reqbuf) != 0)
+ return ENOMEM;
+
+ if (io->fd != -1) {
+ ret = kcmio_unix_socket_write(context, io, req->reqbuf.data,
+ req->reqbuf.len);
+ if (ret)
+ return ret;
+ ret = kcmio_unix_socket_read(context, io, &req->reply_mem, &reply_len);
+ if (ret)
+ return ret;
+ } else {
+ /* We must be using Mach RPC. */
+ ret = kcmio_mach_call(context, io, req->reqbuf.data, req->reqbuf.len,
+ &req->reply_mem, &reply_len);
+ if (ret)
+ return ret;
+ }
+
+ /* Read the status code from the marshalled reply. */
+ k5_input_init(&req->reply, req->reply_mem, reply_len);
+ ret = k5_input_get_uint32_be(&req->reply);
+ return req->reply.status ? KRB5_KCM_MALFORMED_REPLY : ret;
+}
+
+static void
+kcmio_close(struct kcmio *io)
+{
+ if (io != NULL) {
+ kcmio_mach_close(io);
+ if (io->fd != -1)
+ close(io->fd);
+ free(io);
+ }
+}
+
+/* Fetch a zero-terminated name string from req->reply. The returned pointer
+ * is an alias and must not be freed by the caller. */
+static krb5_error_code
+kcmreq_get_name(struct kcmreq *req, const char **name_out)
+{
+ const unsigned char *end;
+ struct k5input *in = &req->reply;
+
+ *name_out = NULL;
+ end = memchr(in->ptr, '\0', in->len);
+ if (end == NULL)
+ return KRB5_KCM_MALFORMED_REPLY;
+ *name_out = (const char *)in->ptr;
+ (void)k5_input_get_bytes(in, end + 1 - in->ptr);
+ return 0;
+}
+
+/* Fetch a UUID list from req->reply. UUID lists are not delimited, so we
+ * consume the rest of the input. */
+static krb5_error_code
+kcmreq_get_uuid_list(struct kcmreq *req, struct uuid_list **uuids_out)
+{
+ struct uuid_list *uuids;
+
+ *uuids_out = NULL;
+
+ if (req->reply.len % KCM_UUID_LEN != 0)
+ return KRB5_KCM_MALFORMED_REPLY;
+
+ uuids = malloc(sizeof(*uuids));
+ if (uuids == NULL)
+ return ENOMEM;
+ uuids->count = req->reply.len / KCM_UUID_LEN;
+ uuids->pos = 0;
+
+ if (req->reply.len > 0) {
+ uuids->uuidbytes = malloc(req->reply.len);
+ if (uuids->uuidbytes == NULL) {
+ free(uuids);
+ return ENOMEM;
+ }
+ memcpy(uuids->uuidbytes, req->reply.ptr, req->reply.len);
+ (void)k5_input_get_bytes(&req->reply, req->reply.len);
+ } else {
+ uuids->uuidbytes = NULL;
+ }
+
+ *uuids_out = uuids;
+ return 0;
+}
+
+static void
+free_uuid_list(struct uuid_list *uuids)
+{
+ if (uuids != NULL)
+ free(uuids->uuidbytes);
+ free(uuids);
+}
+
+static void
+kcmreq_free(struct kcmreq *req)
+{
+ k5_buf_free(&req->reqbuf);
+ free(req->reply_mem);
+}
+
+/* Create a krb5_ccache structure. If io is NULL, make a new connection for
+ * the cache. Otherwise, always take ownership of io. */
+static krb5_error_code
+make_cache(krb5_context context, const char *residual, struct kcmio *io,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ krb5_ccache cache = NULL;
+ struct kcm_cache_data *data = NULL;
+ char *residual_copy = NULL;
+
+ *cache_out = NULL;
+
+ if (io == NULL) {
+ ret = kcmio_connect(context, &io);
+ if (ret)
+ return ret;
+ }
+
+ cache = malloc(sizeof(*cache));
+ if (cache == NULL)
+ goto oom;
+ data = calloc(1, sizeof(*data));
+ if (data == NULL)
+ goto oom;
+ residual_copy = strdup(residual);
+ if (residual_copy == NULL)
+ goto oom;
+ if (k5_cc_mutex_init(&data->lock) != 0)
+ goto oom;
+
+ data->residual = residual_copy;
+ data->io = io;
+ data->changetime = 0;
+ cache->ops = &krb5_kcm_ops;
+ cache->data = data;
+ cache->magic = KV5M_CCACHE;
+ *cache_out = cache;
+ return 0;
+
+oom:
+ free(cache);
+ free(data);
+ free(residual_copy);
+ kcmio_close(io);
+ return ENOMEM;
+}
+
+/* Lock cache's I/O structure and use it to call the KCM daemon. If modify is
+ * true, update the last change time. */
+static krb5_error_code
+cache_call(krb5_context context, krb5_ccache cache, struct kcmreq *req,
+ krb5_boolean modify)
+{
+ krb5_error_code ret;
+ struct kcm_cache_data *data = cache->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ ret = kcmio_call(context, data->io, req);
+ if (modify && !ret)
+ data->changetime = time(NULL);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+/* Try to propagate the KDC time offset from the cache to the krb5 context. */
+static void
+get_kdc_offset(krb5_context context, krb5_ccache cache)
+{
+ struct kcmreq req = EMPTY_KCMREQ;
+ int32_t time_offset;
+
+ kcmreq_init(&req, KCM_OP_GET_KDC_OFFSET, cache);
+ if (cache_call(context, cache, &req, FALSE) != 0)
+ goto cleanup;
+ time_offset = k5_input_get_uint32_be(&req.reply);
+ if (!req.reply.status)
+ goto cleanup;
+ context->os_context.time_offset = time_offset;
+ context->os_context.usec_offset = 0;
+ context->os_context.os_flags &= ~KRB5_OS_TOFFSET_TIME;
+ context->os_context.os_flags |= KRB5_OS_TOFFSET_VALID;
+
+cleanup:
+ kcmreq_free(&req);
+}
+
+/* Try to propagate the KDC offset from the krb5 context to the cache. */
+static void
+set_kdc_offset(krb5_context context, krb5_ccache cache)
+{
+ struct kcmreq req;
+
+ if (context->os_context.os_flags & KRB5_OS_TOFFSET_VALID) {
+ kcmreq_init(&req, KCM_OP_SET_KDC_OFFSET, cache);
+ kcmreq_put32(&req, context->os_context.time_offset);
+ (void)cache_call(context, cache, &req, TRUE);
+ kcmreq_free(&req);
+ }
+}
+
+static const char * KRB5_CALLCONV
+kcm_get_name(krb5_context context, krb5_ccache cache)
+{
+ return ((struct kcm_cache_data *)cache->data)->residual;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
+{
+ krb5_error_code ret;
+ struct kcmreq req = EMPTY_KCMREQ;
+ struct kcmio *io = NULL;
+ const char *defname = NULL;
+
+ *cache_out = NULL;
+
+ ret = kcmio_connect(context, &io);
+ if (ret)
+ goto cleanup;
+
+ if (*residual == '\0') {
+ kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL);
+ ret = kcmio_call(context, io, &req);
+ if (ret)
+ goto cleanup;
+ ret = kcmreq_get_name(&req, &defname);
+ if (ret)
+ goto cleanup;
+ residual = defname;
+ }
+
+ ret = make_cache(context, residual, io, cache_out);
+ io = NULL;
+
+cleanup:
+ kcmio_close(io);
+ kcmreq_free(&req);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_gen_new(krb5_context context, krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ struct kcmreq req = EMPTY_KCMREQ;
+ struct kcmio *io = NULL;
+ const char *name;
+
+ *cache_out = NULL;
+
+ ret = kcmio_connect(context, &io);
+ if (ret)
+ goto cleanup;
+ kcmreq_init(&req, KCM_OP_GEN_NEW, NULL);
+ ret = kcmio_call(context, io, &req);
+ if (ret)
+ goto cleanup;
+ ret = kcmreq_get_name(&req, &name);
+ if (ret)
+ goto cleanup;
+ ret = make_cache(context, name, io, cache_out);
+ io = NULL;
+
+cleanup:
+ kcmreq_free(&req);
+ kcmio_close(io);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_initialize(krb5_context context, krb5_ccache cache, krb5_principal princ)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_INITIALIZE, cache);
+ k5_marshal_princ(&req.reqbuf, 4, princ);
+ ret = cache_call(context, cache, &req, TRUE);
+ kcmreq_free(&req);
+ set_kdc_offset(context, cache);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_close(krb5_context context, krb5_ccache cache)
+{
+ struct kcm_cache_data *data = cache->data;
+
+ k5_cc_mutex_destroy(&data->lock);
+ kcmio_close(data->io);
+ free(data->residual);
+ free(data);
+ free(cache);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_destroy(krb5_context context, krb5_ccache cache)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_DESTROY, cache);
+ ret = cache_call(context, cache, &req, TRUE);
+ kcmreq_free(&req);
+ (void)kcm_close(context, cache);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_store(krb5_context context, krb5_ccache cache, krb5_creds *cred)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_STORE, cache);
+ k5_marshal_cred(&req.reqbuf, 4, cred);
+ ret = cache_call(context, cache, &req, TRUE);
+ kcmreq_free(&req);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *mcred, krb5_creds *cred_out)
+{
+ /* There is a KCM opcode for retrieving creds, but Heimdal's client doesn't
+ * use it. It causes the KCM daemon to actually make a TGS request. */
+ return k5_cc_retrieve_cred_default(context, cache, flags, mcred, cred_out);
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_get_princ(krb5_context context, krb5_ccache cache,
+ krb5_principal *princ_out)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, cache);
+ ret = cache_call(context, cache, &req, FALSE);
+ /* Heimdal KCM can respond with code 0 and no principal. */
+ if (!ret && req.reply.len == 0)
+ ret = KRB5_FCC_NOFILE;
+ if (!ret)
+ ret = k5_unmarshal_princ(req.reply.ptr, req.reply.len, 4, princ_out);
+ kcmreq_free(&req);
+ return map_invalid(ret);
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_start_seq_get(krb5_context context, krb5_ccache cache,
+ krb5_cc_cursor *cursor_out)
+{
+ krb5_error_code ret;
+ struct kcmreq req = EMPTY_KCMREQ;
+ struct uuid_list *uuids;
+
+ *cursor_out = NULL;
+
+ get_kdc_offset(context, cache);
+
+ kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache);
+ ret = cache_call(context, cache, &req, FALSE);
+ if (ret)
+ goto cleanup;
+ ret = kcmreq_get_uuid_list(&req, &uuids);
+ if (ret)
+ goto cleanup;
+ *cursor_out = (krb5_cc_cursor)uuids;
+
+cleanup:
+ kcmreq_free(&req);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
+ krb5_creds *cred_out)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+ struct uuid_list *uuids = (struct uuid_list *)*cursor;
+
+ memset(cred_out, 0, sizeof(*cred_out));
+
+ if (uuids->pos >= uuids->count)
+ return KRB5_CC_END;
+
+ kcmreq_init(&req, KCM_OP_GET_CRED_BY_UUID, cache);
+ k5_buf_add_len(&req.reqbuf, uuids->uuidbytes + (uuids->pos * KCM_UUID_LEN),
+ KCM_UUID_LEN);
+ uuids->pos++;
+ ret = cache_call(context, cache, &req, FALSE);
+ if (!ret)
+ ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, cred_out);
+ kcmreq_free(&req);
+ return map_invalid(ret);
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_end_seq_get(krb5_context context, krb5_ccache cache,
+ krb5_cc_cursor *cursor)
+{
+ free_uuid_list((struct uuid_list *)*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *mcred)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_REMOVE_CRED, cache);
+ kcmreq_put32(&req, flags);
+ k5_marshal_mcred(&req.reqbuf, mcred);
+ ret = cache_call(context, cache, &req, TRUE);
+ kcmreq_free(&req);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
+{
+ /* We don't currently care about any flags for this type. */
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
+{
+ /* We don't currently have any operational flags for this type. */
+ *flags_out = 0;
+ return 0;
+}
+
+/* Construct a per-type cursor, always taking ownership of io and uuids. */
+static krb5_error_code
+make_ptcursor(const char *residual, struct uuid_list *uuids, struct kcmio *io,
+ krb5_cc_ptcursor *cursor_out)
+{
+ krb5_cc_ptcursor cursor = NULL;
+ struct kcm_ptcursor *data = NULL;
+ char *residual_copy = NULL;
+
+ *cursor_out = NULL;
+
+ if (residual != NULL) {
+ residual_copy = strdup(residual);
+ if (residual_copy == NULL)
+ goto oom;
+ }
+ cursor = malloc(sizeof(*cursor));
+ if (cursor == NULL)
+ goto oom;
+ data = malloc(sizeof(*data));
+ if (data == NULL)
+ goto oom;
+
+ data->residual = residual_copy;
+ data->uuids = uuids;
+ data->io = io;
+ data->first = TRUE;
+ cursor->ops = &krb5_kcm_ops;
+ cursor->data = data;
+ *cursor_out = cursor;
+ return 0;
+
+oom:
+ kcmio_close(io);
+ free_uuid_list(uuids);
+ free(residual_copy);
+ free(data);
+ free(cursor);
+ return ENOMEM;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
+{
+ krb5_error_code ret;
+ struct kcmreq req = EMPTY_KCMREQ;
+ struct kcmio *io = NULL;
+ struct uuid_list *uuids = NULL;
+ const char *defname, *primary;
+
+ *cursor_out = NULL;
+
+ /* Don't try to use KCM for the cache collection unless the default cache
+ * name has the KCM type. */
+ defname = krb5_cc_default_name(context);
+ if (defname == NULL || strncmp(defname, "KCM:", 4) != 0)
+ return make_ptcursor(NULL, NULL, NULL, cursor_out);
+
+ ret = kcmio_connect(context, &io);
+ if (ret)
+ return ret;
+
+ /* If defname is a subsidiary cache, return a singleton cursor. */
+ if (strlen(defname) > 4)
+ return make_ptcursor(defname + 4, NULL, io, cursor_out);
+
+ kcmreq_init(&req, KCM_OP_GET_CACHE_UUID_LIST, NULL);
+ ret = kcmio_call(context, io, &req);
+ if (ret == KRB5_FCC_NOFILE) {
+ /* There are no accessible caches; return an empty cursor. */
+ ret = make_ptcursor(NULL, NULL, NULL, cursor_out);
+ goto cleanup;
+ }
+ if (ret)
+ goto cleanup;
+ ret = kcmreq_get_uuid_list(&req, &uuids);
+ if (ret)
+ goto cleanup;
+
+ kcmreq_free(&req);
+ kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL);
+ ret = kcmio_call(context, io, &req);
+ if (ret)
+ goto cleanup;
+ ret = kcmreq_get_name(&req, &primary);
+ if (ret)
+ goto cleanup;
+
+ ret = make_ptcursor(primary, uuids, io, cursor_out);
+ uuids = NULL;
+ io = NULL;
+
+cleanup:
+ free_uuid_list(uuids);
+ kcmio_close(io);
+ kcmreq_free(&req);
+ return ret;
+}
+
+/* Return true if name is an initialized cache. */
+static krb5_boolean
+name_exists(krb5_context context, struct kcmio *io, const char *name)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, NULL);
+ k5_buf_add_len(&req.reqbuf, name, strlen(name) + 1);
+ ret = kcmio_call(context, io, &req);
+ kcmreq_free(&req);
+ return ret == 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret = 0;
+ struct kcmreq req = EMPTY_KCMREQ;
+ struct kcm_ptcursor *data = cursor->data;
+ struct uuid_list *uuids;
+ const unsigned char *id;
+ const char *name;
+
+ *cache_out = NULL;
+
+ /* Return the primary or specified subsidiary cache if we haven't yet. */
+ if (data->first && data->residual != NULL) {
+ data->first = FALSE;
+ if (name_exists(context, data->io, data->residual))
+ return make_cache(context, data->residual, NULL, cache_out);
+ }
+
+ uuids = data->uuids;
+ if (uuids == NULL)
+ return 0;
+
+ while (uuids->pos < uuids->count) {
+ /* Get the name of the next cache. */
+ id = &uuids->uuidbytes[KCM_UUID_LEN * uuids->pos++];
+ kcmreq_free(&req);
+ kcmreq_init(&req, KCM_OP_GET_CACHE_BY_UUID, NULL);
+ k5_buf_add_len(&req.reqbuf, id, KCM_UUID_LEN);
+ ret = kcmio_call(context, data->io, &req);
+ if (ret)
+ goto cleanup;
+ ret = kcmreq_get_name(&req, &name);
+ if (ret)
+ goto cleanup;
+
+ /* Don't yield the primary cache twice. */
+ if (strcmp(name, data->residual) == 0)
+ continue;
+
+ ret = make_cache(context, name, NULL, cache_out);
+ break;
+ }
+
+cleanup:
+ kcmreq_free(&req);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ struct kcm_ptcursor *data = (*cursor)->data;
+
+ free(data->residual);
+ free_uuid_list(data->uuids);
+ kcmio_close(data->io);
+ free(data);
+ free(*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_lastchange(krb5_context context, krb5_ccache cache,
+ krb5_timestamp *time_out)
+{
+ struct kcm_cache_data *data = cache->data;
+
+ /*
+ * KCM has no support for retrieving the last change time. Return the time
+ * of the last change made through this handle, which isn't very useful,
+ * but is the best we can do for now.
+ */
+ k5_cc_mutex_lock(context, &data->lock);
+ *time_out = data->changetime;
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_lock(krb5_context context, krb5_ccache cache)
+{
+ k5_cc_mutex_lock(context, &((struct kcm_cache_data *)cache->data)->lock);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_unlock(krb5_context context, krb5_ccache cache)
+{
+ k5_cc_mutex_unlock(context, &((struct kcm_cache_data *)cache->data)->lock);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+kcm_switch_to(krb5_context context, krb5_ccache cache)
+{
+ krb5_error_code ret;
+ struct kcmreq req;
+
+ kcmreq_init(&req, KCM_OP_SET_DEFAULT_CACHE, cache);
+ ret = cache_call(context, cache, &req, FALSE);
+ kcmreq_free(&req);
+ return ret;
+}
+
+const krb5_cc_ops krb5_kcm_ops = {
+ 0,
+ "KCM",
+ kcm_get_name,
+ kcm_resolve,
+ kcm_gen_new,
+ kcm_initialize,
+ kcm_destroy,
+ kcm_close,
+ kcm_store,
+ kcm_retrieve,
+ kcm_get_princ,
+ kcm_start_seq_get,
+ kcm_next_cred,
+ kcm_end_seq_get,
+ kcm_remove_cred,
+ kcm_set_flags,
+ kcm_get_flags,
+ kcm_ptcursor_new,
+ kcm_ptcursor_next,
+ kcm_ptcursor_free,
+ NULL, /* move */
+ kcm_lastchange,
+ NULL, /* wasdefault */
+ kcm_lock,
+ kcm_unlock,
+ kcm_switch_to,
+};
+
+#endif /* not _WIN32 */
diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c
new file mode 100644
index 0000000000000..4fe3f0d6f1f22
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_keyring.c
@@ -0,0 +1,1755 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_keyring.c */
+/*
+ * Copyright (c) 2006
+ * The Regents of the University of Michigan
+ * ALL RIGHTS RESERVED
+ *
+ * Permission is granted to use, copy, create derivative works
+ * and redistribute this software and such derivative works
+ * for any purpose, so long as the name of The University of
+ * Michigan is not used in any advertising or publicity
+ * pertaining to the use of distribution of this software
+ * without specific, written prior authorization. If the
+ * above copyright notice or any other identification of the
+ * University of Michigan is included in any copy of any
+ * portion of this software, then the disclaimer below must
+ * also be included.
+ *
+ * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
+ * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
+ * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
+ * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
+ * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
+ * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGES.
+ */
+/*
+ * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of
+ * Technology. All Rights Reserved.
+ *
+ * Original stdio support copyright 1995 by Cygnus Support.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * This file implements a collection-enabled credential cache type where the
+ * credentials are stored in the Linux keyring facility.
+ *
+ * A residual of this type can have three forms:
+ * anchor:collection:subsidiary
+ * anchor:collection
+ * collection
+ *
+ * The anchor name is "process", "thread", or "legacy" and determines where we
+ * search for keyring collections. In the third form, the anchor name is
+ * presumed to be "legacy". The anchor keyring for legacy caches is the
+ * session keyring.
+ *
+ * If the subsidiary name is present, the residual identifies a single cache
+ * within a collection. Otherwise, the residual identifies the collection
+ * itself. When a residual identifying a collection is resolved, the
+ * collection's primary key is looked up (or initialized, using the collection
+ * name as the subsidiary name), and the resulting cache's name will use the
+ * first name form and will identify the primary cache.
+ *
+ * Keyring collections are named "_krb_<collection>" and are linked from the
+ * anchor keyring. The keys within a keyring collection are links to cache
+ * keyrings, plus a link to one user key named "krb_ccache:primary" which
+ * contains a serialized representation of the collection version (currently 1)
+ * and the primary name of the collection.
+ *
+ * Cache keyrings contain one user key per credential which contains a
+ * serialized representation of the credential. There is also one user key
+ * named "__krb5_princ__" which contains a serialized representation of the
+ * cache's default principal.
+ *
+ * If the anchor name is "legacy", then the initial primary cache (the one
+ * named with the collection name) is also linked to the session keyring, and
+ * we look for a cache in that location when initializing the collection. This
+ * extra link allows that cache to be visible to old versions of the KEYRING
+ * cache type, and allows us to see caches created by that code.
+ */
+
+#include "cc-int.h"
+
+#ifdef USE_KEYRING_CCACHE
+
+#include <errno.h>
+#include <keyutils.h>
+
+#ifdef DEBUG
+#define KRCC_DEBUG 1
+#endif
+
+#if KRCC_DEBUG
+void debug_print(char *fmt, ...); /* prototype to silence warning */
+#include <syslog.h>
+#define DEBUG_PRINT(x) debug_print x
+void
+debug_print(char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+#ifdef DEBUG_STDERR
+ vfprintf(stderr, fmt, ap);
+#else
+ vsyslog(LOG_ERR, fmt, ap);
+#endif
+ va_end(ap);
+}
+#else
+#define DEBUG_PRINT(x)
+#endif
+
+/*
+ * We try to use the big_key key type for credentials except in legacy caches.
+ * We fall back to the user key type if the kernel does not support big_key.
+ * If the library doesn't support keyctl_get_persistent(), we don't even try
+ * big_key since the two features were added at the same time.
+ */
+#ifdef HAVE_PERSISTENT_KEYRING
+#define KRCC_CRED_KEY_TYPE "big_key"
+#else
+#define KRCC_CRED_KEY_TYPE "user"
+#endif
+
+/*
+ * We use the "user" key type for collection primary names, for cache principal
+ * names, and for credentials in legacy caches.
+ */
+#define KRCC_KEY_TYPE_USER "user"
+
+/*
+ * We create ccaches as separate keyrings
+ */
+#define KRCC_KEY_TYPE_KEYRING "keyring"
+
+/*
+ * Special name of the key within a ccache keyring
+ * holding principal information
+ */
+#define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__"
+
+/*
+ * Special name for the key to communicate the name(s)
+ * of credentials caches to be used for requests.
+ * This should currently contain a single name, but
+ * in the future may contain a list that may be
+ * intelligently chosen from.
+ */
+#define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__"
+
+/*
+ * This name identifies the key containing the name of the current primary
+ * cache within a collection.
+ */
+#define KRCC_COLLECTION_PRIMARY "krb_ccache:primary"
+
+/*
+ * If the library context does not specify a keyring collection, unique ccaches
+ * will be created within this collection.
+ */
+#define KRCC_DEFAULT_UNIQUE_COLLECTION "session:__krb5_unique__"
+
+/*
+ * Collection keyring names begin with this prefix. We use a prefix so that a
+ * cache keyring with the collection name itself can be linked directly into
+ * the anchor, for legacy session keyring compatibility.
+ */
+#define KRCC_CCCOL_PREFIX "_krb_"
+
+/*
+ * For the "persistent" anchor type, we look up or create this fixed keyring
+ * name within the per-UID persistent keyring.
+ */
+#define KRCC_PERSISTENT_KEYRING_NAME "_krb"
+
+/*
+ * Name of the key holding time offsets for the individual cache
+ */
+#define KRCC_TIME_OFFSETS "__krb5_time_offsets__"
+
+/*
+ * Keyring name prefix and length of random name part
+ */
+#define KRCC_NAME_PREFIX "krb_ccache_"
+#define KRCC_NAME_RAND_CHARS 8
+
+#define KRCC_COLLECTION_VERSION 1
+
+#define KRCC_PERSISTENT_ANCHOR "persistent"
+#define KRCC_PROCESS_ANCHOR "process"
+#define KRCC_THREAD_ANCHOR "thread"
+#define KRCC_SESSION_ANCHOR "session"
+#define KRCC_USER_ANCHOR "user"
+#define KRCC_LEGACY_ANCHOR "legacy"
+
+typedef struct _krcc_cursor
+{
+ int numkeys;
+ int currkey;
+ key_serial_t princ_id;
+ key_serial_t offsets_id;
+ key_serial_t *keys;
+} *krcc_cursor;
+
+/*
+ * This represents a credentials cache "file"
+ * where cache_id is the keyring serial number for
+ * this credentials cache "file". Each key
+ * in the keyring contains a separate key.
+ */
+typedef struct _krcc_data
+{
+ char *name; /* Name for this credentials cache */
+ k5_cc_mutex lock; /* synchronization */
+ key_serial_t collection_id; /* collection containing this cache keyring */
+ key_serial_t cache_id; /* keyring representing ccache */
+ key_serial_t princ_id; /* key holding principal info */
+ krb5_timestamp changetime;
+ krb5_boolean is_legacy_type;
+} krcc_data;
+
+/* Global mutex */
+k5_cc_mutex krb5int_krcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+
+extern const krb5_cc_ops krb5_krcc_ops;
+
+static const char *KRB5_CALLCONV
+krcc_get_name(krb5_context context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_start_seq_get(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor);
+
+static krb5_error_code clear_cache_keyring(krb5_context context,
+ krb5_ccache id);
+
+static krb5_error_code make_krcc_data(const char *anchor_name,
+ const char *collection_name,
+ const char *subsidiary_name,
+ key_serial_t cache_id, key_serial_t
+ collection_id, krcc_data **datapp);
+
+static krb5_error_code save_principal(krb5_context context, krb5_ccache id,
+ krb5_principal princ);
+
+static krb5_error_code save_time_offsets(krb5_context context, krb5_ccache id,
+ int32_t time_offset,
+ int32_t usec_offset);
+
+static krb5_error_code get_time_offsets(krb5_context context, krb5_ccache id,
+ int32_t *time_offset,
+ int32_t *usec_offset);
+
+static void krcc_update_change_time(krcc_data *d);
+
+/* Note the following is a stub function for Linux */
+extern krb5_error_code krb5_change_cache(void);
+
+/*
+ * GET_PERSISTENT(uid) acquires the persistent keyring for uid, or falls back
+ * to the user keyring if uid matches the current effective uid.
+ */
+
+static key_serial_t
+get_persistent_fallback(uid_t uid)
+{
+ return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1;
+}
+
+#ifdef HAVE_PERSISTENT_KEYRING
+#define GET_PERSISTENT get_persistent_real
+static key_serial_t
+get_persistent_real(uid_t uid)
+{
+ key_serial_t key;
+
+ key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING);
+ return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) :
+ key;
+}
+#else
+#define GET_PERSISTENT get_persistent_fallback
+#endif
+
+/*
+ * If a process has no explicitly set session keyring, KEY_SPEC_SESSION_KEYRING
+ * will resolve to the user session keyring for ID lookup and reading, but in
+ * some kernel versions, writing to that special keyring will instead create a
+ * new empty session keyring for the process. We do not want that; the keys we
+ * create would be invisible to other processes. We can work around that
+ * behavior by explicitly writing to the user session keyring when it matches
+ * the session keyring. This function returns the keyring we should write to
+ * for the session anchor.
+ */
+static key_serial_t
+session_write_anchor()
+{
+ key_serial_t s, u;
+
+ s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
+ u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
+ return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING;
+}
+
+/*
+ * Find or create a keyring within parent with the given name. If possess is
+ * nonzero, also make sure the key is linked from possess. This is necessary
+ * to ensure that we have possession rights on the key when the parent is the
+ * user or persistent keyring.
+ */
+static krb5_error_code
+find_or_create_keyring(key_serial_t parent, key_serial_t possess,
+ const char *name, key_serial_t *key_out)
+{
+ key_serial_t key;
+
+ *key_out = -1;
+ key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess);
+ if (key == -1) {
+ if (possess != 0) {
+ key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess);
+ if (key == -1)
+ return errno;
+ if (keyctl_link(key, parent) == -1)
+ return errno;
+ } else {
+ key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
+ if (key == -1)
+ return errno;
+ }
+ }
+ *key_out = key;
+ return 0;
+}
+
+/* Parse a residual name into an anchor name, a collection name, and possibly a
+ * subsidiary name. */
+static krb5_error_code
+parse_residual(const char *residual, char **anchor_name_out,
+ char **collection_name_out, char **subsidiary_name_out)
+{
+ krb5_error_code ret;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ const char *sep;
+
+ *anchor_name_out = 0;
+ *collection_name_out = NULL;
+ *subsidiary_name_out = NULL;
+
+ /* Parse out the anchor name. Use the legacy anchor if not present. */
+ sep = strchr(residual, ':');
+ if (sep == NULL) {
+ anchor_name = strdup(KRCC_LEGACY_ANCHOR);
+ if (anchor_name == NULL)
+ goto oom;
+ } else {
+ anchor_name = k5memdup0(residual, sep - residual, &ret);
+ if (anchor_name == NULL)
+ goto oom;
+ residual = sep + 1;
+ }
+
+ /* Parse out the collection and subsidiary name. */
+ sep = strchr(residual, ':');
+ if (sep == NULL) {
+ collection_name = strdup(residual);
+ if (collection_name == NULL)
+ goto oom;
+ subsidiary_name = NULL;
+ } else {
+ collection_name = k5memdup0(residual, sep - residual, &ret);
+ if (collection_name == NULL)
+ goto oom;
+ subsidiary_name = strdup(sep + 1);
+ if (subsidiary_name == NULL)
+ goto oom;
+ }
+
+ *anchor_name_out = anchor_name;
+ *collection_name_out = collection_name;
+ *subsidiary_name_out = subsidiary_name;
+ return 0;
+
+oom:
+ free(anchor_name);
+ free(collection_name);
+ free(subsidiary_name);
+ return ENOMEM;
+}
+
+/*
+ * Return true if residual identifies a subsidiary cache which should be linked
+ * into the anchor so it can be visible to old code. This is the case if the
+ * residual has the legacy anchor and the subsidiary name matches the
+ * collection name.
+ */
+static krb5_boolean
+is_legacy_cache_name(const char *residual)
+{
+ const char *sep, *aname, *cname, *sname;
+ size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1;
+
+ /* Get pointers to the anchor, collection, and subsidiary names. */
+ aname = residual;
+ sep = strchr(residual, ':');
+ if (sep == NULL)
+ return FALSE;
+ alen = sep - aname;
+ cname = sep + 1;
+ sep = strchr(cname, ':');
+ if (sep == NULL)
+ return FALSE;
+ clen = sep - cname;
+ sname = sep + 1;
+
+ return alen == legacy_len && clen == strlen(sname) &&
+ strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 &&
+ strncmp(cname, sname, clen) == 0;
+}
+
+/* If the default cache name for context is a KEYRING cache, parse its residual
+ * string. Otherwise set all outputs to NULL. */
+static krb5_error_code
+get_default(krb5_context context, char **anchor_name_out,
+ char **collection_name_out, char **subsidiary_name_out)
+{
+ const char *defname;
+
+ *anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL;
+ defname = krb5_cc_default_name(context);
+ if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
+ return 0;
+ return parse_residual(defname + 8, anchor_name_out, collection_name_out,
+ subsidiary_name_out);
+}
+
+/* Create a residual identifying a subsidiary cache. */
+static krb5_error_code
+make_subsidiary_residual(const char *anchor_name, const char *collection_name,
+ const char *subsidiary_name, char **residual_out)
+{
+ if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name,
+ subsidiary_name) < 0) {
+ *residual_out = NULL;
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/* Retrieve or create a keyring for collection_name within the anchor, and set
+ * *collection_id_out to its serial number. */
+static krb5_error_code
+get_collection(const char *anchor_name, const char *collection_name,
+ key_serial_t *collection_id_out)
+{
+ krb5_error_code ret;
+ key_serial_t persistent_id, anchor_id, possess_id = 0;
+ char *ckname, *cnend;
+ long uidnum;
+
+ *collection_id_out = 0;
+
+ if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) {
+ /*
+ * The collection name is a uid (or empty for the current effective
+ * uid), and we look up a fixed keyring name within the persistent
+ * keyring for that uid. We link it to the process keyring to ensure
+ * that we have possession rights on the collection key.
+ */
+ if (*collection_name != '\0') {
+ errno = 0;
+ uidnum = strtol(collection_name, &cnend, 10);
+ if (errno || *cnend != '\0')
+ return KRB5_KCC_INVALID_UID;
+ } else {
+ uidnum = geteuid();
+ }
+ persistent_id = GET_PERSISTENT(uidnum);
+ if (persistent_id == -1)
+ return KRB5_KCC_INVALID_UID;
+ return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING,
+ KRCC_PERSISTENT_KEYRING_NAME,
+ collection_id_out);
+ }
+
+ if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) {
+ anchor_id = KEY_SPEC_PROCESS_KEYRING;
+ } else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) {
+ anchor_id = KEY_SPEC_THREAD_KEYRING;
+ } else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) {
+ anchor_id = session_write_anchor();
+ } else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) {
+ /* The user keyring does not confer possession, so we need to link the
+ * collection to the process keyring to maintain possession rights. */
+ anchor_id = KEY_SPEC_USER_KEYRING;
+ possess_id = KEY_SPEC_PROCESS_KEYRING;
+ } else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
+ anchor_id = session_write_anchor();
+ } else {
+ return KRB5_KCC_INVALID_ANCHOR;
+ }
+
+ /* Look up the collection keyring name within the anchor keyring. */
+ if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1)
+ return ENOMEM;
+ ret = find_or_create_keyring(anchor_id, possess_id, ckname,
+ collection_id_out);
+ free(ckname);
+ return ret;
+}
+
+/* Store subsidiary_name into the primary index key for collection_id. */
+static krb5_error_code
+set_primary_name(krb5_context context, key_serial_t collection_id,
+ const char *subsidiary_name)
+{
+ key_serial_t key;
+ uint32_t len = strlen(subsidiary_name), plen = 8 + len;
+ unsigned char *payload;
+
+ payload = malloc(plen);
+ if (payload == NULL)
+ return ENOMEM;
+ store_32_be(KRCC_COLLECTION_VERSION, payload);
+ store_32_be(len, payload + 4);
+ memcpy(payload + 8, subsidiary_name, len);
+ key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
+ payload, plen, collection_id);
+ free(payload);
+ return (key == -1) ? errno : 0;
+}
+
+static krb5_error_code
+parse_index(krb5_context context, int32_t *version, char **primary,
+ const unsigned char *payload, size_t psize)
+{
+ krb5_error_code ret;
+ uint32_t len;
+
+ if (psize < 8)
+ return KRB5_CC_END;
+
+ *version = load_32_be(payload);
+ len = load_32_be(payload + 4);
+ if (len > psize - 8)
+ return KRB5_CC_END;
+ *primary = k5memdup0(payload + 8, len, &ret);
+ return (*primary == NULL) ? ret : 0;
+}
+
+/*
+ * Get or initialize the primary name within collection_id and set
+ * *subsidiary_out to its value. If initializing a legacy collection, look
+ * for a legacy cache and add it to the collection.
+ */
+static krb5_error_code
+get_primary_name(krb5_context context, const char *anchor_name,
+ const char *collection_name, key_serial_t collection_id,
+ char **subsidiary_out)
+{
+ krb5_error_code ret;
+ key_serial_t primary_id, legacy;
+ void *payload = NULL;
+ int payloadlen;
+ int32_t version;
+ char *subsidiary_name = NULL;
+
+ *subsidiary_out = NULL;
+
+ primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER,
+ KRCC_COLLECTION_PRIMARY, 0);
+ if (primary_id == -1) {
+ /* Initialize the primary key using the collection name. We can't name
+ * a key with the empty string, so map that to an arbitrary string. */
+ subsidiary_name = strdup((*collection_name == '\0') ? "tkt" :
+ collection_name);
+ if (subsidiary_name == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ ret = set_primary_name(context, collection_id, subsidiary_name);
+ if (ret)
+ goto cleanup;
+
+ if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
+ /* Look for a cache created by old code. If we find one, add it to
+ * the collection. */
+ legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING,
+ KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0);
+ if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) {
+ ret = errno;
+ goto cleanup;
+ }
+ }
+ } else {
+ /* Read, parse, and free the primary key's payload. */
+ payloadlen = keyctl_read_alloc(primary_id, &payload);
+ if (payloadlen == -1) {
+ ret = errno;
+ goto cleanup;
+ }
+ ret = parse_index(context, &version, &subsidiary_name, payload,
+ payloadlen);
+ if (ret)
+ goto cleanup;
+
+ if (version != KRCC_COLLECTION_VERSION) {
+ ret = KRB5_KCC_UNKNOWN_VERSION;
+ goto cleanup;
+ }
+ }
+
+ *subsidiary_out = subsidiary_name;
+ subsidiary_name = NULL;
+
+cleanup:
+ free(payload);
+ free(subsidiary_name);
+ return ret;
+}
+
+/*
+ * Create a keyring with a unique random name within collection_id. Set
+ * *subsidiary to its name and *cache_id_out to its key serial number.
+ */
+static krb5_error_code
+unique_keyring(krb5_context context, key_serial_t collection_id,
+ char **subsidiary_out, key_serial_t *cache_id_out)
+{
+ key_serial_t key;
+ krb5_error_code ret;
+ char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
+ int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
+ int tries;
+
+ *subsidiary_out = NULL;
+ *cache_id_out = 0;
+
+ memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
+ k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
+
+ /* Loop until we successfully create a new ccache keyring with
+ * a unique name, or we get an error. Limit to 100 tries. */
+ tries = 100;
+ while (tries-- > 0) {
+ ret = krb5int_random_string(context, uniquename + prefixlen,
+ KRCC_NAME_RAND_CHARS);
+ if (ret)
+ goto cleanup;
+
+ key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename,
+ 0);
+ if (key < 0) {
+ /* Name does not already exist. Create it to reserve the name. */
+ key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0,
+ collection_id);
+ if (key < 0) {
+ ret = errno;
+ goto cleanup;
+ }
+ break;
+ }
+ }
+
+ if (tries <= 0) {
+ ret = KRB5_CC_BADNAME;
+ goto cleanup;
+ }
+
+ *subsidiary_out = strdup(uniquename);
+ if (*subsidiary_out == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ *cache_id_out = key;
+ ret = 0;
+cleanup:
+ k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
+ return ret;
+}
+
+static krb5_error_code
+add_cred_key(const char *name, const void *payload, size_t plen,
+ key_serial_t cache_id, krb5_boolean legacy_type,
+ key_serial_t *key_out)
+{
+ key_serial_t key;
+
+ *key_out = -1;
+ if (!legacy_type) {
+ /* Try the preferred cred key type; fall back if no kernel support. */
+ key = add_key(KRCC_CRED_KEY_TYPE, name, payload, plen, cache_id);
+ if (key != -1) {
+ *key_out = key;
+ return 0;
+ } else if (errno != EINVAL && errno != ENODEV) {
+ return errno;
+ }
+ }
+ /* Use the user key type. */
+ key = add_key(KRCC_KEY_TYPE_USER, name, payload, plen, cache_id);
+ if (key == -1)
+ return errno;
+ *key_out = key;
+ return 0;
+}
+
+static void
+update_keyring_expiration(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+ krb5_cc_cursor cursor;
+ krb5_creds creds;
+ krb5_timestamp now, endtime = 0;
+ unsigned int timeout;
+
+ /*
+ * We have no way to know what is the actual timeout set on the keyring.
+ * We also cannot keep track of it in a local variable as another process
+ * can always modify the keyring independently, so just always enumerate
+ * all keys and find out the highest endtime time.
+ */
+
+ /* Find the maximum endtime of all creds in the cache. */
+ if (krcc_start_seq_get(context, id, &cursor) != 0)
+ return;
+ for (;;) {
+ if (krcc_next_cred(context, id, &cursor, &creds) != 0)
+ break;
+ if (creds.times.endtime > endtime)
+ endtime = creds.times.endtime;
+ krb5_free_cred_contents(context, &creds);
+ }
+ (void)krcc_end_seq_get(context, id, &cursor);
+
+ if (endtime == 0) /* No creds with end times */
+ return;
+
+ if (krb5_timeofday(context, &now) != 0)
+ return;
+
+ /* Setting the timeout to zero would reset the timeout, so we set it to one
+ * second instead if creds are already expired. */
+ timeout = (endtime > now) ? endtime - now : 1;
+ (void)keyctl_set_timeout(data->cache_id, timeout);
+}
+
+/* Create or overwrite the cache keyring, and set the default principal. */
+static krb5_error_code KRB5_CALLCONV
+krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krcc_data *data = (krcc_data *)id->data;
+ krb5_os_context os_ctx = &context->os_context;
+ krb5_error_code ret;
+ const char *cache_name, *p;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ ret = clear_cache_keyring(context, id);
+ if (ret)
+ goto out;
+
+ if (!data->cache_id) {
+ /* The key didn't exist at resolve time. Check again and create the
+ * key if it still isn't there. */
+ p = strrchr(data->name, ':');
+ cache_name = (p != NULL) ? p + 1 : data->name;
+ ret = find_or_create_keyring(data->collection_id, 0, cache_name,
+ &data->cache_id);
+ if (ret)
+ goto out;
+ }
+
+ /* If this is the legacy cache in a legacy session collection, link it
+ * directly to the session keyring so that old code can see it. */
+ if (is_legacy_cache_name(data->name))
+ (void)keyctl_link(data->cache_id, session_write_anchor());
+
+ ret = save_principal(context, id, princ);
+
+ /* Save time offset if it is valid and this is not a legacy cache. Legacy
+ * applications would fail to parse the new key in the cache keyring. */
+ if (!is_legacy_cache_name(data->name) &&
+ (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+ ret = save_time_offsets(context, id, os_ctx->time_offset,
+ os_ctx->usec_offset);
+ }
+
+ if (ret == 0)
+ krb5_change_cache();
+
+out:
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+/* Release the ccache handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_close(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_destroy(&data->lock);
+ free(data->name);
+ free(data);
+ free(id);
+ return 0;
+}
+
+/* Clear out a ccache keyring, unlinking all keys within it. Call with the
+ * mutex locked. */
+static krb5_error_code
+clear_cache_keyring(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+ int res;
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+
+ DEBUG_PRINT(("clear_cache_keyring: cache_id %d, princ_id %d\n",
+ data->cache_id, data->princ_id));
+
+ if (data->cache_id) {
+ res = keyctl_clear(data->cache_id);
+ if (res != 0)
+ return errno;
+ }
+ data->princ_id = 0;
+ krcc_update_change_time(data);
+
+ return 0;
+}
+
+/* Destroy the cache keyring and release the handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_destroy(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code ret = 0;
+ krcc_data *data = id->data;
+ int res;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ clear_cache_keyring(context, id);
+ if (data->cache_id) {
+ res = keyctl_unlink(data->cache_id, data->collection_id);
+ if (res < 0) {
+ ret = errno;
+ DEBUG_PRINT(("unlinking key %d from ring %d: %s", data->cache_id,
+ data->collection_id, error_message(errno)));
+ }
+ /* If this is a legacy cache, unlink it from the session anchor. */
+ if (is_legacy_cache_name(data->name))
+ (void)keyctl_unlink(data->cache_id, session_write_anchor());
+ }
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ k5_cc_mutex_destroy(&data->lock);
+ free(data->name);
+ free(data);
+ free(id);
+ krb5_change_cache();
+ return ret;
+}
+
+/* Create a cache handle for a cache ID. */
+static krb5_error_code
+make_cache(krb5_context context, key_serial_t collection_id,
+ key_serial_t cache_id, const char *anchor_name,
+ const char *collection_name, const char *subsidiary_name,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ krb5_os_context os_ctx = &context->os_context;
+ krb5_ccache ccache = NULL;
+ krcc_data *data;
+ key_serial_t pkey = 0;
+
+ /* Determine the key containing principal information, if present. */
+ pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
+ 0);
+ if (pkey < 0)
+ pkey = 0;
+
+ ccache = malloc(sizeof(struct _krb5_ccache));
+ if (!ccache)
+ return ENOMEM;
+
+ ret = make_krcc_data(anchor_name, collection_name, subsidiary_name,
+ cache_id, collection_id, &data);
+ if (ret) {
+ free(ccache);
+ return ret;
+ }
+
+ data->princ_id = pkey;
+ ccache->ops = &krb5_krcc_ops;
+ ccache->data = data;
+ ccache->magic = KV5M_CCACHE;
+ *cache_out = ccache;
+
+ /* Look up time offsets if necessary. */
+ if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
+ !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+ if (get_time_offsets(context, ccache, &os_ctx->time_offset,
+ &os_ctx->usec_offset) == 0) {
+ os_ctx->os_flags &= ~KRB5_OS_TOFFSET_TIME;
+ os_ctx->os_flags |= KRB5_OS_TOFFSET_VALID;
+ }
+ }
+
+ return 0;
+}
+
+/* Create a keyring ccache handle for the given residual string. */
+static krb5_error_code KRB5_CALLCONV
+krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
+{
+ krb5_error_code ret;
+ key_serial_t collection_id, cache_id;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+
+ ret = parse_residual(residual, &anchor_name, &collection_name,
+ &subsidiary_name);
+ if (ret)
+ goto cleanup;
+ ret = get_collection(anchor_name, collection_name, &collection_id);
+ if (ret)
+ goto cleanup;
+
+ if (subsidiary_name == NULL) {
+ /* Retrieve or initialize the primary name for the collection. */
+ ret = get_primary_name(context, anchor_name, collection_name,
+ collection_id, &subsidiary_name);
+ if (ret)
+ goto cleanup;
+ }
+
+ /* Look up the cache keyring ID, if the cache is already initialized. */
+ cache_id = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING,
+ subsidiary_name, 0);
+ if (cache_id < 0)
+ cache_id = 0;
+
+ ret = make_cache(context, collection_id, cache_id, anchor_name,
+ collection_name, subsidiary_name, id);
+ if (ret)
+ goto cleanup;
+
+cleanup:
+ free(anchor_name);
+ free(collection_name);
+ free(subsidiary_name);
+ return ret;
+}
+
+/* Prepare for a sequential iteration over the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_start_seq_get(krb5_context context, krb5_ccache id,
+ krb5_cc_cursor *cursor)
+{
+ krcc_cursor krcursor;
+ krcc_data *data = id->data;
+ void *keys;
+ long size;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id) {
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_FCC_NOFILE;
+ }
+
+ size = keyctl_read_alloc(data->cache_id, &keys);
+ if (size == -1) {
+ DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_CC_IO;
+ }
+
+ krcursor = calloc(1, sizeof(*krcursor));
+ if (krcursor == NULL) {
+ free(keys);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_CC_NOMEM;
+ }
+
+ krcursor->princ_id = data->princ_id;
+ krcursor->offsets_id = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER,
+ KRCC_TIME_OFFSETS, 0);
+ krcursor->numkeys = size / sizeof(key_serial_t);
+ krcursor->keys = keys;
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ *cursor = krcursor;
+ return 0;
+}
+
+/* Get the next credential from the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds)
+{
+ krcc_cursor krcursor;
+ krb5_error_code ret;
+ int psize;
+ void *payload = NULL;
+
+ memset(creds, 0, sizeof(krb5_creds));
+
+ /* The cursor has the entire list of keys. (Note that we don't support
+ * remove_cred.) */
+ krcursor = *cursor;
+ if (krcursor == NULL)
+ return KRB5_CC_END;
+
+ /* If we're pointing past the end of the keys array, there are no more. */
+ if (krcursor->currkey >= krcursor->numkeys)
+ return KRB5_CC_END;
+
+ /* If we're pointing at the entry with the principal, or at the key
+ * with the time offsets, skip it. */
+ while (krcursor->keys[krcursor->currkey] == krcursor->princ_id ||
+ krcursor->keys[krcursor->currkey] == krcursor->offsets_id) {
+ krcursor->currkey++;
+ /* Check if we have now reached the end */
+ if (krcursor->currkey >= krcursor->numkeys)
+ return KRB5_CC_END;
+ }
+
+ /* Read the key; the right size buffer will be allocated and returned. */
+ psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey], &payload);
+ if (psize == -1) {
+ DEBUG_PRINT(("Error reading key %d: %s\n",
+ krcursor->keys[krcursor->currkey],
+ strerror(errno)));
+ return KRB5_FCC_NOFILE;
+ }
+ krcursor->currkey++;
+
+ /* Unmarshal the credential using the file ccache version 4 format. */
+ ret = k5_unmarshal_cred(payload, psize, 4, creds);
+ free(payload);
+ return ret;
+}
+
+/* Release an iteration cursor. */
+static krb5_error_code KRB5_CALLCONV
+krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ krcc_cursor krcursor = *cursor;
+
+ if (krcursor != NULL) {
+ free(krcursor->keys);
+ free(krcursor);
+ }
+ *cursor = NULL;
+ return 0;
+}
+
+/* Create keyring data for a credential cache. */
+static krb5_error_code
+make_krcc_data(const char *anchor_name, const char *collection_name,
+ const char *subsidiary_name, key_serial_t cache_id,
+ key_serial_t collection_id, krcc_data **data_out)
+{
+ krb5_error_code ret;
+ krcc_data *data;
+
+ *data_out = NULL;
+
+ data = malloc(sizeof(krcc_data));
+ if (data == NULL)
+ return KRB5_CC_NOMEM;
+
+ ret = k5_cc_mutex_init(&data->lock);
+ if (ret) {
+ free(data);
+ return ret;
+ }
+
+ ret = make_subsidiary_residual(anchor_name, collection_name,
+ subsidiary_name, &data->name);
+ if (ret) {
+ k5_cc_mutex_destroy(&data->lock);
+ free(data);
+ return ret;
+ }
+ data->princ_id = 0;
+ data->cache_id = cache_id;
+ data->collection_id = collection_id;
+ data->changetime = 0;
+ data->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
+ krcc_update_change_time(data);
+
+ *data_out = data;
+ return 0;
+}
+
+/* Create a new keyring cache with a unique name. */
+static krb5_error_code KRB5_CALLCONV
+krcc_generate_new(krb5_context context, krb5_ccache *id_out)
+{
+ krb5_ccache id = NULL;
+ krb5_error_code ret;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ char *new_subsidiary_name = NULL, *new_residual = NULL;
+ krcc_data *data;
+ key_serial_t collection_id;
+ key_serial_t cache_id = 0;
+
+ *id_out = NULL;
+
+ /* Determine the collection in which we will create the cache.*/
+ ret = get_default(context, &anchor_name, &collection_name,
+ &subsidiary_name);
+ if (ret)
+ return ret;
+ if (anchor_name == NULL) {
+ ret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
+ &collection_name, &subsidiary_name);
+ if (ret)
+ return ret;
+ }
+ if (subsidiary_name != NULL) {
+ k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
+ _("Can't create new subsidiary cache because default cache "
+ "is already a subsidiary"));
+ ret = KRB5_DCC_CANNOT_CREATE;
+ goto cleanup;
+ }
+
+ /* Allocate memory */
+ id = malloc(sizeof(struct _krb5_ccache));
+ if (id == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+
+ id->ops = &krb5_krcc_ops;
+
+ /* Make a unique keyring within the chosen collection. */
+ ret = get_collection(anchor_name, collection_name, &collection_id);
+ if (ret)
+ goto cleanup;
+ ret = unique_keyring(context, collection_id, &new_subsidiary_name,
+ &cache_id);
+ if (ret)
+ goto cleanup;
+
+ ret = make_krcc_data(anchor_name, collection_name, new_subsidiary_name,
+ cache_id, collection_id, &data);
+ if (ret)
+ goto cleanup;
+
+ id->data = data;
+ krb5_change_cache();
+
+cleanup:
+ free(anchor_name);
+ free(collection_name);
+ free(subsidiary_name);
+ free(new_subsidiary_name);
+ free(new_residual);
+ if (ret) {
+ free(id);
+ return ret;
+ }
+ *id_out = id;
+ return 0;
+}
+
+/* Return an alias to the residual string of the cache. */
+static const char *KRB5_CALLCONV
+krcc_get_name(krb5_context context, krb5_ccache id)
+{
+ return ((krcc_data *)id->data)->name;
+}
+
+/* Retrieve a copy of the default principal, if the cache is initialized. */
+static krb5_error_code KRB5_CALLCONV
+krcc_get_principal(krb5_context context, krb5_ccache id,
+ krb5_principal *princ_out)
+{
+ krcc_data *data = id->data;
+ krb5_error_code ret;
+ void *payload = NULL;
+ int psize;
+
+ *princ_out = NULL;
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id || !data->princ_id) {
+ ret = KRB5_FCC_NOFILE;
+ k5_setmsg(context, ret, _("Credentials cache keyring '%s' not found"),
+ data->name);
+ goto errout;
+ }
+
+ psize = keyctl_read_alloc(data->princ_id, &payload);
+ if (psize == -1) {
+ DEBUG_PRINT(("Reading principal key %d: %s\n",
+ data->princ_id, strerror(errno)));
+ ret = KRB5_CC_IO;
+ goto errout;
+ }
+
+ /* Unmarshal the principal using the file ccache version 4 format. */
+ ret = k5_unmarshal_princ(payload, psize, 4, princ_out);
+
+errout:
+ free(payload);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+/* Search for a credential within the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_retrieve(krb5_context context, krb5_ccache id,
+ krb5_flags whichfields, krb5_creds *mcreds,
+ krb5_creds *creds)
+{
+ return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+}
+
+/* Non-functional stub for removing a cred from the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_remove_cred(krb5_context context, krb5_ccache cache,
+ krb5_flags flags, krb5_creds *creds)
+{
+ return KRB5_CC_NOSUPP;
+}
+
+/* Set flags on the cache. (We don't care about any flags.) */
+static krb5_error_code KRB5_CALLCONV
+krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
+{
+ return 0;
+}
+
+/* Get the current operational flags (of which we have none) for the cache. */
+static krb5_error_code KRB5_CALLCONV
+krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags_out)
+{
+ *flags_out = 0;
+ return 0;
+}
+
+/* Store a credential in the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
+{
+ krb5_error_code ret;
+ krcc_data *data = id->data;
+ struct k5buf buf = EMPTY_K5BUF;
+ char *keyname = NULL;
+ key_serial_t cred_key;
+ krb5_timestamp now;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id) {
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_FCC_NOFILE;
+ }
+
+ /* Get the service principal name and use it as the key name */
+ ret = krb5_unparse_name(context, creds->server, &keyname);
+ if (ret)
+ goto errout;
+
+ /* Serialize credential using the file ccache version 4 format. */
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_cred(&buf, 4, creds);
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto errout;
+
+ /* Add new key (credentials) into keyring */
+ DEBUG_PRINT(("krcc_store: adding new key '%s' to keyring %d\n",
+ keyname, data->cache_id));
+ ret = add_cred_key(keyname, buf.data, buf.len, data->cache_id,
+ data->is_legacy_type, &cred_key);
+ if (ret)
+ goto errout;
+
+ krcc_update_change_time(data);
+
+ /* Set appropriate timeouts on cache keys. */
+ ret = krb5_timeofday(context, &now);
+ if (ret)
+ goto errout;
+
+ if (creds->times.endtime > now)
+ (void)keyctl_set_timeout(cred_key, creds->times.endtime - now);
+
+ update_keyring_expiration(context, id);
+
+errout:
+ k5_buf_free(&buf);
+ krb5_free_unparsed_name(context, keyname);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+/* Get the cache's last modification time. (This is currently broken; it
+ * returns only the last change made using this handle.) */
+static krb5_error_code KRB5_CALLCONV
+krcc_last_change_time(krb5_context context, krb5_ccache id,
+ krb5_timestamp *change_time)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ *change_time = data->changetime;
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+/* Lock the cache handle against other threads. (This does not lock the cache
+ * keyring against other processes.) */
+static krb5_error_code KRB5_CALLCONV
+krcc_lock(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ return 0;
+}
+
+/* Unlock the cache handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_unlock(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+static krb5_error_code
+save_principal(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krcc_data *data = id->data;
+ krb5_error_code ret;
+ struct k5buf buf;
+ key_serial_t newkey;
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+
+ /* Serialize princ using the file ccache version 4 format. */
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_princ(&buf, 4, princ);
+ if (k5_buf_status(&buf) != 0)
+ return ENOMEM;
+
+ /* Add new key into keyring */
+#ifdef KRCC_DEBUG
+ {
+ krb5_error_code rc;
+ char *princname = NULL;
+ rc = krb5_unparse_name(context, princ, &princname);
+ DEBUG_PRINT(("save_principal: adding new key '%s' "
+ "to keyring %d for principal '%s'\n",
+ KRCC_SPEC_PRINC_KEYNAME, data->cache_id,
+ rc ? "<unknown>" : princname));
+ if (rc == 0)
+ krb5_free_unparsed_name(context, princname);
+ }
+#endif
+ newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, buf.data,
+ buf.len, data->cache_id);
+ if (newkey < 0) {
+ ret = errno;
+ DEBUG_PRINT(("Error adding principal key: %s\n", strerror(ret)));
+ } else {
+ data->princ_id = newkey;
+ ret = 0;
+ krcc_update_change_time(data);
+ }
+
+ k5_buf_free(&buf);
+ return ret;
+}
+
+/* Add a key to the cache keyring containing the given time offsets. */
+static krb5_error_code
+save_time_offsets(krb5_context context, krb5_ccache id, int32_t time_offset,
+ int32_t usec_offset)
+{
+ krcc_data *data = id->data;
+ key_serial_t newkey;
+ unsigned char payload[8];
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+
+ /* Prepare the payload. */
+ store_32_be(time_offset, payload);
+ store_32_be(usec_offset, payload + 4);
+
+ /* Add new key into keyring. */
+ newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, 8,
+ data->cache_id);
+ if (newkey == -1)
+ return errno;
+ krcc_update_change_time(data);
+ return 0;
+}
+
+/* Retrieve and parse the key in the cache keyring containing time offsets. */
+static krb5_error_code
+get_time_offsets(krb5_context context, krb5_ccache id, int32_t *time_offset,
+ int32_t *usec_offset)
+{
+ krcc_data *data = id->data;
+ krb5_error_code ret = 0;
+ key_serial_t key;
+ void *payload = NULL;
+ int psize;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id) {
+ ret = KRB5_FCC_NOFILE;
+ goto errout;
+ }
+
+ key = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS,
+ 0);
+ if (key == -1) {
+ ret = ENOENT;
+ goto errout;
+ }
+
+ psize = keyctl_read_alloc(key, &payload);
+ if (psize == -1) {
+ DEBUG_PRINT(("Reading time offsets key %d: %s\n",
+ key, strerror(errno)));
+ ret = KRB5_CC_IO;
+ goto errout;
+ }
+
+ if (psize < 8) {
+ ret = KRB5_CC_END;
+ goto errout;
+ }
+ *time_offset = load_32_be(payload);
+ *usec_offset = load_32_be((char *)payload + 4);
+
+errout:
+ free(payload);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+struct krcc_ptcursor_data {
+ key_serial_t collection_id;
+ char *anchor_name;
+ char *collection_name;
+ char *subsidiary_name;
+ char *primary_name;
+ krb5_boolean first;
+ long num_keys;
+ long next_key;
+ key_serial_t *keys;
+};
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
+{
+ struct krcc_ptcursor_data *ptd;
+ krb5_cc_ptcursor cursor;
+ krb5_error_code ret;
+ void *keys;
+ long size;
+
+ *cursor_out = NULL;
+
+ cursor = k5alloc(sizeof(*cursor), &ret);
+ if (cursor == NULL)
+ return ENOMEM;
+ ptd = k5alloc(sizeof(*ptd), &ret);
+ if (ptd == NULL)
+ goto error;
+ cursor->ops = &krb5_krcc_ops;
+ cursor->data = ptd;
+ ptd->first = TRUE;
+
+ ret = get_default(context, &ptd->anchor_name, &ptd->collection_name,
+ &ptd->subsidiary_name);
+ if (ret)
+ goto error;
+
+ /* If there is no default collection, return an empty cursor. */
+ if (ptd->anchor_name == NULL) {
+ *cursor_out = cursor;
+ return 0;
+ }
+
+ ret = get_collection(ptd->anchor_name, ptd->collection_name,
+ &ptd->collection_id);
+ if (ret)
+ goto error;
+
+ if (ptd->subsidiary_name == NULL) {
+ ret = get_primary_name(context, ptd->anchor_name,
+ ptd->collection_name, ptd->collection_id,
+ &ptd->primary_name);
+ if (ret)
+ goto error;
+
+ size = keyctl_read_alloc(ptd->collection_id, &keys);
+ if (size == -1) {
+ ret = errno;
+ goto error;
+ }
+ ptd->keys = keys;
+ ptd->num_keys = size / sizeof(key_serial_t);
+ }
+
+ *cursor_out = cursor;
+ return 0;
+
+error:
+ krcc_ptcursor_free(context, &cursor);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ struct krcc_ptcursor_data *ptd = cursor->data;
+ key_serial_t key, cache_id = 0;
+ const char *first_name, *keytype, *sep, *subsidiary_name;
+ size_t keytypelen;
+ char *description = NULL;
+
+ *cache_out = NULL;
+
+ /* No keyring available */
+ if (ptd->collection_id == 0)
+ return 0;
+
+ if (ptd->first) {
+ /* Look for the primary cache for a collection cursor, or the
+ * subsidiary cache for a subsidiary cursor. */
+ ptd->first = FALSE;
+ first_name = (ptd->primary_name != NULL) ? ptd->primary_name :
+ ptd->subsidiary_name;
+ cache_id = keyctl_search(ptd->collection_id, KRCC_KEY_TYPE_KEYRING,
+ first_name, 0);
+ if (cache_id != -1) {
+ return make_cache(context, ptd->collection_id, cache_id,
+ ptd->anchor_name, ptd->collection_name,
+ first_name, cache_out);
+ }
+ }
+
+ /* A subsidiary cursor yields at most the first cache. */
+ if (ptd->subsidiary_name != NULL)
+ return 0;
+
+ keytype = KRCC_KEY_TYPE_KEYRING ";";
+ keytypelen = strlen(keytype);
+
+ for (; ptd->next_key < ptd->num_keys; ptd->next_key++) {
+ /* Free any previously retrieved key description. */
+ free(description);
+ description = NULL;
+
+ /*
+ * Get the key description, which should have the form:
+ * typename;UID;GID;permissions;description
+ */
+ key = ptd->keys[ptd->next_key];
+ if (keyctl_describe_alloc(key, &description) < 0)
+ continue;
+ sep = strrchr(description, ';');
+ if (sep == NULL)
+ continue;
+ subsidiary_name = sep + 1;
+
+ /* Skip this key if it isn't a keyring. */
+ if (strncmp(description, keytype, keytypelen) != 0)
+ continue;
+
+ /* Don't repeat the primary cache. */
+ if (strcmp(subsidiary_name, ptd->primary_name) == 0)
+ continue;
+
+ /* We found a valid key */
+ ptd->next_key++;
+ ret = make_cache(context, ptd->collection_id, key, ptd->anchor_name,
+ ptd->collection_name, subsidiary_name, cache_out);
+ free(description);
+ return ret;
+ }
+
+ free(description);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ struct krcc_ptcursor_data *ptd = (*cursor)->data;
+
+ if (ptd != NULL) {
+ free(ptd->anchor_name);
+ free(ptd->collection_name);
+ free(ptd->subsidiary_name);
+ free(ptd->primary_name);
+ free(ptd->keys);
+ free(ptd);
+ }
+ free(*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_switch_to(krb5_context context, krb5_ccache cache)
+{
+ krcc_data *data = cache->data;
+ krb5_error_code ret;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ key_serial_t collection_id;
+
+ ret = parse_residual(data->name, &anchor_name, &collection_name,
+ &subsidiary_name);
+ if (ret)
+ goto cleanup;
+ ret = get_collection(anchor_name, collection_name, &collection_id);
+ if (ret)
+ goto cleanup;
+ ret = set_primary_name(context, collection_id, subsidiary_name);
+
+cleanup:
+ free(anchor_name);
+ free(collection_name);
+ free(subsidiary_name);
+ return ret;
+}
+
+/*
+ * Utility routine: called by krcc_* functions to keep
+ * result of krcc_last_change_time up to date.
+ * Value monotonically increases -- based on but not guaranteed to be actual
+ * system time.
+ */
+
+static void
+krcc_update_change_time(krcc_data *data)
+{
+ krb5_timestamp now_time = time(NULL);
+ data->changetime = (data->changetime >= now_time) ?
+ data->changetime + 1 : now_time;
+}
+
+/*
+ * ccache implementation storing credentials in the Linux keyring facility
+ * The default is to put them at the session keyring level.
+ * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will
+ * be stored at the process or thread level respectively.
+ */
+const krb5_cc_ops krb5_krcc_ops = {
+ 0,
+ "KEYRING",
+ krcc_get_name,
+ krcc_resolve,
+ krcc_generate_new,
+ krcc_initialize,
+ krcc_destroy,
+ krcc_close,
+ krcc_store,
+ krcc_retrieve,
+ krcc_get_principal,
+ krcc_start_seq_get,
+ krcc_next_cred,
+ krcc_end_seq_get,
+ krcc_remove_cred,
+ krcc_set_flags,
+ krcc_get_flags, /* added after 1.4 release */
+ krcc_ptcursor_new,
+ krcc_ptcursor_next,
+ krcc_ptcursor_free,
+ NULL, /* move */
+ krcc_last_change_time, /* lastchange */
+ NULL, /* wasdefault */
+ krcc_lock,
+ krcc_unlock,
+ krcc_switch_to,
+};
+
+#else /* !USE_KEYRING_CCACHE */
+
+/*
+ * Export this, but it shouldn't be used.
+ */
+const krb5_cc_ops krb5_krcc_ops = {
+ 0,
+ "KEYRING",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* added after 1.4 release */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+#endif /* USE_KEYRING_CCACHE */
diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c
new file mode 100644
index 0000000000000..0354575c5c168
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_memory.c
@@ -0,0 +1,772 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_memory.c - Memory-based credential cache */
+/*
+ * Copyright 1990,1991,2000,2004,2008 by the Massachusetts Institute of
+ * Technology. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "cc-int.h"
+#include "../krb/int-proto.h"
+#include <errno.h>
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_close
+(krb5_context, krb5_ccache id );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_destroy
+(krb5_context, krb5_ccache id );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_end_seq_get
+(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_generate_new
+(krb5_context, krb5_ccache *id );
+
+static const char * KRB5_CALLCONV krb5_mcc_get_name
+(krb5_context, krb5_ccache id );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_get_principal
+(krb5_context, krb5_ccache id , krb5_principal *princ );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_initialize
+(krb5_context, krb5_ccache id , krb5_principal princ );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_next_cred
+(krb5_context,
+ krb5_ccache id ,
+ krb5_cc_cursor *cursor ,
+ krb5_creds *creds );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_resolve
+(krb5_context, krb5_ccache *id , const char *residual );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_retrieve
+(krb5_context,
+ krb5_ccache id ,
+ krb5_flags whichfields ,
+ krb5_creds *mcreds ,
+ krb5_creds *creds );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_start_seq_get
+(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_store
+(krb5_context, krb5_ccache id , krb5_creds *creds );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_set_flags
+(krb5_context, krb5_ccache id , krb5_flags flags );
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_ptcursor_new
+(krb5_context, krb5_cc_ptcursor *);
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_ptcursor_next
+(krb5_context, krb5_cc_ptcursor, krb5_ccache *);
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_ptcursor_free
+(krb5_context, krb5_cc_ptcursor *);
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_last_change_time
+(krb5_context, krb5_ccache, krb5_timestamp *);
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_lock
+(krb5_context context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_mcc_unlock
+(krb5_context context, krb5_ccache id);
+
+
+extern const krb5_cc_ops krb5_mcc_ops;
+extern krb5_error_code krb5_change_cache (void);
+
+#define KRB5_OK 0
+
+/* Individual credentials within a cache, in a linked list. */
+typedef struct _krb5_mcc_link {
+ struct _krb5_mcc_link *next;
+ krb5_creds *creds;
+} krb5_mcc_link, *krb5_mcc_cursor;
+
+/* Per-cache data header. */
+typedef struct _krb5_mcc_data {
+ char *name;
+ k5_cc_mutex lock;
+ krb5_principal prin;
+ krb5_mcc_cursor link;
+ krb5_timestamp changetime;
+ /* Time offsets for clock-skewed clients. */
+ krb5_int32 time_offset;
+ krb5_int32 usec_offset;
+} krb5_mcc_data;
+
+/* List of memory caches. */
+typedef struct krb5_mcc_list_node {
+ struct krb5_mcc_list_node *next;
+ krb5_mcc_data *cache;
+} krb5_mcc_list_node;
+
+/* Iterator over memory caches. */
+struct krb5_mcc_ptcursor_data {
+ struct krb5_mcc_list_node *cur;
+};
+
+k5_cc_mutex krb5int_mcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+static krb5_mcc_list_node *mcc_head = 0;
+
+static void update_mcc_change_time(krb5_mcc_data *);
+
+static void krb5_mcc_free (krb5_context context, krb5_ccache id);
+
+/*
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * Creates/refreshes the memory cred cache id. If the cache exists, its
+ * contents are destroyed.
+ *
+ * Errors:
+ * system errors
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krb5_os_context os_ctx = &context->os_context;
+ krb5_error_code ret;
+ krb5_mcc_data *d;
+
+ d = (krb5_mcc_data *)id->data;
+ k5_cc_mutex_lock(context, &d->lock);
+
+ krb5_mcc_free(context, id);
+
+ d = (krb5_mcc_data *)id->data;
+ ret = krb5_copy_principal(context, princ,
+ &d->prin);
+ update_mcc_change_time(d);
+
+ if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
+ /* Store client time offsets in the cache */
+ d->time_offset = os_ctx->time_offset;
+ d->usec_offset = os_ctx->usec_offset;
+ }
+
+ k5_cc_mutex_unlock(context, &d->lock);
+ if (ret == KRB5_OK)
+ krb5_change_cache();
+ return ret;
+}
+
+/*
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * Invalidates the id, and frees any resources associated with accessing
+ * the cache.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_close(krb5_context context, krb5_ccache id)
+{
+ free(id);
+ return KRB5_OK;
+}
+
+static void
+krb5_mcc_free(krb5_context context, krb5_ccache id)
+{
+ krb5_mcc_cursor curr,next;
+ krb5_mcc_data *d;
+
+ d = (krb5_mcc_data *) id->data;
+ for (curr = d->link; curr;) {
+ krb5_free_creds(context, curr->creds);
+ next = curr->next;
+ free(curr);
+ curr = next;
+ }
+ d->link = NULL;
+ krb5_free_principal(context, d->prin);
+}
+
+/*
+ * Effects:
+ * Destroys the contents of id. id is invalid after call.
+ *
+ * Errors:
+ * system errors (locks related)
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_destroy(krb5_context context, krb5_ccache id)
+{
+ krb5_mcc_list_node **curr, *node;
+ krb5_mcc_data *d;
+
+ k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+
+ d = (krb5_mcc_data *)id->data;
+ for (curr = &mcc_head; *curr; curr = &(*curr)->next) {
+ if ((*curr)->cache == d) {
+ node = *curr;
+ *curr = node->next;
+ free(node);
+ break;
+ }
+ }
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+
+ k5_cc_mutex_lock(context, &d->lock);
+
+ krb5_mcc_free(context, id);
+ free(d->name);
+ k5_cc_mutex_unlock(context, &d->lock);
+ k5_cc_mutex_destroy(&d->lock);
+ free(d);
+ free(id);
+
+ krb5_change_cache ();
+ return KRB5_OK;
+}
+
+/*
+ * Requires:
+ * residual is a legal path name, and a null-terminated string
+ *
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * creates or accesses a memory-based cred cache that is referenced by
+ * residual.
+ *
+ * Returns:
+ * A filled in krb5_ccache structure "id".
+ *
+ * Errors:
+ * KRB5_CC_NOMEM - there was insufficient memory to allocate the
+ * krb5_ccache. id is undefined.
+ * system errors (mutex locks related)
+ */
+static krb5_error_code new_mcc_data (const char *, krb5_mcc_data **);
+
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
+{
+ krb5_os_context os_ctx = &context->os_context;
+ krb5_ccache lid;
+ krb5_mcc_list_node *ptr;
+ krb5_error_code err;
+ krb5_mcc_data *d;
+
+ k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+ for (ptr = mcc_head; ptr; ptr=ptr->next)
+ if (!strcmp(ptr->cache->name, residual))
+ break;
+ if (ptr)
+ d = ptr->cache;
+ else {
+ err = new_mcc_data(residual, &d);
+ if (err) {
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ return err;
+ }
+ }
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+
+ lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
+ if (lid == NULL)
+ return KRB5_CC_NOMEM;
+
+ if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
+ !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+ /* Use the time offset from the cache entry */
+ os_ctx->time_offset = d->time_offset;
+ os_ctx->usec_offset = d->usec_offset;
+ os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) |
+ KRB5_OS_TOFFSET_VALID);
+ }
+
+ lid->ops = &krb5_mcc_ops;
+ lid->data = d;
+ *id = lid;
+ return KRB5_OK;
+}
+
+/*
+ * Effects:
+ * Prepares for a sequential search of the credentials cache.
+ * Returns a krb5_cc_cursor to be used with krb5_mcc_next_cred and
+ * krb5_mcc_end_seq_get.
+ *
+ * If the cache is modified between the time of this call and the time
+ * of the final krb5_mcc_end_seq_get, the results are undefined.
+ *
+ * Errors:
+ * KRB5_CC_NOMEM
+ * system errors
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_start_seq_get(krb5_context context, krb5_ccache id,
+ krb5_cc_cursor *cursor)
+{
+ krb5_mcc_cursor mcursor;
+ krb5_mcc_data *d;
+
+ d = id->data;
+ k5_cc_mutex_lock(context, &d->lock);
+ mcursor = d->link;
+ k5_cc_mutex_unlock(context, &d->lock);
+ *cursor = (krb5_cc_cursor) mcursor;
+ return KRB5_OK;
+}
+
+/*
+ * Requires:
+ * cursor is a krb5_cc_cursor originally obtained from
+ * krb5_mcc_start_seq_get.
+ *
+ * Modifes:
+ * cursor, creds
+ *
+ * Effects:
+ * Fills in creds with the "next" credentals structure from the cache
+ * id. The actual order the creds are returned in is arbitrary.
+ * Space is allocated for the variable length fields in the
+ * credentials structure, so the object returned must be passed to
+ * krb5_destroy_credential.
+ *
+ * The cursor is updated for the next call to krb5_mcc_next_cred.
+ *
+ * Errors:
+ * system errors
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_next_cred(krb5_context context, krb5_ccache id,
+ krb5_cc_cursor *cursor, krb5_creds *creds)
+{
+ krb5_mcc_cursor mcursor;
+ krb5_error_code retval;
+
+ /* Once the node in the linked list is created, it's never
+ modified, so we don't need to worry about locking here. (Note
+ that we don't support _remove_cred.) */
+ mcursor = (krb5_mcc_cursor) *cursor;
+ if (mcursor == NULL)
+ return KRB5_CC_END;
+ memset(creds, 0, sizeof(krb5_creds));
+ if (mcursor->creds) {
+ retval = k5_copy_creds_contents(context, mcursor->creds, creds);
+ if (retval)
+ return retval;
+ }
+ *cursor = (krb5_cc_cursor)mcursor->next;
+ return KRB5_OK;
+}
+
+/*
+ * Requires:
+ * cursor is a krb5_cc_cursor originally obtained from
+ * krb5_mcc_start_seq_get.
+ *
+ * Modifies:
+ * id, cursor
+ *
+ * Effects:
+ * Finishes sequential processing of the memory credentials ccache id,
+ * and invalidates the cursor (it must never be used after this call).
+ */
+/* ARGSUSED */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ *cursor = 0L;
+ return KRB5_OK;
+}
+
+/* Utility routine: Creates the back-end data for a memory cache, and
+ threads it into the global linked list.
+
+ Call with the global list lock held. */
+static krb5_error_code
+new_mcc_data (const char *name, krb5_mcc_data **dataptr)
+{
+ krb5_error_code err;
+ krb5_mcc_data *d;
+ krb5_mcc_list_node *n;
+
+ d = malloc(sizeof(krb5_mcc_data));
+ if (d == NULL)
+ return KRB5_CC_NOMEM;
+
+ err = k5_cc_mutex_init(&d->lock);
+ if (err) {
+ free(d);
+ return err;
+ }
+
+ d->name = strdup(name);
+ if (d->name == NULL) {
+ k5_cc_mutex_destroy(&d->lock);
+ free(d);
+ return KRB5_CC_NOMEM;
+ }
+ d->link = NULL;
+ d->prin = NULL;
+ d->changetime = 0;
+ d->time_offset = 0;
+ d->usec_offset = 0;
+ update_mcc_change_time(d);
+
+ n = malloc(sizeof(krb5_mcc_list_node));
+ if (n == NULL) {
+ free(d->name);
+ k5_cc_mutex_destroy(&d->lock);
+ free(d);
+ return KRB5_CC_NOMEM;
+ }
+
+ n->cache = d;
+ n->next = mcc_head;
+ mcc_head = n;
+
+ *dataptr = d;
+ return 0;
+}
+
+/*
+ * Effects:
+ * Creates a new memory cred cache whose name is guaranteed to be
+ * unique. The name begins with the string TKT_ROOT (from mcc.h).
+ *
+ * Returns:
+ * The filled in krb5_ccache id.
+ *
+ * Errors:
+ * KRB5_CC_NOMEM - there was insufficient memory to allocate the
+ * krb5_ccache. id is undefined.
+ * system errors (from open, mutex locking)
+ */
+
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_generate_new (krb5_context context, krb5_ccache *id)
+{
+ krb5_ccache lid;
+ char uniquename[8];
+ krb5_error_code err;
+ krb5_mcc_data *d;
+
+ /* Allocate memory */
+ lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
+ if (lid == NULL)
+ return KRB5_CC_NOMEM;
+
+ lid->ops = &krb5_mcc_ops;
+
+ k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+
+ /* Check for uniqueness with mutex locked to avoid race conditions */
+ while (1) {
+ krb5_mcc_list_node *ptr;
+
+ err = krb5int_random_string (context, uniquename, sizeof (uniquename));
+ if (err) {
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ free(lid);
+ return err;
+ }
+
+ for (ptr = mcc_head; ptr; ptr=ptr->next) {
+ if (!strcmp(ptr->cache->name, uniquename)) {
+ break; /* got a match, loop again */
+ }
+ }
+ if (!ptr) break; /* got to the end without finding a match */
+ }
+
+ err = new_mcc_data(uniquename, &d);
+
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ if (err) {
+ free(lid);
+ return err;
+ }
+ lid->data = d;
+ *id = lid;
+ krb5_change_cache ();
+ return KRB5_OK;
+}
+
+/*
+ * Requires:
+ * id is a file credential cache
+ *
+ * Returns:
+ * A pointer to the name of the file cred cache id.
+ */
+const char * KRB5_CALLCONV
+krb5_mcc_get_name (krb5_context context, krb5_ccache id)
+{
+ return (char *) ((krb5_mcc_data *) id->data)->name;
+}
+
+/*
+ * Modifies:
+ * id, princ
+ *
+ * Effects:
+ * Retrieves the primary principal from id, as set with
+ * krb5_mcc_initialize. The principal is returned is allocated
+ * storage that must be freed by the caller via krb5_free_principal.
+ *
+ * Errors:
+ * system errors
+ * ENOMEM
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
+{
+ krb5_mcc_data *ptr = (krb5_mcc_data *)id->data;
+ if (!ptr->prin) {
+ *princ = 0L;
+ return KRB5_FCC_NOFILE;
+ }
+ return krb5_copy_principal(context, ptr->prin, princ);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
+ krb5_creds *mcreds, krb5_creds *creds)
+{
+ return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+}
+
+/*
+ * Non-functional stub implementation for krb5_mcc_remove
+ *
+ * Errors:
+ * KRB5_CC_NOSUPP - not implemented
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *creds)
+{
+ return KRB5_CC_NOSUPP;
+}
+
+
+/*
+ * Requires:
+ * id is a cred cache returned by krb5_mcc_resolve or
+ * krb5_mcc_generate_new.
+ *
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * Sets the operational flags of id to flags.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
+{
+ return KRB5_OK;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
+{
+ *flags = 0;
+ return KRB5_OK;
+}
+
+/*
+ * Modifies:
+ * the memory cache
+ *
+ * Effects:
+ * Save away creds in the ccache.
+ *
+ * Errors:
+ * system errors (mutex locking)
+ * ENOMEM
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_mcc_store(krb5_context ctx, krb5_ccache id, krb5_creds *creds)
+{
+ krb5_error_code err;
+ krb5_mcc_link *new_node;
+ krb5_mcc_data *mptr = (krb5_mcc_data *)id->data;
+
+ new_node = malloc(sizeof(krb5_mcc_link));
+ if (new_node == NULL)
+ return ENOMEM;
+ err = krb5_copy_creds(ctx, creds, &new_node->creds);
+ if (err)
+ goto cleanup;
+ k5_cc_mutex_lock(ctx, &mptr->lock);
+ new_node->next = mptr->link;
+ mptr->link = new_node;
+ update_mcc_change_time(mptr);
+ k5_cc_mutex_unlock(ctx, &mptr->lock);
+ return 0;
+cleanup:
+ free(new_node);
+ return err;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_ptcursor_new(
+ krb5_context context,
+ krb5_cc_ptcursor *cursor)
+{
+ krb5_cc_ptcursor n = NULL;
+ struct krb5_mcc_ptcursor_data *cdata = NULL;
+
+ *cursor = NULL;
+
+ n = malloc(sizeof(*n));
+ if (n == NULL)
+ return ENOMEM;
+ n->ops = &krb5_mcc_ops;
+ cdata = malloc(sizeof(struct krb5_mcc_ptcursor_data));
+ if (cdata == NULL) {
+ free(n);
+ return ENOMEM;
+ }
+ n->data = cdata;
+ k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+ cdata->cur = mcc_head;
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ *cursor = n;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_ptcursor_next(
+ krb5_context context,
+ krb5_cc_ptcursor cursor,
+ krb5_ccache *ccache)
+{
+ struct krb5_mcc_ptcursor_data *cdata = NULL;
+
+ *ccache = NULL;
+ cdata = cursor->data;
+ if (cdata->cur == NULL)
+ return 0;
+
+ *ccache = malloc(sizeof(**ccache));
+ if (*ccache == NULL)
+ return ENOMEM;
+
+ (*ccache)->ops = &krb5_mcc_ops;
+ (*ccache)->data = cdata->cur->cache;
+ k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+ cdata->cur = cdata->cur->next;
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_ptcursor_free(
+ krb5_context context,
+ krb5_cc_ptcursor *cursor)
+{
+ if (*cursor == NULL)
+ return 0;
+ if ((*cursor)->data != NULL)
+ free((*cursor)->data);
+ free(*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_last_change_time(
+ krb5_context context,
+ krb5_ccache id,
+ krb5_timestamp *change_time)
+{
+ krb5_mcc_data *data = (krb5_mcc_data *) id->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ *change_time = data->changetime;
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+/*
+ Utility routine: called by krb5_mcc_* functions to keep
+ result of krb5_mcc_last_change_time up to date
+*/
+
+static void
+update_mcc_change_time(krb5_mcc_data *d)
+{
+ krb5_timestamp now_time = time(NULL);
+ d->changetime = (d->changetime >= now_time) ?
+ d->changetime + 1 : now_time;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_lock(krb5_context context, krb5_ccache id)
+{
+ krb5_mcc_data *data = (krb5_mcc_data *) id->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_mcc_unlock(krb5_context context, krb5_ccache id)
+{
+ krb5_mcc_data *data = (krb5_mcc_data *) id->data;
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+const krb5_cc_ops krb5_mcc_ops = {
+ 0,
+ "MEMORY",
+ krb5_mcc_get_name,
+ krb5_mcc_resolve,
+ krb5_mcc_generate_new,
+ krb5_mcc_initialize,
+ krb5_mcc_destroy,
+ krb5_mcc_close,
+ krb5_mcc_store,
+ krb5_mcc_retrieve,
+ krb5_mcc_get_principal,
+ krb5_mcc_start_seq_get,
+ krb5_mcc_next_cred,
+ krb5_mcc_end_seq_get,
+ krb5_mcc_remove_cred,
+ krb5_mcc_set_flags,
+ krb5_mcc_get_flags,
+ krb5_mcc_ptcursor_new,
+ krb5_mcc_ptcursor_next,
+ krb5_mcc_ptcursor_free,
+ NULL, /* move */
+ krb5_mcc_last_change_time,
+ NULL, /* wasdefault */
+ krb5_mcc_lock,
+ krb5_mcc_unlock,
+ NULL, /* switch_to */
+};
diff --git a/src/lib/krb5/ccache/cc_mslsa.c b/src/lib/krb5/ccache/cc_mslsa.c
new file mode 100644
index 0000000000000..7a80470237163
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_mslsa.c
@@ -0,0 +1,2209 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_mslsa.c */
+/*
+ * Copyright 2007 Secure Endpoints Inc.
+ *
+ * Copyright 2003,2004 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * Copyright 2000 by Carnegie Mellon University
+ *
+ * All Rights Reserved
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of Carnegie Mellon
+ * University not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission.
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
+ * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Implementation of microsoft windows lsa credentials cache
+ */
+
+#ifdef _WIN32
+#define UNICODE
+#define _UNICODE
+
+#include <ntstatus.h>
+#define WIN32_NO_STATUS
+#include "k5-int.h"
+#include "com_err.h"
+#include "cc-int.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <conio.h>
+#include <time.h>
+
+#define SECURITY_WIN32
+#include <security.h>
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#include <ntsecapi.h>
+
+
+#define MAX_MSG_SIZE 256
+#define MAX_MSPRINC_SIZE 1024
+
+/* THREAD SAFETY
+ * The function does_query_ticket_cache_ex2()
+ * contains static variables to cache the responses of the tests being
+ * performed. There is no harm in the test being performed more than
+ * once since the result will always be the same.
+ */
+
+typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
+
+static VOID
+ShowWinError(LPSTR szAPI, DWORD dwError)
+{
+
+ // TODO - Write errors to event log so that scripts that don't
+ // check for errors will still get something in the event log
+
+ // This code is completely unsafe for use on non-English systems
+ // Any call to this function will result in the FormatMessage
+ // call failing and the program terminating. This might have
+ // been acceptable when this code was part of ms2mit.exe as
+ // a standalone executable but it is not appropriate for a library
+
+#ifdef COMMENT
+ WCHAR szMsgBuf[MAX_MSG_SIZE];
+ DWORD dwRes;
+
+ printf("Error calling function %s: %lu\n", szAPI, dwError);
+
+ dwRes = FormatMessage (
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ dwError,
+ MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US),
+ szMsgBuf,
+ MAX_MSG_SIZE,
+ NULL);
+ if (0 == dwRes) {
+ printf("FormatMessage failed with %d\n", GetLastError());
+ ExitProcess(EXIT_FAILURE);
+ }
+
+ printf("%S",szMsgBuf);
+#endif /* COMMENT */
+}
+
+static VOID
+ShowLsaError(LPSTR szAPI, NTSTATUS Status)
+{
+ //
+ // Convert the NTSTATUS to Winerror. Then call ShowWinError().
+ //
+ ShowWinError(szAPI, LsaNtStatusToWinError(Status));
+}
+
+static BOOL
+WINAPI
+UnicodeToANSI(LPTSTR lpInputString, LPSTR lpszOutputString, int nOutStringLen)
+{
+ CPINFO CodePageInfo;
+
+ GetCPInfo(CP_ACP, &CodePageInfo);
+
+ if (CodePageInfo.MaxCharSize > 1) {
+ // Only supporting non-Unicode strings
+ int reqLen = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) lpInputString, -1,
+ NULL, 0, NULL, NULL);
+ if ( reqLen > nOutStringLen)
+ {
+ return FALSE;
+ } else {
+ if (WideCharToMultiByte(CP_ACP,
+ /* WC_NO_BEST_FIT_CHARS | */ WC_COMPOSITECHECK,
+ (LPCWSTR) lpInputString, -1,
+ lpszOutputString,
+ nOutStringLen, NULL, NULL) == 0)
+ return FALSE;
+ }
+ }
+ else
+ {
+ // Looks like unicode, better translate it
+ if (WideCharToMultiByte(CP_ACP,
+ /* WC_NO_BEST_FIT_CHARS | */ WC_COMPOSITECHECK,
+ (LPCWSTR) lpInputString, -1,
+ lpszOutputString,
+ nOutStringLen, NULL, NULL) == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+} // UnicodeToANSI
+
+static VOID
+WINAPI
+ANSIToUnicode(LPCSTR lpInputString, LPWSTR lpszOutputString, int nOutStringLen)
+{
+
+ CPINFO CodePageInfo;
+
+ GetCPInfo(CP_ACP, &CodePageInfo);
+
+ MultiByteToWideChar(CP_ACP, 0, lpInputString, -1,
+ lpszOutputString, nOutStringLen);
+} // ANSIToUnicode
+
+
+static void
+MITPrincToMSPrinc(krb5_context context, krb5_principal principal, UNICODE_STRING * msprinc)
+{
+ char *aname = NULL;
+
+ if (!krb5_unparse_name(context, principal, &aname)) {
+ msprinc->Length = strlen(aname) * sizeof(WCHAR);
+ if ( msprinc->Length <= msprinc->MaximumLength )
+ ANSIToUnicode(aname, msprinc->Buffer, msprinc->MaximumLength);
+ else
+ msprinc->Length = 0;
+ krb5_free_unparsed_name(context,aname);
+ }
+}
+
+static BOOL
+UnicodeStringToMITPrinc(UNICODE_STRING *service, UNICODE_STRING *realm,
+ krb5_context context, krb5_principal *principal)
+{
+ WCHAR princbuf[512];
+ WCHAR realmbuf[512];
+ char aname[512];
+
+ /* Convert the realm to a wchar string. */
+ realmbuf[0] = '\0';
+ wcsncpy(realmbuf, realm->Buffer, realm->Length / sizeof(WCHAR));
+ realmbuf[realm->Length / sizeof(WCHAR)] = 0;
+ /* Convert the principal components to a wchar string. */
+ princbuf[0]=0;
+ wcsncpy(princbuf, service->Buffer, service->Length/sizeof(WCHAR));
+ princbuf[service->Length/sizeof(WCHAR)]=0;
+ wcscat(princbuf, L"@");
+ wcscat(princbuf, realmbuf);
+ if (UnicodeToANSI(princbuf, aname, sizeof(aname))) {
+ if (krb5_parse_name(context, aname, principal) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+static BOOL
+KerbExternalNameToMITPrinc(KERB_EXTERNAL_NAME *msprinc, WCHAR *realm, krb5_context context,
+ krb5_principal *principal)
+{
+ WCHAR princbuf[512],tmpbuf[128];
+ char aname[512];
+ USHORT i;
+ princbuf[0]=0;
+ for (i=0;i<msprinc->NameCount;i++) {
+ wcsncpy(tmpbuf, msprinc->Names[i].Buffer,
+ msprinc->Names[i].Length/sizeof(WCHAR));
+ tmpbuf[msprinc->Names[i].Length/sizeof(WCHAR)]=0;
+ if (princbuf[0])
+ wcscat(princbuf, L"/");
+ wcscat(princbuf, tmpbuf);
+ }
+ wcscat(princbuf, L"@");
+ wcscat(princbuf, realm);
+ if (UnicodeToANSI(princbuf, aname, sizeof(aname))) {
+ if (krb5_parse_name(context, aname, principal) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static time_t
+FileTimeToUnixTime(LARGE_INTEGER *ltime)
+{
+ FILETIME filetime, localfiletime;
+ SYSTEMTIME systime;
+ struct tm utime;
+ filetime.dwLowDateTime=ltime->LowPart;
+ filetime.dwHighDateTime=ltime->HighPart;
+ FileTimeToLocalFileTime(&filetime, &localfiletime);
+ FileTimeToSystemTime(&localfiletime, &systime);
+ utime.tm_sec=systime.wSecond;
+ utime.tm_min=systime.wMinute;
+ utime.tm_hour=systime.wHour;
+ utime.tm_mday=systime.wDay;
+ utime.tm_mon=systime.wMonth-1;
+ utime.tm_year=systime.wYear-1900;
+ utime.tm_isdst=-1;
+ return(mktime(&utime));
+}
+
+static void
+MSSessionKeyToMITKeyblock(KERB_CRYPTO_KEY *mskey, krb5_context context, krb5_keyblock *keyblock)
+{
+ krb5_keyblock tmpblock;
+ tmpblock.magic=KV5M_KEYBLOCK;
+ tmpblock.enctype=mskey->KeyType;
+ tmpblock.length=mskey->Length;
+ tmpblock.contents=mskey->Value;
+ krb5_copy_keyblock_contents(context, &tmpblock, keyblock);
+}
+
+static BOOL
+IsMSSessionKeyNull(KERB_CRYPTO_KEY *mskey)
+{
+ DWORD i;
+
+ if (mskey->KeyType == KERB_ETYPE_NULL)
+ return TRUE;
+
+ for ( i=0; i<mskey->Length; i++ ) {
+ if (mskey->Value[i])
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+MSFlagsToMITFlags(ULONG msflags, ULONG *mitflags)
+{
+ *mitflags=msflags;
+}
+
+static BOOL
+MSTicketToMITTicket(KERB_EXTERNAL_TICKET *msticket, krb5_context context, krb5_data *ticket)
+{
+ krb5_data tmpdata, *newdata = 0;
+ krb5_error_code rc;
+
+ tmpdata.magic=KV5M_DATA;
+ tmpdata.length=msticket->EncodedTicketSize;
+ tmpdata.data=msticket->EncodedTicket;
+
+ // this is ugly and will break krb5_free_data()
+ // now that this is being done within the library it won't break krb5_free_data()
+ rc = krb5_copy_data(context, &tmpdata, &newdata);
+ if (rc)
+ return FALSE;
+
+ memcpy(ticket, newdata, sizeof(krb5_data));
+ free(newdata);
+ return TRUE;
+}
+
+static BOOL
+MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, UNICODE_STRING ClientRealm,
+ krb5_context context, krb5_creds *creds)
+{
+ WCHAR wrealm[128];
+ ZeroMemory(creds, sizeof(krb5_creds));
+ creds->magic=KV5M_CREDS;
+
+ // construct Client Principal
+ wcsncpy(wrealm, ClientRealm.Buffer, ClientRealm.Length/sizeof(WCHAR));
+ wrealm[ClientRealm.Length/sizeof(WCHAR)]=0;
+ if (!KerbExternalNameToMITPrinc(msticket->ClientName, wrealm, context, &creds->client))
+ return FALSE;
+
+ // construct Service Principal
+ wcsncpy(wrealm, msticket->DomainName.Buffer,
+ msticket->DomainName.Length/sizeof(WCHAR));
+ wrealm[msticket->DomainName.Length/sizeof(WCHAR)]=0;
+ if (!KerbExternalNameToMITPrinc(msticket->ServiceName, wrealm, context, &creds->server))
+ return FALSE;
+ MSSessionKeyToMITKeyblock(&msticket->SessionKey, context,
+ &creds->keyblock);
+ MSFlagsToMITFlags(msticket->TicketFlags, &creds->ticket_flags);
+ creds->times.starttime=FileTimeToUnixTime(&msticket->StartTime);
+ creds->times.endtime=FileTimeToUnixTime(&msticket->EndTime);
+ creds->times.renew_till=FileTimeToUnixTime(&msticket->RenewUntil);
+
+ creds->addresses = NULL;
+
+ return MSTicketToMITTicket(msticket, context, &creds->ticket);
+}
+
+/* CacheInfoEx2ToMITCred is used when we do not need the real ticket */
+static BOOL
+CacheInfoEx2ToMITCred(KERB_TICKET_CACHE_INFO_EX2 *info,
+ krb5_context context, krb5_creds *creds)
+{
+ ZeroMemory(creds, sizeof(krb5_creds));
+ creds->magic=KV5M_CREDS;
+
+ // construct Client Principal
+ if (!UnicodeStringToMITPrinc(&info->ClientName, &info->ClientRealm,
+ context, &creds->client))
+ return FALSE;
+
+ // construct Service Principal
+ if (!UnicodeStringToMITPrinc(&info->ServerName, &info->ServerRealm,
+ context, &creds->server))
+ return FALSE;
+
+ creds->keyblock.magic = KV5M_KEYBLOCK;
+ creds->keyblock.enctype = info->SessionKeyType;
+ creds->ticket_flags = info->TicketFlags;
+ MSFlagsToMITFlags(info->TicketFlags, &creds->ticket_flags);
+ creds->times.starttime=FileTimeToUnixTime(&info->StartTime);
+ creds->times.endtime=FileTimeToUnixTime(&info->EndTime);
+ creds->times.renew_till=FileTimeToUnixTime(&info->RenewTime);
+
+ /* MS Tickets are addressless. MIT requires an empty address
+ * not a NULL list of addresses.
+ */
+ creds->addresses = (krb5_address **)malloc(sizeof(krb5_address *));
+ memset(creds->addresses, 0, sizeof(krb5_address *));
+
+ return TRUE;
+}
+
+static BOOL
+PackageConnectLookup(HANDLE *pLogonHandle, ULONG *pPackageId)
+{
+ LSA_STRING Name;
+ NTSTATUS Status;
+
+ Status = LsaConnectUntrusted(
+ pLogonHandle
+ );
+
+ if (FAILED(Status))
+ {
+ ShowLsaError("LsaConnectUntrusted", Status);
+ return FALSE;
+ }
+
+ Name.Buffer = MICROSOFT_KERBEROS_NAME_A;
+ Name.Length = strlen(Name.Buffer);
+ Name.MaximumLength = Name.Length + 1;
+
+ Status = LsaLookupAuthenticationPackage(
+ *pLogonHandle,
+ &Name,
+ pPackageId
+ );
+
+ if (FAILED(Status))
+ {
+ ShowLsaError("LsaLookupAuthenticationPackage", Status);
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+/*
+ * This runtime check is only needed on Windows XP and Server 2003.
+ * It can safely be removed when we no longer wish to support any
+ * versions of those platforms.
+ */
+static BOOL
+does_query_ticket_cache_ex2 (void)
+{
+ static BOOL fChecked = FALSE;
+ static BOOL fEx2Response = FALSE;
+
+ if (!fChecked)
+ {
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ HANDLE LogonHandle;
+ ULONG PackageId;
+ ULONG RequestSize;
+ PKERB_QUERY_TKT_CACHE_REQUEST pCacheRequest = NULL;
+ PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pCacheResponse = NULL;
+ ULONG ResponseSize;
+
+ RequestSize = sizeof(*pCacheRequest) + 1;
+
+ if (!PackageConnectLookup(&LogonHandle, &PackageId))
+ return FALSE;
+
+ pCacheRequest = (PKERB_QUERY_TKT_CACHE_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+ if (!pCacheRequest) {
+ LsaDeregisterLogonProcess(LogonHandle);
+ return FALSE;
+ }
+
+ pCacheRequest->MessageType = KerbQueryTicketCacheEx2Message;
+ pCacheRequest->LogonId.LowPart = 0;
+ pCacheRequest->LogonId.HighPart = 0;
+
+ Status = LsaCallAuthenticationPackage( LogonHandle,
+ PackageId,
+ pCacheRequest,
+ RequestSize,
+ &pCacheResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ LocalFree(pCacheRequest);
+ LsaDeregisterLogonProcess(LogonHandle);
+
+ if (!(FAILED(Status) || FAILED(SubStatus))) {
+ LsaFreeReturnBuffer(pCacheResponse);
+ fEx2Response = TRUE;
+ }
+ fChecked = TRUE;
+ }
+
+ return fEx2Response;
+}
+
+static DWORD
+ConcatenateUnicodeStrings(UNICODE_STRING *pTarget, UNICODE_STRING Source1, UNICODE_STRING Source2)
+{
+ //
+ // The buffers for Source1 and Source2 cannot overlap pTarget's
+ // buffer. Source1.Length + Source2.Length must be <= 0xFFFF,
+ // otherwise we overflow...
+ //
+
+ USHORT TotalSize = Source1.Length + Source2.Length;
+ PBYTE buffer = (PBYTE) pTarget->Buffer;
+
+ if (TotalSize > pTarget->MaximumLength)
+ return ERROR_INSUFFICIENT_BUFFER;
+
+ if ( pTarget->Buffer != Source1.Buffer )
+ memcpy(buffer, Source1.Buffer, Source1.Length);
+ memcpy(buffer + Source1.Length, Source2.Buffer, Source2.Length);
+
+ pTarget->Length = TotalSize;
+ return ERROR_SUCCESS;
+}
+
+static BOOL
+get_STRING_from_registry(HKEY hBaseKey, char * key, char * value, char * outbuf, DWORD outlen)
+{
+ HKEY hKey;
+ DWORD dwCount;
+ LONG rc;
+
+ if (!outbuf || outlen == 0)
+ return FALSE;
+
+ rc = RegOpenKeyExA(hBaseKey, key, 0, KEY_QUERY_VALUE, &hKey);
+ if (rc)
+ return FALSE;
+
+ dwCount = outlen;
+ rc = RegQueryValueExA(hKey, value, 0, 0, (LPBYTE) outbuf, &dwCount);
+ RegCloseKey(hKey);
+
+ return rc?FALSE:TRUE;
+}
+
+static BOOL
+GetSecurityLogonSessionData(PSECURITY_LOGON_SESSION_DATA * ppSessionData)
+{
+ NTSTATUS Status = 0;
+ HANDLE TokenHandle;
+ TOKEN_STATISTICS Stats;
+ DWORD ReqLen;
+ BOOL Success;
+
+ if (!ppSessionData)
+ return FALSE;
+ *ppSessionData = NULL;
+
+ Success = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle );
+ if ( !Success )
+ return FALSE;
+
+ Success = GetTokenInformation( TokenHandle, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen );
+ CloseHandle( TokenHandle );
+ if ( !Success )
+ return FALSE;
+
+ Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData );
+ if ( FAILED(Status) || !ppSessionData )
+ return FALSE;
+
+ return TRUE;
+}
+
+static DWORD
+ConstructTicketRequest(UNICODE_STRING DomainName, PKERB_RETRIEVE_TKT_REQUEST * outRequest, ULONG * outSize)
+{
+ DWORD Error;
+ UNICODE_STRING TargetPrefix;
+ USHORT TargetSize;
+ ULONG RequestSize;
+ PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+
+ *outRequest = NULL;
+ *outSize = 0;
+
+ //
+ // Set up the "krbtgt/" target prefix into a UNICODE_STRING so we
+ // can easily concatenate it later.
+ //
+
+ TargetPrefix.Buffer = L"krbtgt/";
+ TargetPrefix.Length = wcslen(TargetPrefix.Buffer) * sizeof(WCHAR);
+ TargetPrefix.MaximumLength = TargetPrefix.Length;
+
+ //
+ // We will need to concatenate the "krbtgt/" prefix and the
+ // Logon Session's DnsDomainName into our request's target name.
+ //
+ // Therefore, first compute the necessary buffer size for that.
+ //
+ // Note that we might theoretically have integer overflow.
+ //
+
+ TargetSize = TargetPrefix.Length + DomainName.Length;
+
+ //
+ // The ticket request buffer needs to be a single buffer. That buffer
+ // needs to include the buffer for the target name.
+ //
+
+ RequestSize = sizeof(*pTicketRequest) + TargetSize;
+
+ //
+ // Allocate the request buffer and make sure it's zero-filled.
+ //
+
+ pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+ if (!pTicketRequest)
+ return GetLastError();
+
+ //
+ // Concatenate the target prefix with the previous reponse's
+ // target domain.
+ //
+
+ pTicketRequest->TargetName.Length = 0;
+ pTicketRequest->TargetName.MaximumLength = TargetSize;
+ pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+ Error = ConcatenateUnicodeStrings(&(pTicketRequest->TargetName),
+ TargetPrefix,
+ DomainName);
+ *outRequest = pTicketRequest;
+ *outSize = RequestSize;
+ return Error;
+}
+
+static BOOL
+PurgeAllTickets(HANDLE LogonHandle, ULONG PackageId)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ KERB_PURGE_TKT_CACHE_REQUEST PurgeRequest;
+
+ PurgeRequest.MessageType = KerbPurgeTicketCacheMessage;
+ PurgeRequest.LogonId.LowPart = 0;
+ PurgeRequest.LogonId.HighPart = 0;
+ PurgeRequest.ServerName.Buffer = L"";
+ PurgeRequest.ServerName.Length = 0;
+ PurgeRequest.ServerName.MaximumLength = 0;
+ PurgeRequest.RealmName.Buffer = L"";
+ PurgeRequest.RealmName.Length = 0;
+ PurgeRequest.RealmName.MaximumLength = 0;
+ Status = LsaCallAuthenticationPackage(LogonHandle,
+ PackageId,
+ &PurgeRequest,
+ sizeof(PurgeRequest),
+ NULL,
+ NULL,
+ &SubStatus
+ );
+ if (FAILED(Status) || FAILED(SubStatus))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL
+PurgeTicketEx(HANDLE LogonHandle, ULONG PackageId,
+ krb5_context context, krb5_flags flags, krb5_creds *cred)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ KERB_PURGE_TKT_CACHE_EX_REQUEST * pPurgeRequest;
+ DWORD dwRequestLen = sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 4096;
+ char * cname = NULL, * crealm = NULL;
+ char * sname = NULL, * srealm = NULL;
+
+ if (krb5_unparse_name(context, cred->client, &cname))
+ return FALSE;
+
+ if (krb5_unparse_name(context, cred->server, &sname)) {
+ krb5_free_unparsed_name(context, cname);
+ return FALSE;
+ }
+
+ pPurgeRequest = malloc(dwRequestLen);
+ if ( pPurgeRequest == NULL )
+ return FALSE;
+ memset(pPurgeRequest, 0, dwRequestLen);
+
+ crealm = strrchr(cname, '@');
+ *crealm = '\0';
+ crealm++;
+
+ srealm = strrchr(sname, '@');
+ *srealm = '\0';
+ srealm++;
+
+ pPurgeRequest->MessageType = KerbPurgeTicketCacheExMessage;
+ pPurgeRequest->LogonId.LowPart = 0;
+ pPurgeRequest->LogonId.HighPart = 0;
+ pPurgeRequest->Flags = 0;
+ pPurgeRequest->TicketTemplate.ClientName.Buffer = (PWSTR)((CHAR *)pPurgeRequest + sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST));
+ pPurgeRequest->TicketTemplate.ClientName.Length = strlen(cname)*sizeof(WCHAR);
+ pPurgeRequest->TicketTemplate.ClientName.MaximumLength = 256;
+ ANSIToUnicode(cname, pPurgeRequest->TicketTemplate.ClientName.Buffer,
+ pPurgeRequest->TicketTemplate.ClientName.MaximumLength);
+
+ pPurgeRequest->TicketTemplate.ClientRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 512);
+ pPurgeRequest->TicketTemplate.ClientRealm.Length = strlen(crealm)*sizeof(WCHAR);
+ pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength = 256;
+ ANSIToUnicode(crealm, pPurgeRequest->TicketTemplate.ClientRealm.Buffer,
+ pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength);
+
+ pPurgeRequest->TicketTemplate.ServerName.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1024);
+ pPurgeRequest->TicketTemplate.ServerName.Length = strlen(sname)*sizeof(WCHAR);
+ pPurgeRequest->TicketTemplate.ServerName.MaximumLength = 256;
+ ANSIToUnicode(sname, pPurgeRequest->TicketTemplate.ServerName.Buffer,
+ pPurgeRequest->TicketTemplate.ServerName.MaximumLength);
+
+ pPurgeRequest->TicketTemplate.ServerRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1536);
+ pPurgeRequest->TicketTemplate.ServerRealm.Length = strlen(srealm)*sizeof(WCHAR);
+ pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength = 256;
+ ANSIToUnicode(srealm, pPurgeRequest->TicketTemplate.ServerRealm.Buffer,
+ pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength);
+
+ pPurgeRequest->TicketTemplate.StartTime;
+ pPurgeRequest->TicketTemplate.EndTime;
+ pPurgeRequest->TicketTemplate.RenewTime;
+ pPurgeRequest->TicketTemplate.EncryptionType = cred->keyblock.enctype;
+ pPurgeRequest->TicketTemplate.TicketFlags = flags;
+
+ Status = LsaCallAuthenticationPackage( LogonHandle,
+ PackageId,
+ pPurgeRequest,
+ dwRequestLen,
+ NULL,
+ NULL,
+ &SubStatus
+ );
+ free(pPurgeRequest);
+ krb5_free_unparsed_name(context,cname);
+ krb5_free_unparsed_name(context,sname);
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ return FALSE;
+ return TRUE;
+}
+
+static BOOL
+KerbSubmitTicket( HANDLE LogonHandle, ULONG PackageId,
+ krb5_context context, krb5_creds *cred)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ KERB_SUBMIT_TKT_REQUEST * pSubmitRequest;
+ DWORD dwRequestLen;
+ krb5_auth_context auth_context;
+ krb5_keyblock * keyblock = 0;
+ krb5_replay_data replaydata;
+ krb5_data * krb_cred = 0;
+ krb5_error_code rc;
+
+ if (krb5_auth_con_init(context, &auth_context)) {
+ return FALSE;
+ }
+
+ if (krb5_auth_con_setflags(context, auth_context,
+ KRB5_AUTH_CONTEXT_RET_TIME)) {
+ return FALSE;
+ }
+
+ krb5_auth_con_getsendsubkey(context, auth_context, &keyblock);
+ if (keyblock == NULL)
+ krb5_auth_con_getkey(context, auth_context, &keyblock);
+
+ /* make up a key, any key, that can be used to generate the
+ * encrypted KRB_CRED pdu. The Vista release LSA requires
+ * that an enctype other than NULL be used. */
+ if (keyblock == NULL) {
+ keyblock = (krb5_keyblock *)malloc(sizeof(krb5_keyblock));
+ keyblock->enctype = ENCTYPE_ARCFOUR_HMAC;
+ keyblock->length = 16;
+ keyblock->contents = (krb5_octet *)malloc(16);
+ keyblock->contents[0] = 0xde;
+ keyblock->contents[1] = 0xad;
+ keyblock->contents[2] = 0xbe;
+ keyblock->contents[3] = 0xef;
+ keyblock->contents[4] = 0xfe;
+ keyblock->contents[5] = 0xed;
+ keyblock->contents[6] = 0xf0;
+ keyblock->contents[7] = 0xd;
+ keyblock->contents[8] = 0xde;
+ keyblock->contents[9] = 0xad;
+ keyblock->contents[10] = 0xbe;
+ keyblock->contents[11] = 0xef;
+ keyblock->contents[12] = 0xfe;
+ keyblock->contents[13] = 0xed;
+ keyblock->contents[14] = 0xf0;
+ keyblock->contents[15] = 0xd;
+ krb5_auth_con_setsendsubkey(context, auth_context, keyblock);
+ }
+ rc = krb5_mk_1cred(context, auth_context, cred, &krb_cred, &replaydata);
+ if (rc) {
+ krb5_auth_con_free(context, auth_context);
+ if (keyblock)
+ krb5_free_keyblock(context, keyblock);
+ if (krb_cred)
+ krb5_free_data(context, krb_cred);
+ return FALSE;
+ }
+
+ dwRequestLen = sizeof(KERB_SUBMIT_TKT_REQUEST) + krb_cred->length + (keyblock ? keyblock->length : 0);
+
+ pSubmitRequest = (PKERB_SUBMIT_TKT_REQUEST)malloc(dwRequestLen);
+ memset(pSubmitRequest, 0, dwRequestLen);
+
+ pSubmitRequest->MessageType = KerbSubmitTicketMessage;
+ pSubmitRequest->LogonId.LowPart = 0;
+ pSubmitRequest->LogonId.HighPart = 0;
+ pSubmitRequest->Flags = 0;
+
+ if (keyblock) {
+ pSubmitRequest->Key.KeyType = keyblock->enctype;
+ pSubmitRequest->Key.Length = keyblock->length;
+ pSubmitRequest->Key.Offset = sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length;
+ } else {
+ pSubmitRequest->Key.KeyType = ENCTYPE_NULL;
+ pSubmitRequest->Key.Length = 0;
+ pSubmitRequest->Key.Offset = 0;
+ }
+ pSubmitRequest->KerbCredSize = krb_cred->length;
+ pSubmitRequest->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST);
+ memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST),
+ krb_cred->data, krb_cred->length);
+ if (keyblock)
+ memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length,
+ keyblock->contents, keyblock->length);
+ krb5_free_data(context, krb_cred);
+
+ Status = LsaCallAuthenticationPackage( LogonHandle,
+ PackageId,
+ pSubmitRequest,
+ dwRequestLen,
+ NULL,
+ NULL,
+ &SubStatus
+ );
+ free(pSubmitRequest);
+ if (keyblock)
+ krb5_free_keyblock(context, keyblock);
+ krb5_auth_con_free(context, auth_context);
+
+ if (FAILED(Status) || FAILED(SubStatus)) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * A simple function to determine if there is an exact match between two tickets
+ * We rely on the fact that the external tickets contain the raw Kerberos ticket.
+ * If the EncodedTicket fields match, the KERB_EXTERNAL_TICKETs must be the same.
+ */
+static BOOL
+KerbExternalTicketMatch( PKERB_EXTERNAL_TICKET one, PKERB_EXTERNAL_TICKET two )
+{
+ if ( one->EncodedTicketSize != two->EncodedTicketSize )
+ return FALSE;
+
+ if ( memcmp(one->EncodedTicket, two->EncodedTicket, one->EncodedTicketSize) )
+ return FALSE;
+
+ return TRUE;
+}
+
+krb5_boolean
+krb5_is_permitted_tgs_enctype(krb5_context context, krb5_const_principal princ, krb5_enctype etype)
+{
+ krb5_enctype *list, *ptr;
+ krb5_boolean ret;
+
+ if (krb5_get_tgs_ktypes(context, princ, &list))
+ return(0);
+
+ ret = 0;
+
+ for (ptr = list; *ptr; ptr++)
+ if (*ptr == etype)
+ ret = 1;
+
+ krb5_free_enctypes(context, list);
+
+ return(ret);
+}
+
+// to allow the purging of expired tickets from LSA cache. This is necessary
+// to force the retrieval of new TGTs. Microsoft does not appear to retrieve
+// new tickets when they expire. Instead they continue to accept the expired
+// tickets. This is safe to do because the LSA purges its cache when it
+// retrieves a new TGT (ms calls this renew) but not when it renews the TGT
+// (ms calls this refresh).
+// UAC-limited processes are not allowed to obtain a copy of the MSTGT
+// session key. We used to check for UAC-limited processes and refuse all
+// access to the TGT, but this makes the MSLSA ccache completely unusable.
+// Instead we ought to just flag that the tgt session key is not valid.
+
+static BOOL
+GetMSTGT(krb5_context context, HANDLE LogonHandle, ULONG PackageId, KERB_EXTERNAL_TICKET **ticket, BOOL enforce_tgs_enctypes)
+{
+ //
+ // INVARIANTS:
+ //
+ // (FAILED(Status) || FAILED(SubStatus)) ==> error
+ // bIsLsaError ==> LsaCallAuthenticationPackage() error
+ //
+
+ BOOL bIsLsaError = FALSE;
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ DWORD Error;
+
+ KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
+ PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+ PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+ ULONG RequestSize;
+ ULONG ResponseSize;
+ int purge_cache = 0;
+ int ignore_cache = 0;
+ krb5_enctype *etype_list = NULL, *ptr = NULL, etype = 0;
+
+ memset(&CacheRequest, 0, sizeof(KERB_QUERY_TKT_CACHE_REQUEST));
+ CacheRequest.MessageType = KerbRetrieveTicketMessage;
+ CacheRequest.LogonId.LowPart = 0;
+ CacheRequest.LogonId.HighPart = 0;
+
+ Status = LsaCallAuthenticationPackage(
+ LogonHandle,
+ PackageId,
+ &CacheRequest,
+ sizeof(CacheRequest),
+ &pTicketResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ if (FAILED(Status))
+ {
+ // if the call to LsaCallAuthenticationPackage failed we cannot
+ // perform any queries most likely because the Kerberos package
+ // is not available or we do not have access
+ bIsLsaError = TRUE;
+ goto cleanup;
+ }
+
+ if (FAILED(SubStatus)) {
+ PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+ BOOL Success = FALSE;
+ OSVERSIONINFOEX verinfo;
+ int supported = 0;
+
+ // SubStatus 0x8009030E is not documented. However, it appears
+ // to mean there is no TGT
+ if (SubStatus != 0x8009030E) {
+ bIsLsaError = TRUE;
+ goto cleanup;
+ }
+
+ verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ GetVersionEx((OSVERSIONINFO *)&verinfo);
+ supported = (verinfo.dwMajorVersion > 5) ||
+ (verinfo.dwMajorVersion == 5 && verinfo.dwMinorVersion >= 1);
+
+ // If we could not get a TGT from the cache we won't know what the
+ // Kerberos Domain should have been. On Windows XP and 2003 Server
+ // we can extract it from the Security Logon Session Data. However,
+ // the required fields are not supported on Windows 2000. :(
+ if ( supported && GetSecurityLogonSessionData(&pSessionData) ) {
+ if ( pSessionData->DnsDomainName.Buffer ) {
+ Error = ConstructTicketRequest(pSessionData->DnsDomainName,
+ &pTicketRequest, &RequestSize);
+ LsaFreeReturnBuffer(pSessionData);
+ if ( Error )
+ goto cleanup;
+ } else {
+ LsaFreeReturnBuffer(pSessionData);
+ bIsLsaError = TRUE;
+ goto cleanup;
+ }
+ } else {
+ CHAR UserDnsDomain[256];
+ WCHAR UnicodeUserDnsDomain[256];
+ UNICODE_STRING wrapper;
+ if ( !get_STRING_from_registry(HKEY_CURRENT_USER,
+ "Volatile Environment",
+ "USERDNSDOMAIN",
+ UserDnsDomain,
+ sizeof(UserDnsDomain)
+ ) )
+ {
+ goto cleanup;
+ }
+
+ ANSIToUnicode(UserDnsDomain,UnicodeUserDnsDomain,256);
+ wrapper.Buffer = UnicodeUserDnsDomain;
+ wrapper.Length = wcslen(UnicodeUserDnsDomain) * sizeof(WCHAR);
+ wrapper.MaximumLength = 256;
+
+ Error = ConstructTicketRequest(wrapper,
+ &pTicketRequest, &RequestSize);
+ if ( Error )
+ goto cleanup;
+ }
+ } else {
+ /* We have succeeded in obtaining a credential from the cache.
+ * Assuming the enctype is one that we support and the ticket
+ * has not expired and is not marked invalid we will use it.
+ * Otherwise, we must create a new ticket request and obtain
+ * a credential we can use.
+ */
+
+ /* Check Supported Enctypes */
+ if ( !enforce_tgs_enctypes ||
+ IsMSSessionKeyNull(&pTicketResponse->Ticket.SessionKey) ||
+ krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) {
+ FILETIME Now, MinLife, EndTime, LocalEndTime;
+ __int64 temp;
+ // FILETIME is in units of 100 nano-seconds
+ // If obtained tickets are either expired or have a lifetime
+ // less than 20 minutes, retry ...
+ GetSystemTimeAsFileTime(&Now);
+ EndTime.dwLowDateTime=pTicketResponse->Ticket.EndTime.LowPart;
+ EndTime.dwHighDateTime=pTicketResponse->Ticket.EndTime.HighPart;
+ FileTimeToLocalFileTime(&EndTime, &LocalEndTime);
+ temp = Now.dwHighDateTime;
+ temp <<= 32;
+ temp = Now.dwLowDateTime;
+ temp += 1200 * 10000;
+ MinLife.dwHighDateTime = (DWORD)((temp >> 32) & 0xFFFFFFFF);
+ MinLife.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
+ if (CompareFileTime(&MinLife, &LocalEndTime) >= 0) {
+ purge_cache = 1;
+ }
+ if (pTicketResponse->Ticket.TicketFlags & KERB_TICKET_FLAGS_invalid) {
+ ignore_cache = 1; // invalid, need to attempt a TGT request
+ }
+ goto cleanup; // we have a valid ticket, all done
+ } else {
+ // not supported
+ ignore_cache = 1;
+ }
+
+ Error = ConstructTicketRequest(pTicketResponse->Ticket.TargetDomainName,
+ &pTicketRequest, &RequestSize);
+ if ( Error ) {
+ goto cleanup;
+ }
+
+ //
+ // Free the previous response buffer so we can get the new response.
+ //
+
+ if ( pTicketResponse ) {
+ memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
+ LsaFreeReturnBuffer(pTicketResponse);
+ pTicketResponse = NULL;
+ }
+
+ if ( purge_cache ) {
+ //
+ // Purge the existing tickets which we cannot use so new ones can
+ // be requested. It is not possible to purge just the TGT. All
+ // service tickets must be purged.
+ //
+ PurgeAllTickets(LogonHandle, PackageId);
+ }
+ }
+
+ //
+ // Intialize the request of the request.
+ //
+
+ pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+ pTicketRequest->LogonId.LowPart = 0;
+ pTicketRequest->LogonId.HighPart = 0;
+ // Note: pTicketRequest->TargetName set up above
+ pTicketRequest->CacheOptions = ((ignore_cache || !purge_cache) ?
+ KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
+ pTicketRequest->TicketFlags = 0L;
+ pTicketRequest->EncryptionType = 0L;
+
+ Status = LsaCallAuthenticationPackage( LogonHandle,
+ PackageId,
+ pTicketRequest,
+ RequestSize,
+ &pTicketResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ {
+ bIsLsaError = TRUE;
+ goto cleanup;
+ }
+
+ //
+ // Check to make sure the new tickets we received are of a type we support
+ //
+
+ /* Check Supported Enctypes */
+ if ( !enforce_tgs_enctypes ||
+ krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) {
+ goto cleanup; // we have a valid ticket, all done
+ }
+
+ if (krb5_get_tgs_ktypes(context, NULL, &etype_list)) {
+ ptr = etype_list = NULL;
+ etype = ENCTYPE_DES_CBC_CRC;
+ } else {
+ ptr = etype_list + 1;
+ etype = *etype_list;
+ }
+
+ while ( etype ) {
+ // Try once more but this time specify the Encryption Type
+ // (This will not store the retrieved tickets in the LSA cache unless
+ // 0 is supported.)
+ pTicketRequest->EncryptionType = etype;
+ pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
+
+ if ( pTicketResponse ) {
+ memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
+ LsaFreeReturnBuffer(pTicketResponse);
+ pTicketResponse = NULL;
+ }
+
+ Status = LsaCallAuthenticationPackage( LogonHandle,
+ PackageId,
+ pTicketRequest,
+ RequestSize,
+ &pTicketResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ {
+ bIsLsaError = TRUE;
+ goto cleanup;
+ }
+
+ if ( pTicketResponse->Ticket.SessionKey.KeyType == etype &&
+ (!enforce_tgs_enctypes ||
+ krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType)) ) {
+ goto cleanup; // we have a valid ticket, all done
+ }
+
+ if ( ptr ) {
+ etype = *ptr++;
+ } else {
+ etype = 0;
+ }
+ }
+
+cleanup:
+ if ( etype_list )
+ krb5_free_enctypes(context, etype_list);
+
+ if ( pTicketRequest )
+ LocalFree(pTicketRequest);
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ {
+ if (bIsLsaError)
+ {
+ // XXX - Will be fixed later
+ if (FAILED(Status))
+ ShowLsaError("LsaCallAuthenticationPackage", Status);
+ if (FAILED(SubStatus))
+ ShowLsaError("LsaCallAuthenticationPackage", SubStatus);
+ }
+ else
+ {
+ ShowWinError("GetMSTGT", Status);
+ }
+
+ if (pTicketResponse) {
+ memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
+ LsaFreeReturnBuffer(pTicketResponse);
+ pTicketResponse = NULL;
+ }
+ return(FALSE);
+ }
+
+ *ticket = &(pTicketResponse->Ticket);
+ return(TRUE);
+}
+
+static BOOL
+GetQueryTktCacheResponseEx(HANDLE LogonHandle, ULONG PackageId,
+ PKERB_QUERY_TKT_CACHE_EX_RESPONSE * ppResponse)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+
+ KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
+ PKERB_QUERY_TKT_CACHE_EX_RESPONSE pQueryResponse = NULL;
+ ULONG ResponseSize;
+
+ CacheRequest.MessageType = KerbQueryTicketCacheExMessage;
+ CacheRequest.LogonId.LowPart = 0;
+ CacheRequest.LogonId.HighPart = 0;
+
+ Status = LsaCallAuthenticationPackage(
+ LogonHandle,
+ PackageId,
+ &CacheRequest,
+ sizeof(CacheRequest),
+ &pQueryResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
+ *ppResponse = pQueryResponse;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL
+GetQueryTktCacheResponseEx2(HANDLE LogonHandle, ULONG PackageId,
+ PKERB_QUERY_TKT_CACHE_EX2_RESPONSE * ppResponse)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+
+ KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
+ PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pQueryResponse = NULL;
+ ULONG ResponseSize;
+
+ CacheRequest.MessageType = KerbQueryTicketCacheEx2Message;
+ CacheRequest.LogonId.LowPart = 0;
+ CacheRequest.LogonId.HighPart = 0;
+
+ Status = LsaCallAuthenticationPackage(
+ LogonHandle,
+ PackageId,
+ &CacheRequest,
+ sizeof(CacheRequest),
+ &pQueryResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
+ *ppResponse = pQueryResponse;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static BOOL
+GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId,
+ krb5_context context, krb5_creds *creds,
+ PKERB_EXTERNAL_TICKET *ticket)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ ULONG RequestSize;
+ PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+ PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+ ULONG ResponseSize;
+
+ RequestSize = sizeof(*pTicketRequest) + MAX_MSPRINC_SIZE;
+
+ pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+ if (!pTicketRequest)
+ return FALSE;
+
+ pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+ pTicketRequest->LogonId.LowPart = 0;
+ pTicketRequest->LogonId.HighPart = 0;
+
+ pTicketRequest->TargetName.Length = 0;
+ pTicketRequest->TargetName.MaximumLength = MAX_MSPRINC_SIZE;
+ pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+ MITPrincToMSPrinc(context, creds->server, &pTicketRequest->TargetName);
+ pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
+ pTicketRequest->TicketFlags = creds->ticket_flags;
+ pTicketRequest->EncryptionType = creds->keyblock.enctype;
+
+ Status = LsaCallAuthenticationPackage( LogonHandle,
+ PackageId,
+ pTicketRequest,
+ RequestSize,
+ &pTicketResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ LocalFree(pTicketRequest);
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ return(FALSE);
+
+ /* otherwise return ticket */
+ *ticket = &(pTicketResponse->Ticket);
+ return(TRUE);
+}
+
+static BOOL
+GetMSCacheTicketFromCacheInfoEx(HANDLE LogonHandle, ULONG PackageId,
+ PKERB_TICKET_CACHE_INFO_EX tktinfo,
+ PKERB_EXTERNAL_TICKET *ticket)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ ULONG RequestSize;
+ PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+ PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+ ULONG ResponseSize;
+
+ RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
+
+ pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+ if (!pTicketRequest)
+ return FALSE;
+
+ pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+ pTicketRequest->LogonId.LowPart = 0;
+ pTicketRequest->LogonId.HighPart = 0;
+ pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
+ pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
+ pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+ memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
+ pTicketRequest->CacheOptions = 0;
+ pTicketRequest->EncryptionType = tktinfo->EncryptionType;
+ pTicketRequest->TicketFlags = 0;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
+ pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
+ pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
+ pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
+ pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
+
+ Status = LsaCallAuthenticationPackage(
+ LogonHandle,
+ PackageId,
+ pTicketRequest,
+ RequestSize,
+ &pTicketResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ LocalFree(pTicketRequest);
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ return(FALSE);
+
+ /* otherwise return ticket */
+ *ticket = &(pTicketResponse->Ticket);
+
+ /* set the initial flag if we were attempting to retrieve one
+ * because Windows won't necessarily return the initial ticket
+ * to us.
+ */
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial )
+ (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial;
+
+ return(TRUE);
+}
+
+static BOOL
+GetMSCacheTicketFromCacheInfoEx2(HANDLE LogonHandle, ULONG PackageId,
+ PKERB_TICKET_CACHE_INFO_EX2 tktinfo,
+ PKERB_EXTERNAL_TICKET *ticket)
+{
+ NTSTATUS Status = 0;
+ NTSTATUS SubStatus = 0;
+ ULONG RequestSize;
+ PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+ PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+ ULONG ResponseSize;
+
+ RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
+
+ pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+ if (!pTicketRequest)
+ return FALSE;
+
+ pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+ pTicketRequest->LogonId.LowPart = 0;
+ pTicketRequest->LogonId.HighPart = 0;
+ pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
+ pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
+ pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+ memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
+ pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
+ pTicketRequest->EncryptionType = tktinfo->SessionKeyType;
+ pTicketRequest->TicketFlags = 0;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
+ pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
+ pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
+ pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
+ pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
+
+ Status = LsaCallAuthenticationPackage(
+ LogonHandle,
+ PackageId,
+ pTicketRequest,
+ RequestSize,
+ &pTicketResponse,
+ &ResponseSize,
+ &SubStatus
+ );
+
+ LocalFree(pTicketRequest);
+
+ if (FAILED(Status) || FAILED(SubStatus))
+ return(FALSE);
+
+ /* otherwise return ticket */
+ *ticket = &(pTicketResponse->Ticket);
+
+
+ /* set the initial flag if we were attempting to retrieve one
+ * because Windows won't necessarily return the initial ticket
+ * to us.
+ */
+ if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial )
+ (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial;
+
+ return(TRUE);
+}
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_close
+(krb5_context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_destroy
+(krb5_context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_end_seq_get
+(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_generate_new
+(krb5_context, krb5_ccache *id);
+
+static const char * KRB5_CALLCONV krb5_lcc_get_name
+(krb5_context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_get_principal
+(krb5_context, krb5_ccache id, krb5_principal *princ);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_initialize
+(krb5_context, krb5_ccache id, krb5_principal princ);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_next_cred
+(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_resolve
+(krb5_context, krb5_ccache *id, const char *residual);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_retrieve
+(krb5_context, krb5_ccache id, krb5_flags whichfields,
+ krb5_creds *mcreds, krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_start_seq_get
+(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_store
+(krb5_context, krb5_ccache id, krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_set_flags
+(krb5_context, krb5_ccache id, krb5_flags flags);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_get_flags
+(krb5_context, krb5_ccache id, krb5_flags *flags);
+
+extern const krb5_cc_ops krb5_lcc_ops;
+
+krb5_error_code krb5_change_cache (void);
+
+krb5_boolean
+krb5int_cc_creds_match_request(krb5_context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds);
+
+#define KRB5_OK 0
+
+typedef struct _krb5_lcc_data {
+ HANDLE LogonHandle;
+ ULONG PackageId;
+ char * cc_name;
+ krb5_principal princ;
+ krb5_flags flags;
+} krb5_lcc_data;
+
+typedef struct _krb5_lcc_cursor {
+ union {
+ PKERB_QUERY_TKT_CACHE_RESPONSE w2k;
+ PKERB_QUERY_TKT_CACHE_EX_RESPONSE xp;
+ PKERB_QUERY_TKT_CACHE_EX2_RESPONSE ex2;
+ } response;
+ unsigned int index;
+ PKERB_EXTERNAL_TICKET mstgt;
+} krb5_lcc_cursor;
+
+
+/*
+ * Requires:
+ * residual is ignored
+ *
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * Acccess the MS Kerberos LSA cache in the current logon session
+ * Ignore the residual.
+ *
+ * Returns:
+ * A filled in krb5_ccache structure "id".
+ *
+ * Errors:
+ * KRB5_CC_NOMEM - there was insufficient memory to allocate the
+ *
+ * krb5_ccache. id is undefined.
+ * permission errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
+{
+ krb5_ccache lid;
+ krb5_lcc_data *data;
+ HANDLE LogonHandle;
+ ULONG PackageId, i;
+ PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse;
+
+ if (!PackageConnectLookup(&LogonHandle, &PackageId))
+ return KRB5_FCC_NOFILE;
+
+ lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
+ if (lid == NULL) {
+ LsaDeregisterLogonProcess(LogonHandle);
+ return KRB5_CC_NOMEM;
+ }
+
+ lid->ops = &krb5_lcc_ops;
+
+ lid->data = (krb5_pointer) malloc(sizeof(krb5_lcc_data));
+ if (lid->data == NULL) {
+ free(lid);
+ LsaDeregisterLogonProcess(LogonHandle);
+ return KRB5_CC_NOMEM;
+ }
+
+ lid->magic = KV5M_CCACHE;
+ data = (krb5_lcc_data *)lid->data;
+ data->LogonHandle = LogonHandle;
+ data->PackageId = PackageId;
+ data->princ = NULL;
+
+ data->cc_name = (char *)malloc(strlen(residual)+1);
+ if (data->cc_name == NULL) {
+ free(lid->data);
+ free(lid);
+ LsaDeregisterLogonProcess(LogonHandle);
+ return KRB5_CC_NOMEM;
+ }
+ strcpy(data->cc_name, residual);
+
+ /* If there are already tickets present, grab a client principal name. */
+ if (GetQueryTktCacheResponseEx(LogonHandle, PackageId, &pResponse)) {
+ /* Take the first client principal we find; they should all be the
+ * same anyway. */
+ for (i = 0; i < pResponse->CountOfTickets; i++) {
+ if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName,
+ &pResponse->Tickets[i].ClientRealm,
+ context, &data->princ))
+ break;
+
+ }
+ LsaFreeReturnBuffer(pResponse);
+ }
+
+ /*
+ * other routines will get errors on open, and callers must expect them,
+ * if cache is non-existent/unusable
+ */
+ *id = lid;
+ return KRB5_OK;
+}
+
+/*
+ * return success although we do not do anything
+ * We should delete all tickets belonging to the specified principal
+ */
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
+ krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krb5_cc_cursor cursor;
+ krb5_error_code code;
+ krb5_creds cred;
+
+ code = krb5_cc_start_seq_get(context, id, &cursor);
+ if (code) {
+ if (code == KRB5_CC_NOTFOUND)
+ return KRB5_OK;
+ return code;
+ }
+
+ while ( !(code = krb5_cc_next_cred(context, id, &cursor, &cred)) )
+ {
+ if ( krb5_principal_compare(context, princ, cred.client) ) {
+ code = krb5_lcc_remove_cred(context, id, 0, &cred);
+ }
+ krb5_free_cred_contents(context, &cred);
+ }
+
+ if (code == KRB5_CC_END || code == KRB5_CC_NOTFOUND)
+ {
+ krb5_cc_end_seq_get(context, id, &cursor);
+ return KRB5_OK;
+ }
+ return code;
+}
+
+/*
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * Closes the microsoft lsa cache, invalidates the id, and frees any resources
+ * associated with the cache.
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_close(krb5_context context, krb5_ccache id)
+{
+ register int closeval = KRB5_OK;
+ register krb5_lcc_data *data;
+
+ if (id) {
+ data = (krb5_lcc_data *) id->data;
+
+ if (data) {
+ LsaDeregisterLogonProcess(data->LogonHandle);
+ if (data->cc_name)
+ free(data->cc_name);
+ free(data);
+ }
+ free(id);
+ }
+ return closeval;
+}
+
+/*
+ * Effects:
+ * Destroys the contents of id.
+ *
+ * Errors:
+ * system errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_destroy(krb5_context context, krb5_ccache id)
+{
+ register krb5_lcc_data *data;
+
+ if (id) {
+ data = (krb5_lcc_data *) id->data;
+
+ return PurgeAllTickets(data->LogonHandle, data->PackageId) ? KRB5_OK : KRB5_FCC_INTERNAL;
+ }
+ return KRB5_FCC_INTERNAL;
+}
+
+/*
+ * Effects:
+ * Prepares for a sequential search of the credentials cache.
+ * Returns a krb5_cc_cursor to be used with krb5_lcc_next_cred and
+ * krb5_lcc_end_seq_get.
+ *
+ * If the cache is modified between the time of this call and the time
+ * of the final krb5_lcc_end_seq_get, the results are undefined.
+ *
+ * Errors:
+ * KRB5_CC_NOMEM
+ * KRB5_FCC_INTERNAL - system errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ krb5_lcc_cursor *lcursor;
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+
+ lcursor = (krb5_lcc_cursor *) malloc(sizeof(krb5_lcc_cursor));
+ if (lcursor == NULL) {
+ *cursor = 0;
+ return KRB5_CC_NOMEM;
+ }
+
+ /*
+ * obtain a tgt to refresh the ccache in case the ticket is expired
+ */
+ if (!GetMSTGT(context, data->LogonHandle, data->PackageId, &lcursor->mstgt, TRUE)) {
+ free(lcursor);
+ *cursor = 0;
+ return KRB5_CC_NOTFOUND;
+ }
+
+ if ( does_query_ticket_cache_ex2() ) {
+ if (!GetQueryTktCacheResponseEx2(data->LogonHandle, data->PackageId,
+ &lcursor->response.ex2)) {
+ LsaFreeReturnBuffer(lcursor->mstgt);
+ free(lcursor);
+ *cursor = 0;
+ return KRB5_FCC_INTERNAL;
+ }
+ } else
+ if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
+ &lcursor->response.xp)) {
+ LsaFreeReturnBuffer(lcursor->mstgt);
+ free(lcursor);
+ *cursor = 0;
+ return KRB5_FCC_INTERNAL;
+ }
+ lcursor->index = 0;
+ *cursor = (krb5_cc_cursor) lcursor;
+ return KRB5_OK;
+}
+
+
+/*
+ * Requires:
+ * cursor is a krb5_cc_cursor originally obtained from
+ * krb5_lcc_start_seq_get.
+ *
+ * Modifes:
+ * cursor
+ *
+ * Effects:
+ * Fills in creds with the TGT obtained from the MS LSA
+ *
+ * The cursor is updated to indicate TGT retrieval
+ *
+ * Errors:
+ * KRB5_CC_END
+ * KRB5_FCC_INTERNAL - system errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, krb5_creds *creds)
+{
+ krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
+ krb5_lcc_data *data;
+ KERB_EXTERNAL_TICKET *msticket;
+ krb5_error_code retval = KRB5_OK;
+
+ data = (krb5_lcc_data *)id->data;
+
+next_cred:
+ if ( does_query_ticket_cache_ex2() ) {
+ if ( lcursor->index >= lcursor->response.ex2->CountOfTickets ) {
+ if (retval == KRB5_OK)
+ return KRB5_CC_END;
+ else {
+ LsaFreeReturnBuffer(lcursor->mstgt);
+ LsaFreeReturnBuffer(lcursor->response.ex2);
+ free(*cursor);
+ *cursor = 0;
+ return retval;
+ }
+ }
+
+ if ( data->flags & KRB5_TC_NOTICKET ) {
+ if (!CacheInfoEx2ToMITCred( &lcursor->response.ex2->Tickets[lcursor->index++],
+ context, creds)) {
+ retval = KRB5_FCC_INTERNAL;
+ goto next_cred;
+ }
+ return KRB5_OK;
+ } else {
+ if (!GetMSCacheTicketFromCacheInfoEx2(data->LogonHandle,
+ data->PackageId,
+ &lcursor->response.ex2->Tickets[lcursor->index++],&msticket)) {
+ retval = KRB5_FCC_INTERNAL;
+ goto next_cred;
+ }
+ }
+ } else {
+ if (lcursor->index >= lcursor->response.xp->CountOfTickets) {
+ if (retval == KRB5_OK) {
+ return KRB5_CC_END;
+ } else {
+ LsaFreeReturnBuffer(lcursor->mstgt);
+ LsaFreeReturnBuffer(lcursor->response.xp);
+ free(*cursor);
+ *cursor = 0;
+ return retval;
+ }
+ }
+
+ if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle,
+ data->PackageId,
+ &lcursor->response.xp->Tickets[lcursor->index++],
+ &msticket)) {
+ retval = KRB5_FCC_INTERNAL;
+ goto next_cred;
+ }
+ }
+
+ /* Don't return tickets with NULL Session Keys */
+ if ( IsMSSessionKeyNull(&msticket->SessionKey) ) {
+ LsaFreeReturnBuffer(msticket);
+ goto next_cred;
+ }
+
+ /* convert the ticket */
+ if ( does_query_ticket_cache_ex2() ) {
+ if (!MSCredToMITCred(msticket, lcursor->response.ex2->Tickets[lcursor->index-1].ClientRealm, context, creds))
+ retval = KRB5_FCC_INTERNAL;
+ } else {
+ if (!MSCredToMITCred(msticket,
+ lcursor->response.xp->Tickets[lcursor->index -
+ 1].ClientRealm,
+ context, creds))
+ retval = KRB5_FCC_INTERNAL;
+ }
+ LsaFreeReturnBuffer(msticket);
+ return retval;
+}
+
+/*
+ * Requires:
+ * cursor is a krb5_cc_cursor originally obtained from
+ * krb5_lcc_start_seq_get.
+ *
+ * Modifies:
+ * id, cursor
+ *
+ * Effects:
+ * Finishes sequential processing of the file credentials ccache id,
+ * and invalidates the cursor (it must never be used after this call).
+ */
+/* ARGSUSED */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
+
+ if ( lcursor ) {
+ LsaFreeReturnBuffer(lcursor->mstgt);
+ if ( does_query_ticket_cache_ex2() )
+ LsaFreeReturnBuffer(lcursor->response.ex2);
+ else
+ LsaFreeReturnBuffer(lcursor->response.xp);
+ free(*cursor);
+ }
+ *cursor = 0;
+
+ return KRB5_OK;
+}
+
+
+/*
+ * Errors:
+ * KRB5_CC_READONLY - not supported
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_generate_new (krb5_context context, krb5_ccache *id)
+{
+ return KRB5_CC_READONLY;
+}
+
+/*
+ * Requires:
+ * id is a ms lsa credential cache
+ *
+ * Returns:
+ * The ccname specified during the krb5_lcc_resolve call
+ */
+static const char * KRB5_CALLCONV
+krb5_lcc_get_name (krb5_context context, krb5_ccache id)
+{
+
+ if ( !id )
+ return "";
+
+ return (char *) ((krb5_lcc_data *) id->data)->cc_name;
+}
+
+/*
+ * Modifies:
+ * id, princ
+ *
+ * Effects:
+ * Retrieves the primary principal from id, as set with
+ * krb5_lcc_initialize. The principal is returned is allocated
+ * storage that must be freed by the caller via krb5_free_principal.
+ *
+ * Errors:
+ * system errors
+ * KRB5_CC_NOT_KTYPE
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
+{
+ PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse;
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+ ULONG i;
+
+ /* obtain principal */
+ if (data->princ)
+ return krb5_copy_principal(context, data->princ, princ);
+ else {
+ if (GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
+ &pResponse)) {
+ /* Take the first client principal we find; they should all be the
+ * same anyway. */
+ for (i = 0; i < pResponse->CountOfTickets; i++) {
+ if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName,
+ &pResponse->Tickets[i].ClientRealm,
+ context, &data->princ))
+ break;
+ }
+ LsaFreeReturnBuffer(pResponse);
+ if (data->princ)
+ return krb5_copy_principal(context, data->princ, princ);
+ }
+ }
+ return KRB5_CC_NOTFOUND;
+}
+
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
+ krb5_creds *mcreds, krb5_creds *creds)
+{
+ krb5_error_code kret = KRB5_OK;
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+ KERB_EXTERNAL_TICKET *msticket = 0, *mstgt = 0, *mstmp = 0;
+ krb5_creds * mcreds_noflags = 0;
+ krb5_creds fetchcreds;
+ PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse = 0;
+ unsigned int i;
+
+ memset(&fetchcreds, 0, sizeof(krb5_creds));
+
+ /* first try to find out if we have an existing ticket which meets the requirements */
+ kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+ /* This sometimes returns a zero-length ticket; work around it. */
+ if ( !kret && creds->ticket.length > 0 )
+ return KRB5_OK;
+
+ /* if not, we must try to get a ticket without specifying any flags or etypes */
+ kret = krb5_copy_creds(context, mcreds, &mcreds_noflags);
+ if (kret)
+ goto cleanup;
+ mcreds_noflags->ticket_flags = 0;
+ mcreds_noflags->keyblock.enctype = 0;
+
+ if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds_noflags, &msticket)) {
+ kret = KRB5_CC_NOTFOUND;
+ goto cleanup;
+ }
+
+ /* try again to find out if we have an existing ticket which meets the requirements */
+ kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+ /* This sometimes returns a zero-length ticket; work around it. */
+ if ( !kret && creds->ticket.length > 0 )
+ goto cleanup;
+
+ /* if not, obtain a ticket using the request flags and enctype even though it may not
+ * be stored in the LSA cache for future use.
+ */
+ if ( msticket ) {
+ LsaFreeReturnBuffer(msticket);
+ msticket = 0;
+ }
+
+ if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds, &msticket)) {
+ kret = KRB5_CC_NOTFOUND;
+ goto cleanup;
+ }
+
+ /* convert the ticket */
+ /*
+ * We can obtain the correct client realm for a ticket by walking the
+ * cache contents until we find the matching service ticket.
+ */
+
+ if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
+ &pResponse)) {
+ kret = KRB5_FCC_INTERNAL;
+ goto cleanup;
+ }
+
+ for (i = 0; i < pResponse->CountOfTickets; i++) {
+ if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle,
+ data->PackageId,
+ &pResponse->Tickets[i], &mstmp)) {
+ continue;
+ }
+
+ if (KerbExternalTicketMatch(msticket,mstmp))
+ break;
+
+ LsaFreeReturnBuffer(mstmp);
+ mstmp = 0;
+ }
+
+ if (!MSCredToMITCred(msticket, mstmp ?
+ pResponse->Tickets[i].ClientRealm :
+ msticket->DomainName, context, &fetchcreds)) {
+ LsaFreeReturnBuffer(pResponse);
+ kret = KRB5_FCC_INTERNAL;
+ goto cleanup;
+ }
+ LsaFreeReturnBuffer(pResponse);
+
+
+ /* check to see if this ticket matches the request using logic from
+ * k5_cc_retrieve_cred_default()
+ */
+ if ( krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds) ) {
+ *creds = fetchcreds;
+ } else {
+ krb5_free_cred_contents(context, &fetchcreds);
+ kret = KRB5_CC_NOTFOUND;
+ }
+
+cleanup:
+ if ( mstmp )
+ LsaFreeReturnBuffer(mstmp);
+ if ( mstgt )
+ LsaFreeReturnBuffer(mstgt);
+ if ( msticket )
+ LsaFreeReturnBuffer(msticket);
+ if ( mcreds_noflags )
+ krb5_free_creds(context, mcreds_noflags);
+ return kret;
+}
+
+
+/*
+ * We can't write to the MS LSA cache. So we request the cache to obtain a ticket for the same
+ * principal in the hope that next time the application requires a ticket for the service it
+ * is attempt to store, the retrieved ticket will be good enough.
+ *
+ * Errors:
+ * KRB5_CC_READONLY - not supported
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
+{
+ krb5_error_code kret = KRB5_OK;
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+ KERB_EXTERNAL_TICKET *msticket = 0, *msticket2 = 0;
+ krb5_creds * creds_noflags = 0;
+
+ if (krb5_is_config_principal(context, creds->server)) {
+ /* mslsa cannot store config creds, so we have to bail.
+ * The 'right' thing to do would be to return an appropriate error,
+ * but that would require modifying the calling code to check
+ * for that error and ignore it.
+ */
+ return KRB5_OK;
+ }
+
+ if (KerbSubmitTicket( data->LogonHandle, data->PackageId, context, creds ))
+ return KRB5_OK;
+
+ /* If not, lets try to obtain a matching ticket from the KDC */
+ if ( creds->ticket_flags != 0 && creds->keyblock.enctype != 0 ) {
+ /* if not, we must try to get a ticket without specifying any flags or etypes */
+ kret = krb5_copy_creds(context, creds, &creds_noflags);
+ if (kret == 0) {
+ creds_noflags->ticket_flags = 0;
+ creds_noflags->keyblock.enctype = 0;
+
+ GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds_noflags, &msticket2);
+ krb5_free_creds(context, creds_noflags);
+ }
+ }
+
+ GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds, &msticket);
+ if (msticket || msticket2) {
+ if (msticket)
+ LsaFreeReturnBuffer(msticket);
+ if (msticket2)
+ LsaFreeReturnBuffer(msticket2);
+ return KRB5_OK;
+ }
+ return KRB5_CC_READONLY;
+}
+
+/*
+ * Individual credentials can be implemented differently depending
+ * on the operating system version. (undocumented.)
+ *
+ * Errors:
+ * KRB5_CC_READONLY:
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
+ krb5_creds *creds)
+{
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+
+ if (PurgeTicketEx(data->LogonHandle, data->PackageId, context, flags,
+ creds))
+ return KRB5_OK;
+
+ return KRB5_CC_READONLY;
+}
+
+
+/*
+ * Effects:
+ * Set
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
+{
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+
+ data->flags = flags;
+ return KRB5_OK;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
+{
+ krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+
+ *flags = data->flags;
+ return KRB5_OK;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ krb5_cc_ptcursor new_cursor = (krb5_cc_ptcursor )malloc(sizeof(*new_cursor));
+ if (!new_cursor)
+ return ENOMEM;
+ new_cursor->ops = &krb5_lcc_ops;
+ new_cursor->data = (krb5_pointer)(1);
+ *cursor = new_cursor;
+ new_cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *ccache)
+{
+ krb5_error_code code = 0;
+ *ccache = 0;
+ if (cursor->data == NULL)
+ return 0;
+
+ cursor->data = NULL;
+ if ((code = krb5_lcc_resolve(context, ccache, ""))) {
+ if (code != KRB5_FCC_NOFILE)
+ /* Note that we only want to return serious errors.
+ * Any non-zero return code will prevent the cccol iterator
+ * from advancing to the next ccache collection. */
+ return code;
+ }
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ if (*cursor) {
+ free(*cursor);
+ *cursor = NULL;
+ }
+ return 0;
+}
+
+const krb5_cc_ops krb5_lcc_ops = {
+ 0,
+ "MSLSA",
+ krb5_lcc_get_name,
+ krb5_lcc_resolve,
+ krb5_lcc_generate_new,
+ krb5_lcc_initialize,
+ krb5_lcc_destroy,
+ krb5_lcc_close,
+ krb5_lcc_store,
+ krb5_lcc_retrieve,
+ krb5_lcc_get_principal,
+ krb5_lcc_start_seq_get,
+ krb5_lcc_next_cred,
+ krb5_lcc_end_seq_get,
+ krb5_lcc_remove_cred,
+ krb5_lcc_set_flags,
+ krb5_lcc_get_flags,
+ krb5_lcc_ptcursor_new,
+ krb5_lcc_ptcursor_next,
+ krb5_lcc_ptcursor_free,
+ NULL, /* move */
+ NULL, /* lastchange */
+ NULL, /* wasdefault */
+ NULL, /* lock */
+ NULL, /* unlock */
+ NULL, /* switch_to */
+};
+#endif /* _WIN32 */
diff --git a/src/lib/krb5/ccache/cc_retr.c b/src/lib/krb5/ccache/cc_retr.c
new file mode 100644
index 0000000000000..1314d24bd68db
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_retr.c
@@ -0,0 +1,280 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_retr.c */
+/*
+ * Copyright 1990,1991,1999,2007,2008 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include "../krb/int-proto.h"
+
+#define KRB5_OK 0
+
+#define set(bits) (whichfields & bits)
+#define flags_match(a,b) (((a) & (b)) == (a))
+
+static int
+times_match_exact(const krb5_ticket_times *t1, const krb5_ticket_times *t2)
+{
+ return (t1->authtime == t2->authtime &&
+ t1->starttime == t2->starttime &&
+ t1->endtime == t2->endtime &&
+ t1->renew_till == t2->renew_till);
+}
+
+static krb5_boolean
+times_match(const krb5_ticket_times *t1, const krb5_ticket_times *t2)
+{
+ if (t1->renew_till) {
+ if (t1->renew_till > t2->renew_till)
+ return FALSE; /* this one expires too late */
+ }
+ if (t1->endtime) {
+ if (t1->endtime > t2->endtime)
+ return FALSE; /* this one expires too late */
+ }
+ /* only care about expiration on a times_match */
+ return TRUE;
+}
+
+static krb5_boolean
+standard_fields_match(krb5_context context, const krb5_creds *mcreds, const krb5_creds *creds)
+{
+ return (krb5_principal_compare(context, mcreds->client,creds->client)
+ && krb5_principal_compare(context, mcreds->server,creds->server));
+}
+
+/* only match the server name portion, not the server realm portion */
+
+static krb5_boolean
+srvname_match(krb5_context context, const krb5_creds *mcreds, const krb5_creds *creds)
+{
+ krb5_boolean retval;
+ krb5_principal_data p1, p2;
+
+ retval = krb5_principal_compare(context, mcreds->client,creds->client);
+ if (retval != TRUE)
+ return retval;
+ /*
+ * Hack to ignore the server realm for the purposes of the compare.
+ */
+ p1 = *mcreds->server;
+ p2 = *creds->server;
+ p1.realm = p2.realm;
+ return krb5_principal_compare(context, &p1, &p2);
+}
+
+static krb5_boolean
+authdata_match(krb5_authdata *const *mdata, krb5_authdata *const *data)
+{
+ const krb5_authdata *mdatap, *datap;
+
+ if (mdata == data)
+ return TRUE;
+
+ if (mdata == NULL)
+ return *data == NULL;
+
+ if (data == NULL)
+ return *mdata == NULL;
+
+ while ((mdatap = *mdata) && (datap = *data)) {
+ if ((mdatap->ad_type != datap->ad_type) ||
+ (mdatap->length != datap->length) ||
+ (memcmp ((char *)mdatap->contents,
+ (char *)datap->contents, (unsigned) mdatap->length) != 0))
+ return FALSE;
+ mdata++;
+ data++;
+ }
+ return (*mdata == NULL) && (*data == NULL);
+}
+
+static krb5_boolean
+data_match(const krb5_data *data1, const krb5_data *data2)
+{
+ if (!data1) {
+ if (!data2)
+ return TRUE;
+ else
+ return FALSE;
+ }
+ if (!data2) return FALSE;
+
+ return data_eq(*data1, *data2) ? TRUE : FALSE;
+}
+
+static int
+pref (krb5_enctype my_ktype, int nktypes, krb5_enctype *ktypes)
+{
+ int i;
+ for (i = 0; i < nktypes; i++)
+ if (my_ktype == ktypes[i])
+ return i;
+ return -1;
+}
+
+/*
+ * Effects:
+ * Searches the credentials cache for a credential matching mcreds,
+ * with the fields specified by whichfields. If one if found, it is
+ * returned in creds, which should be freed by the caller with
+ * krb5_free_credentials().
+ *
+ * The fields are interpreted in the following way (all constants are
+ * preceded by KRB5_TC_). MATCH_IS_SKEY requires the is_skey field to
+ * match exactly. MATCH_TIMES requires the requested lifetime to be
+ * at least as great as that specified; MATCH_TIMES_EXACT requires the
+ * requested lifetime to be exactly that specified. MATCH_FLAGS
+ * requires only the set bits in mcreds be set in creds;
+ * MATCH_FLAGS_EXACT requires all bits to match.
+ *
+ * Flag SUPPORTED_KTYPES means check all matching entries that have
+ * any supported enctype (according to tgs_enctypes) and return the one
+ * with the enctype listed earliest. Return CC_NOT_KTYPE if a match
+ * is found *except* for having a supported enctype.
+ *
+ * Errors:
+ * system errors
+ * permission errors
+ * KRB5_CC_NOMEM
+ * KRB5_CC_NOT_KTYPE
+ */
+
+krb5_boolean
+krb5int_cc_creds_match_request(krb5_context context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds)
+{
+ if (((set(KRB5_TC_MATCH_SRV_NAMEONLY) &&
+ srvname_match(context, mcreds, creds)) ||
+ standard_fields_match(context, mcreds, creds))
+ &&
+ (! set(KRB5_TC_MATCH_IS_SKEY) ||
+ mcreds->is_skey == creds->is_skey)
+ &&
+ (! set(KRB5_TC_MATCH_FLAGS_EXACT) ||
+ mcreds->ticket_flags == creds->ticket_flags)
+ &&
+ (! set(KRB5_TC_MATCH_FLAGS) ||
+ flags_match(mcreds->ticket_flags, creds->ticket_flags))
+ &&
+ (! set(KRB5_TC_MATCH_TIMES_EXACT) ||
+ times_match_exact(&mcreds->times, &creds->times))
+ &&
+ (! set(KRB5_TC_MATCH_TIMES) ||
+ times_match(&mcreds->times, &creds->times))
+ &&
+ ( ! set(KRB5_TC_MATCH_AUTHDATA) ||
+ authdata_match(mcreds->authdata, creds->authdata))
+ &&
+ (! set(KRB5_TC_MATCH_2ND_TKT) ||
+ data_match (&mcreds->second_ticket, &creds->second_ticket))
+ &&
+ ((! set(KRB5_TC_MATCH_KTYPE))||
+ (mcreds->keyblock.enctype == creds->keyblock.enctype)))
+ return TRUE;
+ return FALSE;
+}
+
+static krb5_error_code
+krb5_cc_retrieve_cred_seq (krb5_context context, krb5_ccache id,
+ krb5_flags whichfields, krb5_creds *mcreds,
+ krb5_creds *creds, int nktypes, krb5_enctype *ktypes)
+{
+ /* This function could be considerably faster if it kept indexing */
+ /* information.. sounds like a "next version" idea to me. :-) */
+
+ krb5_cc_cursor cursor;
+ krb5_error_code kret;
+ krb5_error_code nomatch_err = KRB5_CC_NOTFOUND;
+ struct {
+ krb5_creds creds;
+ int pref;
+ } fetched, best;
+ int have_creds = 0;
+ krb5_flags oflags = 0;
+#define fetchcreds (fetched.creds)
+
+ kret = krb5_cc_start_seq_get(context, id, &cursor);
+ if (kret != KRB5_OK)
+ return kret;
+
+ while (krb5_cc_next_cred(context, id, &cursor, &fetchcreds) == KRB5_OK) {
+ if (krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds))
+ {
+ if (ktypes) {
+ fetched.pref = pref (fetchcreds.keyblock.enctype,
+ nktypes, ktypes);
+ if (fetched.pref < 0)
+ nomatch_err = KRB5_CC_NOT_KTYPE;
+ else if (!have_creds || fetched.pref < best.pref) {
+ if (have_creds)
+ krb5_free_cred_contents (context, &best.creds);
+ else
+ have_creds = 1;
+ best = fetched;
+ continue;
+ }
+ } else {
+ krb5_cc_end_seq_get(context, id, &cursor);
+ *creds = fetchcreds;
+ return KRB5_OK;
+ }
+ }
+
+ /* This one doesn't match */
+ krb5_free_cred_contents(context, &fetchcreds);
+ }
+
+ /* If we get here, a match wasn't found */
+ krb5_cc_end_seq_get(context, id, &cursor);
+ if (have_creds) {
+ *creds = best.creds;
+ return KRB5_OK;
+ } else
+ return nomatch_err;
+}
+
+krb5_error_code
+k5_cc_retrieve_cred_default(krb5_context context, krb5_ccache id,
+ krb5_flags flags, krb5_creds *mcreds,
+ krb5_creds *creds)
+{
+ krb5_enctype *ktypes;
+ int nktypes;
+ krb5_error_code ret;
+
+ if (flags & KRB5_TC_SUPPORTED_KTYPES) {
+ ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes);
+ if (ret)
+ return ret;
+ nktypes = k5_count_etypes (ktypes);
+
+ ret = krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds,
+ nktypes, ktypes);
+ free (ktypes);
+ return ret;
+ } else {
+ return krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds,
+ 0, 0);
+ }
+}
diff --git a/src/lib/krb5/ccache/ccapi/Makefile.in b/src/lib/krb5/ccache/ccapi/Makefile.in
new file mode 100644
index 0000000000000..73657378603fd
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/Makefile.in
@@ -0,0 +1,26 @@
+mydir=lib$(S)krb5$(S)ccache$(S)ccapi
+BUILDTOP=$(REL)..$(S)..$(S)..$(S)..
+LOCALINCLUDES = $(WIN_INCLUDES)
+DEFINES= -DUSE_CCAPI -DUSE_CCAPI_V3
+
+##DOS##WIN_INCLUDES = -I$(top_srcdir)\windows\lib
+
+##DOS##BUILDTOP = ..\..\..\..
+##DOS##PREFIXDIR = ccache\file
+##DOS##OBJFILE = $(OUTPRE)file.lst
+
+STLIBOBJS = \
+ stdcc.o \
+ stdcc_util.o \
+ winccld.o
+
+OBJS = $(OUTPRE)stdcc.$(OBJEXT) $(OUTPRE)stdcc_util.$(OBJEXT) $(OUTPRE)winccld.$(OBJEXT)
+
+SRCS = $(srcdir)/stdcc.c $(srcdir)/stdcc_util.c $(srcdir)/winccld.c
+
+##DOS##LIBOBJS = $(OBJS)
+
+all-unix: all-libobjs
+clean-unix:: clean-libobjs
+
+@libobj_frag@
diff --git a/src/lib/krb5/ccache/ccapi/deps b/src/lib/krb5/ccache/ccapi/deps
new file mode 100644
index 0000000000000..7df6d68520fee
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/deps
@@ -0,0 +1,18 @@
+#
+# Generated makefile dependencies follow.
+#
+stdcc.so stdcc.po $(OUTPRE)stdcc.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/CredentialsCache.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/locate_plugin.h \
+ $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h stdcc.c stdcc.h stdcc_util.h
+stdcc_util.so stdcc_util.po $(OUTPRE)stdcc_util.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/CredentialsCache.h \
+ $(top_srcdir)/include/krb5.h stdcc_util.c stdcc_util.h
+winccld.so winccld.po $(OUTPRE)winccld.$(OBJEXT): winccld.c
diff --git a/src/lib/krb5/ccache/ccapi/stdcc.c b/src/lib/krb5/ccache/ccapi/stdcc.c
new file mode 100644
index 0000000000000..0256a0a5d887a
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/stdcc.c
@@ -0,0 +1,1730 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccapi/stdcc.c - ccache API support functions */
+/*
+ * Copyright 1998, 1999, 2006, 2008 by the Massachusetts Institute of
+ * Technology. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * Written by Frank Dabek July 1998
+ * Updated by Jeffrey Altman June 2006
+ */
+
+#if defined(_WIN32) || defined(USE_CCAPI)
+
+#include "k5-int.h"
+#include "../cc-int.h"
+#include "stdcc.h"
+#include "stdcc_util.h"
+#include "string.h"
+#include <stdio.h>
+
+#if defined(_WIN32)
+#include "winccld.h"
+#endif
+
+#ifndef CC_API_VER2
+#define CC_API_VER2
+#endif
+
+#ifdef DEBUG
+#if defined(_WIN32)
+#include <io.h>
+#define SHOW_DEBUG(buf) MessageBox((HWND)NULL, (buf), "ccapi debug", MB_OK)
+#endif
+/* XXX need macintosh debugging statement if we want to debug */
+/* on the mac */
+#else
+#define SHOW_DEBUG(buf)
+#endif
+
+#ifdef USE_CCAPI_V3
+cc_context_t gCntrlBlock = NULL;
+cc_int32 gCCVersion = 0;
+#else
+apiCB *gCntrlBlock = NULL;
+#endif
+
+/*
+ * declare our global object wanna-be
+ * must be installed in ccdefops.c
+ */
+
+krb5_cc_ops krb5_cc_stdcc_ops = {
+ 0,
+ "API",
+#ifdef USE_CCAPI_V3
+ krb5_stdccv3_get_name,
+ krb5_stdccv3_resolve,
+ krb5_stdccv3_generate_new,
+ krb5_stdccv3_initialize,
+ krb5_stdccv3_destroy,
+ krb5_stdccv3_close,
+ krb5_stdccv3_store,
+ krb5_stdccv3_retrieve,
+ krb5_stdccv3_get_principal,
+ krb5_stdccv3_start_seq_get,
+ krb5_stdccv3_next_cred,
+ krb5_stdccv3_end_seq_get,
+ krb5_stdccv3_remove,
+ krb5_stdccv3_set_flags,
+ krb5_stdccv3_get_flags,
+ krb5_stdccv3_ptcursor_new,
+ krb5_stdccv3_ptcursor_next,
+ krb5_stdccv3_ptcursor_free,
+ NULL, /* move */
+ krb5_stdccv3_last_change_time, /* lastchange */
+ NULL, /* wasdefault */
+ krb5_stdccv3_lock,
+ krb5_stdccv3_unlock,
+ krb5_stdccv3_switch_to,
+#else
+ krb5_stdcc_get_name,
+ krb5_stdcc_resolve,
+ krb5_stdcc_generate_new,
+ krb5_stdcc_initialize,
+ krb5_stdcc_destroy,
+ krb5_stdcc_close,
+ krb5_stdcc_store,
+ krb5_stdcc_retrieve,
+ krb5_stdcc_get_principal,
+ krb5_stdcc_start_seq_get,
+ krb5_stdcc_next_cred,
+ krb5_stdcc_end_seq_get,
+ krb5_stdcc_remove,
+ krb5_stdcc_set_flags,
+ krb5_stdcc_get_flags,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+#endif
+};
+
+#if defined(_WIN32)
+/*
+ * cache_changed be called after the cache changes.
+ * A notification message is is posted out to all top level
+ * windows so that they may recheck the cache based on the
+ * changes made. We register a unique message type with which
+ * we'll communicate to all other processes.
+ */
+static void cache_changed()
+{
+ static unsigned int message = 0;
+
+ if (message == 0)
+ message = RegisterWindowMessage(WM_KERBEROS5_CHANGED);
+
+ PostMessage(HWND_BROADCAST, message, 0, 0);
+}
+#else /* _WIN32 */
+
+static void cache_changed()
+{
+ return;
+}
+#endif /* _WIN32 */
+
+struct err_xlate
+{
+ int cc_err;
+ krb5_error_code krb5_err;
+};
+
+static const struct err_xlate err_xlate_table[] =
+{
+#ifdef USE_CCAPI_V3
+ { ccIteratorEnd, KRB5_CC_END },
+ { ccErrBadParam, KRB5_FCC_INTERNAL },
+ { ccErrNoMem, KRB5_CC_NOMEM },
+ { ccErrInvalidContext, KRB5_FCC_NOFILE },
+ { ccErrInvalidCCache, KRB5_FCC_NOFILE },
+ { ccErrInvalidString, KRB5_FCC_INTERNAL },
+ { ccErrInvalidCredentials, KRB5_FCC_INTERNAL },
+ { ccErrInvalidCCacheIterator, KRB5_FCC_INTERNAL },
+ { ccErrInvalidCredentialsIterator, KRB5_FCC_INTERNAL },
+ { ccErrInvalidLock, KRB5_FCC_INTERNAL },
+ { ccErrBadName, KRB5_CC_BADNAME },
+ { ccErrBadCredentialsVersion, KRB5_FCC_INTERNAL },
+ { ccErrBadAPIVersion, KRB5_FCC_INTERNAL },
+ { ccErrContextLocked, KRB5_FCC_INTERNAL },
+ { ccErrContextUnlocked, KRB5_FCC_INTERNAL },
+ { ccErrCCacheLocked, KRB5_FCC_INTERNAL },
+ { ccErrCCacheUnlocked, KRB5_FCC_INTERNAL },
+ { ccErrBadLockType, KRB5_FCC_INTERNAL },
+ { ccErrNeverDefault, KRB5_FCC_INTERNAL },
+ { ccErrCredentialsNotFound, KRB5_CC_NOTFOUND },
+ { ccErrCCacheNotFound, KRB5_FCC_NOFILE },
+ { ccErrContextNotFound, KRB5_FCC_NOFILE },
+ { ccErrServerUnavailable, KRB5_CC_IO },
+ { ccErrServerInsecure, KRB5_CC_IO },
+ { ccErrServerCantBecomeUID, KRB5_CC_IO },
+ { ccErrTimeOffsetNotSet, KRB5_FCC_INTERNAL },
+ { ccErrBadInternalMessage, KRB5_FCC_INTERNAL },
+ { ccErrNotImplemented, KRB5_FCC_INTERNAL },
+#else
+ { CC_BADNAME, KRB5_CC_BADNAME },
+ { CC_NOTFOUND, KRB5_CC_NOTFOUND },
+ { CC_END, KRB5_CC_END },
+ { CC_IO, KRB5_CC_IO },
+ { CC_WRITE, KRB5_CC_WRITE },
+ { CC_NOMEM, KRB5_CC_NOMEM },
+ { CC_FORMAT, KRB5_CC_FORMAT },
+ { CC_WRITE, KRB5_CC_WRITE },
+ { CC_LOCKED, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_BAD_API_VERSION, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_NO_EXIST, KRB5_FCC_NOFILE },
+ { CC_NOT_SUPP, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_BAD_PARM, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_ERR_CACHE_ATTACH, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_ERR_CACHE_RELEASE, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_ERR_CACHE_FULL, KRB5_FCC_INTERNAL /* XXX */ },
+ { CC_ERR_CRED_VERSION, KRB5_FCC_INTERNAL /* XXX */ },
+#endif
+ { 0, 0 }
+};
+
+/* Note: cc_err_xlate is NOT idempotent. Don't call it multiple times. */
+static krb5_error_code cc_err_xlate(int err)
+{
+ const struct err_xlate *p;
+
+#ifdef USE_CCAPI_V3
+ if (err == ccNoError)
+ return 0;
+#else
+ if (err == CC_NOERROR)
+ return 0;
+#endif
+
+ for (p = err_xlate_table; p->cc_err; p++) {
+ if (err == p->cc_err)
+ return p->krb5_err;
+ }
+
+ return KRB5_FCC_INTERNAL;
+}
+
+
+#ifdef USE_CCAPI_V3
+
+static krb5_error_code stdccv3_get_timeoffset (krb5_context in_context,
+ cc_ccache_t in_ccache)
+{
+ krb5_error_code err = 0;
+
+ if (gCCVersion >= ccapi_version_5) {
+ krb5_os_context os_ctx = (krb5_os_context) &in_context->os_context;
+ cc_time_t time_offset = 0;
+
+ err = cc_ccache_get_kdc_time_offset (in_ccache, cc_credentials_v5,
+ &time_offset);
+
+ if (!err) {
+ os_ctx->time_offset = time_offset;
+ os_ctx->usec_offset = 0;
+ os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) |
+ KRB5_OS_TOFFSET_VALID);
+ }
+
+ if (err == ccErrTimeOffsetNotSet) {
+ err = 0; /* okay if there is no time offset */
+ }
+ }
+
+ return err; /* Don't translate. Callers will translate for us */
+}
+
+static krb5_error_code stdccv3_set_timeoffset (krb5_context in_context,
+ cc_ccache_t in_ccache)
+{
+ krb5_error_code err = 0;
+
+ if (gCCVersion >= ccapi_version_5) {
+ krb5_os_context os_ctx = (krb5_os_context) &in_context->os_context;
+
+ if (!err && os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
+ err = cc_ccache_set_kdc_time_offset (in_ccache,
+ cc_credentials_v5,
+ os_ctx->time_offset);
+ }
+ }
+
+ return err; /* Don't translate. Callers will translate for us */
+}
+
+static krb5_error_code stdccv3_setup (krb5_context context,
+ stdccCacheDataPtr ccapi_data)
+{
+ krb5_error_code err = 0;
+
+ if (!err && !gCntrlBlock) {
+ err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL);
+ }
+
+ if (!err && ccapi_data && !ccapi_data->NamedCache) {
+ /* ccache has not been opened yet. open it. */
+ err = cc_context_open_ccache (gCntrlBlock, ccapi_data->cache_name,
+ &ccapi_data->NamedCache);
+ }
+
+ if (!err && ccapi_data && ccapi_data->NamedCache) {
+ err = stdccv3_get_timeoffset (context, ccapi_data->NamedCache);
+ }
+
+ return err; /* Don't translate. Callers will translate for us */
+}
+
+/* krb5_stdcc_shutdown is exported; use the old name */
+void krb5_stdcc_shutdown()
+{
+ if (gCntrlBlock) { cc_context_release(gCntrlBlock); }
+ gCntrlBlock = NULL;
+ gCCVersion = 0;
+}
+
+/*
+ * -- generate_new --------------------------------
+ *
+ * create a new cache with a unique name, corresponds to creating a
+ * named cache initialize the API here if we have to.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_generate_new (krb5_context context, krb5_ccache *id )
+{
+ krb5_error_code err = 0;
+ krb5_ccache newCache = NULL;
+ stdccCacheDataPtr ccapi_data = NULL;
+ cc_ccache_t ccache = NULL;
+ cc_string_t ccstring = NULL;
+ char *name = NULL;
+
+ if (!err) {
+ err = stdccv3_setup(context, NULL);
+ }
+
+ if (!err) {
+ newCache = (krb5_ccache) malloc (sizeof (*newCache));
+ if (!newCache) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data));
+ if (!ccapi_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ err = cc_context_create_new_ccache (gCntrlBlock, cc_credentials_v5, "",
+ &ccache);
+ }
+
+ if (!err) {
+ err = stdccv3_set_timeoffset (context, ccache);
+ }
+
+ if (!err) {
+ err = cc_ccache_get_name (ccache, &ccstring);
+ }
+
+ if (!err) {
+ name = strdup (ccstring->data);
+ if (!name) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ ccapi_data->cache_name = name;
+ name = NULL; /* take ownership */
+
+ ccapi_data->NamedCache = ccache;
+ ccache = NULL; /* take ownership */
+
+ newCache->ops = &krb5_cc_stdcc_ops;
+ newCache->data = ccapi_data;
+ ccapi_data = NULL; /* take ownership */
+
+ /* return a pointer to the new cache */
+ *id = newCache;
+ newCache = NULL;
+ }
+
+ if (ccstring) { cc_string_release (ccstring); }
+ if (name) { free (name); }
+ if (ccache) { cc_ccache_release (ccache); }
+ if (ccapi_data) { free (ccapi_data); }
+ if (newCache) { free (newCache); }
+
+ return cc_err_xlate (err);
+}
+
+/*
+ * resolve
+ *
+ * create a new cache with the name stored in residual
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_resolve (krb5_context context, krb5_ccache *id , const char *residual )
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = NULL;
+ krb5_ccache ccache = NULL;
+ char *name = NULL;
+ cc_string_t defname = NULL;
+
+ if (id == NULL) { err = KRB5_CC_NOMEM; }
+
+ if (!err) {
+ err = stdccv3_setup (context, NULL);
+ }
+
+ if (!err) {
+ ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data));
+ if (!ccapi_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ ccache = (krb5_ccache ) malloc (sizeof (*ccache));
+ if (!ccache) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ if ((residual == NULL) || (strlen(residual) == 0)) {
+ err = cc_context_get_default_ccache_name(gCntrlBlock, &defname);
+ if (defname)
+ residual = defname->data;
+ }
+ }
+
+ if (!err) {
+ name = strdup (residual);
+ if (!name) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ err = cc_context_open_ccache (gCntrlBlock, residual,
+ &ccapi_data->NamedCache);
+ if (err == ccErrCCacheNotFound) {
+ ccapi_data->NamedCache = NULL;
+ err = 0; /* ccache just doesn't exist yet */
+ }
+ }
+
+ if (!err) {
+ ccapi_data->cache_name = name;
+ name = NULL; /* take ownership */
+
+ ccache->ops = &krb5_cc_stdcc_ops;
+ ccache->data = ccapi_data;
+ ccapi_data = NULL; /* take ownership */
+
+ *id = ccache;
+ ccache = NULL; /* take ownership */
+ }
+
+ if (ccache) { free (ccache); }
+ if (ccapi_data) { free (ccapi_data); }
+ if (name) { free (name); }
+ if (defname) { cc_string_release(defname); }
+
+ return cc_err_xlate (err);
+}
+
+/*
+ * initialize
+ *
+ * initialize the cache, check to see if one already exists for this
+ * principal if not set our principal to this principal. This
+ * searching enables ticket sharing
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_initialize (krb5_context context,
+ krb5_ccache id,
+ krb5_principal princ)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ char *name = NULL;
+ cc_ccache_t ccache = NULL;
+
+ if (id == NULL) { err = KRB5_CC_NOMEM; }
+
+ if (!err) {
+ err = stdccv3_setup (context, NULL);
+ }
+
+ if (!err) {
+ err = krb5_unparse_name(context, princ, &name);
+ }
+
+ if (!err) {
+ err = cc_context_create_ccache (gCntrlBlock, ccapi_data->cache_name,
+ cc_credentials_v5, name,
+ &ccache);
+ }
+
+ if (!err) {
+ err = stdccv3_set_timeoffset (context, ccache);
+ }
+
+ if (!err) {
+ if (ccapi_data->NamedCache) {
+ err = cc_ccache_release (ccapi_data->NamedCache);
+ }
+ ccapi_data->NamedCache = ccache;
+ ccache = NULL; /* take ownership */
+ cache_changed ();
+ }
+
+ if (ccache) { cc_ccache_release (ccache); }
+ if (name ) { krb5_free_unparsed_name(context, name); }
+
+ return cc_err_xlate(err);
+}
+
+/*
+ * store
+ *
+ * store some credentials in our cache
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_store (krb5_context context, krb5_ccache id, krb5_creds *creds )
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_credentials_union *cred_union = NULL;
+
+ if (!err) {
+ err = stdccv3_setup (context, ccapi_data);
+ }
+
+ if (!err) {
+ /* copy the fields from the almost identical structures */
+ err = copy_krb5_creds_to_cc_cred_union (context, creds, &cred_union);
+ }
+
+ if (!err) {
+ err = cc_ccache_store_credentials (ccapi_data->NamedCache, cred_union);
+ }
+
+ if (!err) {
+ cache_changed();
+ }
+
+ if (cred_union) { cred_union_release (cred_union); }
+
+ return cc_err_xlate (err);
+}
+
+/*
+ * start_seq_get
+ *
+ * begin an iterator call to get all of the credentials in the cache
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_start_seq_get (krb5_context context,
+ krb5_ccache id,
+ krb5_cc_cursor *cursor )
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_credentials_iterator_t iterator = NULL;
+
+ if (!err) {
+ err = stdccv3_setup (context, ccapi_data);
+ }
+
+ if (!err) {
+ err = cc_ccache_new_credentials_iterator(ccapi_data->NamedCache,
+ &iterator);
+ }
+
+ if (!err) {
+ *cursor = iterator;
+ }
+
+ return cc_err_xlate (err);
+}
+
+/*
+ * next cred
+ *
+ * - get the next credential in the cache as part of an iterator call
+ * - this maps to call to cc_seq_fetch_creds
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_next_cred (krb5_context context,
+ krb5_ccache id,
+ krb5_cc_cursor *cursor,
+ krb5_creds *creds)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_credentials_t credentials = NULL;
+ cc_credentials_iterator_t iterator = *cursor;
+
+ if (!iterator) { err = KRB5_CC_END; }
+
+ if (!err) {
+ err = stdccv3_setup (context, ccapi_data);
+ }
+
+ /* Note: CCAPI v3 ccaches can contain both v4 and v5 creds */
+ while (!err) {
+ err = cc_credentials_iterator_next (iterator, &credentials);
+
+ if (!err && (credentials->data->version == cc_credentials_v5)) {
+ copy_cc_cred_union_to_krb5_creds(context, credentials->data, creds);
+ break;
+ }
+ }
+
+ if (credentials) { cc_credentials_release (credentials); }
+ if (err == ccIteratorEnd) {
+ cc_credentials_iterator_release (iterator);
+ *cursor = 0;
+ }
+
+ return cc_err_xlate (err);
+}
+
+
+/*
+ * retrieve
+ *
+ * - try to find a matching credential in the cache
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_retrieve (krb5_context context,
+ krb5_ccache id,
+ krb5_flags whichfields,
+ krb5_creds *mcreds,
+ krb5_creds *creds)
+{
+ return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+}
+
+/*
+ * end seq
+ *
+ * just free up the storage assoicated with the cursor (if we can)
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_end_seq_get (krb5_context context,
+ krb5_ccache id,
+ krb5_cc_cursor *cursor)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_credentials_iterator_t iterator = *cursor;
+
+ if (!iterator) { return 0; }
+
+ if (!err) {
+ err = stdccv3_setup (context, ccapi_data);
+ }
+
+ if (!err) {
+ err = cc_credentials_iterator_release(iterator);
+ }
+
+ return cc_err_xlate(err);
+}
+
+/*
+ * close
+ *
+ * - free our pointers to the NC
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_close(krb5_context context,
+ krb5_ccache id)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if (!err) {
+ err = stdccv3_setup (context, NULL);
+ }
+
+ if (!err) {
+ if (ccapi_data) {
+ if (ccapi_data->cache_name) {
+ free (ccapi_data->cache_name);
+ }
+ if (ccapi_data->NamedCache) {
+ err = cc_ccache_release (ccapi_data->NamedCache);
+ }
+ free (ccapi_data);
+ id->data = NULL;
+ }
+ free (id);
+ }
+
+ return cc_err_xlate(err);
+}
+
+/*
+ * destroy
+ *
+ * - free our storage and the cache
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_destroy (krb5_context context,
+ krb5_ccache id)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if (!err) {
+ err = stdccv3_setup(context, ccapi_data);
+ }
+
+ if (!err) {
+ if (ccapi_data) {
+ if (ccapi_data->cache_name) {
+ free(ccapi_data->cache_name);
+ }
+ if (ccapi_data->NamedCache) {
+ /* destroy the named cache */
+ err = cc_ccache_destroy(ccapi_data->NamedCache);
+ if (err == ccErrCCacheNotFound) {
+ err = 0; /* ccache maybe already destroyed */
+ }
+ cache_changed();
+ }
+ free(ccapi_data);
+ id->data = NULL;
+ }
+ free(id);
+ }
+
+ return cc_err_xlate(err);
+}
+
+/*
+ * getname
+ *
+ * - return the name of the named cache
+ */
+const char * KRB5_CALLCONV
+krb5_stdccv3_get_name (krb5_context context,
+ krb5_ccache id )
+{
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if (!ccapi_data) {
+ return NULL;
+ } else {
+ return (ccapi_data->cache_name);
+ }
+}
+
+
+/* get_principal
+ *
+ * - return the principal associated with the named cache
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_get_principal (krb5_context context,
+ krb5_ccache id ,
+ krb5_principal *princ)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_string_t name = NULL;
+
+ if (!err) {
+ err = stdccv3_setup(context, ccapi_data);
+ }
+
+ if (!err) {
+ err = cc_ccache_get_principal (ccapi_data->NamedCache, cc_credentials_v5, &name);
+ }
+
+ if (!err) {
+ err = krb5_parse_name (context, name->data, princ);
+ } else {
+ err = cc_err_xlate (err);
+ }
+
+ if (name) { cc_string_release (name); }
+
+ return err;
+}
+
+/*
+ * set_flags
+ *
+ * - currently a NOP since we don't store any flags in the NC
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_set_flags (krb5_context context,
+ krb5_ccache id,
+ krb5_flags flags)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ err = stdccv3_setup (context, ccapi_data);
+
+ return cc_err_xlate (err);
+}
+
+/*
+ * get_flags
+ *
+ * - currently a NOP since we don't store any flags in the NC
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_get_flags (krb5_context context,
+ krb5_ccache id,
+ krb5_flags *flags)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ err = stdccv3_setup (context, ccapi_data);
+
+ return cc_err_xlate (err);
+}
+
+/*
+ * remove
+ *
+ * - remove the specified credentials from the NC
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_remove (krb5_context context,
+ krb5_ccache id,
+ krb5_flags whichfields,
+ krb5_creds *in_creds)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_credentials_iterator_t iterator = NULL;
+ int found = 0;
+
+ if (!err) {
+ err = stdccv3_setup(context, ccapi_data);
+ }
+
+
+ if (!err) {
+ err = cc_ccache_new_credentials_iterator(ccapi_data->NamedCache,
+ &iterator);
+ }
+
+ /* Note: CCAPI v3 ccaches can contain both v4 and v5 creds */
+ while (!err && !found) {
+ cc_credentials_t credentials = NULL;
+
+ err = cc_credentials_iterator_next (iterator, &credentials);
+
+ if (!err && (credentials->data->version == cc_credentials_v5)) {
+ krb5_creds creds;
+
+ err = copy_cc_cred_union_to_krb5_creds(context,
+ credentials->data, &creds);
+
+ if (!err) {
+ found = krb5int_cc_creds_match_request(context,
+ whichfields,
+ in_creds,
+ &creds);
+ krb5_free_cred_contents (context, &creds);
+ }
+
+ if (!err && found) {
+ err = cc_ccache_remove_credentials (ccapi_data->NamedCache, credentials);
+ }
+ }
+
+ if (credentials) { cc_credentials_release (credentials); }
+ }
+ if (err == ccIteratorEnd) { err = ccErrCredentialsNotFound; }
+
+ if (iterator) {
+ err = cc_credentials_iterator_release(iterator);
+ }
+
+ if (!err) {
+ cache_changed ();
+ }
+
+ return cc_err_xlate (err);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_ptcursor_new(krb5_context context,
+ krb5_cc_ptcursor *cursor)
+{
+ krb5_error_code err = 0;
+ krb5_cc_ptcursor ptcursor = NULL;
+ cc_ccache_iterator_t iterator = NULL;
+
+ ptcursor = malloc(sizeof(*ptcursor));
+ if (ptcursor == NULL) {
+ err = ccErrNoMem;
+ }
+ else {
+ memset(ptcursor, 0, sizeof(*ptcursor));
+ }
+
+ if (!err) {
+ err = stdccv3_setup(context, NULL);
+ }
+ if (!err) {
+ ptcursor->ops = &krb5_cc_stdcc_ops;
+ err = cc_context_new_ccache_iterator(gCntrlBlock, &iterator);
+ }
+
+ if (!err) {
+ ptcursor->data = iterator;
+ }
+
+ if (err) {
+ if (ptcursor) { krb5_stdccv3_ptcursor_free(context, &ptcursor); }
+ // krb5_stdccv3_ptcursor_free sets ptcursor to NULL for us
+ }
+
+ *cursor = ptcursor;
+
+ return cc_err_xlate(err);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_ptcursor_next(
+ krb5_context context,
+ krb5_cc_ptcursor cursor,
+ krb5_ccache *ccache)
+{
+ krb5_error_code err = 0;
+ cc_ccache_iterator_t iterator = NULL;
+
+ krb5_ccache newCache = NULL;
+ stdccCacheDataPtr ccapi_data = NULL;
+ cc_ccache_t ccCache = NULL;
+ cc_string_t ccstring = NULL;
+ char *name = NULL;
+
+ if (!cursor || !cursor->data) {
+ err = ccErrInvalidContext;
+ }
+
+ *ccache = NULL;
+
+ if (!err) {
+ newCache = (krb5_ccache) malloc (sizeof (*newCache));
+ if (!newCache) { err = ccErrNoMem; }
+ }
+
+ if (!err) {
+ ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data));
+ if (!ccapi_data) { err = ccErrNoMem; }
+ }
+
+ if (!err) {
+ iterator = cursor->data;
+ err = cc_ccache_iterator_next(iterator, &ccCache);
+ }
+
+ if (!err) {
+ err = cc_ccache_get_name (ccCache, &ccstring);
+ }
+
+ if (!err) {
+ name = strdup (ccstring->data);
+ if (!name) { err = ccErrNoMem; }
+ }
+
+ if (!err) {
+ ccapi_data->cache_name = name;
+ name = NULL; /* take ownership */
+
+ ccapi_data->NamedCache = ccCache;
+ ccCache = NULL; /* take ownership */
+
+ newCache->ops = &krb5_cc_stdcc_ops;
+ newCache->data = ccapi_data;
+ ccapi_data = NULL; /* take ownership */
+
+ /* return a pointer to the new cache */
+ *ccache = newCache;
+ newCache = NULL;
+ }
+
+ if (name) { free (name); }
+ if (ccstring) { cc_string_release (ccstring); }
+ if (ccCache) { cc_ccache_release (ccCache); }
+ if (ccapi_data) { free (ccapi_data); }
+ if (newCache) { free (newCache); }
+
+ if (err == ccIteratorEnd) {
+ err = ccNoError;
+ }
+
+ return cc_err_xlate(err);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_stdccv3_ptcursor_free(
+ krb5_context context,
+ krb5_cc_ptcursor *cursor)
+{
+ if (*cursor != NULL) {
+ if ((*cursor)->data != NULL) {
+ cc_ccache_iterator_release((cc_ccache_iterator_t)((*cursor)->data));
+ }
+ free(*cursor);
+ *cursor = NULL;
+ }
+ return 0;
+}
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_last_change_time
+(krb5_context context, krb5_ccache id,
+ krb5_timestamp *change_time)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cc_time_t ccapi_change_time = 0;
+
+ *change_time = 0;
+
+ if (!err) {
+ err = stdccv3_setup(context, ccapi_data);
+ }
+ if (!err) {
+ err = cc_ccache_get_change_time (ccapi_data->NamedCache, &ccapi_change_time);
+ }
+ if (!err) {
+ *change_time = ccapi_change_time;
+ }
+
+ return cc_err_xlate (err);
+}
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_lock
+(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if (!err) {
+ err = stdccv3_setup(context, ccapi_data);
+ }
+ if (!err) {
+ err = cc_ccache_lock(ccapi_data->NamedCache, cc_lock_write, cc_lock_block);
+ }
+ return cc_err_xlate(err);
+}
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_unlock
+(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code err = 0;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if (!err) {
+ err = stdccv3_setup(context, ccapi_data);
+ }
+ if (!err) {
+ err = cc_ccache_unlock(ccapi_data->NamedCache);
+ }
+ return cc_err_xlate(err);
+}
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_lock
+(krb5_context context)
+{
+ krb5_error_code err = 0;
+
+ if (!err && !gCntrlBlock) {
+ err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL);
+ }
+ if (!err) {
+ err = cc_context_lock(gCntrlBlock, cc_lock_write, cc_lock_block);
+ }
+ return cc_err_xlate(err);
+}
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_unlock
+(krb5_context context)
+{
+ krb5_error_code err = 0;
+
+ if (!err && !gCntrlBlock) {
+ err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL);
+ }
+ if (!err) {
+ err = cc_context_unlock(gCntrlBlock);
+ }
+ return cc_err_xlate(err);
+}
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_switch_to
+(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = id->data;
+ int err;
+
+ retval = stdccv3_setup(context, ccapi_data);
+ if (retval)
+ return cc_err_xlate(retval);
+
+ err = cc_ccache_set_default(ccapi_data->NamedCache);
+ return cc_err_xlate(err);
+}
+
+#else /* !USE_CCAPI_V3 */
+
+static krb5_error_code stdcc_setup(krb5_context context,
+ stdccCacheDataPtr ccapi_data)
+{
+ int err;
+
+ /* make sure the API has been intialized */
+ if (gCntrlBlock == NULL) {
+#ifdef CC_API_VER2
+ err = cc_initialize(&gCntrlBlock, CC_API_VER_2, NULL, NULL);
+#else
+ err = cc_initialize(&gCntrlBlock, CC_API_VER_1, NULL, NULL);
+#endif
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+ }
+
+ /*
+ * No ccapi_data structure, so we don't need to make sure the
+ * ccache exists.
+ */
+ if (!ccapi_data)
+ return 0;
+
+ /*
+ * The ccache already exists
+ */
+ if (ccapi_data->NamedCache)
+ return 0;
+
+ err = cc_open(gCntrlBlock, ccapi_data->cache_name,
+ CC_CRED_V5, 0L, &ccapi_data->NamedCache);
+ if (err == CC_NOTFOUND)
+ err = CC_NO_EXIST;
+ if (err == CC_NOERROR)
+ return 0;
+
+ ccapi_data->NamedCache = NULL;
+ return cc_err_xlate(err);
+}
+
+void krb5_stdcc_shutdown()
+{
+ if (gCntrlBlock)
+ cc_shutdown(&gCntrlBlock);
+ gCntrlBlock = NULL;
+}
+
+/*
+ * -- generate_new --------------------------------
+ *
+ * create a new cache with a unique name, corresponds to creating a
+ * named cache iniitialize the API here if we have to.
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_generate_new
+(krb5_context context, krb5_ccache *id )
+{
+ krb5_ccache newCache = NULL;
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = NULL;
+ char *name = NULL;
+ cc_time_t change_time;
+ int err;
+
+ if ((retval = stdcc_setup(context, NULL)))
+ return retval;
+
+ retval = KRB5_CC_NOMEM;
+ if (!(newCache = (krb5_ccache) malloc(sizeof(struct _krb5_ccache))))
+ goto errout;
+ if (!(ccapi_data = (stdccCacheDataPtr)malloc(sizeof(stdccCacheData))))
+ goto errout;
+ if (!(name = malloc(256)))
+ goto errout;
+
+ /* create a unique name */
+ cc_get_change_time(gCntrlBlock, &change_time);
+ snprintf(name, 256, "gen_new_cache%d", change_time);
+
+ /* create the new cache */
+ err = cc_create(gCntrlBlock, name, name, CC_CRED_V5, 0L,
+ &ccapi_data->NamedCache);
+ if (err != CC_NOERROR) {
+ retval = cc_err_xlate(err);
+ goto errout;
+ }
+
+ /* setup some fields */
+ newCache->ops = &krb5_cc_stdcc_ops;
+ newCache->data = ccapi_data;
+ ccapi_data->cache_name = name;
+
+ /* return a pointer to the new cache */
+ *id = newCache;
+
+ return 0;
+
+errout:
+ if (newCache)
+ free(newCache);
+ if (ccapi_data)
+ free(ccapi_data);
+ if (name)
+ free(name);
+ return retval;
+}
+
+/*
+ * resolve
+ *
+ * create a new cache with the name stored in residual
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_resolve
+(krb5_context context, krb5_ccache *id , const char *residual )
+{
+ krb5_ccache newCache = NULL;
+ stdccCacheDataPtr ccapi_data = NULL;
+ int err;
+ krb5_error_code retval;
+ char *cName = NULL;
+
+ if ((retval = stdcc_setup(context, NULL)))
+ return retval;
+
+ retval = KRB5_CC_NOMEM;
+ if (!(newCache = (krb5_ccache) malloc(sizeof(struct _krb5_ccache))))
+ goto errout;
+
+ if (!(ccapi_data = (stdccCacheDataPtr)malloc(sizeof(stdccCacheData))))
+ goto errout;
+
+ if (!(cName = strdup(residual)))
+ goto errout;
+
+ newCache->ops = &krb5_cc_stdcc_ops;
+ newCache->data = ccapi_data;
+ ccapi_data->cache_name = cName;
+
+ err = cc_open(gCntrlBlock, cName, CC_CRED_V5, 0L,
+ &ccapi_data->NamedCache);
+ if (err != CC_NOERROR) {
+ ccapi_data->NamedCache = NULL;
+ if (err != CC_NO_EXIST) {
+ retval = cc_err_xlate(err);
+ goto errout;
+ }
+ }
+
+ /* return new cache structure */
+ *id = newCache;
+
+ return 0;
+
+errout:
+ if (newCache)
+ free(newCache);
+ if (ccapi_data)
+ free(ccapi_data);
+ if (cName)
+ free(cName);
+ return retval;
+}
+
+/*
+ * initialize
+ *
+ * initialize the cache, check to see if one already exists for this
+ * principal if not set our principal to this principal. This
+ * searching enables ticket sharing
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_initialize
+(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ stdccCacheDataPtr ccapi_data = NULL;
+ int err;
+ char *cName = NULL;
+ krb5_error_code retval;
+
+ if ((retval = stdcc_setup(context, NULL)))
+ return retval;
+
+ /* test id for null */
+ if (id == NULL) return KRB5_CC_NOMEM;
+
+ if ((retval = krb5_unparse_name(context, princ, &cName)))
+ return retval;
+
+ ccapi_data = id->data;
+
+
+ if (ccapi_data->NamedCache)
+ cc_close(gCntrlBlock, &ccapi_data->NamedCache);
+
+ err = cc_create(gCntrlBlock, ccapi_data->cache_name, cName,
+ CC_CRED_V5, 0L, &ccapi_data->NamedCache);
+ if (err != CC_NOERROR) {
+ krb5_free_unparsed_name(context, cName);
+ return cc_err_xlate(err);
+ }
+
+#if 0
+ /*
+ * Some implementations don't set the principal name
+ * correctly, so we force set it to the correct value.
+ */
+ err = cc_set_principal(gCntrlBlock, ccapi_data->NamedCache,
+ CC_CRED_V5, cName);
+#endif
+ krb5_free_unparsed_name(context, cName);
+ cache_changed();
+
+ return cc_err_xlate(err);
+}
+
+/*
+ * store
+ *
+ * store some credentials in our cache
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_store
+(krb5_context context, krb5_ccache id, krb5_creds *creds )
+{
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = id->data;
+ cred_union *cu = NULL;
+ int err;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+ /* copy the fields from the almost identical structures */
+ dupK5toCC(context, creds, &cu);
+
+ /*
+ * finally store the credential
+ * store will copy (that is duplicate) everything
+ */
+ err = cc_store(gCntrlBlock,
+ ((stdccCacheDataPtr)(id->data))->NamedCache, *cu);
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+
+ /* free the cred union using our local version of cc_free_creds()
+ since we allocated it locally */
+ err = krb5int_free_cc_cred_union(&cu);
+
+ cache_changed();
+ return err;
+}
+
+/*
+ * start_seq_get
+ *
+ * begin an iterator call to get all of the credentials in the cache
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_start_seq_get
+(krb5_context context, krb5_ccache id , krb5_cc_cursor *cursor )
+{
+ stdccCacheDataPtr ccapi_data = id->data;
+ krb5_error_code retval;
+ int err;
+ ccache_cit *iterator;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+#ifdef CC_API_VER2
+ err = cc_seq_fetch_creds_begin(gCntrlBlock, ccapi_data->NamedCache,
+ &iterator);
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+ *cursor = iterator;
+#else
+ /* all we have to do is initialize the cursor */
+ *cursor = NULL;
+#endif
+ return 0;
+}
+
+/*
+ * next cred
+ *
+ * - get the next credential in the cache as part of an iterator call
+ * - this maps to call to cc_seq_fetch_creds
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_next_cred
+(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds)
+{
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = id->data;
+ int err;
+ cred_union *credU = NULL;
+ ccache_cit *iterator;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+#ifdef CC_API_VER2
+ iterator = *cursor;
+ if (iterator == 0)
+ return KRB5_CC_END;
+ err = cc_seq_fetch_creds_next(gCntrlBlock, &credU, iterator);
+
+ if (err == CC_END) {
+ cc_seq_fetch_creds_end(gCntrlBlock, &iterator);
+ *cursor = 0;
+ }
+#else
+ err = cc_seq_fetch_creds(gCntrlBlock, ccapi_data->NamedCache,
+ &credU, (ccache_cit **)cursor);
+#endif
+
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+
+ /* copy data (with translation) */
+ dupCCtoK5(context, credU->cred.pV5Cred, creds);
+
+ /* free our version of the cred - okay to use cc_free_creds() here
+ because we got it from the CCache library */
+ cc_free_creds(gCntrlBlock, &credU);
+
+ return 0;
+}
+
+
+/*
+ * retreive
+ *
+ * - try to find a matching credential in the cache
+ */
+#if 0
+krb5_error_code KRB5_CALLCONV krb5_stdcc_retrieve
+(krb5_context context,
+ krb5_ccache id,
+ krb5_flags whichfields,
+ krb5_creds *mcreds,
+ krb5_creds *creds )
+{
+ krb5_error_code retval;
+ krb5_cc_cursor curs = NULL;
+ krb5_creds *fetchcreds;
+
+ if ((retval = stdcc_setup(context, NULL)))
+ return retval;
+
+ fetchcreds = (krb5_creds *)malloc(sizeof(krb5_creds));
+ if (fetchcreds == NULL) return KRB5_CC_NOMEM;
+
+ /* we're going to use the iterators */
+ krb5_stdcc_start_seq_get(context, id, &curs);
+
+ while (!krb5_stdcc_next_cred(context, id, &curs, fetchcreds)) {
+ /*
+ * look at each credential for a match
+ * use this match routine since it takes the
+ * whichfields and the API doesn't
+ */
+ if (stdccCredsMatch(context, fetchcreds,
+ mcreds, whichfields)) {
+ /* we found it, copy and exit */
+ *creds = *fetchcreds;
+ krb5_stdcc_end_seq_get(context, id, &curs);
+ return 0;
+ }
+ /* free copy allocated by next_cred */
+ krb5_free_cred_contents(context, fetchcreds);
+ }
+
+ /* no luck, end get and exit */
+ krb5_stdcc_end_seq_get(context, id, &curs);
+
+ /* we're not using this anymore so we should get rid of it! */
+ free(fetchcreds);
+
+ return KRB5_CC_NOTFOUND;
+}
+#else
+
+krb5_error_code KRB5_CALLCONV
+krb5_stdcc_retrieve(context, id, whichfields, mcreds, creds)
+ krb5_context context;
+ krb5_ccache id;
+ krb5_flags whichfields;
+ krb5_creds *mcreds;
+ krb5_creds *creds;
+{
+ return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+}
+
+#endif
+
+/*
+ * end seq
+ *
+ * just free up the storage assoicated with the cursor (if we could)
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_end_seq_get
+(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = NULL;
+ int err;
+#ifndef CC_API_VER2
+ cred_union *credU = NULL;
+#endif
+
+ ccapi_data = id->data;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+ if (*cursor == NULL)
+ return 0;
+
+#ifdef CC_API_VER2
+ err = cc_seq_fetch_creds_end(gCntrlBlock, (ccache_cit **)cursor);
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+#else
+ /*
+ * Finish calling cc_seq_fetch_creds to clear out the cursor
+ */
+ while (*cursor) {
+ err = cc_seq_fetch_creds(gCntrlBlock, ccapi_data->NamedCache,
+ &credU, (ccache_cit **)cursor);
+ if (err)
+ break;
+
+ /* okay to call cc_free_creds() here because we got credU from CCache lib */
+ cc_free_creds(gCntrlBlock, &credU);
+ }
+#endif
+
+ return(0);
+}
+
+/*
+ * close
+ *
+ * - free our pointers to the NC
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdcc_close(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if ((retval = stdcc_setup(context, NULL)))
+ return retval;
+
+ /* free it */
+
+ if (ccapi_data) {
+ if (ccapi_data->cache_name)
+ free(ccapi_data->cache_name);
+ if (ccapi_data->NamedCache)
+ cc_close(gCntrlBlock, &ccapi_data->NamedCache);
+ free(ccapi_data);
+ id->data = NULL;
+ }
+ free(id);
+
+ return 0;
+}
+
+/*
+ * destroy
+ *
+ * - free our storage and the cache
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_stdcc_destroy (krb5_context context, krb5_ccache id)
+{
+ int err;
+ krb5_error_code retval;
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if ((retval = stdcc_setup(context, ccapi_data))) {
+ return retval;
+ }
+
+ /* free memory associated with the krb5_ccache */
+ if (ccapi_data) {
+ if (ccapi_data->cache_name)
+ free(ccapi_data->cache_name);
+ if (ccapi_data->NamedCache) {
+ /* destroy the named cache */
+ err = cc_destroy(gCntrlBlock, &ccapi_data->NamedCache);
+ retval = cc_err_xlate(err);
+ cache_changed();
+ }
+ free(ccapi_data);
+ id->data = NULL;
+ }
+ free(id);
+
+ /* If the cache does not exist when we tried to destroy it,
+ that's fine. That means someone else destryoed it since
+ we resolved it. */
+ if (retval == KRB5_FCC_NOFILE)
+ return 0;
+ return retval;
+}
+
+/*
+ * getname
+ *
+ * - return the name of the named cache
+ */
+const char * KRB5_CALLCONV krb5_stdcc_get_name
+(krb5_context context, krb5_ccache id )
+{
+ stdccCacheDataPtr ccapi_data = id->data;
+
+ if (!ccapi_data)
+ return 0;
+
+ return (ccapi_data->cache_name);
+}
+
+
+/* get_principal
+ *
+ * - return the principal associated with the named cache
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_get_principal
+(krb5_context context, krb5_ccache id , krb5_principal *princ)
+{
+ int err;
+ char *name = NULL;
+ stdccCacheDataPtr ccapi_data = id->data;
+ krb5_error_code retval;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+ /* another wrapper */
+ err = cc_get_principal(gCntrlBlock, ccapi_data->NamedCache,
+ &name);
+
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+
+ /* turn it into a krb principal */
+ err = krb5_parse_name(context, name, princ);
+
+ cc_free_principal(gCntrlBlock, &name);
+
+ return err;
+}
+
+/*
+ * set_flags
+ *
+ * - currently a NOP since we don't store any flags in the NC
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_set_flags
+(krb5_context context, krb5_ccache id , krb5_flags flags)
+{
+ stdccCacheDataPtr ccapi_data = id->data;
+ krb5_error_code retval;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+ return 0;
+}
+
+/*
+ * get_flags
+ *
+ * - currently a NOP since we don't store any flags in the NC
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_get_flags
+(krb5_context context, krb5_ccache id , krb5_flags *flags)
+{
+ stdccCacheDataPtr ccapi_data = id->data;
+ krb5_error_code retval;
+
+ if ((retval = stdcc_setup(context, ccapi_data)))
+ return retval;
+
+ return 0;
+}
+
+/*
+ * remove
+ *
+ * - remove the specified credentials from the NC
+ */
+krb5_error_code KRB5_CALLCONV krb5_stdcc_remove
+(krb5_context context, krb5_ccache id,
+ krb5_flags flags, krb5_creds *creds)
+{
+ cred_union *cu = NULL;
+ int err;
+ stdccCacheDataPtr ccapi_data = id->data;
+ krb5_error_code retval;
+
+ if ((retval = stdcc_setup(context, ccapi_data))) {
+ if (retval == KRB5_FCC_NOFILE)
+ return 0;
+ return retval;
+ }
+
+ /* convert to a cred union */
+ dupK5toCC(context, creds, &cu);
+
+ /* remove it */
+ err = cc_remove_cred(gCntrlBlock, ccapi_data->NamedCache, *cu);
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+
+ /* free the cred union using our local version of cc_free_creds()
+ since we allocated it locally */
+ err = krb5int_free_cc_cred_union(&cu);
+ cache_changed();
+ if (err != CC_NOERROR)
+ return cc_err_xlate(err);
+
+ return 0;
+}
+#endif /* !USE_CCAPI_V3 */
+
+#endif /* defined(_WIN32) || defined(USE_CCAPI) */
diff --git a/src/lib/krb5/ccache/ccapi/stdcc.h b/src/lib/krb5/ccache/ccapi/stdcc.h
new file mode 100644
index 0000000000000..1955b4fb4b66c
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/stdcc.h
@@ -0,0 +1,180 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#ifndef __KRB5_STDCC_H__
+#define __KRB5_STDCC_H__
+
+#if defined(_WIN32) || defined(USE_CCAPI)
+
+#include "k5-int.h" /* loads krb5.h */
+#include "../cc-int.h"
+
+#ifdef USE_CCAPI_V3
+#include <CredentialsCache.h>
+#else
+#if defined(_WIN32)
+#include "cacheapi.h"
+#else
+#include <CredentialsCache2.h>
+#endif
+#endif
+
+#define kStringLiteralLen 255
+
+/* globals to be exported */
+extern krb5_cc_ops krb5_cc_stdcc_ops;
+
+/*
+ * structure to stash in the cache's data field
+ */
+typedef struct _stdccCacheData {
+ char *cache_name;
+#ifdef USE_CCAPI_V3
+ cc_ccache_t NamedCache;
+#else
+ ccache_p *NamedCache;
+#endif
+} stdccCacheData, *stdccCacheDataPtr;
+
+
+/* function protoypes */
+
+void krb5_stdcc_shutdown(void);
+
+#ifdef USE_CCAPI_V3
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_close
+(krb5_context, krb5_ccache id );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_destroy
+(krb5_context, krb5_ccache id );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_end_seq_get
+(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_generate_new
+(krb5_context, krb5_ccache *id );
+
+const char * KRB5_CALLCONV krb5_stdccv3_get_name
+(krb5_context, krb5_ccache id );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_get_principal
+(krb5_context, krb5_ccache id , krb5_principal *princ );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_initialize
+(krb5_context, krb5_ccache id , krb5_principal princ );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_next_cred
+(krb5_context,
+ krb5_ccache id ,
+ krb5_cc_cursor *cursor ,
+ krb5_creds *creds );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_resolve
+(krb5_context, krb5_ccache *id , const char *residual );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_retrieve
+(krb5_context,
+ krb5_ccache id ,
+ krb5_flags whichfields ,
+ krb5_creds *mcreds ,
+ krb5_creds *creds );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_start_seq_get
+(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_store
+(krb5_context, krb5_ccache id , krb5_creds *creds );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_set_flags
+(krb5_context, krb5_ccache id , krb5_flags flags );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_get_flags
+(krb5_context, krb5_ccache id , krb5_flags *flags );
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_remove
+(krb5_context, krb5_ccache id , krb5_flags flags, krb5_creds *creds);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_ptcursor_new
+(krb5_context context, krb5_cc_ptcursor *cursor);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_ptcursor_next
+(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *ccache);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_ptcursor_free
+(krb5_context context, krb5_cc_ptcursor *cursor);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_last_change_time
+(krb5_context context, krb5_ccache id,
+ krb5_timestamp *change_time);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_lock
+(krb5_context, krb5_ccache id);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_unlock
+(krb5_context, krb5_ccache id);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_lock
+(krb5_context context);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_unlock
+(krb5_context context);
+
+krb5_error_code KRB5_CALLCONV krb5_stdccv3_switch_to
+(krb5_context context, krb5_ccache id);
+#else
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_close
+(krb5_context, krb5_ccache id );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_destroy
+(krb5_context, krb5_ccache id );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_end_seq_get
+(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_generate_new
+(krb5_context, krb5_ccache *id );
+
+const char * KRB5_CALLCONV krb5_stdcc_get_name
+(krb5_context, krb5_ccache id );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_get_principal
+(krb5_context, krb5_ccache id , krb5_principal *princ );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_initialize
+(krb5_context, krb5_ccache id , krb5_principal princ );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_next_cred
+(krb5_context,
+ krb5_ccache id ,
+ krb5_cc_cursor *cursor ,
+ krb5_creds *creds );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_resolve
+(krb5_context, krb5_ccache *id , const char *residual );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_retrieve
+(krb5_context,
+ krb5_ccache id ,
+ krb5_flags whichfields ,
+ krb5_creds *mcreds ,
+ krb5_creds *creds );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_start_seq_get
+(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_store
+(krb5_context, krb5_ccache id , krb5_creds *creds );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_set_flags
+(krb5_context, krb5_ccache id , krb5_flags flags );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_get_flags
+(krb5_context, krb5_ccache id , krb5_flags *flags );
+
+krb5_error_code KRB5_CALLCONV krb5_stdcc_remove
+(krb5_context, krb5_ccache id , krb5_flags flags, krb5_creds *creds);
+#endif
+
+#endif /* defined(_WIN32) || defined(USE_CCAPI) */
+
+#endif
diff --git a/src/lib/krb5/ccache/ccapi/stdcc_util.c b/src/lib/krb5/ccache/ccapi/stdcc_util.c
new file mode 100644
index 0000000000000..9f44af3d087c1
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/stdcc_util.c
@@ -0,0 +1,1071 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * stdcc_util.c
+ * utility functions used in implementing the ccache api for krb5
+ * not publicly exported
+ * Frank Dabek, July 1998
+ */
+
+#if defined(_WIN32) || defined(USE_CCAPI)
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#if defined(_WIN32)
+#include <malloc.h>
+#endif
+
+#include "stdcc_util.h"
+#include "krb5.h"
+#ifdef _WIN32 /* it's part of krb5.h everywhere else */
+#include "kv5m_err.h"
+#endif
+
+#define fieldSize 255
+
+#ifdef USE_CCAPI_V3
+
+
+static void
+free_cc_array (cc_data **io_cc_array)
+{
+ if (io_cc_array) {
+ unsigned int i;
+
+ for (i = 0; io_cc_array[i]; i++) {
+ if (io_cc_array[i]->data) { free (io_cc_array[i]->data); }
+ free (io_cc_array[i]);
+ }
+ free (io_cc_array);
+ }
+}
+
+static krb5_error_code
+copy_cc_array_to_addresses (krb5_context in_context,
+ cc_data **in_cc_array,
+ krb5_address ***out_addresses)
+{
+ krb5_error_code err = 0;
+
+ if (in_cc_array == NULL) {
+ *out_addresses = NULL;
+
+ } else {
+ unsigned int count, i;
+ krb5_address **addresses = NULL;
+
+ /* get length of array */
+ for (count = 0; in_cc_array[count]; count++);
+ addresses = (krb5_address **) malloc (sizeof (*addresses) * (count + 1));
+ if (!addresses) { err = KRB5_CC_NOMEM; }
+
+ for (i = 0; !err && i < count; i++) {
+ addresses[i] = (krb5_address *) malloc (sizeof (krb5_address));
+ if (!addresses[i]) { err = KRB5_CC_NOMEM; }
+
+ if (!err) {
+ addresses[i]->contents = (krb5_octet *) malloc (sizeof (krb5_octet) *
+ in_cc_array[i]->length);
+ if (!addresses[i]->contents) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ addresses[i]->magic = KV5M_ADDRESS;
+ addresses[i]->addrtype = in_cc_array[i]->type;
+ addresses[i]->length = in_cc_array[i]->length;
+ memcpy (addresses[i]->contents,
+ in_cc_array[i]->data, in_cc_array[i]->length);
+ }
+ }
+
+ if (!err) {
+ addresses[i] = NULL; /* terminator */
+ *out_addresses = addresses;
+ addresses = NULL;
+ }
+
+ if (addresses) { krb5_free_addresses (in_context, addresses); }
+ }
+
+ return err;
+}
+
+static krb5_error_code
+copy_cc_array_to_authdata (krb5_context in_context,
+ cc_data **in_cc_array,
+ krb5_authdata ***out_authdata)
+{
+ krb5_error_code err = 0;
+
+ if (in_cc_array == NULL) {
+ *out_authdata = NULL;
+
+ } else {
+ unsigned int count, i;
+ krb5_authdata **authdata = NULL;
+
+ /* get length of array */
+ for (count = 0; in_cc_array[count]; count++);
+ authdata = (krb5_authdata **) malloc (sizeof (*authdata) * (count + 1));
+ if (!authdata) { err = KRB5_CC_NOMEM; }
+
+ for (i = 0; !err && i < count; i++) {
+ authdata[i] = (krb5_authdata *) malloc (sizeof (krb5_authdata));
+ if (!authdata[i]) { err = KRB5_CC_NOMEM; }
+
+ if (!err) {
+ authdata[i]->contents = (krb5_octet *) malloc (sizeof (krb5_octet) *
+ in_cc_array[i]->length);
+ if (!authdata[i]->contents) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ authdata[i]->magic = KV5M_AUTHDATA;
+ authdata[i]->ad_type = in_cc_array[i]->type;
+ authdata[i]->length = in_cc_array[i]->length;
+ memcpy (authdata[i]->contents,
+ in_cc_array[i]->data, in_cc_array[i]->length);
+ }
+ }
+
+ if (!err) {
+ authdata[i] = NULL; /* terminator */
+ *out_authdata = authdata;
+ authdata = NULL;
+ }
+
+ if (authdata) { krb5_free_authdata (in_context, authdata); }
+ }
+
+ return err;
+}
+
+static krb5_error_code
+copy_addresses_to_cc_array (krb5_context in_context,
+ krb5_address **in_addresses,
+ cc_data ***out_cc_array)
+{
+ krb5_error_code err = 0;
+
+ if (in_addresses == NULL) {
+ *out_cc_array = NULL;
+
+ } else {
+ unsigned int count, i;
+ cc_data **cc_array = NULL;
+
+ /* get length of array */
+ for (count = 0; in_addresses[count]; count++);
+ cc_array = (cc_data **) malloc (sizeof (*cc_array) * (count + 1));
+ if (!cc_array) { err = KRB5_CC_NOMEM; }
+
+ for (i = 0; !err && i < count; i++) {
+ cc_array[i] = (cc_data *) malloc (sizeof (cc_data));
+ if (!cc_array[i]) { err = KRB5_CC_NOMEM; }
+
+ if (!err) {
+ cc_array[i]->data = malloc (in_addresses[i]->length);
+ if (!cc_array[i]->data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ cc_array[i]->type = in_addresses[i]->addrtype;
+ cc_array[i]->length = in_addresses[i]->length;
+ memcpy (cc_array[i]->data, in_addresses[i]->contents, in_addresses[i]->length);
+ }
+ }
+
+ if (!err) {
+ cc_array[i] = NULL; /* terminator */
+ *out_cc_array = cc_array;
+ cc_array = NULL;
+ }
+
+ if (cc_array) { free_cc_array (cc_array); }
+ }
+
+
+ return err;
+}
+
+static krb5_error_code
+copy_authdata_to_cc_array (krb5_context in_context,
+ krb5_authdata **in_authdata,
+ cc_data ***out_cc_array)
+{
+ krb5_error_code err = 0;
+
+ if (in_authdata == NULL) {
+ *out_cc_array = NULL;
+
+ } else {
+ unsigned int count, i;
+ cc_data **cc_array = NULL;
+
+ /* get length of array */
+ for (count = 0; in_authdata[count]; count++);
+ cc_array = (cc_data **) malloc (sizeof (*cc_array) * (count + 1));
+ if (!cc_array) { err = KRB5_CC_NOMEM; }
+
+ for (i = 0; !err && i < count; i++) {
+ cc_array[i] = (cc_data *) malloc (sizeof (cc_data));
+ if (!cc_array[i]) { err = KRB5_CC_NOMEM; }
+
+ if (!err) {
+ cc_array[i]->data = malloc (in_authdata[i]->length);
+ if (!cc_array[i]->data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ cc_array[i]->type = in_authdata[i]->ad_type;
+ cc_array[i]->length = in_authdata[i]->length;
+ memcpy (cc_array[i]->data, in_authdata[i]->contents, in_authdata[i]->length);
+ }
+ }
+
+ if (!err) {
+ cc_array[i] = NULL; /* terminator */
+ *out_cc_array = cc_array;
+ cc_array = NULL;
+ }
+
+ if (cc_array) { free_cc_array (cc_array); }
+ }
+
+
+ return err;
+}
+
+
+/*
+ * copy_cc_credentials_to_krb5_creds
+ * - allocate an empty k5 style ticket and copy info from the cc_creds ticket
+ */
+
+krb5_error_code
+copy_cc_cred_union_to_krb5_creds (krb5_context in_context,
+ const cc_credentials_union *in_cred_union,
+ krb5_creds *out_creds)
+{
+ krb5_error_code err = 0;
+ cc_credentials_v5_t *cv5 = NULL;
+ krb5_int32 offset_seconds = 0, offset_microseconds = 0;
+ krb5_principal client = NULL;
+ krb5_principal server = NULL;
+ char *ticket_data = NULL;
+ char *second_ticket_data = NULL;
+ unsigned char *keyblock_contents = NULL;
+ krb5_address **addresses = NULL;
+ krb5_authdata **authdata = NULL;
+
+ if (in_cred_union->version != cc_credentials_v5) {
+ err = KRB5_CC_NOT_KTYPE;
+ } else {
+ cv5 = in_cred_union->credentials.credentials_v5;
+ }
+
+#if TARGET_OS_MAC
+ if (!err) {
+ err = krb5_get_time_offsets (in_context, &offset_seconds, &offset_microseconds);
+ }
+#endif
+
+ if (!err) {
+ err = krb5_parse_name (in_context, cv5->client, &client);
+ }
+
+ if (!err) {
+ err = krb5_parse_name (in_context, cv5->server, &server);
+ }
+
+ if (!err && cv5->keyblock.data) {
+ keyblock_contents = (unsigned char *) malloc (cv5->keyblock.length);
+ if (!keyblock_contents) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err && cv5->ticket.data) {
+ ticket_data = (char *) malloc (cv5->ticket.length);
+ if (!ticket_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err && cv5->second_ticket.data) {
+ second_ticket_data = (char *) malloc (cv5->second_ticket.length);
+ if (!second_ticket_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ /* addresses */
+ err = copy_cc_array_to_addresses (in_context, cv5->addresses, &addresses);
+ }
+
+ if (!err) {
+ /* authdata */
+ err = copy_cc_array_to_authdata (in_context, cv5->authdata, &authdata);
+ }
+
+ if (!err) {
+ /* principals */
+ out_creds->client = client;
+ client = NULL;
+ out_creds->server = server;
+ server = NULL;
+
+ /* copy keyblock */
+ if (cv5->keyblock.data) {
+ memcpy (keyblock_contents, cv5->keyblock.data, cv5->keyblock.length);
+ }
+ out_creds->keyblock.enctype = cv5->keyblock.type;
+ out_creds->keyblock.length = cv5->keyblock.length;
+ out_creds->keyblock.contents = keyblock_contents;
+ keyblock_contents = NULL;
+
+ /* copy times */
+ out_creds->times.authtime = cv5->authtime + offset_seconds;
+ out_creds->times.starttime = cv5->starttime + offset_seconds;
+ out_creds->times.endtime = cv5->endtime + offset_seconds;
+ out_creds->times.renew_till = cv5->renew_till + offset_seconds;
+ out_creds->is_skey = cv5->is_skey;
+ out_creds->ticket_flags = cv5->ticket_flags;
+
+ /* first ticket */
+ if (cv5->ticket.data) {
+ memcpy(ticket_data, cv5->ticket.data, cv5->ticket.length);
+ }
+ out_creds->ticket.length = cv5->ticket.length;
+ out_creds->ticket.data = ticket_data;
+ ticket_data = NULL;
+
+ /* second ticket */
+ if (cv5->second_ticket.data) {
+ memcpy(second_ticket_data, cv5->second_ticket.data, cv5->second_ticket.length);
+ }
+ out_creds->second_ticket.length = cv5->second_ticket.length;
+ out_creds->second_ticket.data = second_ticket_data;
+ second_ticket_data = NULL;
+
+ out_creds->addresses = addresses;
+ addresses = NULL;
+
+ out_creds->authdata = authdata;
+ authdata = NULL;
+
+ /* zero out magic number */
+ out_creds->magic = 0;
+ }
+
+ if (addresses) { krb5_free_addresses (in_context, addresses); }
+ if (authdata) { krb5_free_authdata (in_context, authdata); }
+ if (keyblock_contents) { free (keyblock_contents); }
+ if (ticket_data) { free (ticket_data); }
+ if (second_ticket_data) { free (second_ticket_data); }
+ if (client) { krb5_free_principal (in_context, client); }
+ if (server) { krb5_free_principal (in_context, server); }
+
+ return err;
+}
+
+/*
+ * copy_krb5_creds_to_cc_credentials
+ * - analagous to above but in the reverse direction
+ */
+krb5_error_code
+copy_krb5_creds_to_cc_cred_union (krb5_context in_context,
+ krb5_creds *in_creds,
+ cc_credentials_union **out_cred_union)
+{
+ krb5_error_code err = 0;
+ cc_credentials_union *cred_union = NULL;
+ cc_credentials_v5_t *cv5 = NULL;
+ char *client = NULL;
+ char *server = NULL;
+ unsigned char *ticket_data = NULL;
+ unsigned char *second_ticket_data = NULL;
+ unsigned char *keyblock_data = NULL;
+ krb5_int32 offset_seconds = 0, offset_microseconds = 0;
+ cc_data **cc_address_array = NULL;
+ cc_data **cc_authdata_array = NULL;
+
+ if (out_cred_union == NULL) { err = KRB5_CC_NOMEM; }
+
+#if TARGET_OS_MAC
+ if (!err) {
+ err = krb5_get_time_offsets (in_context, &offset_seconds, &offset_microseconds);
+ }
+#endif
+
+ if (!err) {
+ cred_union = (cc_credentials_union *) malloc (sizeof (*cred_union));
+ if (!cred_union) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ cv5 = (cc_credentials_v5_t *) malloc (sizeof (*cv5));
+ if (!cv5) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ err = krb5_unparse_name (in_context, in_creds->client, &client);
+ }
+
+ if (!err) {
+ err = krb5_unparse_name (in_context, in_creds->server, &server);
+ }
+
+ if (!err && in_creds->keyblock.contents) {
+ keyblock_data = (unsigned char *) malloc (in_creds->keyblock.length);
+ if (!keyblock_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err && in_creds->ticket.data) {
+ ticket_data = (unsigned char *) malloc (in_creds->ticket.length);
+ if (!ticket_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err && in_creds->second_ticket.data) {
+ second_ticket_data = (unsigned char *) malloc (in_creds->second_ticket.length);
+ if (!second_ticket_data) { err = KRB5_CC_NOMEM; }
+ }
+
+ if (!err) {
+ err = copy_addresses_to_cc_array (in_context, in_creds->addresses, &cc_address_array);
+ }
+
+ if (!err) {
+ err = copy_authdata_to_cc_array (in_context, in_creds->authdata, &cc_authdata_array);
+ }
+
+ if (!err) {
+ /* principals */
+ cv5->client = client;
+ client = NULL;
+ cv5->server = server;
+ server = NULL;
+
+ /* copy more fields */
+ if (in_creds->keyblock.contents) {
+ memcpy(keyblock_data, in_creds->keyblock.contents, in_creds->keyblock.length);
+ }
+ cv5->keyblock.type = in_creds->keyblock.enctype;
+ cv5->keyblock.length = in_creds->keyblock.length;
+ cv5->keyblock.data = keyblock_data;
+ keyblock_data = NULL;
+
+ cv5->authtime = in_creds->times.authtime - offset_seconds;
+ cv5->starttime = in_creds->times.starttime - offset_seconds;
+ cv5->endtime = in_creds->times.endtime - offset_seconds;
+ cv5->renew_till = in_creds->times.renew_till - offset_seconds;
+ cv5->is_skey = in_creds->is_skey;
+ cv5->ticket_flags = in_creds->ticket_flags;
+
+ if (in_creds->ticket.data) {
+ memcpy (ticket_data, in_creds->ticket.data, in_creds->ticket.length);
+ }
+ cv5->ticket.length = in_creds->ticket.length;
+ cv5->ticket.data = ticket_data;
+ ticket_data = NULL;
+
+ if (in_creds->second_ticket.data) {
+ memcpy (second_ticket_data, in_creds->second_ticket.data, in_creds->second_ticket.length);
+ }
+ cv5->second_ticket.length = in_creds->second_ticket.length;
+ cv5->second_ticket.data = second_ticket_data;
+ second_ticket_data = NULL;
+
+ cv5->addresses = cc_address_array;
+ cc_address_array = NULL;
+
+ cv5->authdata = cc_authdata_array;
+ cc_authdata_array = NULL;
+
+ /* Set up the structures to return to the caller */
+ cred_union->version = cc_credentials_v5;
+ cred_union->credentials.credentials_v5 = cv5;
+ cv5 = NULL;
+
+ *out_cred_union = cred_union;
+ cred_union = NULL;
+ }
+
+ if (cc_address_array) { free_cc_array (cc_address_array); }
+ if (cc_authdata_array) { free_cc_array (cc_authdata_array); }
+ if (keyblock_data) { free (keyblock_data); }
+ if (ticket_data) { free (ticket_data); }
+ if (second_ticket_data) { free (second_ticket_data); }
+ if (client) { krb5_free_unparsed_name (in_context, client); }
+ if (server) { krb5_free_unparsed_name (in_context, server); }
+ if (cv5) { free (cv5); }
+ if (cred_union) { free (cred_union); }
+
+ return err;
+}
+
+krb5_error_code
+cred_union_release (cc_credentials_union *in_cred_union)
+{
+ if (in_cred_union) {
+ if (in_cred_union->version == cc_credentials_v5 &&
+ in_cred_union->credentials.credentials_v5) {
+ cc_credentials_v5_t *cv5 = in_cred_union->credentials.credentials_v5;
+
+ /* should use krb5_free_unparsed_name but we have no context */
+ if (cv5->client) { free (cv5->client); }
+ if (cv5->server) { free (cv5->server); }
+
+ if (cv5->keyblock.data) { free (cv5->keyblock.data); }
+ if (cv5->ticket.data) { free (cv5->ticket.data); }
+ if (cv5->second_ticket.data) { free (cv5->second_ticket.data); }
+
+ free_cc_array (cv5->addresses);
+ free_cc_array (cv5->authdata);
+
+ free (cv5);
+
+ } else if (in_cred_union->version == cc_credentials_v4 &&
+ in_cred_union->credentials.credentials_v4) {
+ free (in_cred_union->credentials.credentials_v4);
+ }
+ free ((cc_credentials_union *) in_cred_union);
+ }
+
+ return 0;
+}
+
+#else /* !USE_CCAPI_V3 */
+/*
+ * CopyCCDataArrayToK5
+ * - copy and translate the null terminated arrays of data records
+ * used in k5 tickets
+ */
+int copyCCDataArrayToK5(cc_creds *ccCreds, krb5_creds *v5Creds, char whichArray) {
+
+ if (whichArray == kAddressArray) {
+ if (ccCreds->addresses == NULL) {
+ v5Creds->addresses = NULL;
+ } else {
+
+ krb5_address **addrPtr, *addr;
+ cc_data **dataPtr, *data;
+ unsigned int numRecords = 0;
+
+ /* Allocate the array of pointers: */
+ for (dataPtr = ccCreds->addresses; *dataPtr != NULL; numRecords++, dataPtr++) {}
+
+ v5Creds->addresses = (krb5_address **) malloc (sizeof(krb5_address *) * (numRecords + 1));
+ if (v5Creds->addresses == NULL)
+ return ENOMEM;
+
+ /* Fill in the array, allocating the address structures: */
+ for (dataPtr = ccCreds->addresses, addrPtr = v5Creds->addresses; *dataPtr != NULL; addrPtr++, dataPtr++) {
+
+ *addrPtr = (krb5_address *) malloc (sizeof(krb5_address));
+ if (*addrPtr == NULL)
+ return ENOMEM;
+ data = *dataPtr;
+ addr = *addrPtr;
+
+ addr->addrtype = data->type;
+ addr->magic = KV5M_ADDRESS;
+ addr->length = data->length;
+ addr->contents = (krb5_octet *) malloc (sizeof(krb5_octet) * addr->length);
+ if (addr->contents == NULL)
+ return ENOMEM;
+ memmove(addr->contents, data->data, addr->length); /* copy contents */
+ }
+
+ /* Write terminator: */
+ *addrPtr = NULL;
+ }
+ }
+
+ if (whichArray == kAuthDataArray) {
+ if (ccCreds->authdata == NULL) {
+ v5Creds->authdata = NULL;
+ } else {
+ krb5_authdata **authPtr, *auth;
+ cc_data **dataPtr, *data;
+ unsigned int numRecords = 0;
+
+ /* Allocate the array of pointers: */
+ for (dataPtr = ccCreds->authdata; *dataPtr != NULL; numRecords++, dataPtr++) {}
+
+ v5Creds->authdata = (krb5_authdata **) malloc (sizeof(krb5_authdata *) * (numRecords + 1));
+ if (v5Creds->authdata == NULL)
+ return ENOMEM;
+
+ /* Fill in the array, allocating the address structures: */
+ for (dataPtr = ccCreds->authdata, authPtr = v5Creds->authdata; *dataPtr != NULL; authPtr++, dataPtr++) {
+
+ *authPtr = (krb5_authdata *) malloc (sizeof(krb5_authdata));
+ if (*authPtr == NULL)
+ return ENOMEM;
+ data = *dataPtr;
+ auth = *authPtr;
+
+ auth->ad_type = data->type;
+ auth->magic = KV5M_AUTHDATA;
+ auth->length = data->length;
+ auth->contents = (krb5_octet *) malloc (sizeof(krb5_octet) * auth->length);
+ if (auth->contents == NULL)
+ return ENOMEM;
+ memmove(auth->contents, data->data, auth->length); /* copy contents */
+ }
+
+ /* Write terminator: */
+ *authPtr = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * copyK5DataArrayToCC
+ * - analagous to above, but in the other direction
+ */
+int copyK5DataArrayToCC(krb5_creds *v5Creds, cc_creds *ccCreds, char whichArray)
+{
+ if (whichArray == kAddressArray) {
+ if (v5Creds->addresses == NULL) {
+ ccCreds->addresses = NULL;
+ } else {
+
+ krb5_address **addrPtr, *addr;
+ cc_data **dataPtr, *data;
+ unsigned int numRecords = 0;
+
+ /* Allocate the array of pointers: */
+ for (addrPtr = v5Creds->addresses; *addrPtr != NULL; numRecords++, addrPtr++) {}
+
+ ccCreds->addresses = (cc_data **) malloc (sizeof(cc_data *) * (numRecords + 1));
+ if (ccCreds->addresses == NULL)
+ return ENOMEM;
+
+ /* Fill in the array, allocating the address structures: */
+ for (dataPtr = ccCreds->addresses, addrPtr = v5Creds->addresses; *addrPtr != NULL; addrPtr++, dataPtr++) {
+
+ *dataPtr = (cc_data *) malloc (sizeof(cc_data));
+ if (*dataPtr == NULL)
+ return ENOMEM;
+ data = *dataPtr;
+ addr = *addrPtr;
+
+ data->type = addr->addrtype;
+ data->length = addr->length;
+ data->data = malloc (sizeof(char) * data->length);
+ if (data->data == NULL)
+ return ENOMEM;
+ memmove(data->data, addr->contents, data->length); /* copy contents */
+ }
+
+ /* Write terminator: */
+ *dataPtr = NULL;
+ }
+ }
+
+ if (whichArray == kAuthDataArray) {
+ if (v5Creds->authdata == NULL) {
+ ccCreds->authdata = NULL;
+ } else {
+ krb5_authdata **authPtr, *auth;
+ cc_data **dataPtr, *data;
+ unsigned int numRecords = 0;
+
+ /* Allocate the array of pointers: */
+ for (authPtr = v5Creds->authdata; *authPtr != NULL; numRecords++, authPtr++) {}
+
+ ccCreds->authdata = (cc_data **) malloc (sizeof(cc_data *) * (numRecords + 1));
+ if (ccCreds->authdata == NULL)
+ return ENOMEM;
+
+ /* Fill in the array, allocating the address structures: */
+ for (dataPtr = ccCreds->authdata, authPtr = v5Creds->authdata; *authPtr != NULL; authPtr++, dataPtr++) {
+
+ *dataPtr = (cc_data *) malloc (sizeof(cc_data));
+ if (*dataPtr == NULL)
+ return ENOMEM;
+ data = *dataPtr;
+ auth = *authPtr;
+
+ data->type = auth->ad_type;
+ data->length = auth->length;
+ data->data = malloc (sizeof(char) * data->length);
+ if (data->data == NULL)
+ return ENOMEM;
+ memmove(data->data, auth->contents, data->length); /* copy contents */
+ }
+
+ /* Write terminator: */
+ *dataPtr = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * dupcctok5
+ * - allocate an empty k5 style ticket and copy info from the cc_creds ticket
+ */
+
+void dupCCtoK5(krb5_context context, cc_creds *src, krb5_creds *dest)
+{
+ krb5_int32 offset_seconds = 0, offset_microseconds = 0;
+ int err;
+
+ /*
+ * allocate and copy
+ * copy all of those damn fields back
+ */
+ err = krb5_parse_name(context, src->client, &(dest->client));
+ err = krb5_parse_name(context, src->server, &(dest->server));
+ if (err) return; /* parsename fails w/o krb5.ini for example */
+
+ /* copy keyblock */
+ dest->keyblock.enctype = src->keyblock.type;
+ dest->keyblock.length = src->keyblock.length;
+ dest->keyblock.contents = (krb5_octet *)malloc(dest->keyblock.length);
+ memcpy(dest->keyblock.contents, src->keyblock.data, dest->keyblock.length);
+
+ /* copy times */
+#if TARGET_OS_MAC
+ err = krb5_get_time_offsets(context, &offset_seconds, &offset_microseconds);
+ if (err) return;
+#endif
+ dest->times.authtime = src->authtime + offset_seconds;
+ dest->times.starttime = src->starttime + offset_seconds;
+ dest->times.endtime = src->endtime + offset_seconds;
+ dest->times.renew_till = src->renew_till + offset_seconds;
+ dest->is_skey = src->is_skey;
+ dest->ticket_flags = src->ticket_flags;
+
+ /* more branching fields */
+ err = copyCCDataArrayToK5(src, dest, kAddressArray);
+ if (err) return;
+
+ dest->ticket.length = src->ticket.length;
+ dest->ticket.data = (char *)malloc(src->ticket.length);
+ memcpy(dest->ticket.data, src->ticket.data, src->ticket.length);
+ dest->second_ticket.length = src->second_ticket.length;
+ (dest->second_ticket).data = ( char *)malloc(src->second_ticket.length);
+ memcpy(dest->second_ticket.data, src->second_ticket.data, src->second_ticket.length);
+
+ /* zero out magic number */
+ dest->magic = 0;
+
+ /* authdata */
+ err = copyCCDataArrayToK5(src, dest, kAuthDataArray);
+ if (err) return;
+
+ return;
+}
+
+/*
+ * dupK5toCC
+ * - analagous to above but in the reverse direction
+ */
+void dupK5toCC(krb5_context context, krb5_creds *creds, cred_union **cu)
+{
+ cc_creds *c;
+ int err;
+ krb5_int32 offset_seconds = 0, offset_microseconds = 0;
+
+ if (cu == NULL) return;
+
+ /* allocate the cred_union */
+ *cu = (cred_union *)malloc(sizeof(cred_union));
+ if ((*cu) == NULL)
+ return;
+
+ (*cu)->cred_type = CC_CRED_V5;
+
+ /* allocate creds structure (and install) */
+ c = (cc_creds *)malloc(sizeof(cc_creds));
+ if (c == NULL) return;
+ (*cu)->cred.pV5Cred = c;
+
+ /* convert krb5 principals to flat principals */
+ err = krb5_unparse_name(context, creds->client, &(c->client));
+ err = krb5_unparse_name(context, creds->server, &(c->server));
+ if (err) return;
+
+ /* copy more fields */
+ c->keyblock.type = creds->keyblock.enctype;
+ c->keyblock.length = creds->keyblock.length;
+
+ if (creds->keyblock.contents != NULL) {
+ c->keyblock.data = (unsigned char *)malloc(creds->keyblock.length);
+ memcpy(c->keyblock.data, creds->keyblock.contents, creds->keyblock.length);
+ } else {
+ c->keyblock.data = NULL;
+ }
+
+#if TARGET_OS_MAC
+ err = krb5_get_time_offsets(context, &offset_seconds, &offset_microseconds);
+ if (err) return;
+#endif
+ c->authtime = creds->times.authtime - offset_seconds;
+ c->starttime = creds->times.starttime - offset_seconds;
+ c->endtime = creds->times.endtime - offset_seconds;
+ c->renew_till = creds->times.renew_till - offset_seconds;
+ c->is_skey = creds->is_skey;
+ c->ticket_flags = creds->ticket_flags;
+
+ err = copyK5DataArrayToCC(creds, c, kAddressArray);
+ if (err) return;
+
+ c->ticket.length = creds->ticket.length;
+ if (creds->ticket.data != NULL) {
+ c->ticket.data = (unsigned char *)malloc(creds->ticket.length);
+ memcpy(c->ticket.data, creds->ticket.data, creds->ticket.length);
+ } else {
+ c->ticket.data = NULL;
+ }
+
+ c->second_ticket.length = creds->second_ticket.length;
+ if (creds->second_ticket.data != NULL) {
+ c->second_ticket.data = (unsigned char *)malloc(creds->second_ticket.length);
+ memcpy(c->second_ticket.data, creds->second_ticket.data, creds->second_ticket.length);
+ } else {
+ c->second_ticket.data = NULL;
+ }
+
+ err = copyK5DataArrayToCC(creds, c, kAuthDataArray);
+ if (err) return;
+
+ return;
+}
+
+/* ----- free_cc_cred_union, etc -------------- */
+/*
+ Since the Kerberos5 library allocates a credentials cache structure
+ (in dupK5toCC() above) with its own memory allocation routines - which
+ may be different than how the CCache allocates memory - the Kerb5 library
+ must have its own version of cc_free_creds() to deallocate it. These
+ functions do that. The top-level function to substitue for cc_free_creds()
+ is krb5_free_cc_cred_union().
+
+ If the CCache library wants to use a cred_union structure created by
+ the Kerb5 library, it should make a deep copy of it to "translate" to its
+ own memory allocation space.
+*/
+static void deep_free_cc_data (cc_data data)
+{
+ if (data.data != NULL)
+ free (data.data);
+}
+
+static void deep_free_cc_data_array (cc_data** data) {
+
+ unsigned int i;
+
+ if (data == NULL)
+ return;
+
+ for (i = 0; data [i] != NULL; i++) {
+ deep_free_cc_data (*(data [i]));
+ free (data [i]);
+ }
+
+ free (data);
+}
+
+static void deep_free_cc_v5_creds (cc_creds* creds)
+{
+ if (creds == NULL)
+ return;
+
+ if (creds -> client != NULL)
+ free (creds -> client);
+ if (creds -> server != NULL)
+ free (creds -> server);
+
+ deep_free_cc_data (creds -> keyblock);
+ deep_free_cc_data (creds -> ticket);
+ deep_free_cc_data (creds -> second_ticket);
+
+ deep_free_cc_data_array (creds -> addresses);
+ deep_free_cc_data_array (creds -> authdata);
+
+ free(creds);
+}
+
+static void deep_free_cc_creds (cred_union creds)
+{
+ if (creds.cred_type == CC_CRED_V4) {
+ /* we shouldn't get this, of course */
+ free (creds.cred.pV4Cred);
+ } else if (creds.cred_type == CC_CRED_V5) {
+ deep_free_cc_v5_creds (creds.cred.pV5Cred);
+ }
+}
+
+/* top-level exported function */
+cc_int32 krb5int_free_cc_cred_union (cred_union** creds)
+{
+ if (creds == NULL)
+ return CC_BAD_PARM;
+
+ if (*creds != NULL) {
+ deep_free_cc_creds (**creds);
+ free (*creds);
+ *creds = NULL;
+ }
+
+ return CC_NOERROR;
+}
+#endif
+
+/*
+ * Utility functions...
+ */
+static krb5_boolean
+times_match(t1, t2)
+ register const krb5_ticket_times *t1;
+ register const krb5_ticket_times *t2;
+{
+ if (t1->renew_till) {
+ if (t1->renew_till > t2->renew_till)
+ return FALSE; /* this one expires too late */
+ }
+ if (t1->endtime) {
+ if (t1->endtime > t2->endtime)
+ return FALSE; /* this one expires too late */
+ }
+ /* only care about expiration on a times_match */
+ return TRUE;
+}
+
+static krb5_boolean
+times_match_exact (t1, t2)
+ register const krb5_ticket_times *t1, *t2;
+{
+ return (t1->authtime == t2->authtime
+ && t1->starttime == t2->starttime
+ && t1->endtime == t2->endtime
+ && t1->renew_till == t2->renew_till);
+}
+
+static krb5_boolean
+standard_fields_match(context, mcreds, creds)
+ krb5_context context;
+ register const krb5_creds *mcreds, *creds;
+{
+ return (krb5_principal_compare(context, mcreds->client,creds->client) &&
+ krb5_principal_compare(context, mcreds->server,creds->server));
+}
+
+/* only match the server name portion, not the server realm portion */
+
+static krb5_boolean
+srvname_match(context, mcreds, creds)
+ krb5_context context;
+ register const krb5_creds *mcreds, *creds;
+{
+ krb5_boolean retval;
+ krb5_principal_data p1, p2;
+
+ retval = krb5_principal_compare(context, mcreds->client,creds->client);
+ if (retval != TRUE)
+ return retval;
+ /*
+ * Hack to ignore the server realm for the purposes of the compare.
+ */
+ p1 = *mcreds->server;
+ p2 = *creds->server;
+ p1.realm = p2.realm;
+ return krb5_principal_compare(context, &p1, &p2);
+}
+
+
+static krb5_boolean
+authdata_match(mdata, data)
+ krb5_authdata *const *mdata, *const *data;
+{
+ const krb5_authdata *mdatap, *datap;
+
+ if (mdata == data)
+ return TRUE;
+
+ if (mdata == NULL)
+ return *data == NULL;
+
+ if (data == NULL)
+ return *mdata == NULL;
+
+ while ((mdatap = *mdata)
+ && (datap = *data)
+ && mdatap->ad_type == datap->ad_type
+ && mdatap->length == datap->length
+ && !memcmp ((char *) mdatap->contents, (char *) datap->contents,
+ datap->length)) {
+ mdata++;
+ data++;
+ }
+
+ return !*mdata && !*data;
+}
+
+static krb5_boolean
+data_match(data1, data2)
+ register const krb5_data *data1, *data2;
+{
+ if (!data1) {
+ if (!data2)
+ return TRUE;
+ else
+ return FALSE;
+ }
+ if (!data2) return FALSE;
+
+ if (data1->length != data2->length)
+ return FALSE;
+ else
+ return memcmp(data1->data, data2->data, data1->length) ? FALSE : TRUE;
+}
+
+#define MATCH_SET(bits) (whichfields & bits)
+#define flags_match(a,b) (((a) & (b)) == (a))
+
+/* stdccCredsMatch
+ * - check to see if the creds match based on the whichFields variable
+ * NOTE: if whichfields is zero we are now comparing 'standard fields.'
+ * This is the bug that was killing fetch for a
+ * week. The behaviour is what krb5 expects, however.
+ */
+int stdccCredsMatch(krb5_context context, krb5_creds *base,
+ krb5_creds *match, int whichfields)
+{
+ if (((MATCH_SET(KRB5_TC_MATCH_SRV_NAMEONLY) &&
+ srvname_match(context, match, base)) ||
+ standard_fields_match(context, match, base))
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_IS_SKEY) ||
+ match->is_skey == base->is_skey)
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_FLAGS_EXACT) ||
+ match->ticket_flags == base->ticket_flags)
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_FLAGS) ||
+ flags_match(match->ticket_flags, base->ticket_flags))
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_TIMES_EXACT) ||
+ times_match_exact(&match->times, &base->times))
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_TIMES) ||
+ times_match(&match->times, &base->times))
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_AUTHDATA) ||
+ authdata_match (match->authdata, base->authdata))
+ &&
+ (! MATCH_SET(KRB5_TC_MATCH_2ND_TKT) ||
+ data_match (&match->second_ticket, &base->second_ticket))
+ &&
+ ((! MATCH_SET(KRB5_TC_MATCH_KTYPE))||
+ (match->keyblock.enctype == base->keyblock.enctype))
+ )
+ return TRUE;
+ return FALSE;
+}
+
+#endif /* defined(_WIN32) || defined(USE_CCAPI) */
diff --git a/src/lib/krb5/ccache/ccapi/stdcc_util.h b/src/lib/krb5/ccache/ccapi/stdcc_util.h
new file mode 100644
index 0000000000000..2e5eecc2ba7d2
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/stdcc_util.h
@@ -0,0 +1,49 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* stdcc_util.h
+ *
+ * Frank Dabek, July 1998
+ */
+
+#if defined(_WIN32) || defined(USE_CCAPI)
+
+#include "autoconf.h"
+
+#if USE_CCAPI_V3
+#include <CredentialsCache.h>
+#else
+#if defined(_WIN32)
+#include "cacheapi.h"
+#else
+#include <CredentialsCache2.h>
+#endif
+#endif
+
+#include "krb5.h"
+
+/* protoypes for private functions declared in stdcc_util.c */
+#ifdef USE_CCAPI_V3
+krb5_error_code
+copy_cc_cred_union_to_krb5_creds (krb5_context in_context,
+ const cc_credentials_union *in_cred_union,
+ krb5_creds *out_creds);
+krb5_error_code
+copy_krb5_creds_to_cc_cred_union (krb5_context in_context,
+ krb5_creds *in_creds,
+ cc_credentials_union **out_cred_union);
+
+krb5_error_code
+cred_union_release (cc_credentials_union *in_cred_union);
+#else
+int copyCCDataArrayToK5(cc_creds *cc, krb5_creds *kc, char whichArray);
+int copyK5DataArrayToCC(krb5_creds *kc, cc_creds *cc, char whichArray);
+void dupCCtoK5(krb5_context context, cc_creds *src, krb5_creds *dest);
+void dupK5toCC(krb5_context context, krb5_creds *creds, cred_union **cu);
+cc_int32 krb5int_free_cc_cred_union (cred_union** creds);
+#endif
+int stdccCredsMatch(krb5_context context, krb5_creds *base, krb5_creds *match, int whichfields);
+int bitTst(int var, int mask);
+
+#define kAddressArray 4
+#define kAuthDataArray 5
+
+#endif /* defined(_WIN32) || defined(USE_CCAPI) */
diff --git a/src/lib/krb5/ccache/ccapi/winccld.c b/src/lib/krb5/ccache/ccapi/winccld.c
new file mode 100644
index 0000000000000..8b2e90c42f8c9
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/winccld.c
@@ -0,0 +1,101 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#if defined(_WIN32)
+/*
+ * winccld.c --- routine for dynamically loading the ccache DLL if
+ * it's present.
+ */
+
+#include <windows.h>
+#include <stdio.h>
+#include "k5-int.h"
+#include "stdcc.h"
+
+/* from fcc-proto.h */
+extern const krb5_cc_ops krb5_fcc_ops;
+
+#define KRB5_WINCCLD_C_
+#include "winccld.h"
+
+static int krb5_win_ccdll_loaded = 0;
+
+extern void krb5_win_ccdll_load();
+extern int krb5_is_ccdll_loaded();
+
+/*
+ * return codes
+ */
+#define LF_OK 0
+#define LF_NODLL 1
+#define LF_NOFUNC 2
+
+#ifdef _WIN64
+#define KRBCC_DLL "krbcc64.dll"
+#else
+#define KRBCC_DLL "krbcc32.dll"
+#endif
+
+static int LoadFuncs(const char* dll_name, FUNC_INFO fi[],
+ HINSTANCE* ph, int debug);
+
+static int LoadFuncs(const char* dll_name, FUNC_INFO fi[],
+ HINSTANCE* ph, int debug)
+{
+ HINSTANCE h;
+ int i, n;
+ int error = 0;
+
+ if (ph) *ph = 0;
+
+ for (n = 0; fi[n].func_ptr_var; n++) {
+ *(fi[n].func_ptr_var) = 0;
+ }
+
+ if (!(h = LoadLibrary(dll_name))) {
+ /* Get error for source debugging purposes. */
+ error = (int)GetLastError();
+ return LF_NODLL;
+ }
+
+ if (debug)
+ printf("Loaded %s\n", dll_name);
+ for (i = 0; !error && (i < n); i++) {
+ void* p = (void*)GetProcAddress(h, fi[i].func_name);
+ if (!p) {
+ if (debug)
+ printf("Could not get function: %s\n", fi[i].func_name);
+ error = 1;
+ } else {
+ *(fi[i].func_ptr_var) = p;
+ if (debug)
+ printf("Loaded function %s at 0x%08X\n", fi[i].func_name, p);
+ }
+ }
+ if (error) {
+ for (i = 0; i < n; i++) {
+ *(fi[i].func_ptr_var) = 0;
+ }
+ FreeLibrary(h);
+ return LF_NOFUNC;
+ }
+ if (ph) *ph = h;
+ return LF_OK;
+}
+
+void krb5_win_ccdll_load(context)
+ krb5_context context;
+{
+ krb5_cc_register(context, &krb5_fcc_ops, 0);
+ if (krb5_win_ccdll_loaded)
+ return;
+ if (LoadFuncs(KRBCC_DLL, krbcc_fi, 0, 0))
+ return; /* Error, give up */
+ krb5_win_ccdll_loaded = 1;
+ krb5_cc_dfl_ops = &krb5_cc_stdcc_ops; /* Use stdcc! */
+}
+
+int krb5_is_ccdll_loaded()
+{
+ return krb5_win_ccdll_loaded;
+}
+
+#endif /* Windows */
diff --git a/src/lib/krb5/ccache/ccapi/winccld.h b/src/lib/krb5/ccache/ccapi/winccld.h
new file mode 100644
index 0000000000000..85017abbd0726
--- /dev/null
+++ b/src/lib/krb5/ccache/ccapi/winccld.h
@@ -0,0 +1,204 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * winccld.h -- the dynamic loaded version of the ccache DLL
+ */
+
+
+#ifndef KRB5_WINCCLD_H_
+#define KRB5_WINCCLD_H_
+
+#ifdef USE_CCAPI_V3
+#include <CredentialsCache.h>
+#else
+
+#ifndef CC_API_VER2
+#define CC_API_VER2
+#endif
+
+#include "cacheapi.h"
+#endif
+
+#ifdef USE_CCAPI_V3
+typedef CCACHE_API cc_int32 (*FP_cc_initialize) (
+ cc_context_t* outContext,
+ cc_int32 inVersion,
+ cc_int32* outSupportedVersion,
+ char const** outVendor);
+#else
+typedef cc_int32 (*FP_cc_initialize)(apiCB**, const cc_int32,
+ cc_int32*, const char**);
+typedef cc_int32 (*FP_cc_shutdown)(apiCB**);
+typedef cc_int32 (*FP_cc_get_change_time)(apiCB*, cc_time_t*);
+typedef cc_int32 (*FP_cc_create)(apiCB*, const char*, const char*,
+ const enum cc_cred_vers, const cc_int32, ccache_p**);
+typedef cc_int32 (*FP_cc_open)(apiCB*, const char*, const enum cc_cred_vers,
+ const cc_int32, ccache_p**);
+typedef cc_int32 (*FP_cc_close)(apiCB*, ccache_p**);
+typedef cc_int32 (*FP_cc_destroy)(apiCB*, ccache_p**);
+typedef cc_int32 (*FP_cc_seq_fetch_NCs)(apiCB*, ccache_p**, ccache_cit**);
+typedef cc_int32 (*FP_cc_seq_fetch_NCs_begin)(apiCB*, ccache_cit**);
+typedef cc_int32 (*FP_cc_seq_fetch_NCs_next)(apiCB*, ccache_p**, ccache_cit*);
+typedef cc_int32 (*FP_cc_seq_fetch_NCs_end)(apiCB*, ccache_cit**);
+typedef cc_int32 (*FP_cc_get_NC_info)(apiCB*, struct _infoNC***);
+typedef cc_int32 (*FP_cc_free_NC_info)(apiCB*, struct _infoNC***);
+typedef cc_int32 (*FP_cc_get_name)(apiCB*, const ccache_p*, char**);
+typedef cc_int32 (*FP_cc_set_principal)(apiCB*, const ccache_p*,
+ const enum cc_cred_vers, const char*);
+typedef cc_int32 (*FP_cc_get_principal)(apiCB*, ccache_p*, char**);
+typedef cc_int32 (*FP_cc_get_cred_version)(apiCB*, const ccache_p*,
+ enum cc_cred_vers*);
+typedef cc_int32 (*FP_cc_lock_request)(apiCB*, const ccache_p*,
+ const cc_int32);
+typedef cc_int32 (*FP_cc_store)(apiCB*, const ccache_p*, const cred_union);
+typedef cc_int32 (*FP_cc_remove_cred)(apiCB*, const ccache_p*,
+ const cred_union);
+typedef cc_int32 (*FP_cc_seq_fetch_creds)(apiCB*, const ccache_p*,
+ cred_union**, ccache_cit**);
+typedef cc_int32 (*FP_cc_seq_fetch_creds_begin)(apiCB*, const ccache_p*,
+ ccache_cit**);
+typedef cc_int32 (*FP_cc_seq_fetch_creds_next)(apiCB*, cred_union**,
+ ccache_cit*);
+typedef cc_int32 (*FP_cc_seq_fetch_creds_end)(apiCB*, ccache_cit**);
+typedef cc_int32 (*FP_cc_free_principal)(apiCB*, char**);
+typedef cc_int32 (*FP_cc_free_name)(apiCB*, char** name);
+typedef cc_int32 (*FP_cc_free_creds)(apiCB*, cred_union** pCred);
+#endif
+
+#ifdef KRB5_WINCCLD_C_
+typedef struct _FUNC_INFO {
+ void** func_ptr_var;
+ char* func_name;
+} FUNC_INFO;
+
+#define DECL_FUNC_PTR(x) FP_##x p##x
+#define MAKE_FUNC_INFO(x) { (void**) &p##x, #x }
+#define END_FUNC_INFO { 0, 0 }
+#else
+#define DECL_FUNC_PTR(x) extern FP_##x p##x
+#endif
+
+DECL_FUNC_PTR(cc_initialize);
+#ifndef USE_CCAPI_V3
+DECL_FUNC_PTR(cc_shutdown);
+DECL_FUNC_PTR(cc_get_change_time);
+DECL_FUNC_PTR(cc_create);
+DECL_FUNC_PTR(cc_open);
+DECL_FUNC_PTR(cc_close);
+DECL_FUNC_PTR(cc_destroy);
+#if 0 /* Not used */
+#ifdef CC_API_VER2
+DECL_FUNC_PTR(cc_seq_fetch_NCs_begin);
+DECL_FUNC_PTR(cc_seq_fetch_NCs_next);
+DECL_FUNC_PTR(cc_seq_fetch_NCs_end);
+#else
+DECL_FUNC_PTR(cc_seq_fetch_NCs);
+#endif
+DECL_FUNC_PTR(cc_get_NC_info);
+DECL_FUNC_PTR(cc_free_NC_info);
+#endif
+DECL_FUNC_PTR(cc_get_name);
+DECL_FUNC_PTR(cc_set_principal);
+DECL_FUNC_PTR(cc_get_principal);
+DECL_FUNC_PTR(cc_get_cred_version);
+#if 0 /* Not used */
+DECL_FUNC_PTR(cc_lock_request);
+#endif
+DECL_FUNC_PTR(cc_store);
+DECL_FUNC_PTR(cc_remove_cred);
+#ifdef CC_API_VER2
+DECL_FUNC_PTR(cc_seq_fetch_creds_begin);
+DECL_FUNC_PTR(cc_seq_fetch_creds_next);
+DECL_FUNC_PTR(cc_seq_fetch_creds_end);
+#else
+DECL_FUNC_PTR(cc_seq_fetch_creds);
+#endif
+DECL_FUNC_PTR(cc_free_principal);
+DECL_FUNC_PTR(cc_free_name);
+DECL_FUNC_PTR(cc_free_creds);
+#endif
+
+#ifdef KRB5_WINCCLD_C_
+FUNC_INFO krbcc_fi[] = {
+ MAKE_FUNC_INFO(cc_initialize),
+#ifndef USE_CCAPI_V3
+ MAKE_FUNC_INFO(cc_shutdown),
+ MAKE_FUNC_INFO(cc_get_change_time),
+ MAKE_FUNC_INFO(cc_create),
+ MAKE_FUNC_INFO(cc_open),
+ MAKE_FUNC_INFO(cc_close),
+ MAKE_FUNC_INFO(cc_destroy),
+#if 0 /* Not used */
+ MAKE_FUNC_INFO(cc_seq_fetch_NCs),
+ MAKE_FUNC_INFO(cc_get_NC_info),
+ MAKE_FUNC_INFO(cc_free_NC_info),
+#endif
+ MAKE_FUNC_INFO(cc_get_name),
+ MAKE_FUNC_INFO(cc_set_principal),
+ MAKE_FUNC_INFO(cc_get_principal),
+ MAKE_FUNC_INFO(cc_get_cred_version),
+#if 0 /* Not used */
+ MAKE_FUNC_INFO(cc_lock_request),
+#endif
+ MAKE_FUNC_INFO(cc_store),
+ MAKE_FUNC_INFO(cc_remove_cred),
+#ifdef CC_API_VER2
+ MAKE_FUNC_INFO(cc_seq_fetch_creds_begin),
+ MAKE_FUNC_INFO(cc_seq_fetch_creds_next),
+ MAKE_FUNC_INFO(cc_seq_fetch_creds_end),
+#else
+ MAKE_FUNC_INFO(cc_seq_fetch_creds),
+#endif
+ MAKE_FUNC_INFO(cc_free_principal),
+ MAKE_FUNC_INFO(cc_free_name),
+ MAKE_FUNC_INFO(cc_free_creds),
+#endif
+ END_FUNC_INFO
+};
+#undef MAKE_FUNC_INFO
+#undef END_FUNC_INFO
+#else
+
+#define cc_initialize pcc_initialize
+#ifndef USE_CCAPI_V3
+#define cc_shutdown pcc_shutdown
+#define cc_get_change_time pcc_get_change_time
+#define cc_create pcc_create
+#define cc_open pcc_open
+#define cc_close pcc_close
+#define cc_destroy pcc_destroy
+#if 0 /* Not used */
+#ifdef CC_API_VER2
+#define cc_seq_fetch_NCs_begin pcc_seq_fetch_NCs_begin
+#define cc_seq_fetch_NCs_next pcc_seq_fetch_NCs_next
+#define cc_seq_fetch_NCs_end pcc_seq_fetch_NCs_end
+#else
+#define cc_seq_fetch_NCs pcc_seq_fetch_NCs
+#endif
+#define cc_get_NC_info pcc_get_NC_info
+#define cc_free_NC_info pcc_free_NC_info
+#endif /* End of Not used */
+#define cc_get_name pcc_get_name
+#define cc_set_principal pcc_set_principal
+#define cc_get_principal pcc_get_principal
+#define cc_get_cred_version pcc_get_cred_version
+#if 0 /* Not used */
+#define cc_lock_request pcc_lock_request
+#endif
+#define cc_store pcc_store
+#define cc_remove_cred pcc_remove_cred
+#ifdef CC_API_VER2
+#define cc_seq_fetch_creds_begin pcc_seq_fetch_creds_begin
+#define cc_seq_fetch_creds_next pcc_seq_fetch_creds_next
+#define cc_seq_fetch_creds_end pcc_seq_fetch_creds_end
+#else
+#define cc_seq_fetch_creds pcc_seq_fetch_creds
+#endif
+#define cc_free_principal pcc_free_principal
+#define cc_free_name pcc_free_name
+#define cc_free_creds pcc_free_creds
+#endif
+#endif
+
+#undef DECL_FUNC_PTR
+
+#endif /* KRB5_WINCCLD_H_ */
diff --git a/src/lib/krb5/ccache/ccbase.c b/src/lib/krb5/ccache/ccbase.c
new file mode 100644
index 0000000000000..8198f2b9b9e30
--- /dev/null
+++ b/src/lib/krb5/ccache/ccbase.c
@@ -0,0 +1,579 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccbase.c - Registration functions for ccache */
+/*
+ * Copyright 1990,2004,2008 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "k5-thread.h"
+
+#include "fcc.h"
+#include "cc-int.h"
+
+struct krb5_cc_typelist {
+ const krb5_cc_ops *ops;
+ struct krb5_cc_typelist *next;
+};
+
+struct krb5_cc_typecursor {
+ struct krb5_cc_typelist *tptr;
+};
+/* typedef krb5_cc_typecursor in k5-int.h */
+
+extern const krb5_cc_ops krb5_mcc_ops;
+
+#define NEXT NULL
+
+#ifdef _WIN32
+extern const krb5_cc_ops krb5_lcc_ops;
+static struct krb5_cc_typelist cc_lcc_entry = { &krb5_lcc_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_lcc_entry
+#endif
+
+#ifdef USE_CCAPI_V3
+extern const krb5_cc_ops krb5_cc_stdcc_ops;
+static struct krb5_cc_typelist cc_stdcc_entry = { &krb5_cc_stdcc_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_stdcc_entry
+#endif
+
+static struct krb5_cc_typelist cc_mcc_entry = { &krb5_mcc_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_mcc_entry
+
+#ifndef NO_FILE_CCACHE
+static struct krb5_cc_typelist cc_fcc_entry = { &krb5_cc_file_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_fcc_entry
+#endif
+
+#ifdef USE_KEYRING_CCACHE
+extern const krb5_cc_ops krb5_krcc_ops;
+static struct krb5_cc_typelist cc_krcc_entry = { &krb5_krcc_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_krcc_entry
+#endif /* USE_KEYRING_CCACHE */
+
+#ifndef _WIN32
+extern const krb5_cc_ops krb5_dcc_ops;
+static struct krb5_cc_typelist cc_dcc_entry = { &krb5_dcc_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_dcc_entry
+
+extern const krb5_cc_ops krb5_kcm_ops;
+static struct krb5_cc_typelist cc_kcm_entry = { &krb5_kcm_ops, NEXT };
+#undef NEXT
+#define NEXT &cc_kcm_entry
+#endif /* not _WIN32 */
+
+
+#define INITIAL_TYPEHEAD (NEXT)
+static struct krb5_cc_typelist *cc_typehead = INITIAL_TYPEHEAD;
+static k5_mutex_t cc_typelist_lock = K5_MUTEX_PARTIAL_INITIALIZER;
+
+/* mutex for krb5_cccol_[un]lock */
+static k5_cc_mutex cccol_lock = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+
+static krb5_error_code
+krb5int_cc_getops(krb5_context, const char *, const krb5_cc_ops **);
+
+int
+krb5int_cc_initialize(void)
+{
+ int err;
+
+ err = k5_cc_mutex_finish_init(&cccol_lock);
+ if (err)
+ return err;
+ err = k5_cc_mutex_finish_init(&krb5int_mcc_mutex);
+ if (err)
+ return err;
+ err = k5_mutex_finish_init(&cc_typelist_lock);
+ if (err)
+ return err;
+#ifndef NO_FILE_CCACHE
+ err = k5_cc_mutex_finish_init(&krb5int_cc_file_mutex);
+ if (err)
+ return err;
+#endif
+#ifdef USE_KEYRING_CCACHE
+ err = k5_cc_mutex_finish_init(&krb5int_krcc_mutex);
+ if (err)
+ return err;
+#endif
+ return 0;
+}
+
+void
+krb5int_cc_finalize(void)
+{
+ struct krb5_cc_typelist *t, *t_next;
+ k5_cccol_force_unlock();
+ k5_cc_mutex_destroy(&cccol_lock);
+ k5_mutex_destroy(&cc_typelist_lock);
+#ifndef NO_FILE_CCACHE
+ k5_cc_mutex_destroy(&krb5int_cc_file_mutex);
+#endif
+ k5_cc_mutex_destroy(&krb5int_mcc_mutex);
+#ifdef USE_KEYRING_CCACHE
+ k5_cc_mutex_destroy(&krb5int_krcc_mutex);
+#endif
+ for (t = cc_typehead; t != INITIAL_TYPEHEAD; t = t_next) {
+ t_next = t->next;
+ free(t);
+ }
+}
+
+
+/*
+ * Register a new credentials cache type
+ * If override is set, replace any existing ccache with that type tag
+ */
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_register(krb5_context context, const krb5_cc_ops *ops,
+ krb5_boolean override)
+{
+ struct krb5_cc_typelist *t;
+
+ k5_mutex_lock(&cc_typelist_lock);
+ for (t = cc_typehead;t && strcmp(t->ops->prefix,ops->prefix);t = t->next)
+ ;
+ if (t) {
+ if (override) {
+ t->ops = ops;
+ k5_mutex_unlock(&cc_typelist_lock);
+ return 0;
+ } else {
+ k5_mutex_unlock(&cc_typelist_lock);
+ return KRB5_CC_TYPE_EXISTS;
+ }
+ }
+ if (!(t = (struct krb5_cc_typelist *) malloc(sizeof(*t)))) {
+ k5_mutex_unlock(&cc_typelist_lock);
+ return ENOMEM;
+ }
+ t->next = cc_typehead;
+ t->ops = ops;
+ cc_typehead = t;
+ k5_mutex_unlock(&cc_typelist_lock);
+ return 0;
+}
+
+/*
+ * Resolve a credential cache name into a cred. cache object.
+ *
+ * The name is currently constrained to be of the form "type:residual";
+ *
+ * The "type" portion corresponds to one of the predefined credential
+ * cache types, while the "residual" portion is specific to the
+ * particular cache type.
+ */
+
+#include <ctype.h>
+krb5_error_code KRB5_CALLCONV
+krb5_cc_resolve (krb5_context context, const char *name, krb5_ccache *cache)
+{
+ char *pfx, *cp;
+ const char *resid;
+ unsigned int pfxlen;
+ krb5_error_code err;
+ const krb5_cc_ops *ops;
+
+ if (name == NULL)
+ return KRB5_CC_BADNAME;
+ pfx = NULL;
+ cp = strchr (name, ':');
+ if (!cp) {
+ if (krb5_cc_dfl_ops)
+ return (*krb5_cc_dfl_ops->resolve)(context, cache, name);
+ else
+ return KRB5_CC_BADNAME;
+ }
+
+ pfxlen = cp - name;
+
+ if ( pfxlen == 1 && isalpha((unsigned char) name[0]) ) {
+ /* We found a drive letter not a prefix - use FILE */
+ pfx = strdup("FILE");
+ if (!pfx)
+ return ENOMEM;
+
+ resid = name;
+ } else {
+ resid = name + pfxlen + 1;
+ pfx = k5memdup0(name, pfxlen, &err);
+ if (pfx == NULL)
+ return err;
+ }
+
+ *cache = (krb5_ccache) 0;
+
+ err = krb5int_cc_getops(context, pfx, &ops);
+ if (pfx != NULL)
+ free(pfx);
+ if (err)
+ return err;
+
+ return ops->resolve(context, cache, resid);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_dup(krb5_context context, krb5_ccache in, krb5_ccache *out)
+{
+ return in->ops->resolve(context, out, in->ops->get_name(context, in));
+}
+
+/*
+ * cc_getops
+ *
+ * Internal function to return the ops vector for a given ccache
+ * prefix string.
+ */
+static krb5_error_code
+krb5int_cc_getops(krb5_context context,
+ const char *pfx,
+ const krb5_cc_ops **ops)
+{
+ struct krb5_cc_typelist *tlist;
+
+ k5_mutex_lock(&cc_typelist_lock);
+ for (tlist = cc_typehead; tlist; tlist = tlist->next) {
+ if (strcmp (tlist->ops->prefix, pfx) == 0) {
+ *ops = tlist->ops;
+ k5_mutex_unlock(&cc_typelist_lock);
+ return 0;
+ }
+ }
+ k5_mutex_unlock(&cc_typelist_lock);
+ if (krb5_cc_dfl_ops && !strcmp (pfx, krb5_cc_dfl_ops->prefix)) {
+ *ops = krb5_cc_dfl_ops;
+ return 0;
+ }
+ return KRB5_CC_UNKNOWN_TYPE;
+}
+
+/*
+ * cc_new_unique
+ *
+ * Generate a new unique ccache, given a ccache type and a hint
+ * string. Ignores the hint string for now.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_cc_new_unique(
+ krb5_context context,
+ const char *type,
+ const char *hint,
+ krb5_ccache *id)
+{
+ const krb5_cc_ops *ops;
+ krb5_error_code err;
+
+ *id = NULL;
+
+ TRACE_CC_NEW_UNIQUE(context, type);
+ err = krb5int_cc_getops(context, type, &ops);
+ if (err)
+ return err;
+
+ return ops->gen_new(context, id);
+}
+
+/*
+ * cc_typecursor
+ *
+ * Note: to avoid copying the typelist at cursor creation time, among
+ * other things, we assume that the only additions ever occur to the
+ * typelist.
+ */
+krb5_error_code
+krb5int_cc_typecursor_new(krb5_context context, krb5_cc_typecursor *t)
+{
+ krb5_cc_typecursor n = NULL;
+
+ *t = NULL;
+ n = malloc(sizeof(*n));
+ if (n == NULL)
+ return ENOMEM;
+
+ k5_mutex_lock(&cc_typelist_lock);
+ n->tptr = cc_typehead;
+ k5_mutex_unlock(&cc_typelist_lock);
+ *t = n;
+ return 0;
+}
+
+krb5_error_code
+krb5int_cc_typecursor_next(krb5_context context,
+ krb5_cc_typecursor t,
+ const krb5_cc_ops **ops)
+{
+ *ops = NULL;
+ if (t->tptr == NULL)
+ return 0;
+
+ k5_mutex_lock(&cc_typelist_lock);
+ *ops = t->tptr->ops;
+ t->tptr = t->tptr->next;
+ k5_mutex_unlock(&cc_typelist_lock);
+ return 0;
+}
+
+krb5_error_code
+krb5int_cc_typecursor_free(krb5_context context, krb5_cc_typecursor *t)
+{
+ free(*t);
+ *t = NULL;
+ return 0;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_move(krb5_context context, krb5_ccache src, krb5_ccache dst)
+{
+ krb5_error_code ret = 0;
+ krb5_principal princ = NULL;
+
+ TRACE_CC_MOVE(context, src, dst);
+ ret = krb5_cccol_lock(context);
+ if (ret) {
+ return ret;
+ }
+
+ ret = krb5_cc_lock(context, src);
+ if (ret) {
+ krb5_cccol_unlock(context);
+ return ret;
+ }
+
+ ret = krb5_cc_get_principal(context, src, &princ);
+ if (!ret) {
+ ret = krb5_cc_initialize(context, dst, princ);
+ }
+ if (ret) {
+ krb5_cc_unlock(context, src);
+ krb5_cccol_unlock(context);
+ return ret;
+ }
+
+ ret = krb5_cc_lock(context, dst);
+ if (!ret) {
+ ret = krb5_cc_copy_creds(context, src, dst);
+ krb5_cc_unlock(context, dst);
+ }
+
+ krb5_cc_unlock(context, src);
+ if (!ret) {
+ ret = krb5_cc_destroy(context, src);
+ }
+ krb5_cccol_unlock(context);
+ if (princ) {
+ krb5_free_principal(context, princ);
+ princ = NULL;
+ }
+
+ return ret;
+}
+
+krb5_boolean KRB5_CALLCONV
+krb5_cc_support_switch(krb5_context context, const char *type)
+{
+ const krb5_cc_ops *ops;
+ krb5_error_code err;
+
+ err = krb5int_cc_getops(context, type, &ops);
+ return (err ? FALSE : (ops->switch_to != NULL));
+}
+
+krb5_error_code
+k5_cc_mutex_init(k5_cc_mutex *m)
+{
+ krb5_error_code ret = 0;
+
+ ret = k5_mutex_init(&m->lock);
+ if (ret) return ret;
+ m->owner = NULL;
+ m->refcount = 0;
+
+ return ret;
+}
+
+krb5_error_code
+k5_cc_mutex_finish_init(k5_cc_mutex *m)
+{
+ krb5_error_code ret = 0;
+
+ ret = k5_mutex_finish_init(&m->lock);
+ if (ret) return ret;
+ m->owner = NULL;
+ m->refcount = 0;
+
+ return ret;
+}
+
+void
+k5_cc_mutex_assert_locked(krb5_context context, k5_cc_mutex *m)
+{
+#ifdef DEBUG_THREADS
+ assert(m->refcount > 0);
+ assert(m->owner == context);
+#endif
+ k5_assert_locked(&m->lock);
+}
+
+void
+k5_cc_mutex_assert_unlocked(krb5_context context, k5_cc_mutex *m)
+{
+#ifdef DEBUG_THREADS
+ assert(m->refcount == 0);
+ assert(m->owner == NULL);
+#endif
+ k5_assert_unlocked(&m->lock);
+}
+
+void
+k5_cc_mutex_lock(krb5_context context, k5_cc_mutex *m)
+{
+ /* not locked or already locked by another context */
+ if (m->owner != context) {
+ /* acquire lock, blocking until available */
+ k5_mutex_lock(&m->lock);
+ m->owner = context;
+ m->refcount = 1;
+ }
+ /* already locked by this context, just increase refcount */
+ else {
+ m->refcount++;
+ }
+}
+
+void
+k5_cc_mutex_unlock(krb5_context context, k5_cc_mutex *m)
+{
+ /* verify owner and sanity check refcount */
+ if ((m->owner != context) || (m->refcount < 1)) {
+ return;
+ }
+ /* decrement & unlock when count reaches zero */
+ m->refcount--;
+ if (m->refcount == 0) {
+ m->owner = NULL;
+ k5_mutex_unlock(&m->lock);
+ }
+}
+
+/* necessary to make reentrant locks play nice with krb5int_cc_finalize */
+void
+k5_cc_mutex_force_unlock(k5_cc_mutex *m)
+{
+ m->refcount = 0;
+ m->owner = NULL;
+ if (m->refcount > 0) {
+ k5_mutex_unlock(&m->lock);
+ }
+}
+
+/*
+ * holds on to all pertype global locks as well as typelist lock
+ */
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_lock(krb5_context context)
+{
+ krb5_error_code ret = 0;
+
+ k5_cc_mutex_lock(context, &cccol_lock);
+ k5_mutex_lock(&cc_typelist_lock);
+ k5_cc_mutex_lock(context, &krb5int_cc_file_mutex);
+ k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
+#ifdef USE_KEYRING_CCACHE
+ k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
+#endif
+#ifdef USE_CCAPI_V3
+ ret = krb5_stdccv3_context_lock(context);
+#endif
+ if (ret) {
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ k5_cc_mutex_unlock(context, &krb5int_cc_file_mutex);
+ k5_mutex_unlock(&cc_typelist_lock);
+ k5_cc_mutex_unlock(context, &cccol_lock);
+ return ret;
+ }
+ k5_mutex_unlock(&cc_typelist_lock);
+ return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_unlock(krb5_context context)
+{
+ krb5_error_code ret = 0;
+
+ /* sanity check */
+ k5_cc_mutex_assert_locked(context, &cccol_lock);
+
+ k5_mutex_lock(&cc_typelist_lock);
+
+ /* unlock each type in the opposite order */
+#ifdef USE_CCAPI_V3
+ krb5_stdccv3_context_unlock(context);
+#endif
+#ifdef USE_KEYRING_CCACHE
+ k5_cc_mutex_assert_locked(context, &krb5int_krcc_mutex);
+ k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
+#endif
+ k5_cc_mutex_assert_locked(context, &krb5int_mcc_mutex);
+ k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
+ k5_cc_mutex_assert_locked(context, &krb5int_cc_file_mutex);
+ k5_cc_mutex_unlock(context, &krb5int_cc_file_mutex);
+ k5_mutex_assert_locked(&cc_typelist_lock);
+
+ k5_mutex_unlock(&cc_typelist_lock);
+ k5_cc_mutex_unlock(context, &cccol_lock);
+
+ return ret;
+}
+
+/* necessary to make reentrant locks play nice with krb5int_cc_finalize */
+void
+k5_cccol_force_unlock()
+{
+ /* sanity check */
+ if ((&cccol_lock)->refcount == 0) {
+ return;
+ }
+
+ k5_mutex_lock(&cc_typelist_lock);
+
+ /* unlock each type in the opposite order */
+#ifdef USE_KEYRING_CCACHE
+ k5_cc_mutex_force_unlock(&krb5int_krcc_mutex);
+#endif
+#ifdef USE_CCAPI_V3
+ krb5_stdccv3_context_unlock(NULL);
+#endif
+ k5_cc_mutex_force_unlock(&krb5int_mcc_mutex);
+ k5_cc_mutex_force_unlock(&krb5int_cc_file_mutex);
+
+ k5_mutex_unlock(&cc_typelist_lock);
+ k5_cc_mutex_force_unlock(&cccol_lock);
+}
diff --git a/src/lib/krb5/ccache/cccopy.c b/src/lib/krb5/ccache/cccopy.c
new file mode 100644
index 0000000000000..d71d439ddd51c
--- /dev/null
+++ b/src/lib/krb5/ccache/cccopy.c
@@ -0,0 +1,37 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+#include "k5-int.h"
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_copy_creds(krb5_context context, krb5_ccache incc, krb5_ccache outcc)
+{
+ krb5_error_code code;
+ krb5_cc_cursor cur = 0;
+ krb5_creds creds;
+
+ if ((code = krb5_cc_start_seq_get(context, incc, &cur)))
+ goto cleanup;
+
+ while (!(code = krb5_cc_next_cred(context, incc, &cur, &creds))) {
+ code = krb5_cc_store_cred(context, outcc, &creds);
+ krb5_free_cred_contents(context, &creds);
+ if (code)
+ goto cleanup;
+ }
+
+ if (code != KRB5_CC_END)
+ goto cleanup;
+
+ code = krb5_cc_end_seq_get(context, incc, &cur);
+ cur = 0;
+ if (code)
+ goto cleanup;
+
+ code = 0;
+
+cleanup:
+ /* If set then we are in an error pathway */
+ if (cur)
+ krb5_cc_end_seq_get(context, incc, &cur);
+
+ return(code);
+}
diff --git a/src/lib/krb5/ccache/cccursor.c b/src/lib/krb5/ccache/cccursor.c
new file mode 100644
index 0000000000000..c31a3f5f0b874
--- /dev/null
+++ b/src/lib/krb5/ccache/cccursor.c
@@ -0,0 +1,295 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cccursor.c */
+/*
+ * Copyright 2006, 2007 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * cursor for sequential traversal of ccaches
+ */
+
+#include "cc-int.h"
+#include "../krb/int-proto.h"
+
+#include <assert.h>
+
+struct _krb5_cccol_cursor {
+ krb5_cc_typecursor typecursor;
+ const krb5_cc_ops *ops;
+ krb5_cc_ptcursor ptcursor;
+};
+/* typedef of krb5_cccol_cursor is in krb5.h */
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_cursor_new(krb5_context context,
+ krb5_cccol_cursor *cursor)
+{
+ krb5_error_code ret = 0;
+ krb5_cccol_cursor n = NULL;
+
+ *cursor = NULL;
+ n = malloc(sizeof(*n));
+ if (n == NULL)
+ return ENOMEM;
+
+ n->typecursor = NULL;
+ n->ptcursor = NULL;
+ n->ops = NULL;
+
+ ret = krb5int_cc_typecursor_new(context, &n->typecursor);
+ if (ret)
+ goto errout;
+
+ do {
+ /* Find first backend with ptcursor functionality. */
+ ret = krb5int_cc_typecursor_next(context, n->typecursor, &n->ops);
+ if (ret || n->ops == NULL)
+ goto errout;
+ } while (n->ops->ptcursor_new == NULL);
+
+ ret = n->ops->ptcursor_new(context, &n->ptcursor);
+ if (ret)
+ goto errout;
+
+errout:
+ if (ret) {
+ krb5_cccol_cursor_free(context, &n);
+ }
+ *cursor = n;
+ return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_cursor_next(krb5_context context,
+ krb5_cccol_cursor cursor,
+ krb5_ccache *ccache_out)
+{
+ krb5_error_code ret = 0;
+ krb5_ccache ccache;
+
+ *ccache_out = NULL;
+
+ /* Are we out of backends? */
+ if (cursor->ops == NULL)
+ return 0;
+
+ while (1) {
+ ret = cursor->ops->ptcursor_next(context, cursor->ptcursor, &ccache);
+ if (ret)
+ return ret;
+ if (ccache != NULL) {
+ *ccache_out = ccache;
+ return 0;
+ }
+
+ ret = cursor->ops->ptcursor_free(context, &cursor->ptcursor);
+ if (ret)
+ return ret;
+
+ do {
+ /* Find next type with ptcursor functionality. */
+ ret = krb5int_cc_typecursor_next(context, cursor->typecursor,
+ &cursor->ops);
+ if (ret)
+ return ret;
+ if (cursor->ops == NULL)
+ return 0;
+ } while (cursor->ops->ptcursor_new == NULL);
+
+ ret = cursor->ops->ptcursor_new(context, &cursor->ptcursor);
+ if (ret)
+ return ret;
+ }
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_cursor_free(krb5_context context,
+ krb5_cccol_cursor *cursor)
+{
+ krb5_cccol_cursor c = *cursor;
+
+ if (c == NULL)
+ return 0;
+
+ if (c->ptcursor != NULL)
+ c->ops->ptcursor_free(context, &c->ptcursor);
+ if (c->typecursor != NULL)
+ krb5int_cc_typecursor_free(context, &c->typecursor);
+ free(c);
+
+ *cursor = NULL;
+ return 0;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_last_change_time(krb5_context context,
+ krb5_timestamp *change_time)
+{
+ krb5_error_code ret = 0;
+ krb5_cccol_cursor c = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_timestamp last_time = 0;
+ krb5_timestamp max_change_time = 0;
+
+ *change_time = 0;
+
+ ret = krb5_cccol_cursor_new(context, &c);
+
+ while (!ret) {
+ ret = krb5_cccol_cursor_next(context, c, &ccache);
+ if (ccache) {
+ ret = krb5_cc_last_change_time(context, ccache, &last_time);
+ if (!ret && last_time > max_change_time) {
+ max_change_time = last_time;
+ }
+ ret = 0;
+ }
+ else {
+ break;
+ }
+ }
+ *change_time = max_change_time;
+ return ret;
+}
+
+/*
+ * krb5_cccol_lock and krb5_cccol_unlock are defined in ccbase.c
+ */
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_cache_match(krb5_context context, krb5_principal client,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ krb5_cccol_cursor cursor;
+ krb5_ccache cache = NULL;
+ krb5_principal princ;
+ char *name;
+ krb5_boolean eq;
+
+ *cache_out = NULL;
+ ret = krb5_cccol_cursor_new(context, &cursor);
+ if (ret)
+ return ret;
+
+ while ((ret = krb5_cccol_cursor_next(context, cursor, &cache)) == 0 &&
+ cache != NULL) {
+ ret = krb5_cc_get_principal(context, cache, &princ);
+ if (ret == 0) {
+ eq = krb5_principal_compare(context, princ, client);
+ krb5_free_principal(context, princ);
+ if (eq)
+ break;
+ }
+ krb5_cc_close(context, cache);
+ }
+ krb5_cccol_cursor_free(context, &cursor);
+ if (ret)
+ return ret;
+ if (cache == NULL) {
+ ret = krb5_unparse_name(context, client, &name);
+ if (ret == 0) {
+ k5_setmsg(context, KRB5_CC_NOTFOUND,
+ _("Can't find client principal %s in cache collection"),
+ name);
+ krb5_free_unparsed_name(context, name);
+ }
+ ret = KRB5_CC_NOTFOUND;
+ } else
+ *cache_out = cache;
+ return ret;
+}
+
+/* Store the error state for code from context into errsave, but only if code
+ * indicates an error and errsave is empty. */
+static void
+save_first_error(krb5_context context, krb5_error_code code,
+ struct errinfo *errsave)
+{
+ if (code && code != KRB5_CC_END && !errsave->code)
+ k5_save_ctx_error(context, code, errsave);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cccol_have_content(krb5_context context)
+{
+ krb5_error_code ret;
+ krb5_cccol_cursor col_cursor;
+ krb5_cc_cursor cache_cursor;
+ krb5_ccache cache;
+ krb5_creds creds;
+ krb5_boolean found = FALSE;
+ struct errinfo errsave = EMPTY_ERRINFO;
+ const char *defname;
+
+ ret = krb5_cccol_cursor_new(context, &col_cursor);
+ save_first_error(context, ret, &errsave);
+ if (ret)
+ goto no_entries;
+
+ while (!found) {
+ ret = krb5_cccol_cursor_next(context, col_cursor, &cache);
+ save_first_error(context, ret, &errsave);
+ if (ret || cache == NULL)
+ break;
+
+ ret = krb5_cc_start_seq_get(context, cache, &cache_cursor);
+ save_first_error(context, ret, &errsave);
+ if (ret) {
+ krb5_cc_close(context, cache);
+ continue;
+ }
+ while (!found) {
+ ret = krb5_cc_next_cred(context, cache, &cache_cursor, &creds);
+ save_first_error(context, ret, &errsave);
+ if (ret)
+ break;
+
+ if (!krb5_is_config_principal(context, creds.server))
+ found = TRUE;
+ krb5_free_cred_contents(context, &creds);
+ }
+ krb5_cc_end_seq_get(context, cache, &cache_cursor);
+ krb5_cc_close(context, cache);
+ }
+ krb5_cccol_cursor_free(context, &col_cursor);
+ if (found)
+ return 0;
+
+no_entries:
+ if (errsave.code) {
+ /* Report the first error we encountered. */
+ ret = k5_restore_ctx_error(context, &errsave);
+ k5_wrapmsg(context, ret, KRB5_CC_NOTFOUND,
+ _("No Kerberos credentials available"));
+ } else {
+ /* Report the default cache name. */
+ defname = krb5_cc_default_name(context);
+ if (defname != NULL) {
+ k5_setmsg(context, KRB5_CC_NOTFOUND,
+ _("No Kerberos credentials available "
+ "(default cache: %s)"), defname);
+ }
+ }
+ return KRB5_CC_NOTFOUND;
+}
diff --git a/src/lib/krb5/ccache/ccdefault.c b/src/lib/krb5/ccache/ccdefault.c
new file mode 100644
index 0000000000000..1d3625c4d9d9d
--- /dev/null
+++ b/src/lib/krb5/ccache/ccdefault.c
@@ -0,0 +1,97 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccdefault.c - Find default credential cache */
+/*
+ * Copyright 1990, 2007, 2008 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+
+#ifdef USE_LEASH
+static void (*pLeash_AcquireInitialTicketsIfNeeded)(krb5_context,krb5_principal,char*,int) = NULL;
+static HANDLE hLeashDLL = INVALID_HANDLE_VALUE;
+#ifdef _WIN64
+#define LEASH_DLL "leashw64.dll"
+#else
+#define LEASH_DLL "leashw32.dll"
+#endif
+#endif
+
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_default(krb5_context context, krb5_ccache *ccache)
+{
+ const char *default_name;
+
+ if (!context || context->magic != KV5M_CONTEXT)
+ return KV5M_CONTEXT;
+
+ default_name = krb5_cc_default_name(context);
+ if (default_name == NULL) {
+ /* Could be a bogus context, or an allocation failure, or
+ other things. Unfortunately the API doesn't allow us
+ to find out any specifics. */
+ return KRB5_FCC_INTERNAL;
+ }
+
+ return krb5_cc_resolve(context, default_name, ccache);
+}
+
+/* This is the internal function which opens the default ccache. On
+ platforms supporting the login library's automatic popup dialog to
+ get tickets, this function also updated the library's internal view
+ of the current principal associated with this cache.
+
+ All krb5 and GSS functions which need to open a cache to get a tgt
+ to obtain service tickets should call this function, not
+ krb5_cc_default(). */
+
+krb5_error_code KRB5_CALLCONV
+krb5int_cc_default(krb5_context context, krb5_ccache *ccache)
+{
+ if (!context || context->magic != KV5M_CONTEXT) {
+ return KV5M_CONTEXT;
+ }
+
+#ifdef USE_LEASH
+ if ( hLeashDLL == INVALID_HANDLE_VALUE ) {
+ hLeashDLL = LoadLibrary(LEASH_DLL);
+ if ( hLeashDLL != INVALID_HANDLE_VALUE ) {
+ (FARPROC) pLeash_AcquireInitialTicketsIfNeeded =
+ GetProcAddress(hLeashDLL, "not_an_API_Leash_AcquireInitialTicketsIfNeeded");
+ }
+ }
+
+ if ( pLeash_AcquireInitialTicketsIfNeeded ) {
+ char ccname[256]="";
+ pLeash_AcquireInitialTicketsIfNeeded(context, NULL, ccname, sizeof(ccname));
+ if (ccname[0]) {
+ char * ccdefname = krb5_cc_default_name (context);
+ if (!ccdefname || strcmp (ccdefname, ccname) != 0) {
+ krb5_cc_set_default_name (context, ccname);
+ }
+ }
+ }
+#endif
+
+ return krb5_cc_default (context, ccache);
+}
diff --git a/src/lib/krb5/ccache/ccdefops.c b/src/lib/krb5/ccache/ccdefops.c
new file mode 100644
index 0000000000000..abe0c6d837158
--- /dev/null
+++ b/src/lib/krb5/ccache/ccdefops.c
@@ -0,0 +1,58 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccdefops.c */
+/*
+ * Copyright 1990 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/* Default credentials cache determination. This is a separate file
+ * so that the user can more easily override it. */
+
+#include "k5-int.h"
+
+#if defined(USE_CCAPI)
+
+/*
+ * Macs use the shared, memory based credentials cache
+ * Windows may also use the ccapi cache, but only if the Krbcc32.dll
+ * can be found; otherwise it falls back to using the old
+ * file-based ccache.
+ */
+#include "stdcc.h" /* from ccapi subdir */
+
+const krb5_cc_ops *krb5_cc_dfl_ops = &krb5_cc_stdcc_ops;
+
+#elif defined(NO_FILE_CCACHE)
+
+/* Note that this version isn't likely to work very well for multiple
+ processes. It's mostly a placeholder so we can experiment with
+ building the NO_FILE_CCACHE code on UNIX. */
+
+extern const krb5_cc_ops krb5_mcc_ops;
+const krb5_cc_ops *krb5_cc_dfl_ops = &krb5_mcc_ops;
+
+#else
+
+#include "fcc.h"
+const krb5_cc_ops *krb5_cc_dfl_ops = &krb5_cc_file_ops;
+
+#endif
diff --git a/src/lib/krb5/ccache/ccfns.c b/src/lib/krb5/ccache/ccfns.c
new file mode 100644
index 0000000000000..1084d51912ba5
--- /dev/null
+++ b/src/lib/krb5/ccache/ccfns.c
@@ -0,0 +1,331 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccfns.c - Dispatch methods for credentials cache code.*/
+/*
+ * Copyright 2000, 2007, 2008 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include "../krb/int-proto.h"
+
+const char * KRB5_CALLCONV
+krb5_cc_get_name(krb5_context context, krb5_ccache cache)
+{
+ return cache->ops->get_name(context, cache);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_get_full_name(krb5_context context, krb5_ccache cache,
+ char **fullname_out)
+{
+ char *name;
+
+ *fullname_out = NULL;
+ if (asprintf(&name, "%s:%s", cache->ops->prefix,
+ cache->ops->get_name(context, cache)) < 0)
+ return ENOMEM;
+ *fullname_out = name;
+ return 0;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_gen_new(krb5_context context, krb5_ccache *cache)
+{
+ TRACE_CC_GEN_NEW(context, cache);
+ return (*cache)->ops->gen_new(context, cache);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_initialize(krb5_context context, krb5_ccache cache,
+ krb5_principal principal)
+{
+ TRACE_CC_INIT(context, cache, principal);
+ return cache->ops->init(context, cache, principal);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_destroy(krb5_context context, krb5_ccache cache)
+{
+ TRACE_CC_DESTROY(context, cache);
+ return cache->ops->destroy(context, cache);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_close(krb5_context context, krb5_ccache cache)
+{
+ return cache->ops->close(context, cache);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_store_cred(krb5_context context, krb5_ccache cache,
+ krb5_creds *creds)
+{
+ krb5_error_code ret;
+ krb5_ticket *tkt;
+ krb5_principal s1, s2;
+
+ TRACE_CC_STORE(context, cache, creds);
+ ret = cache->ops->store(context, cache, creds);
+ if (ret) return ret;
+
+ /*
+ * If creds->server and the server in the decoded ticket differ,
+ * store both principals.
+ */
+ s1 = creds->server;
+ ret = decode_krb5_ticket(&creds->ticket, &tkt);
+ /* Bail out on errors in case someone is storing a non-ticket. */
+ if (ret) return 0;
+ s2 = tkt->server;
+ if (!krb5_principal_compare(context, s1, s2)) {
+ creds->server = s2;
+ TRACE_CC_STORE_TKT(context, cache, creds);
+ /* remove any dups */
+ krb5_cc_remove_cred(context, cache, KRB5_TC_MATCH_AUTHDATA, creds);
+ ret = cache->ops->store(context, cache, creds);
+ creds->server = s1;
+ }
+ krb5_free_ticket(context, tkt);
+ return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_retrieve_cred(krb5_context context, krb5_ccache cache,
+ krb5_flags flags, krb5_creds *mcreds,
+ krb5_creds *creds)
+{
+ krb5_error_code ret;
+ krb5_data tmprealm;
+
+ ret = cache->ops->retrieve(context, cache, flags, mcreds, creds);
+ TRACE_CC_RETRIEVE(context, cache, mcreds, ret);
+ if (ret != KRB5_CC_NOTFOUND)
+ return ret;
+ if (!krb5_is_referral_realm(&mcreds->server->realm))
+ return ret;
+
+ /*
+ * Retry using client's realm if service has referral realm.
+ */
+ tmprealm = mcreds->server->realm;
+ mcreds->server->realm = mcreds->client->realm;
+ ret = cache->ops->retrieve(context, cache, flags, mcreds, creds);
+ TRACE_CC_RETRIEVE_REF(context, cache, mcreds, ret);
+ mcreds->server->realm = tmprealm;
+ return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_get_principal(krb5_context context, krb5_ccache cache,
+ krb5_principal *principal)
+{
+ return cache->ops->get_princ(context, cache, principal);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_start_seq_get(krb5_context context, krb5_ccache cache,
+ krb5_cc_cursor *cursor)
+{
+ return cache->ops->get_first(context, cache, cursor);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_next_cred(krb5_context context, krb5_ccache cache,
+ krb5_cc_cursor *cursor, krb5_creds *creds)
+{
+ return cache->ops->get_next(context, cache, cursor, creds);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_end_seq_get(krb5_context context, krb5_ccache cache,
+ krb5_cc_cursor *cursor)
+{
+ return cache->ops->end_get(context, cache, cursor);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
+ krb5_creds *creds)
+{
+ TRACE_CC_REMOVE(context, cache, creds);
+ return cache->ops->remove_cred(context, cache, flags, creds);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
+{
+ return cache->ops->set_flags(context, cache, flags);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags)
+{
+ return cache->ops->get_flags(context, cache, flags);
+}
+
+const char * KRB5_CALLCONV
+krb5_cc_get_type(krb5_context context, krb5_ccache cache)
+{
+ return cache->ops->prefix;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_last_change_time(krb5_context context, krb5_ccache ccache,
+ krb5_timestamp *change_time)
+{
+ return ccache->ops->lastchange(context, ccache, change_time);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_lock(krb5_context context, krb5_ccache ccache)
+{
+ return ccache->ops->lock(context, ccache);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_unlock(krb5_context context, krb5_ccache ccache)
+{
+ return ccache->ops->unlock(context, ccache);
+}
+
+static const char conf_realm[] = "X-CACHECONF:";
+static const char conf_name[] = "krb5_ccache_conf_data";
+
+krb5_error_code
+k5_build_conf_principals(krb5_context context, krb5_ccache id,
+ krb5_const_principal principal,
+ const char *name, krb5_creds *cred)
+{
+ krb5_principal client;
+ krb5_error_code ret;
+ char *pname = NULL;
+
+ memset(cred, 0, sizeof(*cred));
+
+ ret = krb5_cc_get_principal(context, id, &client);
+ if (ret)
+ return ret;
+
+ if (principal) {
+ ret = krb5_unparse_name(context, principal, &pname);
+ if (ret)
+ return ret;
+ }
+
+ ret = krb5_build_principal(context, &cred->server,
+ sizeof(conf_realm) - 1, conf_realm,
+ conf_name, name, pname, (char *)NULL);
+ krb5_free_unparsed_name(context, pname);
+ if (ret) {
+ krb5_free_principal(context, client);
+ return ret;
+ }
+ ret = krb5_copy_principal(context, client, &cred->client);
+ krb5_free_principal(context, client);
+ return ret;
+}
+
+krb5_boolean KRB5_CALLCONV
+krb5_is_config_principal(krb5_context context,
+ krb5_const_principal principal)
+{
+ const krb5_data *realm = &principal->realm;
+
+ if (realm->length != sizeof(conf_realm) - 1 ||
+ memcmp(realm->data, conf_realm, sizeof(conf_realm) - 1) != 0)
+ return FALSE;
+
+ if (principal->length == 0 ||
+ principal->data[0].length != (sizeof(conf_name) - 1) ||
+ memcmp(principal->data[0].data, conf_name, sizeof(conf_name) - 1) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_set_config(krb5_context context, krb5_ccache id,
+ krb5_const_principal principal,
+ const char *key, krb5_data *data)
+{
+ krb5_error_code ret;
+ krb5_creds cred;
+ memset(&cred, 0, sizeof(cred));
+
+ TRACE_CC_SET_CONFIG(context, id, principal, key, data);
+
+ ret = k5_build_conf_principals(context, id, principal, key, &cred);
+ if (ret)
+ goto out;
+
+ if (data == NULL) {
+ ret = krb5_cc_remove_cred(context, id, 0, &cred);
+ } else {
+ ret = krb5int_copy_data_contents(context, data, &cred.ticket);
+ if (ret)
+ goto out;
+ ret = krb5_cc_store_cred(context, id, &cred);
+ }
+out:
+ krb5_free_cred_contents(context, &cred);
+ return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_get_config(krb5_context context, krb5_ccache id,
+ krb5_const_principal principal,
+ const char *key, krb5_data *data)
+{
+ krb5_creds mcred, cred;
+ krb5_error_code ret;
+
+ memset(&cred, 0, sizeof(cred));
+ memset(data, 0, sizeof(*data));
+
+ ret = k5_build_conf_principals(context, id, principal, key, &mcred);
+ if (ret)
+ goto out;
+
+ ret = krb5_cc_retrieve_cred(context, id, 0, &mcred, &cred);
+ if (ret)
+ goto out;
+
+ ret = krb5int_copy_data_contents(context, &cred.ticket, data);
+ if (ret)
+ goto out;
+
+ TRACE_CC_GET_CONFIG(context, id, principal, key, data);
+
+out:
+ krb5_free_cred_contents(context, &cred);
+ krb5_free_cred_contents(context, &mcred);
+ return ret;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_switch(krb5_context context, krb5_ccache cache)
+{
+ if (cache->ops->switch_to == NULL)
+ return 0;
+ return cache->ops->switch_to(context, cache);
+}
diff --git a/src/lib/krb5/ccache/ccmarshal.c b/src/lib/krb5/ccache/ccmarshal.c
new file mode 100644
index 0000000000000..bd6d309d1d4e2
--- /dev/null
+++ b/src/lib/krb5/ccache/ccmarshal.c
@@ -0,0 +1,517 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccmarshal.c - Functions for serializing creds */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This file implements marshalling and unmarshalling of krb5 credentials and
+ * principals in versions 1 through 4 of the FILE ccache format. Version 4 is
+ * also used for the KEYRING ccache type.
+ *
+ * The FILE credential cache format uses fixed 16-bit or 32-bit representations
+ * of integers. In versions 1 and 2 these are in host byte order; in later
+ * versions they are in big-endian byte order. Variable-length fields are
+ * represented with a 32-bit length followed by the field value. There is no
+ * type tagging; field representations are simply concatenated together.
+ *
+ * A psuedo-BNF grammar for the credential and principal formats is:
+ *
+ * credential ::=
+ * client (principal)
+ * server (principal)
+ * keyblock (keyblock)
+ * authtime (32 bits)
+ * starttime (32 bits)
+ * endtime (32 bits)
+ * renew_till (32 bits)
+ * is_skey (1 byte, 0 or 1)
+ * ticket_flags (32 bits)
+ * addresses (addresses)
+ * authdata (authdata)
+ * ticket (data)
+ * second_ticket (data)
+ *
+ * principal ::=
+ * name type (32 bits) [omitted in version 1]
+ * count of components (32 bits) [includes realm in version 1]
+ * realm (data)
+ * component1 (data)
+ * component2 (data)
+ * ...
+ *
+ * keyblock ::=
+ * enctype (16 bits) [repeated twice in version 3; see below]
+ * data
+ *
+ * addresses ::=
+ * count (32 bits)
+ * address1
+ * address2
+ * ...
+ *
+ * address ::=
+ * addrtype (16 bits)
+ * data
+ *
+ * authdata ::=
+ * count (32 bits)
+ * authdata1
+ * authdata2
+ * ...
+ *
+ * authdata ::=
+ * ad_type (16 bits)
+ * data
+ *
+ * data ::=
+ * length (32 bits)
+ * value (length bytes)
+ *
+ * When version 3 was current (before release 1.0), the keyblock had separate
+ * key type and enctype fields, and both were recorded. At present we record
+ * the enctype field twice when writing the version 3 format and ignore the
+ * second value when reading it.
+ */
+
+#include "k5-input.h"
+#include "cc-int.h"
+
+/* Read a 16-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static uint16_t
+get16(struct k5input *in, int version)
+{
+ return (version < 3) ? k5_input_get_uint16_n(in) :
+ k5_input_get_uint16_be(in);
+}
+
+/* Read a 32-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static uint32_t
+get32(struct k5input *in, int version)
+{
+ return (version < 3) ? k5_input_get_uint32_n(in) :
+ k5_input_get_uint32_be(in);
+}
+
+/* Read a 32-bit length and make a copy of that many bytes. Return NULL on
+ * error. */
+static void *
+get_len_bytes(struct k5input *in, int version, unsigned int *len_out)
+{
+ krb5_error_code ret;
+ unsigned int len = get32(in, version);
+ const void *bytes = k5_input_get_bytes(in, len);
+ void *copy;
+
+ *len_out = 0;
+ if (bytes == NULL)
+ return NULL;
+ copy = k5memdup0(bytes, len, &ret);
+ if (copy == NULL) {
+ k5_input_set_status(in, ret);
+ return NULL;
+ }
+ *len_out = len;
+ return copy;
+}
+
+/* Like get_len_bytes, but put the result in data. */
+static void
+get_data(struct k5input *in, int version, krb5_data *data)
+{
+ unsigned int len;
+ void *bytes = get_len_bytes(in, version, &len);
+
+ *data = (bytes == NULL) ? empty_data() : make_data(bytes, len);
+}
+
+static krb5_principal
+unmarshal_princ(struct k5input *in, int version)
+{
+ krb5_error_code ret;
+ krb5_principal princ;
+ uint32_t i, ncomps;
+
+ princ = k5alloc(sizeof(krb5_principal_data), &ret);
+ if (princ == NULL) {
+ k5_input_set_status(in, ret);
+ return NULL;
+ }
+ princ->magic = KV5M_PRINCIPAL;
+ /* Version 1 does not store the principal name type, and counts the realm
+ * in the number of components. */
+ princ->type = (version == 1) ? KRB5_NT_UNKNOWN : get32(in, version);
+ ncomps = get32(in, version);
+ if (version == 1)
+ ncomps--;
+ if (ncomps > in->len) { /* Sanity check to avoid large allocations */
+ ret = EINVAL;
+ goto error;
+ }
+ if (ncomps != 0) {
+ princ->data = k5calloc(ncomps, sizeof(krb5_data), &ret);
+ if (princ->data == NULL)
+ goto error;
+ princ->length = ncomps;
+ }
+ get_data(in, version, &princ->realm);
+ for (i = 0; i < ncomps; i++)
+ get_data(in, version, &princ->data[i]);
+ return princ;
+
+error:
+ k5_input_set_status(in, ret);
+ krb5_free_principal(NULL, princ);
+ return NULL;
+}
+
+static void
+unmarshal_keyblock(struct k5input *in, int version, krb5_keyblock *kb)
+{
+ memset(kb, 0, sizeof(*kb));
+ kb->magic = KV5M_KEYBLOCK;
+ /* enctypes can be negative, so sign-extend the 16-bit result. */
+ kb->enctype = (int16_t)get16(in, version);
+ /* Version 3 stores the enctype twice. */
+ if (version == 3)
+ (void)get16(in, version);
+ kb->contents = get_len_bytes(in, version, &kb->length);
+}
+
+static krb5_address *
+unmarshal_addr(struct k5input *in, int version)
+{
+ krb5_address *addr;
+
+ addr = calloc(1, sizeof(*addr));
+ if (addr == NULL) {
+ k5_input_set_status(in, ENOMEM);
+ return NULL;
+ }
+ addr->magic = KV5M_ADDRESS;
+ addr->addrtype = get16(in, version);
+ addr->contents = get_len_bytes(in, version, &addr->length);
+ return addr;
+}
+
+static krb5_address **
+unmarshal_addrs(struct k5input *in, int version)
+{
+ krb5_address **addrs;
+ size_t i, count;
+
+ count = get32(in, version);
+ if (count > in->len) { /* Sanity check to avoid large allocations */
+ k5_input_set_status(in, EINVAL);
+ return NULL;
+ }
+ addrs = calloc(count + 1, sizeof(*addrs));
+ if (addrs == NULL) {
+ k5_input_set_status(in, ENOMEM);
+ return NULL;
+ }
+ for (i = 0; i < count; i++)
+ addrs[i] = unmarshal_addr(in, version);
+ return addrs;
+}
+
+static krb5_authdata *
+unmarshal_authdatum(struct k5input *in, int version)
+{
+ krb5_authdata *ad;
+
+ ad = calloc(1, sizeof(*ad));
+ if (ad == NULL) {
+ k5_input_set_status(in, ENOMEM);
+ return NULL;
+ }
+ ad->magic = KV5M_ADDRESS;
+ /* Authdata types can be negative, so sign-extend the get16 result. */
+ ad->ad_type = (int16_t)get16(in, version);
+ ad->contents = get_len_bytes(in, version, &ad->length);
+ return ad;
+}
+
+static krb5_authdata **
+unmarshal_authdata(struct k5input *in, int version)
+{
+ krb5_authdata **authdata;
+ size_t i, count;
+
+ count = get32(in, version);
+ if (count > in->len) { /* Sanity check to avoid large allocations */
+ k5_input_set_status(in, EINVAL);
+ return NULL;
+ }
+ authdata = calloc(count + 1, sizeof(*authdata));
+ if (authdata == NULL) {
+ k5_input_set_status(in, ENOMEM);
+ return NULL;
+ }
+ for (i = 0; i < count; i++)
+ authdata[i] = unmarshal_authdatum(in, version);
+ return authdata;
+}
+
+/* Unmarshal a credential using the specified file ccache version (expressed as
+ * an integer from 1 to 4). Does not check for trailing garbage. */
+krb5_error_code
+k5_unmarshal_cred(const unsigned char *data, size_t len, int version,
+ krb5_creds *creds)
+{
+ struct k5input in;
+
+ k5_input_init(&in, data, len);
+ creds->client = unmarshal_princ(&in, version);
+ creds->server = unmarshal_princ(&in, version);
+ unmarshal_keyblock(&in, version, &creds->keyblock);
+ creds->times.authtime = get32(&in, version);
+ creds->times.starttime = get32(&in, version);
+ creds->times.endtime = get32(&in, version);
+ creds->times.renew_till = get32(&in, version);
+ creds->is_skey = k5_input_get_byte(&in);
+ creds->ticket_flags = get32(&in, version);
+ creds->addresses = unmarshal_addrs(&in, version);
+ creds->authdata = unmarshal_authdata(&in, version);
+ get_data(&in, version, &creds->ticket);
+ get_data(&in, version, &creds->second_ticket);
+ if (in.status) {
+ krb5_free_cred_contents(NULL, creds);
+ memset(creds, 0, sizeof(*creds));
+ }
+ return (in.status == EINVAL) ? KRB5_CC_FORMAT : in.status;
+}
+
+/* Unmarshal a principal using the specified file ccache version (expressed as
+ * an integer from 1 to 4). Does not check for trailing garbage. */
+krb5_error_code
+k5_unmarshal_princ(const unsigned char *data, size_t len, int version,
+ krb5_principal *princ_out)
+{
+ struct k5input in;
+ krb5_principal princ;
+
+ *princ_out = NULL;
+ k5_input_init(&in, data, len);
+ princ = unmarshal_princ(&in, version);
+ if (in.status)
+ krb5_free_principal(NULL, princ);
+ else
+ *princ_out = princ;
+ return (in.status == EINVAL) ? KRB5_CC_FORMAT : in.status;
+}
+
+/* Store a 16-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static void
+put16(struct k5buf *buf, int version, uint16_t num)
+{
+ char n[2];
+
+ if (version < 3)
+ store_16_n(num, n);
+ else
+ store_16_be(num, n);
+ k5_buf_add_len(buf, n, 2);
+}
+
+/* Store a 32-bit integer in host byte order for versions 1 and 2, or in
+ * big-endian byte order for later versions.*/
+static void
+put32(struct k5buf *buf, int version, uint32_t num)
+{
+ char n[4];
+
+ if (version < 3)
+ store_32_n(num, n);
+ else
+ store_32_be(num, n);
+ k5_buf_add_len(buf, n, 4);
+}
+
+static void
+put_len_bytes(struct k5buf *buf, int version, const void *bytes,
+ unsigned int len)
+{
+ put32(buf, version, len);
+ k5_buf_add_len(buf, bytes, len);
+}
+
+static void
+put_data(struct k5buf *buf, int version, krb5_data *data)
+{
+ put_len_bytes(buf, version, data->data, data->length);
+}
+
+void
+k5_marshal_princ(struct k5buf *buf, int version, krb5_principal princ)
+{
+ int32_t i, ncomps;
+
+ /* Version 1 does not store the principal name type, and counts the realm
+ * in the number of components. */
+ if (version != 1)
+ put32(buf, version, princ->type);
+ ncomps = princ->length + ((version == 1) ? 1 : 0);
+ put32(buf, version, ncomps);
+ put_data(buf, version, &princ->realm);
+ for (i = 0; i < princ->length; i++)
+ put_data(buf, version, &princ->data[i]);
+}
+
+static void
+marshal_keyblock(struct k5buf *buf, int version, krb5_keyblock *kb)
+{
+ put16(buf, version, kb->enctype);
+ /* Version 3 stores the enctype twice. */
+ if (version == 3)
+ put16(buf, version, kb->enctype);
+ put_len_bytes(buf, version, kb->contents, kb->length);
+}
+
+static void
+marshal_addrs(struct k5buf *buf, int version, krb5_address **addrs)
+{
+ size_t i, count;
+
+ for (count = 0; addrs != NULL && addrs[count] != NULL; count++);
+ put32(buf, version, count);
+ for (i = 0; i < count; i++) {
+ put16(buf, version, addrs[i]->addrtype);
+ put_len_bytes(buf, version, addrs[i]->contents, addrs[i]->length);
+ }
+}
+
+static void
+marshal_authdata(struct k5buf *buf, int version, krb5_authdata **authdata)
+{
+ size_t i, count;
+
+ for (count = 0; authdata != NULL && authdata[count] != NULL; count++);
+ put32(buf, version, count);
+ for (i = 0; i < count; i++) {
+ put16(buf, version, authdata[i]->ad_type);
+ put_len_bytes(buf, version, authdata[i]->contents,
+ authdata[i]->length);
+ }
+}
+
+/* Marshal a credential using the specified file ccache version (expressed as
+ * an integer from 1 to 4). */
+void
+k5_marshal_cred(struct k5buf *buf, int version, krb5_creds *creds)
+{
+ char is_skey;
+
+ k5_marshal_princ(buf, version, creds->client);
+ k5_marshal_princ(buf, version, creds->server);
+ marshal_keyblock(buf, version, &creds->keyblock);
+ put32(buf, version, creds->times.authtime);
+ put32(buf, version, creds->times.starttime);
+ put32(buf, version, creds->times.endtime);
+ put32(buf, version, creds->times.renew_till);
+ is_skey = creds->is_skey;
+ k5_buf_add_len(buf, &is_skey, 1);
+ put32(buf, version, creds->ticket_flags);
+ marshal_addrs(buf, version, creds->addresses);
+ marshal_authdata(buf, version, creds->authdata);
+ put_data(buf, version, &creds->ticket);
+ put_data(buf, version, &creds->second_ticket);
+}
+
+#define SC_CLIENT_PRINCIPAL 0x0001
+#define SC_SERVER_PRINCIPAL 0x0002
+#define SC_SESSION_KEY 0x0004
+#define SC_TICKET 0x0008
+#define SC_SECOND_TICKET 0x0010
+#define SC_AUTHDATA 0x0020
+#define SC_ADDRESSES 0x0040
+
+/* Construct the header flags field for a matching credential for the Heimdal
+ * KCM format. */
+static uint32_t
+mcred_header(krb5_creds *mcred)
+{
+ uint32_t header = 0;
+
+ if (mcred->client != NULL)
+ header |= SC_CLIENT_PRINCIPAL;
+ if (mcred->server != NULL)
+ header |= SC_SERVER_PRINCIPAL;
+ if (mcred->keyblock.enctype != ENCTYPE_NULL)
+ header |= SC_SESSION_KEY;
+ if (mcred->ticket.length > 0)
+ header |= SC_TICKET;
+ if (mcred->second_ticket.length > 0)
+ header |= SC_SECOND_TICKET;
+ if (mcred->authdata != NULL && *mcred->authdata != NULL)
+ header |= SC_AUTHDATA;
+ if (mcred->addresses != NULL && *mcred->addresses != NULL)
+ header |= SC_ADDRESSES;
+ return header;
+}
+
+/*
+ * Marshal a matching credential in the Heimdal KCM format. Matching
+ * credentials are used to identify an existing credential to retrieve or
+ * remove from a cache.
+ */
+void
+k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred)
+{
+ const int version = 4; /* subfields use v4 file format */
+ uint32_t header;
+ char is_skey;
+
+ header = mcred_header(mcred);
+ put32(buf, version, header);
+ if (mcred->client != NULL)
+ k5_marshal_princ(buf, version, mcred->client);
+ if (mcred->server != NULL)
+ k5_marshal_princ(buf, version, mcred->server);
+ if (mcred->keyblock.enctype != ENCTYPE_NULL)
+ marshal_keyblock(buf, version, &mcred->keyblock);
+ put32(buf, version, mcred->times.authtime);
+ put32(buf, version, mcred->times.starttime);
+ put32(buf, version, mcred->times.endtime);
+ put32(buf, version, mcred->times.renew_till);
+ is_skey = mcred->is_skey;
+ k5_buf_add_len(buf, &is_skey, 1);
+ put32(buf, version, mcred->ticket_flags);
+ if (mcred->addresses != NULL && *mcred->addresses != NULL)
+ marshal_addrs(buf, version, mcred->addresses);
+ if (mcred->authdata != NULL && *mcred->authdata != NULL)
+ marshal_authdata(buf, version, mcred->authdata);
+ if (mcred->ticket.length > 0)
+ put_data(buf, version, &mcred->ticket);
+ if (mcred->second_ticket.length > 0)
+ put_data(buf, version, &mcred->second_ticket);
+}
diff --git a/src/lib/krb5/ccache/ccselect.c b/src/lib/krb5/ccache/ccselect.c
new file mode 100644
index 0000000000000..2f3071a272a2a
--- /dev/null
+++ b/src/lib/krb5/ccache/ccselect.c
@@ -0,0 +1,179 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccselect.c - krb5_cc_select API and module loader */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include <krb5/ccselect_plugin.h>
+#include "../krb/int-proto.h"
+
+struct ccselect_module_handle {
+ struct krb5_ccselect_vtable_st vt;
+ krb5_ccselect_moddata data;
+ int priority;
+};
+
+static void
+free_handles(krb5_context context, struct ccselect_module_handle **handles)
+{
+ struct ccselect_module_handle *h, **hp;
+
+ if (handles == NULL)
+ return;
+ for (hp = handles; *hp != NULL; hp++) {
+ h = *hp;
+ if (h->vt.fini)
+ h->vt.fini(context, h->data);
+ free(h);
+ }
+ free(handles);
+}
+
+static krb5_error_code
+load_modules(krb5_context context)
+{
+ krb5_error_code ret;
+ struct ccselect_module_handle **list = NULL, *handle;
+ krb5_plugin_initvt_fn *modules = NULL, *mod;
+ size_t count;
+
+#ifndef _WIN32
+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "k5identity",
+ ccselect_k5identity_initvt);
+ if (ret != 0)
+ goto cleanup;
+#endif
+
+ ret = k5_plugin_register(context, PLUGIN_INTERFACE_CCSELECT, "realm",
+ ccselect_realm_initvt);
+ if (ret != 0)
+ goto cleanup;
+
+ ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CCSELECT, &modules);
+ if (ret != 0)
+ goto cleanup;
+
+ /* Allocate a large enough list of handles. */
+ for (count = 0; modules[count] != NULL; count++);
+ list = k5calloc(count + 1, sizeof(*list), &ret);
+ if (list == NULL)
+ goto cleanup;
+
+ /* Initialize each module, ignoring ones that fail. */
+ count = 0;
+ for (mod = modules; *mod != NULL; mod++) {
+ handle = k5alloc(sizeof(*handle), &ret);
+ if (handle == NULL)
+ goto cleanup;
+ ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt);
+ if (ret != 0) { /* Failed vtable init is non-fatal. */
+ TRACE_CCSELECT_VTINIT_FAIL(context, ret);
+ free(handle);
+ continue;
+ }
+ handle->data = NULL;
+ ret = handle->vt.init(context, &handle->data, &handle->priority);
+ if (ret != 0) { /* Failed initialization is non-fatal. */
+ TRACE_CCSELECT_INIT_FAIL(context, handle->vt.name, ret);
+ free(handle);
+ continue;
+ }
+ list[count++] = handle;
+ list[count] = NULL;
+ }
+ list[count] = NULL;
+
+ ret = 0;
+ context->ccselect_handles = list;
+ list = NULL;
+
+cleanup:
+ k5_plugin_free_modules(context, modules);
+ free_handles(context, list);
+ return ret;
+}
+
+static krb5_error_code
+choose(krb5_context context, struct ccselect_module_handle *h,
+ krb5_principal server, krb5_ccache *cache_out,
+ krb5_principal *princ_out)
+{
+ return h->vt.choose(context, h->data, server, cache_out, princ_out);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_select(krb5_context context, krb5_principal server,
+ krb5_ccache *cache_out, krb5_principal *princ_out)
+{
+ krb5_error_code ret;
+ int priority;
+ struct ccselect_module_handle **hp, *h;
+ krb5_ccache cache;
+ krb5_principal princ;
+
+ *cache_out = NULL;
+ *princ_out = NULL;
+
+ if (context->ccselect_handles == NULL) {
+ ret = load_modules(context);
+ if (ret)
+ return ret;
+ }
+
+ /* Consult authoritative modules first, then heuristic ones. */
+ for (priority = KRB5_CCSELECT_PRIORITY_AUTHORITATIVE;
+ priority >= KRB5_CCSELECT_PRIORITY_HEURISTIC; priority--) {
+ for (hp = context->ccselect_handles; *hp != NULL; hp++) {
+ h = *hp;
+ if (h->priority != priority)
+ continue;
+ ret = choose(context, h, server, &cache, &princ);
+ if (ret == 0) {
+ TRACE_CCSELECT_MODCHOICE(context, h->vt.name, server, cache,
+ princ);
+ *cache_out = cache;
+ *princ_out = princ;
+ return 0;
+ } else if (ret == KRB5_CC_NOTFOUND) {
+ TRACE_CCSELECT_MODNOTFOUND(context, h->vt.name, server, princ);
+ *princ_out = princ;
+ return ret;
+ } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
+ TRACE_CCSELECT_MODFAIL(context, h->vt.name, ret, server);
+ return ret;
+ }
+ }
+ }
+
+ TRACE_CCSELECT_NOTFOUND(context, server);
+ return KRB5_CC_NOTFOUND;
+}
+
+void
+k5_ccselect_free_context(krb5_context context)
+{
+ free_handles(context, context->ccselect_handles);
+ context->ccselect_handles = NULL;
+}
diff --git a/src/lib/krb5/ccache/ccselect_k5identity.c b/src/lib/krb5/ccache/ccselect_k5identity.c
new file mode 100644
index 0000000000000..bee54165873a4
--- /dev/null
+++ b/src/lib/krb5/ccache/ccselect_k5identity.c
@@ -0,0 +1,210 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccselect_k5identity.c - k5identity ccselect module */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include <krb5/ccselect_plugin.h>
+#include <ctype.h>
+
+#ifndef _WIN32
+
+#include <pwd.h>
+
+static krb5_error_code
+k5identity_init(krb5_context context, krb5_ccselect_moddata *data_out,
+ int *priority_out)
+{
+ *data_out = NULL;
+ *priority_out = KRB5_CCSELECT_PRIORITY_AUTHORITATIVE;
+ return 0;
+}
+
+/* Match data (folded to lowercase if fold_case is set) against pattern. */
+static krb5_boolean
+fnmatch_data(const char *pattern, krb5_data *data, krb5_boolean fold_case)
+{
+ krb5_error_code ret;
+ char *str, *p;
+ int res;
+
+ str = k5memdup0(data->data, data->length, &ret);
+ if (str == NULL)
+ return FALSE;
+
+ if (fold_case) {
+ for (p = str; *p != '\0'; p++) {
+ if (isupper((unsigned char)*p))
+ *p = tolower((unsigned char)*p);
+ }
+ }
+
+ res = fnmatch(pattern, str, 0);
+ free(str);
+ return (res == 0);
+}
+
+/* Return true if server satisfies the constraint given by name and value. */
+static krb5_boolean
+check_constraint(krb5_context context, const char *name, const char *value,
+ krb5_principal server)
+{
+ if (strcmp(name, "realm") == 0) {
+ return fnmatch_data(value, &server->realm, FALSE);
+ } else if (strcmp(name, "service") == 0) {
+ return (server->type == KRB5_NT_SRV_HST && server->length >= 2 &&
+ fnmatch_data(value, &server->data[0], FALSE));
+ } else if (strcmp(name, "host") == 0) {
+ return (server->type == KRB5_NT_SRV_HST && server->length >= 2 &&
+ fnmatch_data(value, &server->data[1], TRUE));
+ }
+ /* Assume unrecognized constraints are critical. */
+ return FALSE;
+}
+
+/*
+ * If line begins with a valid principal and server matches the constraints
+ * listed afterwards, set *princ_out to the client principal described in line
+ * and return true. Otherwise return false. May destructively affect line.
+ */
+static krb5_boolean
+parse_line(krb5_context context, char *line, krb5_principal server,
+ krb5_principal *princ_out)
+{
+ const char *whitespace = " \t\r\n";
+ char *princ, *princ_end, *field, *field_end, *sep;
+
+ *princ_out = NULL;
+
+ /* Find the bounds of the principal. */
+ princ = line + strspn(line, whitespace);
+ if (*princ == '#')
+ return FALSE;
+ princ_end = princ + strcspn(princ, whitespace);
+ if (princ_end == princ)
+ return FALSE;
+
+ /* Check all constraints. */
+ field = princ_end + strspn(princ_end, whitespace);
+ while (*field != '\0') {
+ field_end = field + strcspn(field, whitespace);
+ if (*field_end != '\0')
+ *field_end++ = '\0';
+ sep = strchr(field, '=');
+ if (sep == NULL) /* Malformed line. */
+ return FALSE;
+ *sep = '\0';
+ if (!check_constraint(context, field, sep + 1, server))
+ return FALSE;
+ field = field_end + strspn(field_end, whitespace);
+ }
+
+ *princ_end = '\0';
+ return (krb5_parse_name(context, princ, princ_out) == 0);
+}
+
+/* Determine the current user's homedir. Allow HOME to override the result for
+ * non-secure profiles; otherwise, use the euid's homedir from passwd. */
+static char *
+get_homedir(krb5_context context)
+{
+ const char *homedir = NULL;
+ char pwbuf[BUFSIZ];
+ struct passwd pwx, *pwd;
+
+ if (!context->profile_secure)
+ homedir = getenv("HOME");
+
+ if (homedir == NULL) {
+ if (k5_getpwuid_r(geteuid(), &pwx, pwbuf, sizeof(pwbuf), &pwd) != 0)
+ return NULL;
+ homedir = pwd->pw_dir;
+ }
+
+ return strdup(homedir);
+}
+
+static krb5_error_code
+k5identity_choose(krb5_context context, krb5_ccselect_moddata data,
+ krb5_principal server, krb5_ccache *cache_out,
+ krb5_principal *princ_out)
+{
+ krb5_error_code ret;
+ krb5_principal princ = NULL;
+ char *filename, *homedir;
+ FILE *fp;
+ char buf[256];
+
+ *cache_out = NULL;
+ *princ_out = NULL;
+
+ /* Open the .k5identity file. */
+ homedir = get_homedir(context);
+ if (homedir == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ ret = k5_path_join(homedir, ".k5identity", &filename);
+ free(homedir);
+ if (ret)
+ return ret;
+ fp = fopen(filename, "r");
+ free(filename);
+ if (fp == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ /* Look for a line with constraints matched by server. */
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (parse_line(context, buf, server, &princ))
+ break;
+ }
+ fclose(fp);
+ if (princ == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ /* Look for a ccache with the appropriate client principal. If we don't
+ * find out, set *princ_out to indicate the desired client principal. */
+ ret = krb5_cc_cache_match(context, princ, cache_out);
+ if (ret == 0 || ret == KRB5_CC_NOTFOUND)
+ *princ_out = princ;
+ else
+ krb5_free_principal(context, princ);
+ return ret;
+}
+
+krb5_error_code
+ccselect_k5identity_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_ccselect_vtable vt;
+
+ if (maj_ver != 1)
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ vt = (krb5_ccselect_vtable)vtable;
+ vt->name = "k5identity";
+ vt->init = k5identity_init;
+ vt->choose = k5identity_choose;
+ return 0;
+}
+
+#endif /* not _WIN32 */
diff --git a/src/lib/krb5/ccache/ccselect_realm.c b/src/lib/krb5/ccache/ccselect_realm.c
new file mode 100644
index 0000000000000..3a4b4af136a9a
--- /dev/null
+++ b/src/lib/krb5/ccache/ccselect_realm.c
@@ -0,0 +1,95 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ccselect_realm.c - realm ccselect module */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include <krb5/ccselect_plugin.h>
+
+static krb5_error_code
+realm_init(krb5_context context, krb5_ccselect_moddata *data_out,
+ int *priority_out)
+{
+ *data_out = NULL;
+ *priority_out = KRB5_CCSELECT_PRIORITY_HEURISTIC;
+ return 0;
+}
+
+static krb5_error_code
+realm_choose(krb5_context context, krb5_ccselect_moddata data,
+ krb5_principal server, krb5_ccache *cache_out,
+ krb5_principal *princ_out)
+{
+ krb5_error_code ret;
+ krb5_cccol_cursor cursor;
+ krb5_ccache cache;
+ krb5_principal princ;
+
+ *cache_out = NULL;
+ *princ_out = NULL;
+
+ if (krb5_is_referral_realm(&server->realm))
+ return KRB5_PLUGIN_NO_HANDLE;
+
+ /* Scan the collection for a cache with a client principal in the same
+ * realm as the server principal. */
+ ret = krb5_cccol_cursor_new(context, &cursor);
+ if (ret)
+ return ret;
+ while ((ret = krb5_cccol_cursor_next(context, cursor, &cache)) == 0 &&
+ cache != NULL) {
+ ret = krb5_cc_get_principal(context, cache, &princ);
+ if (ret == 0) {
+ if (data_eq(princ->realm, server->realm))
+ break;
+ krb5_free_principal(context, princ);
+ }
+ krb5_cc_close(context, cache);
+ }
+ krb5_cccol_cursor_free(context, &cursor);
+ if (ret)
+ return ret;
+
+ if (cache == NULL)
+ return KRB5_PLUGIN_NO_HANDLE;
+ *cache_out = cache;
+ *princ_out = princ;
+ return 0;
+}
+
+krb5_error_code
+ccselect_realm_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ krb5_ccselect_vtable vt;
+
+ if (maj_ver != 1)
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ vt = (krb5_ccselect_vtable)vtable;
+ vt->name = "realm";
+ vt->init = realm_init;
+ vt->choose = realm_choose;
+ return 0;
+}
diff --git a/src/lib/krb5/ccache/deps b/src/lib/krb5/ccache/deps
new file mode 100644
index 0000000000000..9cd2e00d456c8
--- /dev/null
+++ b/src/lib/krb5/ccache/deps
@@ -0,0 +1,221 @@
+#
+# Generated makefile dependencies follow.
+#
+ccbase.so ccbase.po $(OUTPRE)ccbase.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h ccbase.c \
+ fcc.h
+cccopy.so cccopy.po $(OUTPRE)cccopy.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cccopy.c
+cccursor.so cccursor.po $(OUTPRE)cccursor.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h cccursor.c
+ccdefault.so ccdefault.po $(OUTPRE)ccdefault.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ ccdefault.c
+ccdefops.so ccdefops.po $(OUTPRE)ccdefops.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ ccdefops.c fcc.h
+ccmarshal.so ccmarshal.po $(OUTPRE)ccmarshal.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-input.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h ccmarshal.c
+ccselect.so ccselect.po $(OUTPRE)ccselect.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/ccselect_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ cc-int.h ccselect.c
+ccselect_k5identity.so ccselect_k5identity.po $(OUTPRE)ccselect_k5identity.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/ccselect_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h ccselect_k5identity.c
+ccselect_realm.so ccselect_realm.po $(OUTPRE)ccselect_realm.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/ccselect_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h ccselect_realm.c
+cc_dir.so cc_dir.po $(OUTPRE)cc_dir.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h cc_dir.c
+cc_retr.so cc_retr.po $(OUTPRE)cc_retr.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ cc-int.h cc_retr.c
+cc_file.so cc_file.po $(OUTPRE)cc_file.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h cc_file.c
+cc_kcm.so cc_kcm.po $(OUTPRE)cc_kcm.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-input.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/kcm.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h cc_kcm.c
+cc_memory.so cc_memory.po $(OUTPRE)cc_memory.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h cc_memory.c
+cc_keyring.so cc_keyring.po $(OUTPRE)cc_keyring.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ cc-int.h cc_keyring.c
+ccfns.so ccfns.po $(OUTPRE)ccfns.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../krb/int-proto.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ cc-int.h ccfns.c
+ser_cc.so ser_cc.po $(OUTPRE)ser_cc.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h ser_cc.c
+t_cc.so t_cc.po $(OUTPRE)t_cc.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h cc-int.h t_cc.c
+t_cccol.so t_cccol.po $(OUTPRE)t_cccol.$(OBJEXT): $(BUILDTOP)/include/krb5/krb5.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/krb5.h t_cccol.c
+t_cccursor.so t_cccursor.po $(OUTPRE)t_cccursor.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ t_cccursor.c
+t_marshal.so t_marshal.po $(OUTPRE)t_marshal.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ cc-int.h t_marshal.c
diff --git a/src/lib/krb5/ccache/fcc.h b/src/lib/krb5/ccache/fcc.h
new file mode 100644
index 0000000000000..b31c3a7af7ed9
--- /dev/null
+++ b/src/lib/krb5/ccache/fcc.h
@@ -0,0 +1,41 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/fcc.h */
+/*
+ * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ *
+ * This file contains constant and function declarations used in the
+ * file-based credential cache routines.
+ */
+
+#ifndef __KRB5_FILE_CCACHE__
+#define __KRB5_FILE_CCACHE__
+
+extern const krb5_cc_ops krb5_cc_file_ops;
+
+krb5_error_code krb5int_fcc_new_unique(krb5_context context, char *template,
+ krb5_ccache *id);
+
+#endif /* __KRB5_FILE_CCACHE__ */
diff --git a/src/lib/krb5/ccache/kcmrpc.defs b/src/lib/krb5/ccache/kcmrpc.defs
new file mode 100644
index 0000000000000..997bae6dc1538
--- /dev/null
+++ b/src/lib/krb5/ccache/kcmrpc.defs
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2009 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <mach/std_types.defs>
+#include <mach/mach_types.defs>
+
+type k5_kcm_inband_msg = array [ * : 2048 ] of char;
+type k5_kcm_outband_msg = array [] of char;
+
+import "kcmrpc_types.h";
+
+subsystem mheim_ipc 1;
+userprefix k5_kcmrpc_;
+serverprefix k5_kcmrpc_server_;
+
+routine call(
+ server_port : mach_port_t;
+ ServerAuditToken client_creds : audit_token_t;
+ sreplyport reply_port : mach_port_make_send_once_t;
+ in requestin : k5_kcm_inband_msg;
+ in requestout : k5_kcm_outband_msg;
+ out returnvalue : int;
+ out replyin : k5_kcm_inband_msg;
+ out replyout : k5_kcm_outband_msg, dealloc);
diff --git a/src/lib/krb5/ccache/kcmrpc_types.h b/src/lib/krb5/ccache/kcmrpc_types.h
new file mode 100644
index 0000000000000..f43b41e5cdb05
--- /dev/null
+++ b/src/lib/krb5/ccache/kcmrpc_types.h
@@ -0,0 +1,39 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/kcmrpc_types.h - KCM RPC type definitions */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef KCMRPC_H
+#define KCMRPC_H
+
+typedef char k5_kcm_inband_msg[2048];
+typedef char *k5_kcm_outband_msg;
+
+#endif
diff --git a/src/lib/krb5/ccache/scc.h b/src/lib/krb5/ccache/scc.h
new file mode 100644
index 0000000000000..6c23614b60cbf
--- /dev/null
+++ b/src/lib/krb5/ccache/scc.h
@@ -0,0 +1,88 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/scc.h */
+/*
+ * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ *
+ * This file contains constant and function declarations used in the
+ * file-based credential cache routines.
+ */
+
+#ifndef __KRB5_FILE_CCACHE__
+#define __KRB5_FILE_CCACHE__
+
+#include "k5-int.h"
+#include <stdio.h>
+
+#define KRB5_OK 0
+
+#define KRB5_SCC_MAXLEN 100
+
+/*
+ * SCC version 2 contains type information for principals. SCC
+ * version 1 does not. The code will accept either, and depending on
+ * what KRB5_SCC_DEFAULT_FVNO is set to, it will create version 1 or
+ * version 2 SCC caches.
+ *
+ */
+
+#define KRB5_SCC_FVNO_1 0x0501 /* krb v5, scc v1 */
+#define KRB5_SCC_FVNO_2 0x0502 /* krb v5, scc v2 */
+#define KRB5_SCC_FVNO_3 0x0503 /* krb v5, scc v3 */
+#define KRB5_SCC_FVNO_4 0x0504 /* krb v5, scc v4 */
+
+#define SCC_OPEN_AND_ERASE 1
+#define SCC_OPEN_RDWR 2
+#define SCC_OPEN_RDONLY 3
+
+/* Credential file header tags.
+ * The header tags are constructed as:
+ * krb5_ui_2 tag
+ * krb5_ui_2 len
+ * krb5_octet data[len]
+ * This format allows for older versions of the fcc processing code to skip
+ * past unrecognized tag formats.
+ */
+#define SCC_TAG_DELTATIME 1
+
+#ifndef TKT_ROOT
+#define TKT_ROOT "/tmp/tkt"
+#endif
+
+typedef struct _krb5_scc_data {
+ char *filename;
+ FILE *file;
+ krb5_flags flags;
+ char stdio_buffer[BUFSIZ];
+ int version;
+} krb5_scc_data;
+
+/* An off_t can be arbitrarily complex */
+typedef struct _krb5_scc_cursor {
+ long pos;
+} krb5_scc_cursor;
+
+/* DO NOT ADD ANYTHING AFTER THIS #endif */
+#endif /* __KRB5_FILE_CCACHE__ */
diff --git a/src/lib/krb5/ccache/ser_cc.c b/src/lib/krb5/ccache/ser_cc.c
new file mode 100644
index 0000000000000..fd6aed50d74f9
--- /dev/null
+++ b/src/lib/krb5/ccache/ser_cc.c
@@ -0,0 +1,215 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/ser_cc.c - Serialize credential cache context */
+/*
+ * Copyright 1995 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+
+/*
+ * Routines to deal with externalizing krb5_ccache.
+ * krb5_ccache_size();
+ * krb5_ccache_externalize();
+ * krb5_ccache_internalize();
+ */
+static krb5_error_code krb5_ccache_size
+(krb5_context, krb5_pointer, size_t *);
+static krb5_error_code krb5_ccache_externalize
+(krb5_context, krb5_pointer, krb5_octet **, size_t *);
+static krb5_error_code krb5_ccache_internalize
+(krb5_context,krb5_pointer *, krb5_octet **, size_t *);
+
+/*
+ * Serialization entry for this type.
+ */
+static const krb5_ser_entry krb5_ccache_ser_entry = {
+ KV5M_CCACHE, /* Type */
+ krb5_ccache_size, /* Sizer routine */
+ krb5_ccache_externalize, /* Externalize routine */
+ krb5_ccache_internalize /* Internalize routine */
+};
+
+/*
+ * krb5_ccache_size() - Determine the size required to externalize
+ * this krb5_ccache variant.
+ */
+static krb5_error_code
+krb5_ccache_size(krb5_context kcontext, krb5_pointer arg, size_t *sizep)
+{
+ krb5_error_code kret;
+ krb5_ccache ccache;
+ size_t required;
+
+ kret = EINVAL;
+ if ((ccache = (krb5_ccache) arg)) {
+ /*
+ * Saving FILE: variants of krb5_ccache requires at minimum:
+ * krb5_int32 for KV5M_CCACHE
+ * krb5_int32 for length of ccache name.
+ * krb5_int32 for KV5M_CCACHE
+ */
+ required = sizeof(krb5_int32) * 3;
+ if (ccache->ops->prefix)
+ required += (strlen(ccache->ops->prefix)+1);
+
+ /*
+ * The ccache name is formed as follows:
+ * <prefix>:<name>
+ */
+ required += strlen(krb5_cc_get_name(kcontext, ccache));
+
+ kret = 0;
+ *sizep += required;
+ }
+ return(kret);
+}
+
+/*
+ * krb5_ccache_externalize() - Externalize the krb5_ccache.
+ */
+static krb5_error_code
+krb5_ccache_externalize(krb5_context kcontext, krb5_pointer arg, krb5_octet **buffer, size_t *lenremain)
+{
+ krb5_error_code kret;
+ krb5_ccache ccache;
+ size_t required;
+ krb5_octet *bp;
+ size_t remain;
+ char *ccname;
+ const char *fnamep;
+
+ required = 0;
+ bp = *buffer;
+ remain = *lenremain;
+ kret = EINVAL;
+ if ((ccache = (krb5_ccache) arg)) {
+ kret = ENOMEM;
+ if (!krb5_ccache_size(kcontext, arg, &required) &&
+ (required <= remain)) {
+ /* Our identifier */
+ (void) krb5_ser_pack_int32(KV5M_CCACHE, &bp, &remain);
+
+ fnamep = krb5_cc_get_name(kcontext, ccache);
+
+ if (ccache->ops->prefix) {
+ if (asprintf(&ccname, "%s:%s", ccache->ops->prefix, fnamep) < 0)
+ ccname = NULL;
+ } else
+ ccname = strdup(fnamep);
+
+ if (ccname) {
+ /* Put the length of the file name */
+ (void) krb5_ser_pack_int32((krb5_int32) strlen(ccname),
+ &bp, &remain);
+
+ /* Put the name */
+ (void) krb5_ser_pack_bytes((krb5_octet *) ccname,
+ strlen(ccname),
+ &bp, &remain);
+
+ /* Put the trailer */
+ (void) krb5_ser_pack_int32(KV5M_CCACHE, &bp, &remain);
+ kret = 0;
+ *buffer = bp;
+ *lenremain = remain;
+ free(ccname);
+ }
+ }
+ }
+ return(kret);
+}
+
+/*
+ * krb5_ccache_internalize() - Internalize the krb5_ccache.
+ */
+static krb5_error_code
+krb5_ccache_internalize(krb5_context kcontext, krb5_pointer *argp, krb5_octet **buffer, size_t *lenremain)
+{
+ krb5_error_code kret;
+ krb5_ccache ccache;
+ krb5_int32 ibuf;
+ krb5_octet *bp;
+ size_t remain;
+ char *ccname = NULL;
+
+ *argp = NULL;
+
+ bp = *buffer;
+ remain = *lenremain;
+
+ /* Read our magic number. */
+ kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
+ if (kret)
+ return kret;
+ if (ibuf != KV5M_CCACHE)
+ return EINVAL;
+
+ /* Unpack and validate the length of the ccache name. */
+ kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
+ if (kret)
+ return kret;
+ if (ibuf < 0 || (krb5_ui_4) ibuf > remain)
+ return EINVAL;
+
+ /* Allocate and unpack the name. */
+ ccname = malloc(ibuf + 1);
+ if (!ccname)
+ return ENOMEM;
+ kret = krb5_ser_unpack_bytes((krb5_octet *) ccname, (size_t) ibuf,
+ &bp, &remain);
+ if (kret)
+ goto cleanup;
+ ccname[ibuf] = '\0';
+
+ /* Read the second magic number. */
+ kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
+ if (kret)
+ goto cleanup;
+ if (ibuf != KV5M_CCACHE) {
+ kret = EINVAL;
+ goto cleanup;
+ }
+
+ /* Resolve the named credential cache. */
+ kret = krb5_cc_resolve(kcontext, ccname, &ccache);
+ if (kret)
+ goto cleanup;
+
+ *buffer = bp;
+ *lenremain = remain;
+ *argp = ccache;
+
+cleanup:
+ free(ccname);
+ return(kret);
+}
+
+/*
+ * Register the ccache serializer.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_ser_ccache_init(krb5_context kcontext)
+{
+ return(krb5_register_serializer(kcontext, &krb5_ccache_ser_entry));
+}
diff --git a/src/lib/krb5/ccache/t_cc.c b/src/lib/krb5/ccache/t_cc.c
new file mode 100644
index 0000000000000..6069cabd33aab
--- /dev/null
+++ b/src/lib/krb5/ccache/t_cc.c
@@ -0,0 +1,439 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/t_cc.c */
+/*
+ * Copyright 2000 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "cc-int.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "autoconf.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include "com_err.h"
+
+#define KRB5_OK 0
+
+krb5_creds test_creds;
+
+int debug=0;
+
+static void
+init_structs(void)
+{
+ static int add=0x12345;
+
+ static krb5_address addr;
+
+ static krb5_address *addrs[] = {
+ &addr,
+ 0,
+ };
+
+ addr.magic = KV5M_ADDRESS;
+ addr.addrtype = ADDRTYPE_INET;
+ addr.length = 4;
+ addr.contents = (krb5_octet *) &add;
+
+ test_creds.magic = KV5M_CREDS;
+ test_creds.client = NULL;
+ test_creds.server = NULL;
+
+ test_creds.keyblock.magic = KV5M_KEYBLOCK;
+ test_creds.keyblock.contents = 0;
+ test_creds.keyblock.enctype = 1;
+ test_creds.keyblock.length = 1;
+ test_creds.keyblock.contents = (unsigned char *) "1";
+ test_creds.times.authtime = 1111;
+ test_creds.times.starttime = 2222;
+ test_creds.times.endtime = 3333;
+ test_creds.times.renew_till = 4444;
+ test_creds.is_skey = 1;
+ test_creds.ticket_flags = 5555;
+ test_creds.addresses = addrs;
+
+#define SET_TICKET(ent, str) {ent.magic = KV5M_DATA; ent.length = sizeof(str); ent.data = str;}
+ SET_TICKET(test_creds.ticket, "This is ticket 1");
+ SET_TICKET(test_creds.second_ticket, "This is ticket 2");
+ test_creds.authdata = NULL;
+}
+
+static krb5_error_code
+init_test_cred(krb5_context context)
+{
+ krb5_error_code kret;
+ unsigned int i;
+ krb5_authdata *a;
+#define REALM "REALM"
+ kret = krb5_build_principal(context, &test_creds.client, sizeof(REALM), REALM,
+ "client-comp1", "client-comp2", NULL);
+ if(kret)
+ return kret;
+
+ kret = krb5_build_principal(context, &test_creds.server, sizeof(REALM), REALM,
+ "server-comp1", "server-comp2", NULL);
+ if(kret) {
+ krb5_free_principal(context, test_creds.client);
+ test_creds.client = 0;
+ goto cleanup;
+ }
+
+ test_creds.authdata = malloc (3 * sizeof(krb5_authdata *));
+ if (!test_creds.authdata) {
+ kret = ENOMEM;
+ goto cleanup;
+ }
+
+ for (i = 0 ; i <= 2 ; i++) {
+ test_creds.authdata[i] = 0;
+ }
+ a = (krb5_authdata *) malloc(sizeof(krb5_authdata));
+ if(!a) {
+ kret = ENOMEM;
+ goto cleanup;
+ }
+ a->magic = KV5M_AUTHDATA;
+ a->ad_type = KRB5_AUTHDATA_IF_RELEVANT;
+ a->contents = (krb5_octet * ) malloc(1);
+ if(!a->contents) {
+ free(a);
+ kret = ENOMEM;
+ goto cleanup;
+ }
+ a->contents[0]=5;
+ a->length = 1;
+ test_creds.authdata[0] = a;
+
+ a = (krb5_authdata *) malloc(sizeof(krb5_authdata));
+ if(!a) {
+ kret = ENOMEM;
+ goto cleanup;
+ }
+ a->magic = KV5M_AUTHDATA;
+ a->ad_type = KRB5_AUTHDATA_KDC_ISSUED;
+ a->contents = (krb5_octet * ) malloc(2);
+ if(!a->contents) {
+ free(a);
+ kret = ENOMEM;
+ goto cleanup;
+ }
+ a->contents[0]=4;
+ a->contents[1]=6;
+ a->length = 2;
+ test_creds.authdata[1] = a;
+
+cleanup:
+ if(kret) {
+ if (test_creds.client) {
+ krb5_free_principal(context, test_creds.client);
+ test_creds.client = 0;
+ }
+ if (test_creds.server) {
+ krb5_free_principal(context, test_creds.server);
+ test_creds.server = 0;
+
+ }
+ if (test_creds.authdata) {
+ krb5_free_authdata(context, test_creds.authdata);
+ test_creds.authdata = 0;
+ }
+ }
+
+ return kret;
+}
+
+static void
+free_test_cred(krb5_context context)
+{
+ krb5_free_principal(context, test_creds.client);
+
+ krb5_free_principal(context, test_creds.server);
+
+ if(test_creds.authdata) {
+ krb5_free_authdata(context, test_creds.authdata);
+ test_creds.authdata = 0;
+ }
+}
+
+#define CHECK(kret,msg) \
+ if (kret != KRB5_OK) { \
+ com_err(msg, kret, ""); \
+ fflush(stderr); \
+ exit(1); \
+ } else if(debug) printf("%s went ok\n", msg);
+
+#define CHECK_STR(str,msg) \
+ if (str == 0) { \
+ com_err(msg, kret, ""); \
+ exit(1); \
+ } else if(debug) printf("%s went ok\n", msg);
+
+#define CHECK_BOOL(expr,errstr,msg) \
+ if (expr) { \
+ fprintf(stderr, "%s %s\n", msg, errstr); \
+ exit(1); \
+ } else if(debug) printf("%s went ok\n", msg);
+
+#define CHECK_FAIL(experr, kret, msg) \
+ if (experr != kret) { CHECK(kret, msg);}
+
+static void
+cc_test(krb5_context context, const char *name, krb5_flags flags)
+{
+ krb5_ccache id, id2;
+ krb5_creds creds;
+ krb5_error_code kret;
+ krb5_cc_cursor cursor;
+ krb5_principal tmp;
+
+ const char *c_name;
+ char newcache[300];
+ char *save_type;
+
+ kret = init_test_cred(context);
+ CHECK(kret, "init_creds");
+
+ kret = krb5_cc_resolve(context, name, &id);
+ CHECK(kret, "resolve");
+ kret = krb5_cc_initialize(context, id, test_creds.client);
+ CHECK(kret, "initialize");
+
+ c_name = krb5_cc_get_name(context, id);
+ CHECK_STR(c_name, "get_name");
+
+ c_name = krb5_cc_get_type(context, id);
+ CHECK_STR(c_name, "get_type");
+ save_type=strdup(c_name);
+ CHECK_STR(save_type, "copying type");
+
+ kret = krb5_cc_store_cred(context, id, &test_creds);
+ CHECK(kret, "store");
+
+ kret = krb5_cc_get_principal(context, id, &tmp);
+ CHECK(kret, "get_principal");
+
+ CHECK_BOOL(krb5_realm_compare(context, tmp, test_creds.client) != TRUE,
+ "realms do not match", "realm_compare");
+
+
+ CHECK_BOOL(krb5_principal_compare(context, tmp, test_creds.client) != TRUE,
+ "principals do not match", "principal_compare");
+
+ krb5_free_principal(context, tmp);
+
+ kret = krb5_cc_set_flags (context, id, flags);
+ CHECK(kret, "set_flags");
+
+ kret = krb5_cc_start_seq_get(context, id, &cursor);
+ CHECK(kret, "start_seq_get");
+ kret = 0;
+ while (kret != KRB5_CC_END) {
+ if(debug) printf("Calling next_cred\n");
+ kret = krb5_cc_next_cred(context, id, &cursor, &creds);
+ if(kret == KRB5_CC_END) {
+ if(debug) printf("next_cred: ok at end\n");
+ }
+ else {
+ CHECK(kret, "next_cred");
+ krb5_free_cred_contents(context, &creds);
+ }
+
+ }
+ kret = krb5_cc_end_seq_get(context, id, &cursor);
+ CHECK(kret, "end_seq_get");
+
+ kret = krb5_cc_close(context, id);
+ CHECK(kret, "close");
+
+
+ /* ------------------------------------------------- */
+ kret = krb5_cc_resolve(context, name, &id);
+ CHECK(kret, "resolve2");
+
+ {
+ /* Copy the cache test*/
+ snprintf(newcache, sizeof(newcache), "%s.new", name);
+ kret = krb5_cc_resolve(context, newcache, &id2);
+ CHECK(kret, "resolve of new cache");
+
+ /* This should fail as the new creds are not initialized */
+ kret = krb5_cc_copy_creds(context, id, id2);
+ CHECK_FAIL(KRB5_FCC_NOFILE, kret, "copy_creds");
+
+ kret = krb5_cc_initialize(context, id2, test_creds.client);
+ CHECK(kret, "initialize of id2");
+
+ kret = krb5_cc_copy_creds(context, id, id2);
+ CHECK(kret, "copy_creds");
+
+ kret = krb5_cc_destroy(context, id2);
+ CHECK(kret, "destroy new cache");
+ }
+
+ /* Destroy the first cache */
+ kret = krb5_cc_destroy(context, id);
+ CHECK(kret, "destroy");
+
+ /* ----------------------------------------------------- */
+ /* Tests the generate new code */
+ kret = krb5_cc_new_unique(context, save_type,
+ NULL, &id2);
+ CHECK(kret, "new_unique");
+
+ kret = krb5_cc_initialize(context, id2, test_creds.client);
+ CHECK(kret, "initialize");
+
+ kret = krb5_cc_store_cred(context, id2, &test_creds);
+ CHECK(kret, "store");
+
+ kret = krb5_cc_destroy(context, id2);
+ CHECK(kret, "destroy id2");
+
+ free(save_type);
+ free_test_cred(context);
+
+}
+
+/*
+ * Checks if a credential type is registered with the library
+ */
+static int
+check_registered(krb5_context context, const char *prefix)
+{
+ char name[300];
+ krb5_error_code kret;
+ krb5_ccache id;
+
+ snprintf(name, sizeof(name), "%s/tmp/cctest.%ld", prefix, (long) getpid());
+
+ kret = krb5_cc_resolve(context, name, &id);
+ if(kret != KRB5_OK) {
+ if(kret == KRB5_CC_UNKNOWN_TYPE)
+ return 0;
+ com_err("Checking on credential type", kret, "%s", prefix);
+ fflush(stderr);
+ return 0;
+ }
+
+ kret = krb5_cc_close(context, id);
+ if(kret != KRB5_OK) {
+ com_err("Checking on credential type - closing", kret, "%s", prefix);
+ fflush(stderr);
+ }
+
+ return 1;
+}
+
+
+static void
+do_test(krb5_context context, const char *prefix)
+{
+ char name[300];
+
+ snprintf(name, sizeof(name), "%s/tmp/cctest.%ld", prefix, (long) getpid());
+ printf("Starting test on %s\n", name);
+ cc_test (context, name, 0);
+ cc_test (context, name, !0);
+ printf("Test on %s passed\n", name);
+}
+
+static void
+test_misc(krb5_context context)
+{
+ /* Tests for certain error returns */
+ krb5_error_code kret;
+ krb5_ccache id;
+ const krb5_cc_ops *ops_save;
+
+ fprintf(stderr, "Testing miscellaneous error conditions\n");
+
+ kret = krb5_cc_resolve(context, "unknown_method_ep:/tmp/name", &id);
+ if (kret != KRB5_CC_UNKNOWN_TYPE) {
+ CHECK(kret, "resolve unknown type");
+ }
+
+ /* Test for not specifiying a cache type with no defaults */
+ ops_save = krb5_cc_dfl_ops;
+ krb5_cc_dfl_ops = 0;
+
+ kret = krb5_cc_resolve(context, "/tmp/e", &id);
+ if (kret != KRB5_CC_BADNAME) {
+ CHECK(kret, "resolve no builtin type");
+ }
+
+ krb5_cc_dfl_ops = ops_save;
+
+}
+extern const krb5_cc_ops krb5_mcc_ops;
+extern const krb5_cc_ops krb5_fcc_ops;
+
+int
+main(void)
+{
+ krb5_context context;
+ krb5_error_code kret;
+
+ if ((kret = krb5_init_context(&context))) {
+ printf("Couldn't initialize krb5 library: %s\n",
+ error_message(kret));
+ exit(1);
+ }
+
+ kret = krb5_cc_register(context, &krb5_mcc_ops,0);
+ if(kret && kret != KRB5_CC_TYPE_EXISTS) {
+ CHECK(kret, "register_mem");
+ }
+
+ kret = krb5_cc_register(context, &krb5_fcc_ops,0);
+ if(kret && kret != KRB5_CC_TYPE_EXISTS) {
+ CHECK(kret, "register_mem");
+ }
+
+ /* Registering a second time tests for error return */
+ kret = krb5_cc_register(context, &krb5_fcc_ops,0);
+ if(kret != KRB5_CC_TYPE_EXISTS) {
+ CHECK(kret, "register_mem");
+ }
+
+ /* Registering with override should work */
+ kret = krb5_cc_register(context, &krb5_fcc_ops,1);
+ CHECK(kret, "register_mem override");
+
+ init_structs();
+
+ test_misc(context);
+ do_test(context, "");
+
+ if (check_registered(context, "KEYRING:process:"))
+ do_test(context, "KEYRING:process:");
+ else
+ printf("Skiping KEYRING: test - unregistered type\n");
+
+ do_test(context, "MEMORY:");
+ do_test(context, "FILE:");
+
+ krb5_free_context(context);
+ return 0;
+}
diff --git a/src/lib/krb5/ccache/t_cccol.c b/src/lib/krb5/ccache/t_cccol.c
new file mode 100644
index 0000000000000..444806e5a2ab0
--- /dev/null
+++ b/src/lib/krb5/ccache/t_cccol.c
@@ -0,0 +1,363 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/t_cccol.py - Test ccache collection via API */
+/*
+ * Copyright (C) 2013 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <krb5.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+static krb5_context ctx;
+
+/* Check that code is 0. Display an error message first if it is not. */
+static void
+check(krb5_error_code code)
+{
+ const char *errmsg;
+
+ if (code != 0) {
+ errmsg = krb5_get_error_message(ctx, code);
+ fprintf(stderr, "%s\n", errmsg);
+ krb5_free_error_message(ctx, errmsg);
+ }
+ assert(code == 0);
+}
+
+/* Construct a list of the names of each credential cache in the collection. */
+static void
+get_collection_names(char ***list_out, size_t *count_out)
+{
+ krb5_cccol_cursor cursor;
+ krb5_ccache cache;
+ char **list = NULL;
+ size_t count = 0;
+ char *name;
+
+ check(krb5_cccol_cursor_new(ctx, &cursor));
+ while (1) {
+ check(krb5_cccol_cursor_next(ctx, cursor, &cache));
+ if (cache == NULL)
+ break;
+ check(krb5_cc_get_full_name(ctx, cache, &name));
+ krb5_cc_close(ctx, cache);
+ list = realloc(list, (count + 1) * sizeof(*list));
+ assert(list != NULL);
+ list[count++] = name;
+ }
+ krb5_cccol_cursor_free(ctx, &cursor);
+ *list_out = list;
+ *count_out = count;
+}
+
+/* Return true if list contains name. */
+static krb5_boolean
+in_list(char **list, size_t count, const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ if (strcmp(list[i], name) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Release the memory for a list of credential cache names. */
+static void
+free_list(char **list, size_t count)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ krb5_free_string(ctx, list[i]);
+ free(list);
+}
+
+/*
+ * Check that the cache names within the current collection begin with first
+ * (unless first is NULL), that the other elements match the remaining
+ * arguments in some order. others must be the number of additional cache
+ * names.
+ */
+static void
+check_collection(const char *first, size_t others, ...)
+{
+ va_list ap;
+ char **list;
+ size_t count, i;
+ const char *name;
+
+ get_collection_names(&list, &count);
+ if (first != NULL) {
+ assert(strcmp(first, list[0]) == 0);
+ assert(count == others + 1);
+ } else {
+ assert(count == others);
+ }
+ va_start(ap, others);
+ for (i = 0; i < others; i++) {
+ name = va_arg(ap, const char *);
+ assert(in_list(list, count, name));
+ }
+ va_end(ap);
+ free_list(list, count);
+}
+
+/* Check that the name of cache matches expected_name. */
+static void
+check_name(krb5_ccache cache, const char *expected_name)
+{
+ char *name;
+
+ check(krb5_cc_get_full_name(ctx, cache, &name));
+ assert(strcmp(name, expected_name) == 0);
+ krb5_free_string(ctx, name);
+}
+
+/* Check that when collection_name is resolved, the resulting cache's name
+ * matches expected_name. */
+static void
+check_primary_name(const char *collection_name, const char *expected_name)
+{
+ krb5_ccache cache;
+
+ check(krb5_cc_resolve(ctx, collection_name, &cache));
+ check_name(cache, expected_name);
+ krb5_cc_close(ctx, cache);
+}
+
+/* Check that when name is resolved, the resulting cache's principal matches
+ * expected_princ, or has no principal if expected_princ is NULL. */
+static void
+check_princ(const char *name, krb5_principal expected_princ)
+{
+ krb5_ccache cache;
+ krb5_principal princ;
+
+ check(krb5_cc_resolve(ctx, name, &cache));
+ if (expected_princ != NULL) {
+ check(krb5_cc_get_principal(ctx, cache, &princ));
+ assert(krb5_principal_compare(ctx, princ, expected_princ));
+ krb5_free_principal(ctx, princ);
+ } else {
+ assert(krb5_cc_get_principal(ctx, cache, &princ) != 0);
+ }
+ krb5_cc_close(ctx, cache);
+}
+
+/* Check that krb5_cc_cache_match on princ returns a cache whose name matches
+ * expected_name, or that the match fails if expected_name is NULL. */
+static void
+check_match(krb5_principal princ, const char *expected_name)
+{
+ krb5_ccache cache;
+
+ if (expected_name != NULL) {
+ check(krb5_cc_cache_match(ctx, princ, &cache));
+ check_name(cache, expected_name);
+ krb5_cc_close(ctx, cache);
+ } else {
+ assert(krb5_cc_cache_match(ctx, princ, &cache) != 0);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_ccache ccinitial, ccu1, ccu2;
+ krb5_principal princ1, princ2, princ3;
+ const char *collection_name, *typename;
+ char *initial_primary_name, *unique1_name, *unique2_name;
+
+ /*
+ * Get the collection name from the command line. This is a ccache name
+ * with collection semantics, like DIR:/path/to/directory. This test
+ * program assumes that the collection is empty to start with.
+ */
+ assert(argc == 2);
+ collection_name = argv[1];
+
+ /*
+ * Set the default ccache for the context to be the collection name, so the
+ * library can find the collection.
+ */
+ check(krb5_init_context(&ctx));
+ check(krb5_cc_set_default_name(ctx, collection_name));
+
+ /*
+ * Resolve the collection name. Since the collection is empty, this should
+ * generate a subsidiary name of an uninitialized cache. Getting the name
+ * of the resulting cache should give us the subsidiary name, not the
+ * collection name. This resulting subsidiary name should be consistent if
+ * we resolve the collection name again, and the collection should still be
+ * empty since we haven't initialized the cache.
+ */
+ check(krb5_cc_resolve(ctx, collection_name, &ccinitial));
+ check(krb5_cc_get_full_name(ctx, ccinitial, &initial_primary_name));
+ assert(strcmp(initial_primary_name, collection_name) != 0);
+ check_primary_name(collection_name, initial_primary_name);
+ check_collection(NULL, 0);
+ check_princ(collection_name, NULL);
+ check_princ(initial_primary_name, NULL);
+
+ /*
+ * Before initializing the primary ccache, generate and initialize two
+ * unique caches of the collection's type. Check that the cache names
+ * resolve to the generated caches and appear in the collection. (They
+ * might appear before being initialized; that's not currently considered
+ * important). The primary cache for the collection should remain as the
+ * unitialized cache from the previous step.
+ */
+ typename = krb5_cc_get_type(ctx, ccinitial);
+ check(krb5_cc_new_unique(ctx, typename, NULL, &ccu1));
+ check(krb5_cc_get_full_name(ctx, ccu1, &unique1_name));
+ check(krb5_parse_name(ctx, "princ1@X", &princ1));
+ check(krb5_cc_initialize(ctx, ccu1, princ1));
+ check_princ(unique1_name, princ1);
+ check_match(princ1, unique1_name);
+ check_collection(NULL, 1, unique1_name);
+ check(krb5_cc_new_unique(ctx, typename, NULL, &ccu2));
+ check(krb5_cc_get_full_name(ctx, ccu2, &unique2_name));
+ check(krb5_parse_name(ctx, "princ2@X", &princ2));
+ check(krb5_cc_initialize(ctx, ccu2, princ2));
+ check_princ(unique2_name, princ2);
+ check_match(princ1, unique1_name);
+ check_match(princ2, unique2_name);
+ check_collection(NULL, 2, unique1_name, unique2_name);
+ assert(strcmp(unique1_name, initial_primary_name) != 0);
+ assert(strcmp(unique1_name, collection_name) != 0);
+ assert(strcmp(unique2_name, initial_primary_name) != 0);
+ assert(strcmp(unique2_name, collection_name) != 0);
+ assert(strcmp(unique2_name, unique1_name) != 0);
+ check_primary_name(collection_name, initial_primary_name);
+
+ /*
+ * Initialize the initial primary cache. Make sure it didn't change names,
+ * that the previously retrieved name and the collection name both resolve
+ * to the initialized cache, and that it now appears first in the
+ * collection.
+ */
+ check(krb5_parse_name(ctx, "princ3@X", &princ3));
+ check(krb5_cc_initialize(ctx, ccinitial, princ3));
+ check_name(ccinitial, initial_primary_name);
+ check_princ(initial_primary_name, princ3);
+ check_princ(collection_name, princ3);
+ check_match(princ3, initial_primary_name);
+ check_collection(initial_primary_name, 2, unique1_name, unique2_name);
+
+ /*
+ * Switch the primary cache to each cache we have open. One each switch,
+ * check the primary name, check that the collection resolves to the
+ * expected cache, and check that the new primary name appears first in the
+ * collection.
+ */
+ check(krb5_cc_switch(ctx, ccu1));
+ check_primary_name(collection_name, unique1_name);
+ check_princ(collection_name, princ1);
+ check_collection(unique1_name, 2, initial_primary_name, unique2_name);
+ check(krb5_cc_switch(ctx, ccu2));
+ check_primary_name(collection_name, unique2_name);
+ check_princ(collection_name, princ2);
+ check_collection(unique2_name, 2, initial_primary_name, unique1_name);
+ check(krb5_cc_switch(ctx, ccinitial));
+ check_primary_name(collection_name, initial_primary_name);
+ check_princ(collection_name, princ3);
+ check_collection(initial_primary_name, 2, unique1_name, unique2_name);
+
+ /*
+ * Temporarily set the context default ccache to a subsidiary name, and
+ * check that iterating over the collection yields that subsidiary cache
+ * and no others.
+ */
+ check(krb5_cc_set_default_name(ctx, unique1_name));
+ check_collection(unique1_name, 0);
+ check(krb5_cc_set_default_name(ctx, collection_name));
+
+ /*
+ * Destroy the primary cache. Make sure this causes both the initial
+ * primary name and the collection name to resolve to an uninitialized
+ * cache. Make sure the primary name doesn't change and doesn't appear in
+ * the collection any more.
+ */
+ check(krb5_cc_destroy(ctx, ccinitial));
+ check_princ(initial_primary_name, NULL);
+ check_princ(collection_name, NULL);
+ check_primary_name(collection_name, initial_primary_name);
+ check_match(princ1, unique1_name);
+ check_match(princ2, unique2_name);
+ check_match(princ3, NULL);
+ check_collection(NULL, 2, unique1_name, unique2_name);
+
+ /*
+ * Switch to the first unique cache after destroying the primary cache.
+ * Check that the collection name resolves to this cache and that the new
+ * primary name appears first in the collection.
+ */
+ check(krb5_cc_switch(ctx, ccu1));
+ check_primary_name(collection_name, unique1_name);
+ check_princ(collection_name, princ1);
+ check_collection(unique1_name, 1, unique2_name);
+
+ /*
+ * Destroy the second unique cache (which is not the current primary),
+ * check that it is on longer initialized, and check that it no longer
+ * appears in the collection. Check that destroying the non-primary cache
+ * doesn't affect the primary name.
+ */
+ check(krb5_cc_destroy(ctx, ccu2));
+ check_princ(unique2_name, NULL);
+ check_match(princ2, NULL);
+ check_collection(unique1_name, 0);
+ check_primary_name(collection_name, unique1_name);
+ check_match(princ1, unique1_name);
+ check_princ(collection_name, princ1);
+
+ /*
+ * Destroy the first unique cache. Check that the collection is empty and
+ * still has the same primary name.
+ */
+ check(krb5_cc_destroy(ctx, ccu1));
+ check_princ(unique1_name, NULL);
+ check_princ(collection_name, NULL);
+ check_primary_name(collection_name, unique1_name);
+ check_match(princ1, NULL);
+ check_collection(NULL, 0);
+
+ krb5_free_string(ctx, initial_primary_name);
+ krb5_free_string(ctx, unique1_name);
+ krb5_free_string(ctx, unique2_name);
+ krb5_free_principal(ctx, princ1);
+ krb5_free_principal(ctx, princ2);
+ krb5_free_principal(ctx, princ3);
+ krb5_free_context(ctx);
+ return 0;
+}
diff --git a/src/lib/krb5/ccache/t_cccol.py b/src/lib/krb5/ccache/t_cccol.py
new file mode 100755
index 0000000000000..f7f17856476cd
--- /dev/null
+++ b/src/lib/krb5/ccache/t_cccol.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+from k5test import *
+
+realm = K5Realm(create_kdb=False)
+
+keyctl = which('keyctl')
+out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1)
+test_keyring = (keyctl is not None and
+ 'Unknown credential cache type' not in out)
+if not test_keyring:
+ skipped('keyring collection tests', 'keyring support not built')
+
+# Run the collection test program against each collection-enabled type.
+realm.run(['./t_cccol', 'DIR:' + os.path.join(realm.testdir, 'cc')])
+if test_keyring:
+ def cleanup_keyring(anchor, name):
+ out = realm.run(['keyctl', 'list', anchor])
+ if ('keyring: ' + name + '\n') in out:
+ keyid = realm.run(['keyctl', 'search', anchor, 'keyring', name])
+ realm.run(['keyctl', 'unlink', keyid.strip(), anchor])
+
+ # Use the test directory as the collection name to avoid colliding
+ # with other build trees.
+ cname = realm.testdir
+ col_ringname = '_krb_' + cname
+
+ # Remove any keys left behind by previous failed test runs.
+ cleanup_keyring('@s', cname)
+ cleanup_keyring('@s', col_ringname)
+ cleanup_keyring('@u', col_ringname)
+
+ # Run test program over each subtype, cleaning up as we go. Don't
+ # test the persistent subtype, since it supports only one
+ # collection and might be in actual use.
+ realm.run(['./t_cccol', 'KEYRING:' + cname])
+ cleanup_keyring('@s', col_ringname)
+ realm.run(['./t_cccol', 'KEYRING:legacy:' + cname])
+ cleanup_keyring('@s', col_ringname)
+ realm.run(['./t_cccol', 'KEYRING:session:' + cname])
+ cleanup_keyring('@s', col_ringname)
+ realm.run(['./t_cccol', 'KEYRING:user:' + cname])
+ cleanup_keyring('@u', col_ringname)
+ realm.run(['./t_cccol', 'KEYRING:process:abcd'])
+ realm.run(['./t_cccol', 'KEYRING:thread:abcd'])
+
+realm.stop()
+
+# Test cursor semantics using real ccaches.
+realm = K5Realm(create_host=False)
+
+realm.addprinc('alice', password('alice'))
+realm.addprinc('bob', password('bob'))
+
+ccdir = os.path.join(realm.testdir, 'cc')
+dccname = 'DIR:%s' % ccdir
+duser = 'DIR::%s/tkt1' % ccdir
+dalice = 'DIR::%s/tkt2' % ccdir
+dbob = 'DIR::%s/tkt3' % ccdir
+dnoent = 'DIR::%s/noent' % ccdir
+realm.kinit('user', password('user'), flags=['-c', duser])
+realm.kinit('alice', password('alice'), flags=['-c', dalice])
+realm.kinit('bob', password('bob'), flags=['-c', dbob])
+
+if test_keyring:
+ cleanup_keyring('@s', col_ringname)
+ krccname = 'KEYRING:session:' + cname
+ kruser = '%s:tkt1' % krccname
+ kralice = '%s:tkt2' % krccname
+ krbob = '%s:tkt3' % krccname
+ krnoent = '%s:noent' % krccname
+ realm.kinit('user', password('user'), flags=['-c', kruser])
+ realm.kinit('alice', password('alice'), flags=['-c', kralice])
+ realm.kinit('bob', password('bob'), flags=['-c', krbob])
+
+def cursor_test(testname, args, expected):
+ outlines = realm.run(['./t_cccursor'] + args).splitlines()
+ outlines.sort()
+ expected.sort()
+ if outlines != expected:
+ fail('Output not expected for %s\n' % testname +
+ 'Expected output:\n\n' + '\n'.join(expected) + '\n\n' +
+ 'Actual output:\n\n' + '\n'.join(outlines))
+
+fccname = 'FILE:%s' % realm.ccache
+cursor_test('file-default', [], [fccname])
+cursor_test('file-default2', [realm.ccache], [fccname])
+cursor_test('file-default3', [fccname], [fccname])
+
+cursor_test('dir', [dccname], [duser, dalice, dbob])
+cursor_test('dir-subsidiary', [duser], [duser])
+cursor_test('dir-nofile', [dnoent], [])
+
+if test_keyring:
+ cursor_test('keyring', [krccname], [kruser, kralice, krbob])
+ cursor_test('keyring-subsidiary', [kruser], [kruser])
+ cursor_test('keyring-noent', [krnoent], [])
+
+mfoo = 'MEMORY:foo'
+mbar = 'MEMORY:bar'
+cursor_test('filemem', [fccname, mfoo, mbar], [fccname, mfoo, mbar])
+cursor_test('dirmem', [dccname, mfoo], [duser, dalice, dbob, mfoo])
+if test_keyring:
+ cursor_test('keyringmem', [krccname, mfoo], [kruser, kralice, krbob, mfoo])
+
+# Test krb5_cccol_have_content.
+realm.run(['./t_cccursor', dccname, 'CONTENT'])
+realm.run(['./t_cccursor', fccname, 'CONTENT'])
+realm.run(['./t_cccursor', realm.ccache, 'CONTENT'])
+realm.run(['./t_cccursor', mfoo, 'CONTENT'], expected_code=1)
+if test_keyring:
+ realm.run(['./t_cccursor', krccname, 'CONTENT'])
+ cleanup_keyring('@s', col_ringname)
+
+# Make sure FILE doesn't yield a nonexistent default cache.
+realm.run([kdestroy])
+cursor_test('noexist', [], [])
+realm.run(['./t_cccursor', fccname, 'CONTENT'], expected_code=1)
+
+success('Renewing credentials')
diff --git a/src/lib/krb5/ccache/t_cccursor.c b/src/lib/krb5/ccache/t_cccursor.c
new file mode 100644
index 0000000000000..4323b77e2e329
--- /dev/null
+++ b/src/lib/krb5/ccache/t_cccursor.c
@@ -0,0 +1,81 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/t_cccursor.c - Simple test harness for cccol API */
+/*
+ * Copyright 2011 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * Displays a list of caches returned by the cccol cursor. The first argument,
+ * if given, is set to the default cache name for the context before iterating.
+ * Any remaining argments are resolved as caches and kept open during the
+ * iteration. If the argument "CONTENT" is given as one of the cache names,
+ * immediately exit with status 0 if the collection contains credentials and 1
+ * if it does not.
+ */
+
+#include "k5-int.h"
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context ctx;
+ krb5_cccol_cursor cursor;
+ krb5_ccache cache, hold[64];
+ int i;
+ char *name;
+
+ assert(krb5_init_context(&ctx) == 0);
+ if (argc > 1)
+ assert(krb5_cc_set_default_name(ctx, argv[1]) == 0);
+
+ if (argc > 2) {
+ assert(argc < 60);
+ for (i = 2; i < argc; i++) {
+ if (strcmp(argv[i], "CONTENT") == 0) {
+ ret = krb5_cccol_have_content(ctx);
+ krb5_free_context(ctx);
+ return ret != 0;
+ }
+ assert(krb5_cc_resolve(ctx, argv[i], &hold[i - 2]) == 0);
+ }
+ }
+
+ assert(krb5_cccol_cursor_new(ctx, &cursor) == 0);
+ while (1) {
+ assert(krb5_cccol_cursor_next(ctx, cursor, &cache) == 0);
+ if (cache == NULL)
+ break;
+ assert(krb5_cc_get_full_name(ctx, cache, &name) == 0);
+ printf("%s\n", name);
+ krb5_free_string(ctx, name);
+ krb5_cc_close(ctx, cache);
+ }
+ assert(krb5_cccol_cursor_free(ctx, &cursor) == 0);
+
+ for (i = 2; i < argc; i++)
+ krb5_cc_close(ctx, hold[i - 2]);
+
+ krb5_free_context(ctx);
+ return 0;
+}
diff --git a/src/lib/krb5/ccache/t_marshal.c b/src/lib/krb5/ccache/t_marshal.c
new file mode 100644
index 0000000000000..144554c30bbe9
--- /dev/null
+++ b/src/lib/krb5/ccache/t_marshal.c
@@ -0,0 +1,407 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/t_marshal.c - test program for cred marshalling */
+/*
+ * Copyright (C) 2014 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cc-int.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
+/*
+ * Versions 1 and 2 of the ccache format use native byte order representations
+ * of integers. The test data below is from a little-endian platform. Skip
+ * those tests on big-endian platforms by starting at version 3.
+ */
+#ifdef K5_BE
+#define FIRST_VERSION 3
+#else
+#define FIRST_VERSION 1
+#endif
+
+/* Each test contains the expected binary representation of a credential cache
+ * divided into the header, the default principal, and two credentials. */
+const struct test {
+ size_t headerlen;
+ const unsigned char header[256];
+ size_t princlen;
+ const unsigned char princ[256];
+ size_t cred1len;
+ const unsigned char cred1[256];
+ size_t cred2len;
+ const unsigned char cred2[256];
+} tests[4] = {
+ {
+ /* Version 1 header */
+ 2,
+ "\x05\x01",
+ /* Version 1 principal */
+ 33,
+ "\x02\x00\x00\x00\x0B\x00\x00\x00\x4B\x52\x42\x54\x45\x53\x54\x2E"
+ "\x43\x4F\x4D\x0A\x00\x00\x00\x74\x65\x73\x74\x63\x6C\x69\x65\x6E"
+ "\x74",
+ /* Version 1 cred 1 */
+ 165,
+ "\x02\x00\x00\x00\x0B\x00\x00\x00\x4B\x52\x42\x54\x45\x53\x54\x2E"
+ "\x43\x4F\x4D\x0A\x00\x00\x00\x74\x65\x73\x74\x63\x6C\x69\x65\x6E"
+ "\x74\x03\x00\x00\x00\x0B\x00\x00\x00\x45\x58\x41\x4D\x50\x4C\x45"
+ "\x2E\x43\x4F\x4D\x04\x00\x00\x00\x74\x65\x73\x74\x04\x00\x00\x00"
+ "\x68\x6F\x73\x74\x11\x00\x10\x00\x00\x00\x00\x01\x02\x03\x04\x05"
+ "\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x0B\x00\x00\x00\xDE\x00"
+ "\x00\x00\x05\x0D\x00\x00\x00\xCA\x9A\x3B\x00\x00\x00\x80\x40\x01"
+ "\x00\x00\x00\x02\x00\x04\x00\x00\x00\x0A\x00\x00\x01\x02\x00\x00"
+ "\x00\x00\x02\x0A\x00\x00\x00\x73\x69\x67\x6E\x74\x69\x63\x6B\x65"
+ "\x74\x9C\xFF\x00\x00\x00\x00\x06\x00\x00\x00\x74\x69\x63\x6B\x65"
+ "\x74\x00\x00\x00\x00",
+ /* Version 1 cred 2 */
+ 113,
+ "\x02\x00\x00\x00\x0B\x00\x00\x00\x4B\x52\x42\x54\x45\x53\x54\x2E"
+ "\x43\x4F\x4D\x0A\x00\x00\x00\x74\x65\x73\x74\x63\x6C\x69\x65\x6E"
+ "\x74\x01\x00\x00\x00\x00\x00\x00\x00\x17\x00\x10\x00\x00\x00\x0F"
+ "\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00"
+ "\x74\x69\x63\x6B\x65\x74\x07\x00\x00\x00\x32\x74\x69\x63\x6B\x65"
+ "\x74"
+ },
+ {
+ /* Version 2 header */
+ 2,
+ "\x05\x02",
+ /* Version 2 principal */
+ 37,
+ "\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x00\x00\x00\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x0A\x00\x00\x00\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74",
+ /* Version 2 cred 1 */
+ 173,
+ "\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x00\x00\x00\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x0A\x00\x00\x00\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74\x01\x00\x00\x00\x02\x00\x00\x00\x0B\x00\x00"
+ "\x00\x45\x58\x41\x4D\x50\x4C\x45\x2E\x43\x4F\x4D\x04\x00\x00\x00"
+ "\x74\x65\x73\x74\x04\x00\x00\x00\x68\x6F\x73\x74\x11\x00\x10\x00"
+ "\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D"
+ "\x0E\x0F\x0B\x00\x00\x00\xDE\x00\x00\x00\x05\x0D\x00\x00\x00\xCA"
+ "\x9A\x3B\x00\x00\x00\x80\x40\x01\x00\x00\x00\x02\x00\x04\x00\x00"
+ "\x00\x0A\x00\x00\x01\x02\x00\x00\x00\x00\x02\x0A\x00\x00\x00\x73"
+ "\x69\x67\x6E\x74\x69\x63\x6B\x65\x74\x9C\xFF\x00\x00\x00\x00\x06"
+ "\x00\x00\x00\x74\x69\x63\x6B\x65\x74\x00\x00\x00\x00",
+ /* Version 2 cred 2 */
+ 121,
+ "\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x00\x00\x00\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x0A\x00\x00\x00\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x17\x00\x10\x00\x00\x00\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07"
+ "\x06\x05\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x06\x00\x00\x00\x74\x69\x63\x6B\x65\x74\x07\x00"
+ "\x00\x00\x32\x74\x69\x63\x6B\x65\x74"
+ },
+ {
+ /* Version 3 header */
+ 2,
+ "\x05\x03",
+ /* Version 3 principal */
+ 37,
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x00\x00\x00\x0A\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74",
+ /* Version 3 cred 1 */
+ 175,
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x00\x00\x00\x0A\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00"
+ "\x0B\x45\x58\x41\x4D\x50\x4C\x45\x2E\x43\x4F\x4D\x00\x00\x00\x04"
+ "\x74\x65\x73\x74\x00\x00\x00\x04\x68\x6F\x73\x74\x00\x11\x00\x11"
+ "\x00\x00\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B"
+ "\x0C\x0D\x0E\x0F\x00\x00\x00\x0B\x00\x00\x00\xDE\x00\x00\x0D\x05"
+ "\x3B\x9A\xCA\x00\x00\x40\x80\x00\x00\x00\x00\x00\x01\x00\x02\x00"
+ "\x00\x00\x04\x0A\x00\x00\x01\x00\x00\x00\x02\x02\x00\x00\x00\x00"
+ "\x0A\x73\x69\x67\x6E\x74\x69\x63\x6B\x65\x74\xFF\x9C\x00\x00\x00"
+ "\x00\x00\x00\x00\x06\x74\x69\x63\x6B\x65\x74\x00\x00\x00\x00",
+ /* Version 3 cred 2 */
+ 123,
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x00\x00\x00\x0A\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x17\x00\x17\x00\x00\x00\x10\x0F\x0E\x0D\x0C\x0B\x0A\x09"
+ "\x08\x07\x06\x05\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x74\x69\x63\x6B\x65\x74"
+ "\x00\x00\x00\x07\x32\x74\x69\x63\x6B\x65\x74"
+ },
+ {
+ /* Version 4 header */
+ 16,
+ "\x05\x04\x00\x0C\x00\x01\x00\x08\x00\x00\x01\x2C\x00\x00\xD4\x31",
+ /* Verion 4 principal */
+ 37,
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x00\x00\x00\x0A\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74",
+ /* Version 4 cred 1 */
+ 173,
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x00\x00\x00\x0A\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00"
+ "\x0B\x45\x58\x41\x4D\x50\x4C\x45\x2E\x43\x4F\x4D\x00\x00\x00\x04"
+ "\x74\x65\x73\x74\x00\x00\x00\x04\x68\x6F\x73\x74\x00\x11\x00\x00"
+ "\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D"
+ "\x0E\x0F\x00\x00\x00\x0B\x00\x00\x00\xDE\x00\x00\x0D\x05\x3B\x9A"
+ "\xCA\x00\x00\x40\x80\x00\x00\x00\x00\x00\x01\x00\x02\x00\x00\x00"
+ "\x04\x0A\x00\x00\x01\x00\x00\x00\x02\x02\x00\x00\x00\x00\x0A\x73"
+ "\x69\x67\x6E\x74\x69\x63\x6B\x65\x74\xFF\x9C\x00\x00\x00\x00\x00"
+ "\x00\x00\x06\x74\x69\x63\x6B\x65\x74\x00\x00\x00\x00",
+ /* Version 4 cred 2 */
+ 121,
+ "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x0B\x4B\x52\x42\x54"
+ "\x45\x53\x54\x2E\x43\x4F\x4D\x00\x00\x00\x0A\x74\x65\x73\x74\x63"
+ "\x6C\x69\x65\x6E\x74\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x17\x00\x00\x00\x10\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07"
+ "\x06\x05\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x06\x74\x69\x63\x6B\x65\x74\x00\x00"
+ "\x00\x07\x32\x74\x69\x63\x6B\x65\x74"
+ }
+};
+
+static void
+verify_princ(krb5_principal p)
+{
+ assert(p->length == 1);
+ assert(data_eq_string(p->realm, "KRBTEST.COM"));
+ assert(data_eq_string(p->data[0], "testclient"));
+}
+
+static void
+verify_cred1(const krb5_creds *c)
+{
+ uint32_t ipaddr = ntohl(0x0A000001);
+
+ verify_princ(c->client);
+ assert(c->server->length == 2);
+ assert(data_eq_string(c->server->realm, "EXAMPLE.COM"));
+ assert(data_eq_string(c->server->data[0], "test"));
+ assert(data_eq_string(c->server->data[1], "host"));
+ assert(c->keyblock.enctype == ENCTYPE_AES128_CTS_HMAC_SHA1_96);
+ assert(c->keyblock.length == 16);
+ assert(memcmp(c->keyblock.contents,
+ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9\xA\xB\xC\xD\xE\xF",
+ 16) == 0);
+ assert(c->times.authtime == 11);
+ assert(c->times.starttime == 222);
+ assert(c->times.endtime == 3333);
+ assert(c->times.renew_till == 1000000000);
+ assert(c->is_skey == FALSE);
+ assert(c->ticket_flags == (TKT_FLG_FORWARDABLE | TKT_FLG_RENEWABLE));
+ assert(c->addresses != NULL && c->addresses[0] != NULL);
+ assert(c->addresses[0]->addrtype == ADDRTYPE_INET);
+ assert(c->addresses[0]->length == 4);
+ assert(memcmp(c->addresses[0]->contents, &ipaddr, 4) == 0);
+ assert(c->addresses[1] == NULL);
+ assert(c->authdata != NULL && c->authdata[0] != NULL);
+ assert(c->authdata[0]->ad_type == KRB5_AUTHDATA_SIGNTICKET);
+ assert(c->authdata[0]->length == 10);
+ assert(memcmp(c->authdata[0]->contents, "signticket", 10) == 0);
+ assert(c->authdata[1] != NULL);
+ assert(c->authdata[1]->ad_type == -100);
+ assert(c->authdata[1]->length == 0);
+ assert(c->authdata[2] == NULL);
+ assert(data_eq_string(c->ticket, "ticket"));
+ assert(c->second_ticket.length == 0);
+}
+
+static void
+verify_cred2(const krb5_creds *c)
+{
+ verify_princ(c->client);
+ assert(c->server->length == 0);
+ assert(c->server->realm.length == 0);
+ assert(c->keyblock.enctype == ENCTYPE_ARCFOUR_HMAC);
+ assert(c->keyblock.length == 16);
+ assert(memcmp(c->keyblock.contents,
+ "\xF\xE\xD\xC\xB\xA\x9\x8\x7\x6\x5\x4\x3\x2\x1\x0",
+ 16) == 0);
+ assert(c->times.authtime == 0);
+ assert(c->times.starttime == 0);
+ assert(c->times.endtime == 0);
+ assert(c->times.renew_till == 0);
+ assert(c->is_skey == TRUE);
+ assert(c->ticket_flags == 0);
+ assert(c->addresses == NULL || c->addresses[0] == NULL);
+ assert(c->authdata == NULL || c->authdata[0] == NULL);
+ assert(data_eq_string(c->ticket, "ticket"));
+ assert(data_eq_string(c->second_ticket, "2ticket"));
+}
+
+int
+main(int argc, char **argv)
+{
+ krb5_context context;
+ krb5_ccache cache;
+ krb5_principal princ;
+ krb5_creds cred1, cred2;
+ krb5_cc_cursor cursor;
+ const char *filename;
+ char *ccname, filebuf[256];
+ int version, fd;
+ const struct test *t;
+ struct k5buf buf;
+
+ if (argc != 2)
+ abort();
+ filename = argv[1];
+ if (asprintf(&ccname, "FILE:%s", filename) == -1)
+ abort();
+
+ if (krb5_init_context(&context) != 0)
+ abort();
+
+ for (version = FIRST_VERSION; version <= 4; version++) {
+ t = &tests[version - 1];
+
+ /* Test principal unmarshalling and marshalling. */
+ if (k5_unmarshal_princ(t->princ, t->princlen, version, &princ) != 0)
+ abort();
+ verify_princ(princ);
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_princ(&buf, version, princ);
+ assert(buf.len == t->princlen);
+ assert(memcmp(t->princ, buf.data, buf.len) == 0);
+ k5_buf_free(&buf);
+
+ /* Test cred1 unmarshalling and marshalling. */
+ if (k5_unmarshal_cred(t->cred1, t->cred1len, version, &cred1) != 0)
+ abort();
+ verify_cred1(&cred1);
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_cred(&buf, version, &cred1);
+ assert(buf.len == t->cred1len);
+ assert(memcmp(t->cred1, buf.data, buf.len) == 0);
+ k5_buf_free(&buf);
+
+ /* Test cred2 unmarshalling and marshalling. */
+ if (k5_unmarshal_cred(t->cred2, t->cred2len, version, &cred2) != 0)
+ abort();
+ verify_cred2(&cred2);
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_cred(&buf, version, &cred2);
+ assert(buf.len == t->cred2len);
+ assert(memcmp(t->cred2, buf.data, buf.len) == 0);
+ k5_buf_free(&buf);
+
+ /* Write a ccache containing the principal and creds. Use the same
+ * time offset as the version 4 test data used. */
+ context->fcc_default_format = 0x0500 + version;
+ context->os_context.time_offset = 300;
+ context->os_context.usec_offset = 54321;
+ context->os_context.os_flags = KRB5_OS_TOFFSET_VALID;
+ if (krb5_cc_resolve(context, ccname, &cache) != 0)
+ abort();
+ if (krb5_cc_initialize(context, cache, princ) != 0)
+ abort();
+ if (krb5_cc_store_cred(context, cache, &cred1) != 0)
+ abort();
+ if (krb5_cc_store_cred(context, cache, &cred2) != 0)
+ abort();
+ if (krb5_cc_close(context, cache) != 0)
+ abort();
+
+ /* Verify the cache representation against the test data. */
+ fd = open(filename, O_RDONLY);
+ if (fd == -1)
+ abort();
+ if (read(fd, filebuf, t->headerlen) != (ssize_t)t->headerlen)
+ abort();
+ assert(memcmp(filebuf, t->header, t->headerlen) == 0);
+ if (read(fd, filebuf, t->princlen) != (ssize_t)t->princlen)
+ abort();
+ assert(memcmp(filebuf, t->princ, t->princlen) == 0);
+ if (read(fd, filebuf, t->cred1len) != (ssize_t)t->cred1len)
+ abort();
+ assert(memcmp(filebuf, t->cred1, t->cred1len) == 0);
+ if (read(fd, filebuf, t->cred2len) != (ssize_t)t->cred2len)
+ abort();
+ assert(memcmp(filebuf, t->cred2, t->cred2len) == 0);
+ close(fd);
+
+ krb5_free_principal(context, princ);
+ krb5_free_cred_contents(context, &cred1);
+ krb5_free_cred_contents(context, &cred2);
+
+ /* Write a cache containing the test data. */
+ fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0700);
+ if (fd == -1)
+ abort();
+ if (write(fd, t->header, t->headerlen) != (ssize_t)t->headerlen)
+ abort();
+ if (write(fd, t->princ, t->princlen) != (ssize_t)t->princlen)
+ abort();
+ if (write(fd, t->cred1, t->cred1len) != (ssize_t)t->cred1len)
+ abort();
+ if (write(fd, t->cred2, t->cred2len) != (ssize_t)t->cred2len)
+ abort();
+ close(fd);
+
+ /* Read the cache and verify that it matches. */
+ if (krb5_cc_resolve(context, ccname, &cache) != 0)
+ abort();
+ if (krb5_cc_get_principal(context, cache, &princ) != 0)
+ abort();
+ /* Not every version stores the time offset, but at least it shouldn't
+ * have changed from when we set it before. */
+ assert(context->os_context.time_offset == 300);
+ assert(context->os_context.usec_offset == 54321);
+ verify_princ(princ);
+ if (krb5_cc_start_seq_get(context, cache, &cursor) != 0)
+ abort();
+ if (krb5_cc_next_cred(context, cache, &cursor, &cred1) != 0)
+ abort();
+ verify_cred1(&cred1);
+ krb5_free_cred_contents(context, &cred1);
+ if (krb5_cc_next_cred(context, cache, &cursor, &cred2) != 0)
+ abort();
+ verify_cred2(&cred2);
+ krb5_free_cred_contents(context, &cred2);
+ if (krb5_cc_next_cred(context, cache, &cursor, &cred2) != KRB5_CC_END)
+ abort();
+ if (krb5_cc_end_seq_get(context, cache, &cursor) != 0)
+ abort();
+ if (krb5_cc_close(context, cache) != 0)
+ abort();
+ krb5_free_principal(context, princ);
+ }
+
+ (void)unlink(filename);
+ free(ccname);
+ krb5_free_context(context);
+ return 0;
+}
diff --git a/src/lib/krb5/ccache/t_memory.c b/src/lib/krb5/ccache/t_memory.c
new file mode 100644
index 0000000000000..6d103a00d1588
--- /dev/null
+++ b/src/lib/krb5/ccache/t_memory.c
@@ -0,0 +1,138 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/t_memory.c */
+/*
+ * Copyright 1990 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "mcc.h"
+
+krb5_data client1 = {
+#define DATA "client1-comp1"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_data client2 = {
+#define DATA "client1-comp2"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_data server1 = {
+#define DATA "server1-comp1"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_data server2 = {
+#define DATA "server1-comp2"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_creds test_creds = {
+ NULL,
+ NULL,
+ {
+ 1,
+ 1,
+ (unsigned char *) "1"
+ },
+ {
+ 1111,
+ 2222,
+ 3333,
+ 4444
+ },
+ 1,
+ 5555,
+ {
+#define TICKET "This is ticket 1"
+ sizeof(TICKET),
+ TICKET,
+#undef TICKET
+ },
+ {
+#define TICKET "This is ticket 2"
+ sizeof(TICKET),
+ TICKET,
+#undef TICKET
+ },
+};
+
+void
+init_test_cred()
+{
+ test_creds.client = (krb5_principal) malloc(sizeof(krb5_data *)*3);
+ test_creds.client[0] = &client1;
+ test_creds.client[1] = &client2;
+ test_creds.client[2] = NULL;
+
+ test_creds.server = (krb5_principal) malloc(sizeof(krb5_data *)*3);
+ test_creds.server[0] = &server1;
+ test_creds.server[1] = &server2;
+ test_creds.server[2] = NULL;
+}
+
+#define CHECK(kret,msg) \
+ if (kret != KRB5_OK) { \
+ printf("%s returned %d\n", msg, kret); \
+ };
+
+void
+mcc_test()
+{
+ krb5_ccache id;
+ krb5_creds creds;
+ krb5_error_code kret;
+ krb5_cc_cursor cursor;
+
+ init_test_cred();
+
+ kret = krb5_mcc_resolve(context, &id, "/tmp/tkt_test");
+ CHECK(kret, "resolve");
+ kret = krb5_mcc_initialize(context, id, test_creds.client);
+ CHECK(kret, "initialize");
+ kret = krb5_mcc_store(context, id, &test_creds);
+ CHECK(kret, "store");
+
+ kret = krb5_mcc_start_seq_get(context, id, &cursor);
+ CHECK(kret, "start_seq_get");
+ kret = 0;
+ while (kret != KRB5_CC_END) {
+ printf("Calling next_cred\n");
+ kret = krb5_mcc_next_cred(context, id, &cursor, &creds);
+ CHECK(kret, "next_cred");
+ }
+ kret = krb5_mcc_end_seq_get(context, id, &cursor);
+ CHECK(kret, "end_seq_get");
+
+ kret = krb5_mcc_destroy(context, id);
+ CHECK(kret, "destroy");
+ kret = krb5_mcc_close(context, id);
+ CHECK(kret, "close");
+}
diff --git a/src/lib/krb5/ccache/t_stdio.c b/src/lib/krb5/ccache/t_stdio.c
new file mode 100644
index 0000000000000..15185e301cad3
--- /dev/null
+++ b/src/lib/krb5/ccache/t_stdio.c
@@ -0,0 +1,168 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/t_stdio.c */
+/*
+ * Copyright 1991 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "scc.h"
+
+krb5_data client1 = {
+#define DATA "client1-comp1"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_data client2 = {
+#define DATA "client1-comp2"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_data server1 = {
+#define DATA "server1-comp1"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+krb5_data server2 = {
+#define DATA "server1-comp2"
+ sizeof(DATA),
+ DATA,
+#undef DATA
+};
+
+int x = 0x12345;
+krb5_address addr = {
+ ADDRTYPE_INET,
+ 4,
+ (krb5_octet *) &x,
+};
+
+krb5_address *addrs[] = {
+ &addr,
+ 0,
+};
+
+krb5_creds test_creds = {
+ NULL,
+ NULL,
+ {
+ 1,
+ 1,
+ (unsigned char *) "1"
+ },
+ {
+ 1111,
+ 2222,
+ 3333,
+ 4444,
+ },
+ 1,
+ 5555,
+ addrs,
+ {
+#define TICKET "This is ticket 1"
+ sizeof(TICKET),
+ TICKET,
+#undef TICKET
+ },
+ {
+#define TICKET "This is ticket 2"
+ sizeof(TICKET),
+ TICKET,
+#undef TICKET
+ },
+};
+
+void
+init_test_cred()
+{
+ test_creds.client = (krb5_principal) malloc(sizeof(krb5_data *)*3);
+ test_creds.client[0] = &client1;
+ test_creds.client[1] = &client2;
+ test_creds.client[2] = NULL;
+
+ test_creds.server = (krb5_principal) malloc(sizeof(krb5_data *)*3);
+ test_creds.server[0] = &server1;
+ test_creds.server[1] = &server2;
+ test_creds.server[2] = NULL;
+}
+
+#define CHECK(kret,msg) \
+ if (kret != KRB5_OK) { \
+ com_err(msg, kret, ""); \
+ } else printf("%s went ok\n", msg);
+
+int flags = 0;
+void
+scc_test()
+{
+ krb5_ccache id;
+ krb5_creds creds;
+ krb5_error_code kret;
+ krb5_cc_cursor cursor;
+
+ init_test_cred();
+
+ kret = krb5_scc_resolve(context, &id, "/tmp/tkt_test");
+ CHECK(kret, "resolve");
+ kret = krb5_scc_initialize(context, id, test_creds.client);
+ CHECK(kret, "initialize");
+ kret = krb5_scc_store(id, &test_creds);
+ CHECK(kret, "store");
+
+ kret = krb5_scc_set_flags (id, flags);
+ CHECK(kret, "set_flags");
+ kret = krb5_scc_start_seq_get(id, &cursor);
+ CHECK(kret, "start_seq_get");
+ kret = 0;
+ while (kret != KRB5_CC_END) {
+ printf("Calling next_cred\n");
+ kret = krb5_scc_next_cred(id, &cursor, &creds);
+ CHECK(kret, "next_cred");
+ }
+ kret = krb5_scc_end_seq_get(id, &cursor);
+ CHECK(kret, "end_seq_get");
+
+ kret = krb5_scc_close(id);
+ CHECK(kret, "close");
+
+
+ kret = krb5_scc_resolve(&id, "/tmp/tkt_test");
+ CHECK(kret, "resolve");
+ kret = krb5_scc_destroy(id);
+ CHECK(kret, "destroy");
+}
+
+int remove (s) char*s; { return unlink(s); }
+int main () {
+ initialize_krb5_error_table ();
+ init_test_cred ();
+ scc_test ();
+ flags = !flags;
+ scc_test ();
+ return 0;
+}