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

#pragma ident	"@(#)nis_sec_mechs.c	1.3	99/05/04 SMI"

/*
 * This module contains the interfaces for the NIS+ security mechanisms.
 */

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <rpc/rpc.h>
#include <netconfig.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <thread.h>
#include <synch.h>
#include <dlfcn.h>
#include <rpcsvc/nis_dhext.h>


/*
 * NIS+ security file
 */

#define	NIS_SEC_CF_MAX_LINELEN		512

/* the min number of fields allowable per line */
#define	NIS_SEC_CF_MIN_FIELDS		5
/* the max number of fields processed per line */
#define	NIS_SEC_CF_MAX_FIELDS		7

/* field "Not Applicable" char */
#define	NIS_SEC_CF_NA_CHAR	'-'
#define	NIS_SEC_CF_NA_CMP(a)	((a)[0] == NIS_SEC_CF_NA_CHAR && (a)[1] == '\0')


static const char	*cf_entry_type_mech_str = "mech";
static const char	*cf_mech_des_str = NIS_SEC_CF_DES_ALIAS;
static const char	*cf_mech_dh1920_str = "dh192-0";

static const char	*cf_secserv_default_str = "default";
static const char	*cf_secserv_none_str = "none";
static const char	*cf_secserv_integrity_str = "integrity";
static const char	*cf_secserv_privacy_str = "privacy";

static mutex_t		nis_sec_cf_lock = DEFAULTMUTEX;


/*
 * GSS mechanisms file
 *
 * This is currently a private NIS+ interface but at some point in the future
 * can be broken out and made available to other apps that need to access
 * GSS backends.
 */

#define	MF_MAX_LINELEN	256
#define	MF_MAX_FLDLEN	MAXDHNAME

/* mech file entry type */
typedef struct {
	char *mechname;
	char *oid;
	char *libname;
	/* the 4th field is not used by user land apps */
} mfent_t;

static const char	mech_file[] = "/etc/gss/mech";
static const int	mech_file_flds_max = 3;
static const int	mech_file_flds_min = 3;
static mutex_t		mech_file_lock = DEFAULTMUTEX;
static const char	dh_str[] = "diffie_hellman";


#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/"

#else   /* __sparc */

#define	MECH_LIB_PREFIX2	"ia64/"

#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


static void
list_free_all(void (*free_ent)(), void **mpp)
{
	void **tpp = mpp;

	if (tpp) {
		for (; *tpp; tpp++)
			(*free_ent)(*tpp);
		free(mpp);
	}
}

static void **
list_append_ent(void *ent, void **list, uint_t cnt, void (*free_ent)())
{
	void **new_l;

	if (! (new_l = realloc(list, sizeof (*list) * (cnt + 1)))) {
		list_free_all(free_ent, list);
		return (NULL);
	}
	*(new_l + cnt - 1) = ent;
	*(new_l + cnt) = NULL;

	return (new_l);
}

static void **
list_copy(void *(*cp_ent)(), void **mpp)
{
	void	**tpp_h;
	void	**tpp;
	void	*tp;
	int 	diff;

	if (! mpp)
		return (NULL);

	for (tpp = mpp; *tpp; tpp++)
		;

	diff = tpp - mpp;

	if (! (tpp_h = calloc(diff + 1, sizeof (*mpp))))
		return (NULL);

	for (tpp = tpp_h; *mpp; mpp++) {
		if (! (tp = (*cp_ent)(*mpp))) {
			free(tpp_h);
			return (NULL);
		}
		*tpp++ = tp;
	}

	return (tpp_h);
}

static char *
nextline(fd, line)
	FILE *fd;
	char *line;
{
	char *cp;

	if (fgets(line, NIS_SEC_CF_MAX_LINELEN, fd) == NULL)
		return (NULL);
	cp = index(line, '\n');
	if (cp)
		*cp = '\0';
	return (line);
}

static int
nextfield(char **cpp, char *op, int n)
{

	int max;
	char *dst = op;
	char *cp = *cpp;

	while (*cp == ' ' || *cp == '\t')
		cp++;
	if (*cp == '\0' || *cp == '#')
		return (0);

	max = (int)op + n;
	while (*cp && *cp != ' ' && *cp != '\t' && *cp != '#' &&
		(int)dst < max)
		*dst++ = *cp++;
	*dst = '\0';

	if ((int)dst >= max)
		/* not much else to do but move past current field */
		while (*cp && *cp != ' ' && *cp != '\t' && *cp != '#')
			cp++;

	*cpp = cp;

	return (1);
}


static rpc_gss_service_t
str_to_secserv_t(const char *s)
{

	if (s) {
		if (strncmp(cf_secserv_none_str, s,
			    strlen(cf_secserv_none_str)) == 0)
			return (rpc_gss_svc_none);
		if (strncmp(cf_secserv_integrity_str, s,
			    strlen(cf_secserv_integrity_str)) == 0)
			return (rpc_gss_svc_integrity);
		if (strncmp(cf_secserv_privacy_str, s,
			    strlen(cf_secserv_privacy_str)) == 0)
			return (rpc_gss_svc_privacy);
	}

	return (rpc_gss_svc_default);
}

/*
 * Return TRUE if all the chars up to the NUL are of the digit type.
 * Else return FALSE.
 */
static bool_t
isnumberstr(const char *s)
{

	for (; *s; s++)
		if (! isdigit(*s))
			return (FALSE);

	return (TRUE);
}

/*
 * Free security file mechanism entry.
 */
static void
sf_free_mech_ent(mechanism_t *mp)
{
	if (mp) {
		if (mp->mechname)
			free(mp->mechname);
		if (mp->alias)
			free(mp->alias);
		if (mp->qop)
			free(mp->qop);
		free(mp);
	}
}

static void
free_fields(char **cpp, int cnt)
{
	char **tpp = cpp;

	if (cpp) {
		if (cnt)
			for (; cnt > 0; cnt--, tpp++)
				if (*tpp)
					free(*tpp);
				else
					break;
		free(cpp);
	}
}

/*
 * Generic parse-linestr-of-config-file routine.  Arg linep is ptr
 * (which will be modified) to the input string .  Arg minflds is the
 * minimum number of fields expected.  Arg maxflds is the max number
 * of fields that will be parsed.  Arg bufsiz is the max len of each
 * field that will  be copied to the return area.
 *
 * If there are less fields in the entry than the max number,
 * the remainding ptrs will be 0.
 *
 * Returns a ptr to an array of ptrs to strings on success else
 * NULL on failure.
 *
 * The caller must free the storage (of a successful return only).
 */
static char **
parse_line(char *linep, int minflds, int maxflds, int bufsiz)
{
	char **fpp = calloc(maxflds, sizeof (linep));
	char **tpp = fpp;
	char *cp;
	int	i;

	if (! fpp)
		return (NULL);

	if (! (cp = (char *)malloc(bufsiz))) {
		free(fpp);
		return (NULL);
	}

	for (i = 0; i < maxflds; i++, tpp++) {
		char *tp;
		if (! nextfield(&linep, cp, bufsiz)) {
			if (i < minflds) {
				free_fields(fpp, i);
				free(cp);
				return (NULL);
			} else
				return (fpp);
		}
		if (! (tp = strdup(cp))) {
			free_fields(fpp, i);
			free(cp);
			return (NULL);
		}
		*tpp = tp;
	}

	return (fpp);
}

/*
 * Return a ptr to a mechanism entry read from a line of the sec conf file.
 * Return NULL on EOF or error.
 *
 * An alias field of "des" (case not sig) will override any settings
 * in the keylen or algtype fields like so:
 *    keylen  = 192
 *    algtype = 0
 */

static mechanism_t *
get_secfile_ent(FILE *fptr)
{
	mechanism_t	*m;
	char		*cp;
	char		**flds;  /* line fields */
	const int	num_flds_min = NIS_SEC_CF_MIN_FIELDS;
	const int	num_flds_max = NIS_SEC_CF_MAX_FIELDS;
	char		line[NIS_SEC_CF_MAX_LINELEN + 1 ] = {0};
	const int	line_len = NIS_SEC_CF_MAX_LINELEN + 1;

	/*
	 * NIS+ security conf file layout
	 * <Entry_type>
	 * mech 	<GSS_mechanism_name> <Mech_bit_size> <Mech_alg_type>
	 * 		<Alias> <GSS_quality_of_protection> <GSS_sec_svc>
	 *
	 * QOP and sec_svc are optional.
	 */
	const int	mn_offset = 1; /* mechname */
	const int	kl_offset = 2; /* key length */
	const int	at_offset = 3; /* alg type */
	const int	al_offset = 4; /* mech alias */
	const int	qp_offset = 5; /* qop */
	const int	ss_offset = 6; /* security svc */

cont:
	while (((cp = nextline(fptr, line)) != NULL) &&
		(*cp == '#' || *cp == '\0'))
		;
	if (cp == NULL)
		return (NULL);

	if (! (flds = parse_line(cp, num_flds_min, num_flds_max,
					line_len)))
		goto cont;

	if (strncmp(cf_entry_type_mech_str, *flds,
		    strlen(cf_entry_type_mech_str)))
		goto cont;

	if (! (m = malloc(sizeof (mechanism_t)))) {
		free_fields(flds, num_flds_max);
		return (NULL);
	}

	/* mechanism name */
	m->mechname = NIS_SEC_CF_NA_CMP(*(flds + mn_offset)) ? NULL
		: strdup(*(flds + mn_offset));

	/* mechanism alias */
	m->alias = NIS_SEC_CF_NA_CMP(*(flds + al_offset)) ? NULL
		: strdup(*(flds + al_offset));

	/*
	 * qop: optional field
	 * Make qop NULL if the field was empty or was "default" or
	 * was '-'.
	 */
	if (! *(flds + qp_offset) ||
	    (strncasecmp(*(flds + qp_offset), cf_secserv_default_str,
				strlen(cf_secserv_default_str)) == 0) ||
	    NIS_SEC_CF_NA_CMP(*(flds + qp_offset)))
		m->qop = NULL;
	else
		m->qop = strdup(*(flds + qp_offset));

	/* security service: optional field */
	m->secserv  = str_to_secserv_t(*(flds + ss_offset));

	/* mech alias */
	if (*(flds + al_offset) &&
	    (strncasecmp(*(flds + al_offset), cf_mech_des_str,
				strlen(cf_mech_des_str)) == 0)) {
		/* we've got the AUTH_DES compat line */
		m->keylen = 192;
		m->algtype = 0;
	} else {
		/* key length (bits) */
		if (NIS_SEC_CF_NA_CMP(*(flds + kl_offset)))
			m->keylen = NIS_SEC_CF_NA_KA;
		else if (!isnumberstr(*(flds + kl_offset))) {
			free_fields(flds, num_flds_max);
			sf_free_mech_ent(m);
			goto cont;
		} else
			m->keylen =  atoi(*(flds + kl_offset));

		/* algorithm type */
		if (NIS_SEC_CF_NA_CMP(*(flds + at_offset)))
			m->algtype = NIS_SEC_CF_NA_KA;
		else if (!isnumberstr(*(flds + at_offset))) {
			free_fields(flds, num_flds_max);
			sf_free_mech_ent(m);
			goto cont;
		} else
			m->algtype =  atoi(*(flds + at_offset));
	}

	free_fields(flds, num_flds_max);

	return (m);
}

/*
 * Return TRUE if both entries have the same
 * mechname/alias/keylen/algotype combo.  Else return FALSE.
 */
static bool_t
equal_entries(const mechanism_t *mp, const mechanism_t *tp)
{
	if (mp && tp) {
		if (mp->keylen != tp->keylen)
			return (FALSE);
		if (mp->algtype != tp->algtype)
			return (FALSE);

		/* both NULL, the 2 are equal */
		if (!mp->mechname && !tp->mechname)
			return (TRUE);
		/* only one NULL, not equal */
		if (!mp->mechname || !tp->mechname)
			return (FALSE);
		if (strcmp(mp->mechname, tp->mechname) != 0)
			return (FALSE);

		if (!mp->alias && !tp->alias)
			return (TRUE);
		if (!mp->alias || !tp->alias)
			return (FALSE);
		if (strcmp(mp->alias, tp->alias) != 0)
			return (FALSE);
	}

	return (TRUE);
}

static mechanism_t *
sf_copy_mech_ent(mechanism_t *mp)
{
	mechanism_t *tp = calloc(1, sizeof (*mp));

	if (! mp || ! tp)
		return (NULL);

	tp->mechname = mp->mechname ? strdup(mp->mechname) : NULL;
	tp->alias = mp->alias ? strdup(mp->alias) : NULL;
	tp->qop = mp->qop ? strdup(mp->qop) : NULL;
	tp->keylen = mp->keylen;
	tp->algtype = mp->algtype;
	tp->secserv = mp->secserv;

	return (tp);
}

/*
 * Return TRUE if the mechname/alias/keylen/algtype combo
 * already exists in the no dups array.  Else return FALSE.
 */
static bool_t
member_of_dups(mechanism_t **t, const mechanism_t *mp)
{

	if (t)
		for (; *t; t++)
			if (equal_entries(mp, *t))
				return (TRUE);

	return (FALSE);
}

/*
 * Return a list of valid mechanisms ranked by sequence in the NIS+
 * security conf file.  Return NULL if there are no valid entries.
 * On success, the last pointer of the array of pointers will be NULL.
 *
 * If input arg 'qop_secserv' is TRUE, include duplicate
 * mechname/alias/keylen/algtype entries that differ only in the QOP
 * and security service.  Else, duplicates are omitted.
 *
 * The list of mechanisms are gauranteed to be valid ones installed
 * on the system.
 *
 * This implementation returns copies of the "master" list.  The "master"
 * list will updated if the file is modified.
 */

mechanism_t **
__nis_get_mechanisms(bool_t qop_secserv)
{
	/*
	 * 'mechs' is the "master" list of valid mechanisms from
	 * the NIS+ security conf file.
	 * 'mechs_no_dups' is the "master" list of valid mechanisms
	 * that differ only in QOP/SecuritySvc fields.
	 */
	static mechanism_t	**mechs = NULL;
	static mechanism_t	**mechs_no_dups = NULL;

	mechanism_t	*mp;
	mechanism_t	**tmechs = NULL;	 /* temp mechs */
	mechanism_t	**tmechs_no_dups = NULL; /* temp mechs sans dups */
	int		ent_cnt = 0;		 /* valid cf file entry count */
	int		ent_cnt_no_dups = 0;	 /* valid cf count, no dups */
	static uint_t	last = 0;
	struct stat	sbuf;
	FILE 		*fptr;

	if (stat(NIS_SEC_CF_PATHNAME, &sbuf) != 0)
		return (NULL);

	(void) mutex_lock(&nis_sec_cf_lock);
	if (sbuf.st_mtime > last) {
		last = sbuf.st_mtime;

		if (mechs) {
			/* free old master lists */
			__nis_release_mechanisms(mechs);
			if (mechs_no_dups)
				free(mechs_no_dups);
		}
		mechs = mechs_no_dups = NULL;

		if (! (fptr = fopen(NIS_SEC_CF_PATHNAME, "r"))) {
			(void) mutex_unlock(&nis_sec_cf_lock);
			return (NULL);
		}

		while (mp = get_secfile_ent(fptr)) {
			/*
			 * Make sure entry is either the AUTH_DES compat
			 * one or a valid GSS one that is installed.
			 */
			if (! (AUTH_DES_COMPAT_CHK(mp) ||
				(NIS_SEC_CF_GSS_MECH(mp) &&
					rpc_gss_is_installed(mp->mechname)))) {
				continue;
			}

			ent_cnt++;
			if (! (tmechs = (mechanism_t **)
				list_append_ent((void *)mp, (void **)tmechs,
					ent_cnt,
					(void (*)()) sf_free_mech_ent)))
				return (NULL);

			if (member_of_dups(tmechs_no_dups, mp))
				continue;

			ent_cnt_no_dups++;
			if (! (tmechs_no_dups = (mechanism_t **)
				list_append_ent((void *)mp,
					(void **)tmechs_no_dups,
					ent_cnt_no_dups,
					(void (*)()) sf_free_mech_ent)))
				return (NULL);
		}
		(void) fclose(fptr);

		/* set master lists to point to new built ones */
		mechs = tmechs;
		mechs_no_dups = tmechs_no_dups;
	}
	(void) mutex_unlock(&nis_sec_cf_lock);

	if (qop_secserv)
		/* return a copy of the list with possible dups */
		return (mechs ?
			(mechanism_t **)list_copy(
				(void *(*)()) sf_copy_mech_ent,
				(void **)mechs) :
			NULL);

	/* return a copy of the list without dups */
	return (mechs_no_dups ?
		(mechanism_t **)list_copy((void *(*)()) sf_copy_mech_ent,
						(void **)mechs_no_dups) :
		NULL);
}

/*
 * Search the mechs (no dups array) for an entry (mechname or alias)
 * that matches (case not sig) the given mechname.  On target match,
 * load the given memory locations pointed to by args keylen and
 * algtype with values from the matched entry.
 *
 * The AUTH_DES "compat" line (alias == "des") will return 192-0
 * (overriding the fields in the conf file).
 *
 * For any other entry, a conf file field of '-' (not applicable),
 * in the keylen or algtype field will result in the locations for
 * keylen and algtype being set to -1. (this is actually done in
 * __nis_get_mechanisms()).
 *
 * Returns 0 on success and -1 on failure.
 */
int
__nis_translate_mechanism(const char *mechname, int *keylen, int *algtype)
{
	mechanism_t **mpp;
	mechanism_t **mpp_h;

	if (! mechname || ! keylen || ! algtype)
		return (-1);

	/* AUTH_DES */
	if (strcmp(mechname, NIS_SEC_CF_DES_ALIAS) == 0) {
		*keylen = AUTH_DES_KEYLEN;
		*algtype = AUTH_DES_ALGTYPE;
		return (0);
	}

	if (! (mpp = __nis_get_mechanisms(FALSE)))
		return (-1);

	mpp_h = mpp;
	for (; *mpp; mpp++) {
		mechanism_t *mp = *mpp;
		if (mp->mechname &&
		    (! strcasecmp(mechname, mp->mechname))) {
				*keylen = mp->keylen;
				*algtype = mp->algtype;
				__nis_release_mechanisms(mpp_h);
				return (0);
		}
		if (mp->alias &&
		    (! strcasecmp(mechname, mp->alias))) {
				*keylen = mp->keylen;
				*algtype = mp->algtype;
				__nis_release_mechanisms(mpp_h);
				return (0);
		}
	}

	__nis_release_mechanisms(mpp_h);
	return (-1);
}


/*
 * Translate a mechname to an alias name.
 *
 * Returns alias on success or NULL on failure.
 *
 * Note alias will be the nullstring CSTYLE(on success) if cf
 * alias field was "Not Applicable".
 */
char *
__nis_mechname2alias(const char *mechname,	/* in */
			char *alias,		/* out */
			size_t bufsize)		/* in */
{
	mechanism_t **mpp;
	mechanism_t **mpp_h;

	if (! mechname || ! alias)
		return (NULL);

	if (! (mpp = __nis_get_mechanisms(FALSE)))
		return (NULL);

	mpp_h = mpp;
	for (; *mpp; mpp++) {
		mechanism_t *mp = *mpp;
		int len;

		if (mp->mechname &&
		    (strcasecmp(mechname, mp->mechname) == 0)) {
			if (mp->alias) {
				if ((len = strlen(mp->alias)) < bufsize) {
					(void) strncpy(alias, mp->alias,
							len + 1);
					__nis_release_mechanisms(mpp_h);
					return (alias);
				}
			} else { /* cf file entry alias field was NA */
				alias[0] = '\0';
				__nis_release_mechanisms(mpp_h);
				return (alias);
			}

		}
	}

	__nis_release_mechanisms(mpp_h);
	return (NULL);
}

void
__nis_release_mechanisms(mechanism_t **mpp)
{
	list_free_all(sf_free_mech_ent, (void **)mpp);
}

/*
 * Convert an authtype (ie. DH640-0) to mechanism alias (ie. dh640-0).
 * Input the authtype ptr, the mechalis ptr and the size of the mechalias
 * buf.
 *
 * If mechalias buf is not large enough, truncate and don't indicate failure.
 *
 * Return the mechalias ptr on success or NULL on failure CSTYLE(any of
 * the input args are NULL/0).
 */
char *
__nis_authtype2mechalias(
	const char *authtype,	/* in */
	char *mechalias,	/* out */
	size_t mechaliaslen)	/* in */
{
	char *dst = mechalias;
	const char *src = authtype;
	const char *max = src + mechaliaslen;

	if (! src || ! dst || mechaliaslen == 0)
		return (NULL);

	while (*src && src < max - 1)
		*dst++ = tolower(*src++);

	*dst = '\0';

	return (mechalias);
}

/*
 * Convert an mechalias (ie. dh640-0) to authtype (ie. DH640-0).
 * Input the authtype ptr, the mechalis ptr and the size of the mechalias
 * buf.
 *
 * A special mechalias of "dh192-0" will get converted to "DES".
 *
 * If authtype buf is not large enough, truncate and don't indicate failure.
 *
 * Return the authtype ptr on success or NULL on failure (any of
 * the input args are NULL/0.
 */
char *
__nis_mechalias2authtype(
	const char *mechalias,	/* in */
	char *authtype,		/* out */
	size_t authtypelen)	/* in */

{
	const char *src = mechalias;
	char *dst = authtype;
	const char *max = src + authtypelen;
	const int slen = strlen(cf_mech_dh1920_str);

	if (! src || ! dst || ! authtypelen)
		return (NULL);

	if (strncasecmp(src, cf_mech_dh1920_str, slen + 1)
	    == 0) {
		if (slen >= authtypelen)
			return (NULL);
		(void) strcpy(authtype, AUTH_DES_AUTH_TYPE);
		return (authtype);
	}

	while (*src && src < max - 1)
		*dst++ = toupper(*src++);

	*dst = '\0';

	return (authtype);
}

/*
 * Given a DH key length and algorithm type, return the mech
 * alias string.  If the keyalg is not the classic AUTH_DES,
 * then search the NIS+ security cf.
 *
 * On success return the mech alias string address.  Return
 * NULL on failure.  Failure occurs if keylen or algtype is
 * not found or the length of the input buf is too small
 * or input args are bogus.  Alias buf will not be
 * changed on failure.
 */
char *
__nis_keyalg2mechalias(
	keylen_t	keylen,		/* in */
	algtype_t	algtype,	/* in */
	char		*alias,		/* out */
	size_t		alias_len)	/* in */
{
	mechanism_t	**mechs;  /* array of mechanisms */

	if (! alias)
		return (NULL);

	if (AUTH_DES_KEY(keylen, algtype)) {
		if (alias_len > strlen(NIS_SEC_CF_DES_ALIAS)) {
			(void) strcpy(alias, NIS_SEC_CF_DES_ALIAS);
			return (alias);
		}
		else
			return (NULL);
	} else
		if (mechs = __nis_get_mechanisms(FALSE)) {
			mechanism_t **mpp;

			for (mpp = mechs; *mpp; mpp++) {
				mechanism_t *mp = *mpp;

				if (! VALID_MECH_ENTRY(mp) ||
				    AUTH_DES_COMPAT_CHK(mp))
					continue;

				if (keylen == mp->keylen &&
				    algtype == mp->algtype && mp->alias) {
					int al_len = strlen(mp->alias);

					if (alias_len > al_len) {
						(void) strncpy(alias, mp->alias,
							al_len + 1);
						return (alias);
					} else {
						__nis_release_mechanisms(mechs);
						return (NULL);
					}
				}
			}
			__nis_release_mechanisms(mechs);
		}

	return (NULL);
}

/*
 * Given the key length and algorithm type, return the auth type
 * string suitable for the cred table.
 *
 * Return the authtype on success and NULL on failure.
 */
char *
__nis_keyalg2authtype(
	keylen_t keylen,	/* in */
	algtype_t algtype,	/* in */
	char *authtype,		/* out */
	size_t authtype_len)	/* in */
{
	char alias[MECH_MAXALIASNAME+1] = {0};


	if (! authtype || authtype_len == 0)
		return (NULL);

	if (! __nis_keyalg2mechalias(keylen, algtype, alias, sizeof (alias)))
		return (NULL);

	if (! __nis_mechalias2authtype(alias, authtype, authtype_len))
		return (NULL);

	return (authtype);
}

/*
 * Return a ptr to the next mech file entry or NULL on EOF.
 * The caller should free the storage of a successful return.
 */
static mfent_t *
get_mechfile_ent(FILE *fptr)
{
	mfent_t		*m;
	char		*cp;
	char		**flds;
	char		line[MF_MAX_LINELEN] = {0};


cont:
	while (((cp = nextline(fptr, line)) != NULL) &&
		(*cp == '#' || *cp == '\0'))
		;
	if (cp == NULL)
		return (NULL);

	if (! (flds = parse_line(cp, mech_file_flds_min,
					mech_file_flds_max, MF_MAX_FLDLEN)))
		goto cont;

	if (! (m = malloc(sizeof (mfent_t)))) {
		free_fields(flds, mech_file_flds_max);
		return (NULL);
	}

	m->mechname = strdup(*flds);
	m->oid = strdup(*(flds + 1));
	m->libname = strdup(*(flds + 2));

	free_fields(flds, mech_file_flds_max);

	return (m);
}

static mfent_t *
mf_copy_ent(mfent_t *mp)
{
	mfent_t *tp = calloc(1, sizeof (*mp));

	if (! mp || ! tp)
		return (NULL);

	tp->mechname = mp->mechname ? strdup(mp->mechname) : NULL;
	tp->oid = mp->oid ? strdup(mp->oid) : NULL;
	tp->libname = mp->libname ? strdup(mp->libname) : NULL;

	return (tp);
}


static void
mf_free_ent(mfent_t *mp)
{
	if (mp) {
		if (mp->mechname)
			free(mp->mechname);
		if (mp->oid)
			free(mp->oid);
		if (mp->libname)
			free(mp->libname);
		free(mp);
	}
}


static void
mf_free_mechs(mfent_t **mpp)
{
	list_free_all(mf_free_ent, (void **)mpp);
}

/*
 * Return a copy of the list of the mech file entries.  The ptr to the last
 * entry will be NULL on success.  The master list will be updated when
 * the mechs file is modified.
 *
 * Return NULL if the file does not exist or no valid mechs exist in the
 * file.
 */
static mfent_t **
mf_get_mechs()
{
	static mfent_t	**mechs = NULL;		/* master mechs list */
	mfent_t		*mp;			/* a mech entry */
	mfent_t		**tmechs = NULL;	/* temp mechs list */
	uint_t		ent_cnt = 0;		/* valid cf file entry count */
	static uint_t	last = 0;		/* file last modified date */
	struct stat	sbuf;
	FILE 		*fptr;

	if (stat(mech_file, &sbuf) != 0)
		return (NULL);

	(void) mutex_lock(&mech_file_lock);
	if (sbuf.st_mtime > last) {
		last = sbuf.st_mtime;

		if (mechs) {
			/* free old master list */
			mf_free_mechs(mechs);
			mechs = NULL;
		}

		if (! (fptr = fopen(mech_file, "r"))) {
			(void) mutex_unlock(&mech_file_lock);
			return (NULL);
		}

		while (mp = get_mechfile_ent(fptr)) {
			ent_cnt++;
			if (! (tmechs = (mfent_t **)list_append_ent((void *)mp,
						(void **)tmechs,
						ent_cnt,
						(void (*)()) mf_free_ent)))
				return (NULL);
		}
		(void) fclose(fptr);

		mechs = tmechs;  /* set master list to pt to newly built one */
	}
	(void) mutex_unlock(&mech_file_lock);

	/* return a copy of the master list */
	return (mechs ? (mfent_t **)list_copy((void *(*)()) mf_copy_ent,
						(void **)mechs) : NULL);
}

/*
 * Translate a full mechname to it's corresponding library name
 * as specified in the mech file.
 */
char *
mechfile_name2lib(const char *mechname, char *libname, int len)
{
	mfent_t **mechs = mf_get_mechs();
	mfent_t **mpp;

	if (! mechs || ! mechname || ! libname || ! len)
		return (NULL);

	for (mpp = mechs; *mpp; mpp++) {
		mfent_t *mp = *mpp;

		if (mp->mechname && strcasecmp(mechname, mp->mechname) == 0) {
			if (strlen(mp->libname) < len) {
				(void) strcpy(libname, mp->libname);
					mf_free_mechs(mechs);
					return (libname);
				}
		}
	}
	mf_free_mechs(mechs);

	return (NULL);
}

/*
 * Given a key length and algo type, return the appro DH mech library
 * name.
 */
char *
__nis_get_mechanism_library(keylen_t keylen, algtype_t algtype,
			    char *buffer, size_t buflen)
{
	char mechname[MAXDHNAME + 1];

	if (keylen == 0 || ! buffer || buflen == 0)
		return (NULL);

	(void) sprintf(mechname, "%s_%d_%d", dh_str, keylen, algtype);

	if (! mechfile_name2lib(mechname, buffer, buflen))
		return (NULL);

	return (buffer);
}

/*
 * Input key length, algorithm type, and a string identifying a symbol
 * (example: "__gen_dhkeys").
 *
 * Returns a function pointer to the specified symbol in the appropriate
 * key length/algorithm type library, or NULL if the symbol isn't found.
 */
void *
__nis_get_mechanism_symbol(keylen_t keylen,
				algtype_t algtype,
				const char *symname)

{
	void *handle;
	char libname[MAXDHNAME+1];
	char libpath[MAXPATHLEN+1];

	if (! __nis_get_mechanism_library(keylen, algtype, libname, MAXDHNAME))
		return (NULL);

	if (strlen(MECH_LIB_PREFIX) + strlen(libname) + 1 > sizeof (libpath))
		return (NULL);

	(void) sprintf(libpath, "%s%s", MECH_LIB_PREFIX, libname);

	if (! (handle = dlopen(libpath, RTLD_LAZY)))
		return (NULL);

	return (dlsym(handle, symname));
}
