/* #pragma ident	"@(#)g_initialize.c	1.36	05/02/02 SMI" */

/*
 * Copyright 1996 by Sun Microsystems, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of Sun Microsystems not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission. Sun Microsystems makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * SUN MICROSYSTEMS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL SUN MICROSYSTEMS 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.
 */

/*
 * This function will initialize the gssapi mechglue library
 */

#include "mglueP.h"
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifndef _WIN32
#include <glob.h>
#endif

#define	M_DEFAULT	"default"

#include "k5-thread.h"
#include "k5-plugin.h"
#include "osconf.h"
#ifdef _GSS_STATIC_LINK
#include "gssapiP_krb5.h"
#include "gssapiP_spnego.h"
#endif

#define MECH_SYM "gss_mech_initialize"
#define MECH_INTERPOSER_SYM "gss_mech_interposer"

#ifndef MECH_CONF
#define	MECH_CONF "/etc/gss/mech"
#endif
#define MECH_CONF_PATTERN MECH_CONF ".d/*.conf"

/* Local functions */
static void addConfigEntry(const char *oidStr, const char *oid,
			   const char *sharedLib, const char *kernMod,
			   const char *modOptions, const char *modType);
static gss_mech_info searchMechList(gss_const_OID);
static void loadConfigFile(const char *);
#if defined(_WIN32)
#ifndef MECH_KEY
#define MECH_KEY "SOFTWARE\\gss\\mech"
#endif
static time_t getRegKeyModTime(HKEY hBaseKey, const char *keyPath);
static time_t getRegConfigModTime(const char *keyPath);
static void getRegKeyValue(HKEY key, const char *keyPath, const char *valueName, void **data, DWORD *dataLen);
static void loadConfigFromRegistry(HKEY keyBase, const char *keyPath);
#endif
static void updateMechList(void);
static void initMechList(void);
static void loadInterMech(gss_mech_info aMech);
static void freeMechList(void);

static OM_uint32 build_mechSet(void);
static void free_mechSet(void);

/*
 * list of mechanism libraries and their entry points.
 * the list also maintains state of the mech libraries (loaded or not).
 */
static gss_mech_info g_mechList = NULL;
static gss_mech_info g_mechListTail = NULL;
static k5_mutex_t g_mechListLock = K5_MUTEX_PARTIAL_INITIALIZER;
static time_t g_confFileModTime = (time_t)-1;
static time_t g_confLastCall = (time_t)0;

static gss_OID_set_desc g_mechSet = { 0, NULL };
static k5_mutex_t g_mechSetLock = K5_MUTEX_PARTIAL_INITIALIZER;

MAKE_INIT_FUNCTION(gssint_mechglue_init);
MAKE_FINI_FUNCTION(gssint_mechglue_fini);

int
gssint_mechglue_init(void)
{
	int err;

#ifdef SHOW_INITFINI_FUNCS
	printf("gssint_mechglue_init\n");
#endif

	add_error_table(&et_ggss_error_table);

	err = k5_mutex_finish_init(&g_mechSetLock);
	if (err)
		return err;
	err = k5_mutex_finish_init(&g_mechListLock);
	if (err)
		return err;

#ifdef _GSS_STATIC_LINK
	err = gss_krb5int_lib_init();
	if (err)
		return err;
	err = gss_spnegoint_lib_init();
	if (err)
		return err;
#endif

	err = gssint_mecherrmap_init();
	return err;
}

void
gssint_mechglue_fini(void)
{
	if (!INITIALIZER_RAN(gssint_mechglue_init) || PROGRAM_EXITING()) {
#ifdef SHOW_INITFINI_FUNCS
		printf("gssint_mechglue_fini: skipping\n");
#endif
		return;
	}

#ifdef SHOW_INITFINI_FUNCS
	printf("gssint_mechglue_fini\n");
#endif
#ifdef _GSS_STATIC_LINK
	gss_spnegoint_lib_fini();
	gss_krb5int_lib_fini();
#endif
	k5_mutex_destroy(&g_mechSetLock);
	k5_mutex_destroy(&g_mechListLock);
	free_mechSet();
	freeMechList();
	remove_error_table(&et_ggss_error_table);
	gssint_mecherrmap_destroy();
}

int
gssint_mechglue_initialize_library(void)
{
	return CALL_INIT_FUNCTION(gssint_mechglue_init);
}

/*
 * function used to reclaim the memory used by a gss_OID structure.
 * This routine requires direct access to the mechList.
 */
OM_uint32 KRB5_CALLCONV
gss_release_oid(minor_status, oid)
OM_uint32 *minor_status;
gss_OID *oid;
{
	OM_uint32 major;
	gss_mech_info aMech;

	if (minor_status != NULL)
	    *minor_status = 0;

	if (minor_status == NULL || oid == NULL)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	*minor_status = gssint_mechglue_initialize_library();
	if (*minor_status != 0)
		return (GSS_S_FAILURE);

	k5_mutex_lock(&g_mechListLock);
	aMech = g_mechList;
	while (aMech != NULL) {

		/*
		 * look through the loaded mechanism libraries for
		 * gss_internal_release_oid until one returns success.
		 * gss_internal_release_oid will only return success when
		 * the OID was recognized as an internal mechanism OID. if no
		 * mechanisms recognize the OID, then call the generic version.
		 */
		if (aMech->mech && aMech->mech->gss_internal_release_oid) {
			major = aMech->mech->gss_internal_release_oid(
					minor_status, oid);
			if (major == GSS_S_COMPLETE) {
				k5_mutex_unlock(&g_mechListLock);
				return (GSS_S_COMPLETE);
			}
			map_error(minor_status, aMech->mech);
		}
		aMech = aMech->next;
	} /* while */
	k5_mutex_unlock(&g_mechListLock);

	return (generic_gss_release_oid(minor_status, oid));
} /* gss_release_oid */

/*
 * Wrapper around inquire_attrs_for_mech to determine whether a mechanism has
 * the deprecated attribute.  Must be called without g_mechSetLock since it
 * will call into the mechglue.
 */
static int
is_deprecated(gss_OID element)
{
	OM_uint32 major, minor;
	gss_OID_set mech_attrs = GSS_C_NO_OID_SET;
	int deprecated = 0;

	major = gss_inquire_attrs_for_mech(&minor, element, &mech_attrs, NULL);
	if (major == GSS_S_COMPLETE) {
		gss_test_oid_set_member(&minor, (gss_OID)GSS_C_MA_DEPRECATED,
					mech_attrs, &deprecated);
	}

	if (mech_attrs != GSS_C_NO_OID_SET)
		gss_release_oid_set(&minor, &mech_attrs);

	return deprecated;
}

/*
 * Removes mechs with the deprecated attribute from an OID set.  Must be
 * called without g_mechSetLock held since it calls into the mechglue.
 */
static void
prune_deprecated(gss_OID_set mech_set)
{
	OM_uint32 i, j;

	j = 0;
	for (i = 0; i < mech_set->count; i++) {
	    if (!is_deprecated(&mech_set->elements[i]))
		mech_set->elements[j++] = mech_set->elements[i];
	    else
		gssalloc_free(mech_set->elements[i].elements);
	}
	mech_set->count = j;
}

/*
 * this function will return an oid set indicating available mechanisms.
 * The set returned is based on configuration file entries and
 * NOT on the loaded mechanisms.  This function does not check if any
 * of these can actually be loaded.
 * Deprecated mechanisms will not be returned.
 * This routine needs direct access to the mechanism list.
 * To avoid reading the configuration file each call, we will save a
 * a mech oid set, and only update it once the file has changed.
 */
OM_uint32 KRB5_CALLCONV
gss_indicate_mechs(minorStatus, mechSet_out)
OM_uint32 *minorStatus;
gss_OID_set *mechSet_out;
{
	OM_uint32 status;

	/* Initialize outputs. */

	if (minorStatus != NULL)
		*minorStatus = 0;

	if (mechSet_out != NULL)
		*mechSet_out = GSS_C_NO_OID_SET;

	/* Validate arguments. */
	if (minorStatus == NULL || mechSet_out == NULL)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	*minorStatus = gssint_mechglue_initialize_library();
	if (*minorStatus != 0)
		return (GSS_S_FAILURE);

	if (build_mechSet())
		return GSS_S_FAILURE;

	/*
	 * need to lock the g_mechSet in case someone tries to update it while
	 * I'm copying it.
	 */
	k5_mutex_lock(&g_mechSetLock);
	status = generic_gss_copy_oid_set(minorStatus, &g_mechSet, mechSet_out);
	k5_mutex_unlock(&g_mechSetLock);

	if (*mechSet_out != GSS_C_NO_OID_SET)
		prune_deprecated(*mechSet_out);

	return (status);
} /* gss_indicate_mechs */


/* Call with g_mechSetLock held, or during final cleanup.  */
static void
free_mechSet(void)
{
	unsigned int i;

	if (g_mechSet.count != 0) {
		for (i = 0; i < g_mechSet.count; i++)
			free(g_mechSet.elements[i].elements);
		free(g_mechSet.elements);
		g_mechSet.elements = NULL;
		g_mechSet.count = 0;
	}
}

static OM_uint32
build_mechSet(void)
{
	gss_mech_info mList;
	size_t i;
	size_t count;
	gss_OID curItem;

	/*
	 * lock the mutex since we will be updating
	 * the mechList structure
	 * we need to keep the lock while we build the mechanism list
	 * since we are accessing parts of the mechList which could be
	 * modified.
	 */
	k5_mutex_lock(&g_mechListLock);

	updateMechList();

	/*
	 * we need to lock the mech set so that no one else will
	 * try to read it as we are re-creating it
	 */
	k5_mutex_lock(&g_mechSetLock);

	/* if the oid list already exists we must free it first */
	free_mechSet();

	/* determine how many elements to have in the list */
	mList = g_mechList;
	count = 0;
	while (mList != NULL) {
		count++;
		mList = mList->next;
	}

	/* this should always be true, but.... */
	if (count > 0) {
		g_mechSet.elements =
			(gss_OID) calloc(count, sizeof (gss_OID_desc));
		if (g_mechSet.elements == NULL) {
			k5_mutex_unlock(&g_mechSetLock);
			k5_mutex_unlock(&g_mechListLock);
			return (GSS_S_FAILURE);
		}

		(void) memset(g_mechSet.elements, 0,
			      count * sizeof (gss_OID_desc));

		/* now copy each oid element */
		count = 0;
		for (mList = g_mechList; mList != NULL; mList = mList->next) {
			/* Don't expose interposer mechanisms. */
			if (mList->is_interposer)
				continue;
			curItem = &(g_mechSet.elements[count]);
			curItem->elements = (void*)
				malloc(mList->mech_type->length);
			if (curItem->elements == NULL) {
				/*
				 * this is nasty - we must delete the
				 * part of the array already copied
				 */
				for (i = 0; i < count; i++) {
					free(g_mechSet.elements[i].
					     elements);
				}
				free(g_mechSet.elements);
				g_mechSet.count = 0;
				g_mechSet.elements = NULL;
				k5_mutex_unlock(&g_mechSetLock);
				k5_mutex_unlock(&g_mechListLock);
				return (GSS_S_FAILURE);
			}
			g_OID_copy(curItem, mList->mech_type);
			count++;
		}
		g_mechSet.count = count;
	}

	k5_mutex_unlock(&g_mechSetLock);
	k5_mutex_unlock(&g_mechListLock);

	return GSS_S_COMPLETE;
}


/*
 * this function has been added for use by modules that need to
 * know what (if any) optional parameters are supplied in the
 * config file (MECH_CONF).
 * It will return the option string for a specified mechanism.
 * caller is responsible for freeing the memory
 */
char *
gssint_get_modOptions(oid)
const gss_OID oid;
{
	gss_mech_info aMech;
	char *modOptions = NULL;

	if (gssint_mechglue_initialize_library() != 0)
		return (NULL);

	/* make sure we have fresh data */
	k5_mutex_lock(&g_mechListLock);
	updateMechList();

	if ((aMech = searchMechList(oid)) == NULL ||
		aMech->optionStr == NULL) {
		k5_mutex_unlock(&g_mechListLock);
		return (NULL);
	}

	if (aMech->optionStr)
		modOptions = strdup(aMech->optionStr);
	k5_mutex_unlock(&g_mechListLock);

	return (modOptions);
} /* gssint_get_modOptions */

/* Return the mtime of filename or its eventual symlink target (if it is a
 * symlink), whichever is larger.  Return (time_t)-1 if lstat or stat fails. */
static time_t
check_link_mtime(const char *filename, time_t *mtime_out)
{
	struct stat st1, st2;

	if (lstat(filename, &st1) != 0)
		return (time_t)-1;
	if (!S_ISLNK(st1.st_mode))
		return st1.st_mtime;
	if (stat(filename, &st2) != 0)
		return (time_t)-1;
	return (st1.st_mtime > st2.st_mtime) ? st1.st_mtime : st2.st_mtime;
}

/* Load pathname if it is newer than last.  Update *highest to the maximum of
 * its current value and pathname's mod time. */
static void
load_if_changed(const char *pathname, time_t last, time_t *highest)
{
	time_t mtime;

	mtime = check_link_mtime(pathname, &mtime);
	if (mtime == (time_t)-1)
		return;
	if (mtime > *highest || *highest == (time_t)-1)
		*highest = mtime;
	if (mtime > last || last == (time_t)-1)
		loadConfigFile(pathname);
}

#ifndef _WIN32
/* Try to load any config files which have changed since the last call.  Config
 * files are MECH_CONF and any files matching MECH_CONF_PATTERN. */
static void
loadConfigFiles()
{
	glob_t globbuf;
	time_t highest = (time_t)-1, now;
	char **path;
	const char *val;

	/* Don't glob and stat more than once per second. */
	if (time(&now) == (time_t)-1 || now == g_confLastCall)
		return;
	g_confLastCall = now;

	val = secure_getenv("GSS_MECH_CONFIG");
	if (val != NULL) {
		load_if_changed(val, g_confFileModTime, &g_confFileModTime);
		return;
	}

	load_if_changed(MECH_CONF, g_confFileModTime, &highest);

	memset(&globbuf, 0, sizeof(globbuf));
	if (glob(MECH_CONF_PATTERN, 0, NULL, &globbuf) == 0) {
		for (path = globbuf.gl_pathv; *path != NULL; path++)
			load_if_changed(*path, g_confFileModTime, &highest);
	}
	globfree(&globbuf);

	g_confFileModTime = highest;
}
#endif

/*
 * determines if the mechList needs to be updated from file
 * and performs the update.
 * this functions must be called with a lock of g_mechListLock
 */
static void
updateMechList(void)
{
	gss_mech_info minfo;

#if defined(_WIN32)
	time_t lastConfModTime = getRegConfigModTime(MECH_KEY);
	if (g_confFileModTime >= lastConfModTime &&
	    g_confFileModTime != (time_t)-1)
		return;
	g_confFileModTime = lastConfModTime;
	loadConfigFromRegistry(HKEY_CURRENT_USER, MECH_KEY);
	loadConfigFromRegistry(HKEY_LOCAL_MACHINE, MECH_KEY);
#else /* _WIN32 */
	loadConfigFiles();
#endif /* !_WIN32 */

	/* Load any unloaded interposer mechanisms immediately, to make sure we
	 * interpose other mechanisms before they are used. */
	for (minfo = g_mechList; minfo != NULL; minfo = minfo->next) {
		if (minfo->is_interposer && minfo->mech == NULL)
			loadInterMech(minfo);
	}
} /* updateMechList */

/* Update the mech list from system configuration if we have never done so.
 * Must be invoked with the g_mechListLock mutex held. */
static void
initMechList(void)
{
	static int lazy_init = 0;

	if (lazy_init == 0) {
		updateMechList();
		lazy_init = 1;
	}
}

static void
releaseMechInfo(gss_mech_info *pCf)
{
	gss_mech_info cf;
	OM_uint32 minor_status;

	if (*pCf == NULL) {
		return;
	}

	cf = *pCf;

	if (cf->kmodName != NULL)
		free(cf->kmodName);
	if (cf->uLibName != NULL)
		free(cf->uLibName);
	if (cf->mechNameStr != NULL)
		free(cf->mechNameStr);
	if (cf->optionStr != NULL)
		free(cf->optionStr);
	if (cf->mech_type != GSS_C_NO_OID &&
	    cf->mech_type != &cf->mech->mech_type)
		generic_gss_release_oid(&minor_status, &cf->mech_type);
	if (cf->freeMech)
		zapfree(cf->mech, sizeof(*cf->mech));
	if (cf->int_mech_type != GSS_C_NO_OID)
		generic_gss_release_oid(&minor_status, &cf->int_mech_type);

	memset(cf, 0, sizeof(*cf));
	free(cf);

	*pCf = NULL;
}

#ifdef _GSS_STATIC_LINK
/*
 * Register a mechanism.  Called with g_mechListLock held.
 */
int
gssint_register_mechinfo(gss_mech_info template)
{
	gss_mech_info cf, new_cf;

	new_cf = calloc(1, sizeof(*new_cf));
	if (new_cf == NULL) {
		return ENOMEM;
	}

	new_cf->dl_handle = template->dl_handle;
	/* copy mech so we can rewrite canonical mechanism OID */
	new_cf->mech = (gss_mechanism)calloc(1, sizeof(struct gss_config));
	if (new_cf->mech == NULL) {
		releaseMechInfo(&new_cf);
		return ENOMEM;
	}
	*new_cf->mech = *template->mech;
	if (template->mech_type != NULL)
		new_cf->mech->mech_type = *(template->mech_type);
	new_cf->mech_type = &new_cf->mech->mech_type;
	new_cf->priority = template->priority;
	new_cf->freeMech = 1;
	new_cf->next = NULL;

	if (template->kmodName != NULL) {
		new_cf->kmodName = strdup(template->kmodName);
		if (new_cf->kmodName == NULL) {
			releaseMechInfo(&new_cf);
			return ENOMEM;
		}
	}
	if (template->uLibName != NULL) {
		new_cf->uLibName = strdup(template->uLibName);
		if (new_cf->uLibName == NULL) {
			releaseMechInfo(&new_cf);
			return ENOMEM;
		}
	}
	if (template->mechNameStr != NULL) {
		new_cf->mechNameStr = strdup(template->mechNameStr);
		if (new_cf->mechNameStr == NULL) {
			releaseMechInfo(&new_cf);
			return ENOMEM;
		}
	}
	if (template->optionStr != NULL) {
		new_cf->optionStr = strdup(template->optionStr);
		if (new_cf->optionStr == NULL) {
			releaseMechInfo(&new_cf);
			return ENOMEM;
		}
	}
	if (g_mechList == NULL) {
		g_mechList = new_cf;
		g_mechListTail = new_cf;
		return 0;
	} else if (new_cf->priority < g_mechList->priority) {
		new_cf->next = g_mechList;
		g_mechList = new_cf;
		return 0;
	}

	for (cf = g_mechList; cf != NULL; cf = cf->next) {
		if (cf->next == NULL ||
		    new_cf->priority < cf->next->priority) {
			new_cf->next = cf->next;
			cf->next = new_cf;
			if (g_mechListTail == cf) {
				g_mechListTail = new_cf;
			}
			break;
		}
	}

	return 0;
}
#endif /* _GSS_STATIC_LINK */

#define GSS_ADD_DYNAMIC_METHOD(_dl, _mech, _symbol) \
	do { \
		struct errinfo errinfo; \
		\
		memset(&errinfo, 0, sizeof(errinfo)); \
		if (krb5int_get_plugin_func(_dl, \
					    #_symbol, \
					    (void (**)())&(_mech)->_symbol, \
					    &errinfo) || errinfo.code) {  \
			(_mech)->_symbol = NULL; \
			k5_clear_error(&errinfo); \
			} \
	} while (0)

/*
 * If _symbol is undefined in the shared object but the shared object
 * is linked against the mechanism glue, it's possible for dlsym() to
 * return the mechanism glue implementation. Guard against that.
 */
#define GSS_ADD_DYNAMIC_METHOD_NOLOOP(_dl, _mech, _symbol)	\
	do {							\
		GSS_ADD_DYNAMIC_METHOD(_dl, _mech, _symbol);	\
		if ((_mech)->_symbol == _symbol)		\
		    (_mech)->_symbol = NULL;			\
	} while (0)

static gss_mechanism
build_dynamicMech(void *dl, const gss_OID mech_type)
{
	gss_mechanism mech;

	mech = (gss_mechanism)calloc(1, sizeof(*mech));
	if (mech == NULL) {
		return NULL;
	}

	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_release_cred);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_init_sec_context);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_accept_sec_context);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_process_context_token);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_delete_sec_context);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_context_time);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_get_mic);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_verify_mic);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_unwrap);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_display_status);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_indicate_mechs);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_compare_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_display_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_release_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_cred);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_add_cred);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_sec_context);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_sec_context);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_cred_by_mech);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_names_for_mech);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_context);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_internal_release_oid);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_size_limit);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_localname);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_authorize_localname);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_duplicate_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_store_cred);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_sec_context_by_oid);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_cred_by_oid);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_set_sec_context_option);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_set_cred_option);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_mech_invoke);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_aead);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_unwrap_aead);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_iov);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_unwrap_iov);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_wrap_iov_length);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_complete_auth_token);
	/* Services4User (introduced in 1.8) */
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred_impersonate_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_add_cred_impersonate_name);
	/* Naming extensions (introduced in 1.8) */
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_display_name_ext);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_name);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_get_name_attribute);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_set_name_attribute);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_delete_name_attribute);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_name_composite);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_map_name_to_any);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_release_any_name_mapping);
        /* RFC 4401 (introduced in 1.8) */
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_pseudo_random);
	/* RFC 4178 (introduced in 1.8; gss_get_neg_mechs not implemented) */
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_set_neg_mechs);
        /* draft-ietf-sasl-gs2 */
        GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_saslname_for_mech);
        GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_mech_for_saslname);
        /* RFC 5587 */
        GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_inquire_attrs_for_mech);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_acquire_cred_from);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_store_cred_into);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_acquire_cred_with_password);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_export_cred);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_import_cred);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_sec_context_by_mech);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_name_by_mech);
	GSS_ADD_DYNAMIC_METHOD(dl, mech, gssspi_import_cred_by_mech);
	/* draft-zhu-negoex */
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_query_meta_data);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_exchange_meta_data);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gssspi_query_mechanism_info);
	/* gss_get_mic_iov extensions (added 1.12, implementable 1.20) */
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_get_mic_iov);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_verify_mic_iov);
	GSS_ADD_DYNAMIC_METHOD_NOLOOP(dl, mech, gss_get_mic_iov_length);

	assert(mech_type != GSS_C_NO_OID);

	mech->mech_type = *(mech_type);

	return mech;
}

#define RESOLVE_GSSI_SYMBOL(_dl, _mech, _psym, _nsym)			\
	do {								\
		struct errinfo errinfo;					\
		memset(&errinfo, 0, sizeof(errinfo));			\
		if (krb5int_get_plugin_func(_dl,			\
					    "gssi" #_nsym,		\
					    (void (**)())&(_mech)->_psym \
					    ## _nsym,			\
					    &errinfo) || errinfo.code) { \
			(_mech)->_psym ## _nsym = NULL;			\
			k5_clear_error(&errinfo);			\
		}							\
	} while (0)

/* Build an interposer mechanism function table from dl. */
static gss_mechanism
build_interMech(void *dl, const gss_OID mech_type)
{
	gss_mechanism mech;

	mech = calloc(1, sizeof(*mech));
	if (mech == NULL) {
		return NULL;
	}

	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _acquire_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _release_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _init_sec_context);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _accept_sec_context);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _process_context_token);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _delete_sec_context);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _context_time);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _get_mic);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _verify_mic);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _unwrap);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _display_status);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _indicate_mechs);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _compare_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _display_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _import_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _release_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _add_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_sec_context);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _import_sec_context);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_cred_by_mech);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_names_for_mech);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_context);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _internal_release_oid);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_size_limit);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _localname);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _authorize_localname);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _duplicate_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _store_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_sec_context_by_oid);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_cred_by_oid);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _set_sec_context_option);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _set_cred_option);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _mech_invoke);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_aead);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _unwrap_aead);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_iov);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _unwrap_iov);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _wrap_iov_length);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _complete_auth_token);
	/* Services4User (introduced in 1.8) */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _acquire_cred_impersonate_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _add_cred_impersonate_name);
	/* Naming extensions (introduced in 1.8) */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _display_name_ext);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_name);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _get_name_attribute);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _set_name_attribute);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _delete_name_attribute);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_name_composite);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _map_name_to_any);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _release_any_name_mapping);
	/* RFC 4401 (introduced in 1.8) */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _pseudo_random);
	/* RFC 4178 (introduced in 1.8; get_neg_mechs not implemented) */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _set_neg_mechs);
	/* draft-ietf-sasl-gs2 */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_saslname_for_mech);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_mech_for_saslname);
	/* RFC 5587 */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _inquire_attrs_for_mech);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _acquire_cred_from);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _store_cred_into);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _acquire_cred_with_password);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _export_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _import_cred);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _import_sec_context_by_mech);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _import_name_by_mech);
	RESOLVE_GSSI_SYMBOL(dl, mech, gssspi, _import_cred_by_mech);
	/* gss_get_mic_iov extensions (added 1.12, implementable 1.20) */
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _get_mic_iov);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _verify_mic_iov);
	RESOLVE_GSSI_SYMBOL(dl, mech, gss, _get_mic_iov_length);

	mech->mech_type = *mech_type;
	return mech;
}

/*
 * Concatenate an interposer mech OID and a real mech OID to create an
 * identifier for the interposed mech.  (The concatenation will not be a valid
 * DER OID encoding, but the OID is only used internally.)
 */
static gss_OID
interposed_oid(gss_OID pre, gss_OID real)
{
	gss_OID o;

	o = (gss_OID)malloc(sizeof(gss_OID_desc));
	if (!o)
		return NULL;

	o->length = pre->length + real->length;
	o->elements = malloc(o->length);
	if (!o->elements) {
		free(o);
		return NULL;
	}

	memcpy(o->elements, pre->elements, pre->length);
	memcpy((char *)o->elements + pre->length, real->elements,
	       real->length);

	return o;
}

static void
loadInterMech(gss_mech_info minfo)
{
	struct plugin_file_handle *dl = NULL;
	struct errinfo errinfo;
	gss_OID_set (*isym)(const gss_OID);
	gss_OID_set list;
	gss_OID oid;
	OM_uint32 min;
	gss_mech_info mi;
	size_t i;

	memset(&errinfo, 0, sizeof(errinfo));

	if (krb5int_open_plugin(minfo->uLibName, &dl, &errinfo) != 0 ||
	    errinfo.code != 0) {
		return;
	}

	if (krb5int_get_plugin_func(dl, MECH_INTERPOSER_SYM,
				    (void (**)())&isym, &errinfo) != 0)
		goto cleanup;

	/* Get a list of mechs to interpose. */
	list = (*isym)(minfo->mech_type);
	if (!list)
		goto cleanup;
	minfo->mech = build_interMech(dl, minfo->mech_type);
	if (minfo->mech == NULL)
		goto cleanup;
	minfo->freeMech = 1;

	/* Add interposer fields for each interposed mech. */
	for (i = 0; i < list->count; i++) {
		/* Skip this mech if it doesn't exist or is already
		 * interposed. */
		oid = &list->elements[i];
		mi = searchMechList(oid);
		if (mi == NULL || mi->int_mech_type != NULL)
			continue;

		/* Construct a special OID to represent the interposed mech. */
		mi->int_mech_type = interposed_oid(minfo->mech_type, oid);
		if (mi->int_mech_type == NULL)
			continue;

		/* Save an alias to the interposer's function table. */
		mi->int_mech = minfo->mech;
	}
	(void)gss_release_oid_set(&min, &list);

	minfo->dl_handle = dl;
	dl = NULL;

cleanup:
	if (dl != NULL)
		krb5int_close_plugin(dl);
	k5_clear_error(&errinfo);
}

static void
freeMechList(void)
{
	gss_mech_info cf, next_cf;

	for (cf = g_mechList; cf != NULL; cf = next_cf) {
		next_cf = cf->next;
		releaseMechInfo(&cf);
	}
}

/*
 * Determine the mechanism to use for a caller-specified mech OID.  For the
 * real mech OID of an interposed mech, return the interposed OID.  For an
 * interposed mech OID (which an interposer mech uses when re-entering the
 * mechglue), return the real mech OID.  The returned OID is an alias and
 * should not be modified or freed.
 */
OM_uint32
gssint_select_mech_type(OM_uint32 *minor, gss_const_OID oid,
			gss_OID *selected_oid)
{
	gss_mech_info minfo;
	OM_uint32 status;

	*selected_oid = GSS_C_NO_OID;

	if (gssint_mechglue_initialize_library() != 0)
		return GSS_S_FAILURE;

	k5_mutex_lock(&g_mechListLock);

	/* Read conf file at least once so that interposer plugins have a
	 * chance of getting initialized. */
	initMechList();

	minfo = g_mechList;
	if (oid == GSS_C_NULL_OID)
		oid = minfo->mech_type;
	while (minfo != NULL) {
		if (g_OID_equal(minfo->mech_type, oid)) {
			if (minfo->int_mech_type != GSS_C_NO_OID)
				*selected_oid = minfo->int_mech_type;
			else
				*selected_oid = minfo->mech_type;
			status = GSS_S_COMPLETE;
			goto done;
		} else if ((minfo->int_mech_type != GSS_C_NO_OID) &&
			   (g_OID_equal(minfo->int_mech_type, oid))) {
			*selected_oid = minfo->mech_type;
			status = GSS_S_COMPLETE;
			goto done;
		}
		minfo = minfo->next;
	}
	status = GSS_S_BAD_MECH;

done:
	k5_mutex_unlock(&g_mechListLock);
	return status;
}

/* If oid is an interposed OID, return the corresponding real mech OID.  If
 * it's a real mech OID, return it unmodified.  Otherwised return null. */
gss_OID
gssint_get_public_oid(gss_const_OID oid)
{
	gss_mech_info minfo;
	gss_OID public_oid = GSS_C_NO_OID;

	/* if oid is null -> then get default which is the first in the list */
	if (oid == GSS_C_NO_OID)
		return GSS_C_NO_OID;

	if (gssint_mechglue_initialize_library() != 0)
		return GSS_C_NO_OID;

	k5_mutex_lock(&g_mechListLock);

	for (minfo = g_mechList; minfo != NULL; minfo = minfo->next) {
		if (minfo->is_interposer)
			continue;
		if (g_OID_equal(minfo->mech_type, oid) ||
		    ((minfo->int_mech_type != GSS_C_NO_OID) &&
		     (g_OID_equal(minfo->int_mech_type, oid)))) {
			public_oid = minfo->mech_type;
			break;
		}
	}

	k5_mutex_unlock(&g_mechListLock);
	return public_oid;
}

/* Translate a vector of oids (as from a union cred struct) into a set of
 * public OIDs using gssint_get_public_oid. */
OM_uint32
gssint_make_public_oid_set(OM_uint32 *minor_status, gss_OID oids, int count,
			   gss_OID_set *public_set)
{
	OM_uint32 status, tmpmin;
	gss_OID_set set;
	gss_OID public_oid;
	int i;

	*public_set = GSS_C_NO_OID_SET;

	status = generic_gss_create_empty_oid_set(minor_status, &set);
	if (GSS_ERROR(status))
		return status;

	for (i = 0; i < count; i++) {
		public_oid = gssint_get_public_oid(&oids[i]);
		if (public_oid == GSS_C_NO_OID)
			continue;
		status = generic_gss_add_oid_set_member(minor_status,
							public_oid, &set);
		if (GSS_ERROR(status)) {
			(void) generic_gss_release_oid_set(&tmpmin, &set);
			return status;
		}
	}

	*public_set = set;
	return GSS_S_COMPLETE;
}

/*
 * Register a mechanism.  Called with g_mechListLock held.
 */

/*
 * given the mechanism type, return the mechanism structure
 * containing the mechanism library entry points.
 * will return NULL if mech type is not found
 * This function will also trigger the loading of the mechanism
 * module if it has not been already loaded.
 */
gss_mechanism
gssint_get_mechanism(gss_const_OID oid)
{
	gss_mech_info aMech;
	gss_mechanism (*sym)(const gss_OID);
	struct plugin_file_handle *dl;
	struct errinfo errinfo;

	if (gssint_mechglue_initialize_library() != 0)
		return (NULL);

	k5_mutex_lock(&g_mechListLock);

	/* Check if the mechanism is already loaded. */
	aMech = g_mechList;
	if (oid == GSS_C_NULL_OID)
		oid = aMech->mech_type;
	while (aMech != NULL) {
		if (g_OID_equal(aMech->mech_type, oid) && aMech->mech) {
			k5_mutex_unlock(&g_mechListLock);
			return aMech->mech;
		} else if (aMech->int_mech_type != GSS_C_NO_OID &&
			   g_OID_equal(aMech->int_mech_type, oid)) {
			k5_mutex_unlock(&g_mechListLock);
			return aMech->int_mech;
		}
		aMech = aMech->next;
	}

	/*
	 * might need to re-read the configuration file before loading
	 * the mechanism to ensure we have the latest info.
	 */
	updateMechList();

	aMech = searchMechList(oid);

	/* is the mechanism present in the list ? */
	if (aMech == NULL) {
		k5_mutex_unlock(&g_mechListLock);
		return ((gss_mechanism)NULL);
	}

	/* has another thread loaded the mech */
	if (aMech->mech) {
		k5_mutex_unlock(&g_mechListLock);
		return (aMech->mech);
	}

	memset(&errinfo, 0, sizeof(errinfo));

	if (krb5int_open_plugin(aMech->uLibName, &dl, &errinfo) != 0 ||
	    errinfo.code != 0) {
		k5_clear_error(&errinfo);
		k5_mutex_unlock(&g_mechListLock);
		return ((gss_mechanism)NULL);
	}

	if (krb5int_get_plugin_func(dl, MECH_SYM, (void (**)())&sym,
				    &errinfo) == 0) {
		/* Call the symbol to get the mechanism table */
		aMech->mech = (*sym)(aMech->mech_type);
	} else {
		/* Try dynamic dispatch table */
		k5_clear_error(&errinfo);
		aMech->mech = build_dynamicMech(dl, aMech->mech_type);
		aMech->freeMech = 1;
	}
	if (aMech->mech == NULL) {
		(void) krb5int_close_plugin(dl);
		k5_mutex_unlock(&g_mechListLock);
		return ((gss_mechanism)NULL);
	}

	aMech->dl_handle = dl;

	k5_mutex_unlock(&g_mechListLock);
	return (aMech->mech);
} /* gssint_get_mechanism */

/*
 * this routine is used for searching the list of mechanism data.
 *
 * this needs to be called with g_mechListLock held.
 */
static gss_mech_info searchMechList(gss_const_OID oid)
{
	gss_mech_info aMech = g_mechList;

	/* if oid is null -> then get default which is the first in the list */
	if (oid == GSS_C_NULL_OID)
		return (aMech);

	while (aMech != NULL) {
		if (g_OID_equal(aMech->mech_type, oid))
			return (aMech);
		aMech = aMech->next;
	}

	/* none found */
	return ((gss_mech_info) NULL);
} /* searchMechList */

/* Return the first non-whitespace character starting from str. */
static char *
skip_whitespace(char *str)
{
	while (isspace(*str))
		str++;
	return str;
}

/* Truncate str at the first whitespace character and return the first
 * non-whitespace character after that point. */
static char *
delimit_ws(char *str)
{
	while (*str != '\0' && !isspace(*str))
		str++;
	if (*str != '\0')
		*str++ = '\0';
	return skip_whitespace(str);
}

/* Truncate str at the first occurrence of delimiter and return the first
 * non-whitespace character after that point. */
static char *
delimit(char *str, char delimiter)
{
	while (*str != '\0' && *str != delimiter)
		str++;
	if (*str != '\0')
		*str++ = '\0';
	return skip_whitespace(str);
}

/*
 * loads the configuration file
 * this is called while having a mutex lock on the mechanism list
 * entries for libraries that have been loaded can't be modified
 * mechNameStr and mech_type fields are not updated during updates
 */
static void
loadConfigFile(const char *fileName)
{
	char *sharedLib, *kernMod, *modOptions, *modType, *oid, *next;
	char buffer[BUFSIZ], *oidStr;
	FILE *confFile;

	if ((confFile = fopen(fileName, "r")) == NULL) {
		return;
	}

	(void) memset(buffer, 0, sizeof (buffer));
	while (fgets(buffer, BUFSIZ, confFile) != NULL) {

		/* ignore lines beginning with # */
		if (*buffer == '#')
			continue;

		/* Parse out the name, oid, and shared library path. */
		oidStr = buffer;
		oid = delimit_ws(oidStr);
		if (*oid == '\0')
			continue;
		sharedLib = delimit_ws(oid);
		if (*sharedLib == '\0')
			continue;
		next = delimit_ws(sharedLib);

		/* Parse out the kernel module name if present. */
		if (*next != '\0' && *next != '[' && *next != '<') {
			kernMod = next;
			next = delimit_ws(kernMod);
		} else {
			kernMod = NULL;
		}

		/* Parse out the module options if present. */
		if (*next == '[') {
			modOptions = next + 1;
			next = delimit(modOptions, ']');
		} else {
			modOptions = NULL;
		}

		/* Parse out the module type if present. */
		if (*next == '<') {
			modType = next + 1;
			(void)delimit(modType, '>');
		} else {
			modType = NULL;
		}

		addConfigEntry(oidStr, oid, sharedLib, kernMod, modOptions,
			       modType);
	} /* while */
	(void) fclose(confFile);
} /* loadConfigFile */

#if defined(_WIN32)

static time_t
filetimeToTimet(const FILETIME *ft)
{
	ULARGE_INTEGER ull;

	ull.LowPart = ft->dwLowDateTime;
	ull.HighPart = ft->dwHighDateTime;
	return (time_t)(ull.QuadPart / 10000000ULL - 11644473600ULL);
}

static time_t
getRegConfigModTime(const char *keyPath)
{
	time_t currentUserModTime = getRegKeyModTime(HKEY_CURRENT_USER,
						     keyPath);
	time_t localMachineModTime = getRegKeyModTime(HKEY_LOCAL_MACHINE,
						      keyPath);

	return currentUserModTime > localMachineModTime ? currentUserModTime :
		localMachineModTime;
}

static time_t
getRegKeyModTime(HKEY hBaseKey, const char *keyPath)
{
	HKEY hConfigKey;
	HRESULT rc;
	int iSubKey = 0;
	time_t modTime = 0, keyModTime;
	FILETIME keyLastWriteTime;
	char subKeyName[256];

	if ((rc = RegOpenKeyEx(hBaseKey, keyPath, 0, KEY_ENUMERATE_SUB_KEYS,
			       &hConfigKey)) != ERROR_SUCCESS) {
		/* TODO: log error message */
		return 0;
	}
	do {
		int subKeyNameSize=sizeof(subKeyName)/sizeof(subKeyName[0]);
		if ((rc = RegEnumKeyEx(hConfigKey, iSubKey++, subKeyName,
				       &subKeyNameSize, NULL, NULL, NULL,
				       &keyLastWriteTime)) != ERROR_SUCCESS) {
			break;
		}
		keyModTime = filetimeToTimet(&keyLastWriteTime);
		if (modTime < keyModTime) {
			modTime = keyModTime;
		}
	} while (1);
	RegCloseKey(hConfigKey);
	return modTime;
}

static void
getRegKeyValue(HKEY hKey, const char *keyPath, const char *valueName,
	       void **data, DWORD* dataLen)
{
	DWORD sizeRequired=*dataLen;
	HRESULT hr;
	/* Get data length required */
	if ((hr = RegGetValue(hKey, keyPath, valueName, RRF_RT_REG_SZ, NULL,
			      NULL, &sizeRequired)) != ERROR_SUCCESS) {
		/* TODO: LOG registry error */
		return;
	}
	/* adjust data buffer size if necessary */
	if (*dataLen < sizeRequired) {
		*dataLen = sizeRequired;
		*data = realloc(*data, sizeRequired);
		if (!*data) {
			*dataLen = 0;
			/* TODO: LOG OOM ERROR! */
			return;
		}
	}
	/* get data */
	if ((hr = RegGetValue(hKey, keyPath, valueName, RRF_RT_REG_SZ, NULL,
			      *data, &sizeRequired)) != ERROR_SUCCESS) {
		/* LOG registry error */
		return;
	}
}

static void
loadConfigFromRegistry(HKEY hBaseKey, const char *keyPath)
{
	HKEY hConfigKey;
	DWORD iSubKey, nSubKeys, maxSubKeyNameLen, modTypeLen;
	char *oidStr = NULL, *oid = NULL, *sharedLib = NULL, *kernMod = NULL;
	char *modOptions = NULL, *modType = NULL;
	DWORD oidStrLen = 0, oidLen = 0, sharedLibLen = 0, kernModLen = 0;
	DWORD modOptionsLen = 0;
	HRESULT rc;

	if ((rc = RegOpenKeyEx(hBaseKey, keyPath, 0,
			       KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,
			       &hConfigKey)) != ERROR_SUCCESS) {
		/* TODO: log registry error */
		return;
	}

	if ((rc = RegQueryInfoKey(hConfigKey,
		NULL, /* lpClass */
		NULL, /* lpcClass */
		NULL, /* lpReserved */
		&nSubKeys,
		&maxSubKeyNameLen,
		NULL, /* lpcMaxClassLen */
		NULL, /* lpcValues */
		NULL, /* lpcMaxValueNameLen */
		NULL, /* lpcMaxValueLen */
		NULL, /* lpcbSecurityDescriptor */
		NULL  /* lpftLastWriteTime */ )) != ERROR_SUCCESS) {
		goto cleanup;
	}
	oidStr = malloc(++maxSubKeyNameLen);
	if (!oidStr) {
		goto cleanup;
	}
	for (iSubKey=0; iSubKey<nSubKeys; iSubKey++) {
		oidStrLen = maxSubKeyNameLen;
		if ((rc = RegEnumKeyEx(hConfigKey, iSubKey, oidStr, &oidStrLen,
				       NULL, NULL, NULL, NULL)) !=
		    ERROR_SUCCESS) {
			/* TODO: log registry error */
			continue;
		}
		getRegKeyValue(hConfigKey, oidStr, "OID", &oid, &oidLen);
		getRegKeyValue(hConfigKey, oidStr, "Shared Library",
			       &sharedLib, &sharedLibLen);
		getRegKeyValue(hConfigKey, oidStr, "Kernel Module", &kernMod,
			       &kernModLen);
		getRegKeyValue(hConfigKey, oidStr, "Options", &modOptions,
			       &modOptionsLen);
		getRegKeyValue(hConfigKey, oidStr, "Type", &modType,
			       &modTypeLen);
		addConfigEntry(oidStr, oid, sharedLib, kernMod, modOptions,
			       modType);
	}
cleanup:
	RegCloseKey(hConfigKey);
	if (oidStr) {
		free(oidStr);
	}
	if (oid) {
		free(oid);
	}
	if (sharedLib) {
		free(sharedLib);
	}
	if (kernMod) {
		free(kernMod);
	}
	if (modOptions) {
		free(modOptions);
	}
}
#endif

static void
addConfigEntry(const char *oidStr, const char *oid, const char *sharedLib,
	       const char *kernMod, const char *modOptions,
	       const char *modType)
{
#if defined(_WIN32)
	const char *sharedPath;
#else
	char sharedPath[sizeof (MECH_LIB_PREFIX) + BUFSIZ];
#endif
	char *tmpStr;
	gss_OID mechOid;
	gss_mech_info aMech, tmp;
	OM_uint32 minor;
	gss_buffer_desc oidBuf;

	if ((!oid) || (!oidStr)) {
		return;
	}
	/*
	 * check if an entry for this oid already exists
	 * if it does, and the library is already loaded then
	 * we can't modify it, so skip it
	 */
	oidBuf.value = (void *)oid;
	oidBuf.length = strlen(oid);
	if (generic_gss_str_to_oid(&minor, &oidBuf, &mechOid)
		!= GSS_S_COMPLETE) {
		return;
	}

	aMech = searchMechList(mechOid);
	if (aMech && aMech->mech) {
		generic_gss_release_oid(&minor, &mechOid);
		return;
	}

	/*
	 * If that's all, then this is a corrupt entry. Skip it.
	 */
	if (! *sharedLib) {
		generic_gss_release_oid(&minor, &mechOid);
		return;
	}
#if defined(_WIN32)
	sharedPath = sharedLib;
#else
	if (sharedLib[0] == '/')
		snprintf(sharedPath, sizeof(sharedPath), "%s", sharedLib);
	else
		snprintf(sharedPath, sizeof(sharedPath), "%s%s",
			 MECH_LIB_PREFIX, sharedLib);
#endif
	/*
	 * are we creating a new mechanism entry or
	 * just modifying existing (non loaded) mechanism entry
	 */
	if (aMech) {
		/*
		 * delete any old values and set new
		 * mechNameStr and mech_type are not modified
		 */
		if (aMech->kmodName) {
			free(aMech->kmodName);
			aMech->kmodName = NULL;
		}

		if (aMech->optionStr) {
			free(aMech->optionStr);
			aMech->optionStr = NULL;
		}

		if ((tmpStr = strdup(sharedPath)) != NULL) {
			if (aMech->uLibName)
				free(aMech->uLibName);
			aMech->uLibName = tmpStr;
		}

		if (kernMod) /* this is an optional parameter */
			aMech->kmodName = strdup(kernMod);

		if (modOptions) /* optional module options */
			aMech->optionStr = strdup(modOptions);

		/* the oid is already set */
		generic_gss_release_oid(&minor, &mechOid);
		return;
	}

	/* adding a new entry */
	aMech = calloc(1, sizeof (struct gss_mech_config));
	if (aMech == NULL) {
		generic_gss_release_oid(&minor, &mechOid);
		return;
	}
	aMech->mech_type = mechOid;
	aMech->uLibName = strdup(sharedPath);
	aMech->mechNameStr = strdup(oidStr);
	aMech->freeMech = 0;

	/* check if any memory allocations failed - bad news */
	if (aMech->uLibName == NULL || aMech->mechNameStr == NULL) {
		if (aMech->uLibName)
			free(aMech->uLibName);
		if (aMech->mechNameStr)
			free(aMech->mechNameStr);
		generic_gss_release_oid(&minor, &mechOid);
		free(aMech);
		return;
	}
	if (kernMod)	/* this is an optional parameter */
		aMech->kmodName = strdup(kernMod);

	if (modOptions)
		aMech->optionStr = strdup(modOptions);

	if (modType && strcmp(modType, "interposer") == 0)
		aMech->is_interposer = 1;

	/*
	 * add the new entry to the end of the list - make sure
	 * that only complete entries are added because other
	 * threads might currently be searching the list.
	 */
	tmp = g_mechListTail;
	g_mechListTail = aMech;

	if (tmp != NULL)
		tmp->next = aMech;

	if (g_mechList == NULL)
		g_mechList = aMech;
}

