diff options
Diffstat (limited to 'src/lib/krb5/ccache')
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; +} |
