/*
 * Copyright (c) 1996-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)g_initialize.c	1.28	99/05/04 SMI"

/*
 * This file contains functions to initialize the gssapi library and
 * load mechanism libraries.
 *
 * It also contain functions requiring direct access to the mechanism's
 * list (gss_inidicate_mechs and gss_release_oid) as well as support
 * functions which translate the mechanism strings to oids and vise versa.
 *
 * The mechanism libraries are loaded on demand.  This is triggered
 * through the get_mechanism function call.
 *
 * Updates to the mechList are performed with the following restrictions:
 *	- once a library is loaded, none of the fields are updated
 *	- existing entiries for non-loaded mechs, will have the
 *		library and kernel module names updated only
 *		(i.e. the mech oid and mech name will not be updated)
 */

#include <mechglueP.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <synch.h>
#include <dlfcn.h>
#include <libintl.h>


#ifndef TEXT_DOMAIN
#error TEXT_DOMAIN not defined
#endif

#define	MECH_CONF "/etc/gss/mech"

#define	MECH_LIB_PREFIX1	"/usr/lib/"

/*
 * This #ifdef mess figures out if we are to be compiled into
 * a sparcv9/lp64 binary for the purposes of figuring the absolute location
 * of gss-api mechanism modules.
 */
#ifdef	_LP64

#ifdef	__sparc

#define	MECH_LIB_PREFIX2	"sparcv9/"

#elif defined(__ia64)

#define	MECH_LIB_PREFIX2	"ia64/"

#else	/* __sparc */

you need to define where under /usr the LP64 libraries live for this platform

#endif	/* __sparc */

#else	/* _LP64 */

#define	MECH_LIB_PREFIX2	""

#endif	/* _LP64 */

#define	MECH_LIB_DIR		"gss/"

#define	MECH_LIB_PREFIX	MECH_LIB_PREFIX1 MECH_LIB_PREFIX2 MECH_LIB_DIR


#ifndef	MECH_SYM
#define	MECH_SYM "gss_mech_initialize"
#endif

#define	M_DEFAULT	"default"

/* Local functions */
static gss_mech_info searchMechList(const gss_OID);
static void loadConfigFile(const char *);
static void updateMechList(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 mutex_t g_mechListLock;
static time_t g_confFileModTime = (time_t)0;

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

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

	*minor_status = 0;

	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.
		 */

		/*
		 * we can walk the mechanism list without a mutex, because we
		 * are only looking at fields which once read will never change.
		 * Mechanism entries are always added to the end, and as
		 * complete entries.
		 */
		if (aMech->mech && aMech->mech->gss_internal_release_oid) {
			major = aMech->mech->gss_internal_release_oid(
					aMech->mech->context,
					minor_status, oid);
			if (major == GSS_S_COMPLETE)
				return (GSS_S_COMPLETE);
		}
		aMech = aMech->next;
	} /* while */

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


/*
 * 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.
 * 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.
 */
static time_t g_mechSetTime = (time_t)0;
static gss_OID_set_desc g_mechSet = { 0, NULL };
static mutex_t g_mechSetLock;


OM_uint32
gss_indicate_mechs(minorStatus, mechSet)
OM_uint32 *minorStatus;
gss_OID_set *mechSet;
{
	gss_mech_info mList;
	char *fileName;
	struct stat fileInfo;
	int count, i, j;
	gss_OID curItem;

	if (!minorStatus)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	*minorStatus = 0;


	/* check output parameter */
	if (mechSet == NULL)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	if ((fileName = getenv("GSSAPI_MECH_CONF")) == NULL)
		fileName = MECH_CONF;

	/*
	 * If we have already computed the mechanisms supported and if it
	 * is still valid; make a copy and return to caller,
	 * otherwise build it first.
	 */
	if ((stat(fileName, &fileInfo) == 0 &&
		fileInfo.st_mtime > g_mechSetTime)) {
		/*
		 * 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.
		 */
		mutex_lock(&g_mechListLock);

		/*
		 * this checks for the case when we need to re-construct the
		 * g_mechSet structure, but the mechanism list is upto date
		 * (because it has been read by someone calling
		 * __gss_get_mechanism)
		 */
		if (fileInfo.st_mtime > g_confFileModTime)
		{
			g_confFileModTime = fileInfo.st_mtime;
			loadConfigFile(fileName);
		}

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

		/* if the oid list already exists we must free it first */
		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;
		}

		/* 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) {
				mutex_unlock(&g_mechSetLock);
				mutex_unlock(&g_mechListLock);
				return (GSS_S_FAILURE);
			}

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

			/* now copy each oid element */
			g_mechSet.count = count;
			count = 0;
			mList = g_mechList;
			while (mList != NULL) {
				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;
					mutex_unlock(&g_mechSetLock);
					mutex_unlock(&g_mechListLock);
					return (GSS_S_FAILURE);
				}
				g_OID_copy(curItem, mList->mech_type);
				count++;
				mList = mList->next;
			}
		}

		g_mechSetTime = fileInfo.st_mtime;
		mutex_unlock(&g_mechSetLock);
		mutex_unlock(&g_mechListLock);
	} /* if g_mechSet is out of date or not initialized */


	/*
	 * the mech set is created and it is up to date
	 * so just copy it to caller
	 */
	if ((*mechSet =
		(gss_OID_set) malloc(sizeof (gss_OID_set_desc))) == NULL)
	{
		return (GSS_S_FAILURE);
	}

	/*
	 * need to lock the g_mechSet in case someone tries to update it while
	 * I'm copying it.
	 */
	mutex_lock(&g_mechSetLock);

	/* allocate space for the oid structures */
	if (((*mechSet)->elements =
		(void*) calloc(g_mechSet.count, sizeof (gss_OID_desc)))
		== NULL)
	{
		mutex_unlock(&g_mechSetLock);
		free(*mechSet);
		*mechSet = NULL;
		return (GSS_S_FAILURE);
	}

	/* now copy the oid structures */
	memcpy((*mechSet)->elements, g_mechSet.elements,
		g_mechSet.count * sizeof (gss_OID_desc));

	(*mechSet)->count = g_mechSet.count;

	/* still need to copy each of the oid elements arrays */
	for (i = 0; i < (*mechSet)->count; i++) {
		curItem = &((*mechSet)->elements[i]);
		curItem->elements =
			(void *) malloc(g_mechSet.elements[i].length);
		if (curItem->elements == NULL) {
			mutex_unlock(&g_mechSetLock);
			/*
			 * must still free the allocated elements for
			 * each allocated gss_OID_desc
			 */
			for (j = 0; j < i; j++) {
				free((*mechSet)->elements[j].elements);
			}
			free((*mechSet)->elements);
			free(mechSet);
			*mechSet = NULL;
			return (GSS_S_FAILURE);
		}
		g_OID_copy(curItem, &g_mechSet.elements[i]);
	}
	mutex_unlock(&g_mechSetLock);
	return (GSS_S_COMPLETE);
} /* gss_indicate_mechs */


/*
 * this function has been added for use by gssd.
 * It will return the kernel module name for a specified mechanism.
 * caller is responsible for freeing the memory
 */
char *
__gss_get_kmodName(oid)
const gss_OID oid;
{
	gss_mech_info aMech;
	char *kmodName = NULL;

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

	/* searching the list does not require a lock */
	if ((aMech = searchMechList(oid)) == NULL || aMech->kmodName == NULL) {
		return (NULL);
	}

	/*
	 * need to obtain a lock on this structure in case someone else
	 * will try to update it during the copy
	 */
	mutex_lock(&g_mechListLock);
	if (aMech->kmodName)
		kmodName = strdup(aMech->kmodName);
	mutex_unlock(&g_mechListLock);

	return (kmodName);
} /* __gss_get_kmodName */


/*
 * given a mechanism string return the mechanism oid
 */
OM_uint32
__gss_mech_to_oid(const char *mechStr, gss_OID* oid)
{
	gss_mech_info aMech;

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

	*oid = GSS_C_NULL_OID;

	if ((mechStr == NULL) || (strlen(mechStr) == 0) ||
		(strcasecmp(mechStr, M_DEFAULT) == 0))
		return (GSS_S_COMPLETE);

	/* ensure we have fresh data */
	mutex_lock(&g_mechListLock);
	updateMechList();
	mutex_unlock(&g_mechListLock);

	aMech = g_mechList;

	/* no lock required - only looking at fields that are not updated */
	while (aMech != NULL) {
		if ((aMech->mechNameStr) &&
			strcmp(aMech->mechNameStr, mechStr) == 0) {
			*oid = aMech->mech_type;
			return (GSS_S_COMPLETE);
		}
		aMech = aMech->next;
	}
	return (GSS_S_FAILURE);
} /* __gss_mech_to_oid */


/*
 * given the mechanism oid, return the mechanism oid
 */
const char *
__gss_oid_to_mech(const gss_OID oid)
{
	gss_mech_info aMech;

	if (oid == GSS_C_NULL_OID)
		return (M_DEFAULT);

	/* ensure we have fresh data */
	mutex_lock(&g_mechListLock);
	updateMechList();
	mutex_unlock(&g_mechListLock);

	if ((aMech = searchMechList(oid)) == NULL)
		return (NULL);

	return (aMech->mechNameStr);
} /* __gss_oid_to_mech */


/*
 * return a list of mechanism strings supported
 * upon return the array is terminated with a NULL entry
 */
OM_uint32
__gss_get_mechanisms(char *mechArray[], int arrayLen)
{
	gss_mech_info aMech;
	int i;

	if (mechArray == NULL || arrayLen < 1)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	/* ensure we have fresh data */
	mutex_lock(&g_mechListLock);
	updateMechList();
	mutex_unlock(&g_mechListLock);

	aMech = g_mechList;

	/* no lock required - only looking at fields that are not updated */
	for (i = 1; i < arrayLen; i++) {
		if (aMech != NULL) {
			*mechArray = aMech->mechNameStr;
			mechArray++;
			aMech = aMech->next;
		} else
			break;
	}
	*mechArray = NULL;
	return (GSS_S_COMPLETE);
} /* gss_get_mechanisms */


/*
 * 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)
{
	char *fileName;
	struct stat fileInfo;

	if ((fileName = getenv("GSSAPI_MECH_CONF")) == NULL)
		fileName = MECH_CONF;

	/* check if mechList needs updating */
	if (stat(fileName, &fileInfo) == 0 &&
		(fileInfo.st_mtime > g_confFileModTime)) {
		loadConfigFile(fileName);
		g_confFileModTime = fileInfo.st_mtime;
	}
} /* updateMechList */


/*
 * 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
__gss_get_mechanism(oid)
const gss_OID oid;
{
	gss_mech_info aMech;
	gss_mechanism (*sym)(const gss_OID);
	void *dl;

	/* check if the mechanism is already loaded */
	if ((aMech = searchMechList(oid)) != NULL && aMech->mech) {
		return (aMech->mech);
	}

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

	aMech = searchMechList(oid);

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

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

	/* we found the mechanism, but it is not loaded */
	if ((dl = dlopen(aMech->uLibName, RTLD_NOW)) == NULL) {
		fprintf(stderr,
			"dlopen(%s): %s\n", aMech->uLibName, dlerror());
		mutex_unlock(&g_mechListLock);
		return ((gss_mechanism)NULL);
	}

	if ((sym = (gss_mechanism (*)(const gss_OID))dlsym(dl, MECH_SYM))
			== NULL) {
		dlclose(dl);
		fprintf(stderr,
			dgettext(TEXT_DOMAIN,
				"unable to initialize mechanism"
				" library [%s]\n"),
				aMech->uLibName);
		mutex_unlock(&g_mechListLock);
		return ((gss_mechanism)NULL);
	}

	/* Call the symbol to get the mechanism table */
	aMech->mech = (*sym)(aMech->mech_type);
	if (aMech->mech == NULL) {
		dlclose(dl);
		fprintf(stderr,
			dgettext(TEXT_DOMAIN,
				"unable to initialize mechanism"
				" library [%s]\n"),
			aMech->uLibName);
		mutex_unlock(&g_mechListLock);
		return ((gss_mechanism)NULL);
	}

	mutex_unlock(&g_mechListLock);
	return (aMech->mech);
} /* __gss_get_mechanism */


/*
 * this routine is used for searching the list of mechanism data.
 * it needs not be mutex protected because we only add new structures
 * from the end and they are fully initialized before being added.
 */
static gss_mech_info searchMechList(oid)
const gss_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 */


/*
 * 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(fileName)
const char *fileName;
{
	char buffer[BUFSIZ], *oidStr, *oid, *sharedLib, *kernMod, *endp;
	char sharedPath[sizeof (MECH_LIB_PREFIX) + BUFSIZ];
	char *tmpStr;
	FILE *confFile;
	gss_OID mechOid;
	gss_mech_info aMech, tmp;
	OM_uint32 minor;
	gss_buffer_desc oidBuf;

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

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

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

		/*
		 * find the first white-space character after
		 * the mechanism name
		 */
		oidStr = buffer;
		for (oid = buffer; *oid && !isspace(*oid); oid++);

		/* Now find the first non-white-space character */
		if (*oid) {
			*oid = '\0';
			oid++;
			while (*oid && isspace(*oid))
				oid++;
		}

		/*
		 * If that's all, then this is a corrupt entry. Skip it.
		 */
		if (! *oid)
			continue;

		/* Find the end of the oid and make sure it is NULL-ended */
		for (endp = oid; *endp && !isspace(*endp); endp++)
			;

		if (*endp) {
			*endp = '\0';
		}

		/*
		 * 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) {
			fprintf(stderr,
				dgettext(TEXT_DOMAIN, "invalid mechanism oid"
					" [%s] in configuration file"),
				oid);
			continue;
		}

		aMech = searchMechList(mechOid);
		if (aMech && aMech->mech) {
			free(mechOid->elements);
			free(mechOid);
			continue;
		}

		/* Find the start of the shared lib name */
		for (sharedLib = endp+1; *sharedLib && isspace(*sharedLib);
			sharedLib++)
			;

		/*
		 * If that's all, then this is a corrupt entry. Skip it.
		 */
		if (! *sharedLib) {
			free(mechOid->elements);
			free(mechOid);
			continue;
		}

		/*
		 * Find the end of the shared lib name and make sure it is
		 *  NULL-terminated.
		 */
		for (endp = sharedLib; *endp && !isspace(*endp); endp++)
			;

		if (*endp) {
			*endp = '\0';
		}

		/* Find the start of the optional kernel module lib name */
		for (kernMod = endp+1; *kernMod && isspace(*kernMod);
			kernMod++)
			;

		if (*kernMod) {
			/*
			 * Find the end of the shared lib name and make sure
			 * it is NULL-terminated.
			 */
			for (endp = kernMod; *endp && !isspace(*endp); endp++)
				;

			if (*endp) {
				*endp = '\0';
			}
		} else
			kernMod = NULL;

		strcpy(sharedPath, MECH_LIB_PREFIX);
		strcat(sharedPath, sharedLib);

		/*
		 * 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 ((tmpStr = strdup(sharedPath)) != NULL) {
				if (aMech->uLibName)
					free(aMech->uLibName);
				aMech->uLibName = tmpStr;
			}

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

			/* the oid is already set */
			free(mechOid->elements);
			free(mechOid);
			continue;
		}

		/* adding a new entry */
		aMech = malloc(sizeof (struct gss_mech_config));
		if (aMech == NULL) {
			free(mechOid->elements);
			free(mechOid);
			continue;
		}
		memset(aMech, 0, sizeof (struct gss_mech_config));
		aMech->mech_type = mechOid;
		aMech->uLibName = strdup(sharedPath);
		aMech->mechNameStr = strdup(oidStr);

		/* 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);
			free(mechOid->elements);
			free(mechOid);
			free(aMech);
			continue;
		}
		if (kernMod)	/* this is an optional parameter */
			aMech->kmodName = strdup(kernMod);

		/*
		 * 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;
	} /* while */
	fclose(confFile);
} /* loadConfigFile */
