root/crypto/krb5/src/lib/gssapi/mechglue/g_initialize.c
/* #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(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(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(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(void)
{
        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->dl_handle != NULL)
                krb5int_close_plugin(cf->dl_handle);
        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 (**)(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 (**)(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 (**)(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 (**)(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;
}