OpenSolaris_b135/cmd/fs.d/autofs/ns_fnmount.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * ns_fnmount.c
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <rpc/rpc.h>
#include <rpcsvc/nis.h>
#include <xfn/xfn.h>
#include "automount.h"
#include "ns_fnutils.h"


/*
 * The maximum sizes of map names, key names, composite names, and status
 * descriptions, including the trailing '\0'.
 */
#define	MAPNAMESZ	(size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
#define	KEYNAMESZ	(size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
#define	COMPNAMESZ	(size_t)(MAPNAMESZ - FNPREFIXLEN + KEYNAMESZ - 2)
#define	DESCSZ		(size_t)512

typedef struct mapent	mapent;
typedef struct mapline	mapline;


/*
 * The name of an attribute.
 */
static const FN_identifier_t attr_exported = {FN_ID_STRING, 8, "exported"};


/*
 * Given a request by a particular user to mount the name "key" under
 * map/context "map", and a set of default mount options, return (in
 * "res") either a list of mapents giving the mounts that need to be
 * performed, or a symbolic link to be created for a user-relative
 * context.  If "shallow" is true return, in place of the list of
 * mapents, a single mapent representing an indirect mount point.
 *
 *	void
 *	getmapent_fn(char *key, char *map, char *opts, uid_t uid,
 *	      bool_t shallow, getmapent_fn_res *res);
 */

/*
 * Given a reference, its composite name, default mount options, and a
 * mapent root, return a list of mapents to mount.  If "shallow" is
 * true return, in place of the list of mapents, a single mapent
 * representing an indirect mount point.  The map and key strings are
 * pieces of the composite name such that:
 * "FNPREFIX/cname" == "map/key".
 */
static mapent *
process_ref(const FN_ref_t *ref, const char *cname, char *map, char *key,
    char *opts, char *root, bool_t shallow, FN_status_t *status);

/*
 * Traverse the namespace to find a frontier below ref along which
 * future mounts may need to be triggered.  Add to mapents the
 * corresponding direct autofs mount points.
 *     map:	map name for ref
 *     maplen:	strlen(map)
 *     mntpnt:	suffix of map where the current mount request begins
 *		(starts off as "", and grows as we traverse the namespace)
 *     opts:	default mount options
 *     status:	passed from above to avoid having to allocate one on each call
 * Works by calling frontier_aux() on each name bound under ref.
 * Return the new mapents, or free mapents and return NULL on failure.
 */
static mapent *
frontier(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
    char *mntpnt, char *opts, FN_status_t *status);

/*
 * Called by frontier(), once for each "name" that it finds.  map is
 * passed unchanged from frontier().  ref is the reference named by
 * "map/name".  If ref is found to be along the frontier, add the
 * corresponding direct autofs mount point to mapents.  Otherwise
 * continue traversing the namespace to find the frontier.  Other
 * arguments and the return value are as for frontier().
 */
static mapent *
frontier_aux(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
    char *mntpnt, const char *name, char *opts, FN_status_t *status);

/*
 * Given a reference with an address type of ADDR_HOST and its
 * composite name, check the attr_exported attribute to determine if
 * the corresponding directory is exported.  Return FALSE on error.
 */
static bool_t
exported(const FN_ref_t *ref, const char *cname, FN_status_t *status);

/*
 * Find a reference's address type and, if "data" is not NULL, its
 * data string.  If there is no address of a known type, set *typep to
 * NUM_ADDRTYPES; if there are several, stop after finding the first.
 * Return 0 on success.
 */
static int
addr_from_ref(const FN_ref_t *ref, const char *cname, addrtype_t *typep,
    char *data, size_t datasz);

/*
 * Decode an address's data into a string.  Return 0 on success.
 */
static int
str_from_addr(const char *cname, const FN_ref_addr_t *addr, char str[],
    size_t strsz);

/*
 * Given a map name and its current length, append "/name".  Return
 * the new length.  On error, syslog a warning and return 0.
 */
static size_t
append_mapname(char *map, size_t maplen, const char *name);

/*
 * Concatenate two strings using the given separator.  The result is a
 * newly-allocated string, or NULL on error.
 */
static char *
concat(const char *s1, char sep, const char *s2);

/*
 * Add the "nosuid" option to a mapent.  Also check for a sneaky
 * hacker trying to override this option by manually inserting a
 * multiple mount entry into the XFN namespace.  Return FALSE on error.
 */
static bool_t
safe_mapent(mapent *me);

/*
 * Append "nosuid" to a list of options.  The result is a
 * newly-allocated string, or NULL on error.
 */
static char *
safe_opts(const char *opts);

/*
 * Trim comments and trailing whitespace from ml->linebuf, then
 * unquote it and leave the result in ml.  Return 0 on success.
 */
static int
trim_line(mapline *ml);

/*
 * Determine whether ml contains an option string (such as "-ro") and
 * nothing else.
 */
static bool_t
opts_only(const mapline *ml);

/*
 * Allocate a new mapent structure.  The arguments must have been
 * malloc'ed, and are owned by the mapent; they are freed if
 * new_mapent() fails.  If any argument is NULL, the call fails and a
 * memory allocation failure is logged.  A root argument of 'noroot'
 * indicates that the map_root field does not need to be set (it's
 * only needed in the first of a list of mapents).
 */
static char *noroot = "[no root]";
static mapent *
new_mapent(char *root, char *mntpnt, char *fstype, char *mntopts, char *host,
    char *dir);

/*
 * Determine whether cname is a user-relative binding -- such as "myself" --
 * in the initial context.
 */
static bool_t
is_user_relative(const char *cname);

/*
 * Given the name of a user-relative binding, return an equivalent
 * name that is not user-relative.
 */
static char *
equiv_name(FN_ctx_t *, const char *cname, FN_status_t *);

void
getmapent_fn(char *key, char *map, char *opts, uid_t uid, bool_t shallow,
    getmapent_fn_res *res)
{
	size_t			maplen;
	FN_status_t		*status;
	FN_ctx_t		*init_ctx = NULL;
	int			statcode;
	char			cname[COMPNAMESZ];
	FN_composite_name_t	*compname;
	FN_ref_t		*ref;
	char			mapname[MAPNAMESZ];
	char			*root;

	res->type = FN_NONE;
	res->m_or_l.mapents = NULL;

	if (init_fn() != 0) {
		return;
	}

	/*
	 * For direct mounts, the key is the entire path, and the map
	 * name already has the final key component appended.  Split
	 * apart the map name and key.  The "root" of the mapent is
	 * "/key" for indirect mounts, and "" for direct mounts.
	 */
	strcpy(mapname, map);
	if (key[0] == '/') {
		key = strrchr(key, '/') + 1;
		*strrchr(mapname, '/') = '\0';
		root = strdup("");
	} else {
		root = concat("", '/', key);
	}
	map = mapname;
	maplen = strlen(map);

	if ((maplen - FNPREFIXLEN + strlen(key)) >= COMPNAMESZ) {
		if (verbose) {
			syslog(LOG_ERR, "name %s/%s too long", map, key);
		}
		return;
	}
	if (maplen == FNPREFIXLEN) {
		strcpy(cname, key);
	} else {
		sprintf(cname, "%s/%s", map + FNPREFIXLEN + 1, key);
	}

	status = fn_status_create();
	if (status == NULL) {
		if (verbose) {
			syslog(LOG_ERR, "Could not create FNS status object");
		}
		return;
	}
	init_ctx = _fn_ctx_handle_from_initial_with_uid(uid, 0, status);
	if (init_ctx == NULL) {
		logstat(status, "", "No initial context");
		goto done;
	}

#ifndef XFN1ENV
	if (is_user_relative(cname)) {
		res->type = FN_SYMLINK;
		res->m_or_l.symlink = equiv_name(init_ctx, cname, status);
		goto done;
	}
#endif

	if ((compname = new_cname(cname)) == NULL) {
		goto done;
	}
	ref = fn_ctx_lookup(init_ctx, compname, status);
	statcode = fn_status_code(status);
	fn_composite_name_destroy(compname);

	if (trace > 1 && !shallow) {
		trace_prt(1, "  FNS traversal: %s\n", cname);
	}

	if (ref == NULL) {
		if ((statcode != FN_E_NAME_NOT_FOUND) &&
		    (statcode != FN_E_NOT_A_CONTEXT)) {
			logstat(status, "lookup failed on", cname);
		}
		goto done;
	}

	res->type = FN_MAPENTS;
	res->m_or_l.mapents =
	    process_ref(ref, cname, map, key, opts, root, shallow, status);
	fn_ref_destroy(ref);
done:
	fn_ctx_handle_destroy(init_ctx);
	fn_status_destroy(status);
}


static mapent *
process_ref(const FN_ref_t *ref, const char *cname, char *map, char *key,
    char *opts, char *root, bool_t shallow, FN_status_t *status)
{
	addrtype_t	addrtype;
	mapline		ml;
	char		*addrdata = ml.linebuf;
	mapent		*mapents;
	bool_t		self;
	char		*homedir;
	size_t		maplen;
	char		*colon;
	char		*nfshost;
	char		*nfsdir;

	if ((reftype(ref) < NUM_REFTYPES) &&
	    (addr_from_ref(ref, cname, &addrtype, addrdata, LINESZ) == 0)) {

		switch (addrtype) {
		case ADDR_MOUNT:
			if (trim_line(&ml) != 0) {
				return (NULL);
			}
			if (opts_only(&ml)) {
				/* parse_entry() can't handle such lines */
				if (macro_expand("&", ml.linebuf,
				    ml.lineqbuf, LINESZ)) {
					syslog(LOG_ERR,
					"%s/%s: opts too long (max %d chars)",
					    FNPREFIX, cname, LINESZ - 1);
					return (NULL);
				}
				opts = ml.linebuf + 1;	/* skip '-' */
				goto indirect;
			}
			mapents = parse_entry(key, map, opts, &ml, NULL, 0,
			    TRUE);
			if (mapents == NULL || !safe_mapent(mapents)) {
				free_mapent(mapents);
				return (NULL);
			}
			free(mapents->map_root);
			mapents->map_root = root;
			break;

		case ADDR_HOST:
			/*
			 * Address is of the form "host:dir".
			 * If "dir" is not supplied, it defaults to "/".
			 */
			colon = strchr(addrdata, ':');
			if (colon == NULL || colon[1] == '\0') {
				nfsdir = strdup("/");
			} else {
				*colon = '\0';
				nfsdir = strdup(colon + 1);
			}
			nfshost = strdup(addrdata);
			/*
			 * If nfshost is the local host, the NFS mount
			 * request will be converted to a loopback
			 * mount.  Otherwise check that the file system
			 * is exported.
			 */
			if (nfshost != NULL) {
				self = self_check(nfshost);
				if (!self && !exported(ref, cname, status)) {
					if (transient(status)) {
						return (NULL);
					} else {
						goto indirect;
					}
				}
			}
			mapents = new_mapent(root, strdup(""), strdup("nfs"),
			    safe_opts(opts), nfshost, nfsdir);
			if (self && !shallow) {
				return (mapents);
			}
			break;

		case ADDR_USER:
			homedir = strdup(addrdata);
			homedir[strcspn(homedir, " \t\r\n")] = '\0';
			mapents = new_mapent(root, strdup(""), strdup("lofs"),
			    strdup(opts), strdup(""), homedir);
			break;
		}

		if (mapents == NULL) {
			return (NULL);
		}
		if (shallow) {
			mapents->map_root = NULL;	/* don't free "root" */
			free_mapent(mapents);
			goto indirect;
		}

		/* "map" => "map/key" */
		if ((maplen = append_mapname(map, strlen(map), key)) == 0) {
			return (mapents);
		}
		return (frontier(mapents, ref, map, maplen, map + maplen,
		    opts, status));
	}

	/* Ref type wasn't recognized. */

indirect:
	/* Install an indirect autofs mount point. */
	return (new_mapent(root, strdup(""), strdup("autofs"), strdup(opts),
	    strdup(""), concat(map, '/', key)));
}


/*
 * All that this function really does is call frontier_aux() on every
 * name bound under ref.  The rest is error checking(!)
 *
 * The error handling strategy is to reject the entire mount request
 * (by freeing mapents) if any (potentially) transient error occurs,
 * and to treat nontransient errors as holes in the affected portions
 * of the namespace.
 */
static mapent *
frontier(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
    char *mntpnt, char *opts, FN_status_t *status)
{
	FN_ctx_t		*ctx;
	FN_bindinglist_t	*bindings = NULL;
	FN_ref_t		*child_ref;
	FN_string_t		*child_s;
	const char		*child;
	unsigned int		statcode;

	ctx = fn_ctx_handle_from_ref(ref, XFN2(0) status);
	if (ctx == NULL) {
		if (fn_status_code(status) != FN_E_NO_SUPPORTED_ADDRESS) {
			logstat(status, "from_ref failed for", map);
		}
		goto checkerr_return;
	}

	bindings = fn_ctx_list_bindings(ctx, empty_cname, status);
	fn_ctx_handle_destroy(ctx);
	if (bindings == NULL) {
		logstat(status, "list_bindings failed for", map);
		goto checkerr_return;
	}

	while ((child_s = fn_bindinglist_next(bindings, &child_ref, status))
	    != NULL) {
		child = (const char *)fn_string_str(child_s, &statcode);
		if (child == NULL) {
			if (verbose) {
				syslog(LOG_ERR,
				    "FNS string error listing %s", map);
			}
			fn_string_destroy(child_s);
			goto err_return;
		}
		mapents = frontier_aux(mapents, child_ref, map, maplen,
		    mntpnt, child, opts, status);
		fn_string_destroy(child_s);
		fn_ref_destroy(child_ref);
		if (mapents == NULL) {
			goto noerr_return;
		}
	}
	if (fn_status_is_success(status)) {
		goto noerr_return;
	} else {
		logstat(status, "error while listing", map);
		/* Fall through to checkerr_return. */
	}

checkerr_return:
	if (!transient(status)) {
		goto noerr_return;
	}
err_return:
	free_mapent(mapents);
	mapents = NULL;
noerr_return:
	fn_bindinglist_destroy(bindings XFN1(status));
	return (mapents);
}


static mapent *
frontier_aux(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
    char *mntpnt, const char *name, char *opts, FN_status_t *status)
{
	addrtype_t	addrtype;
	bool_t		at_frontier;
	mapent		*me;
	size_t		maplen_save = maplen;
	char		*cname = map + FNPREFIXLEN + 1;	/* for error msgs */

	if (reftype(ref) >= NUM_REFTYPES) {
		/*
		 * We could instead install an indirect autofs mount point
		 * here.  That would allow, for example, a user to be bound
		 * beneath a file system.
		 */
		return (mapents);
	}

	/* "map" => "map/name" */
	if ((maplen = append_mapname(map, maplen, name)) == 0) {
		return (mapents);
	}
	if (trace > 1) {
		trace_prt(1, "  FNS traversal: %s/\n", cname);
	}

	/*
	 * If this is an address type that we know how to mount, then
	 * we have reached the frontier.
	 */
	at_frontier = (addr_from_ref(ref, cname, &addrtype, NULL, 0) == 0);
	/*
	 * For an ADDR_HOST address, treat a non-exported directory as
	 * if the address type were not known:  continue searching for
	 * exported subdirectories.
	 */
	if (at_frontier && (addrtype == ADDR_HOST)) {
		if (!exported(ref, cname, status)) {
			if (transient(status)) {
				free_mapent(mapents);
				return (NULL);
			} else {
				at_frontier = FALSE;
			}
		}
	}
	/*
	 * If we have reached the frontier, install a direct autofs
	 * mount point (which will trigger the actual mount if the
	 * user steps on it later).  Otherwise, continue traversing
	 * the namespace looking for known address types.
	 */
	if (at_frontier) {
		opts = (opts[0] != '\0')
		    ? concat(opts, ',', "direct")
		    : strdup("direct");
		me = new_mapent(noroot, strdup(mntpnt), strdup("autofs"), opts,
		    strdup(""), strdup(map));
		if (me != NULL) {
			/* Link new mapent into list (not at the head). */
			me->map_next = mapents->map_next;
			mapents->map_next = me;
		} else {
			free_mapent(mapents);
			mapents = NULL;
		}
	} else {
		mapents =
		    frontier(mapents, ref, map, maplen, mntpnt, opts, status);
	}
	map[maplen_save] = '\0';	/* "map/name" => "map" */
	return (mapents);
}


static bool_t
exported(const FN_ref_t *ref, const char *cname, FN_status_t *status)
{
	FN_ctx_t		*ctx;
	FN_attribute_t		*attr;

	ctx = fn_ctx_handle_from_ref(ref, XFN2(0) status);
	if (ctx == NULL) {
		logstat(status, "from_ref failed for", cname);
		return (FALSE);
	}
	attr = fn_attr_get(ctx, empty_cname, &attr_exported, XFN2(1) status);
	fn_ctx_handle_destroy(ctx);

	switch (fn_status_code(status)) {
	case FN_SUCCESS:
		fn_attribute_destroy(attr);
		break;
	case FN_E_NO_SUCH_ATTRIBUTE:
		break;
	default:
		logstat(status, "could not get attributes for", cname);
	}
	return (attr != NULL);
}


static int
addr_from_ref(const FN_ref_t *ref, const char *cname, addrtype_t *typep,
    char *data, size_t datasz)
{
	const FN_ref_addr_t	*addr;
	void			*iter_pos;

	addr = fn_ref_first(ref, &iter_pos);
	if (addr == NULL) {
		if (verbose) {
			syslog(LOG_ERR, "FNS ref with no address: %s", cname);
		}
		return (-1);
	}
	while (addr != NULL) {
		*typep = addrtype(addr);
		if (*typep < NUM_ADDRTYPES) {
			return ((data != NULL)
			    ? str_from_addr(cname, addr, data, datasz)
			    : 0);
		}
		addr = fn_ref_next(ref, &iter_pos);
	}
	return (-1);
}


static int
str_from_addr(const char *cname, const FN_ref_addr_t *addr, char str[],
    size_t strsz)
{
	XDR	xdr;
	int	res;

	xdrmem_create(&xdr, (caddr_t)fn_ref_addr_data(addr),
	    fn_ref_addr_length(addr), XDR_DECODE);
	if (!xdr_string(&xdr, &str, strsz)) {
		if (verbose) {
			syslog(LOG_ERR,
			    "Could not decode FNS address for %s", cname);
		}
		res = -1;
	} else {
		res = 0;
	}
	xdr_destroy(&xdr);
	return (res);
}

static size_t
append_mapname(char *map, size_t maplen, const char *name)
{
	size_t namelen = strlen(name);

	if (maplen + 1 + namelen >= MAPNAMESZ) {
		if (verbose) {
			syslog(LOG_ERR, "FNS name %s/%s too long",
			    map + FNPREFIXLEN + 1, name);
		}
		return (0);
	}
	sprintf(map + maplen, "/%s", name);
	return (maplen + 1 + namelen);
}


static char *
concat(const char *s1, char sep, const char *s2)
{
	char *s = malloc(strlen(s1) + 1 + strlen(s2) + 1);

	if (s != NULL) {
		sprintf(s, "%s%c%s", s1, sep, s2);
	}
	return (s);
}


static bool_t
safe_mapent(mapent *me)
{
	char	*opts;

	if (me->map_next != NULL) {
		/* Multiple mounts don't belong in XFN namespace. */
		return (NULL);
	}
	opts = me->map_mntopts;
	me->map_mntopts = safe_opts(opts);
	free(opts);
	return (me->map_mntopts != NULL);
}


static char *
safe_opts(const char *opts)
{
	char	*start;
	size_t	len;

	if (opts[0] == '\0') {
		return (strdup(MNTOPT_NOSUID));
	}

	/* A quick-and-dirty check to see if "nosuid" is already there. */
	start = strstr(opts, MNTOPT_NOSUID);
	len = sizeof (MNTOPT_NOSUID) - 1;	/* "-1" for trailing '\0' */
	if (start != NULL) {
		while (start > opts && isspace(*(start - 1))) {
			start--;
		}
		if ((start == opts || *(start - 1) == ',') &&
		    opts[len] == ',' || opts[len] == '\0') {
			return (strdup(opts));
		}
	}
	return (concat(opts, ',', MNTOPT_NOSUID));
}


static int
trim_line(mapline *ml)
{
	char	*end;	/* pointer to '\0' at end of linebuf */

	end = ml->linebuf + strcspn(ml->linebuf, "#");
	while ((end > ml->linebuf) && isspace(end[-1])) {
		end--;
	}
	if (end <= ml->linebuf) {
		return (-1);
	}
	*end = '\0';
	unquote(ml->linebuf, ml->lineqbuf);
	return (0);
}


static bool_t
opts_only(const mapline *ml)
{
	const char *s = ml->linebuf;
	const char *q = ml->lineqbuf;

	if (*s != '-') {
		return (FALSE);
	}
	for (; *s != '\0'; s++, q++) {
		if (isspace(*s) && (*q == ' ')) {
			return (FALSE);
		}
	}
	return (TRUE);
}


static mapent *
new_mapent(char *root, char *mntpnt, char *fstype, char *mntopts, char *host,
    char *dir)
{
	mapent		*me;
	struct mapfs	*mfs;
	char		*mounter = NULL;

	me = calloc(1, sizeof (*me));
	mfs = calloc(1, sizeof (*mfs));
	if (fstype != NULL) {
		mounter = strdup(fstype);
	}
	if ((mntpnt == NULL) || (fstype == NULL) || (mntopts == NULL) ||
	    (host == NULL) || (dir == NULL) || (me == NULL) || (mfs == NULL) ||
	    (mounter == NULL) || (root == NULL)) {
		log_mem_failure();
		free(me);
		free(mfs);
		free(mounter);
		free(root);
		free(mntpnt);
		free(fstype);
		free(mntopts);
		free(host);
		free(dir);
		return (NULL);
	}
	me->map_root	= (root != noroot) ? root : NULL;
	me->map_fstype	= fstype;
	me->map_mounter	= mounter;
	me->map_mntpnt	= mntpnt;
	me->map_mntopts	= mntopts;
	me->map_fsw	= NULL;
	me->map_fswq    = NULL;
	me->map_fs	= mfs;
	mfs->mfs_host	= host;
	mfs->mfs_dir	= dir;
	me->map_mntlevel = -1;
	me->map_modified = FALSE;
	me->map_faked = FALSE;
	me->map_err = 0;		/* MAPENT_NOERR */
	return (me);
}


#ifndef XFN1ENV

/*
 * User-relative bindings in the initial context, and the leading components
 * of their non-user-relative equivalents.  Leading components are listed in
 * the order in which they should be tried.  Each list is NULL-terminated
 * (the compiler generously does this for us).
 * For "myorgunit", for example, we first check if it is equivalent to
 * "thisorgunit".  If not, we translate it into "org/<something>".
 */
#define	MAX_LEADS 3

static struct {
	const char	*binding;
	const char	*leads[MAX_LEADS + 1];
} user_rel[] = {
	{"thisuser",	{"user", "thisorgunit", "org"}},
	{"myself",	{"user", "thisorgunit", "org"}},
	{"_myself",	{"_user", "_thisorgunit", "_orgunit"}},
	{"myorgunit",	{"thisorgunit", "org"}},
	{"_myorgunit",	{"_thisorgunit", "_orgunit"}},
	{"myens",	{"thisens"}},
	{"_myens",	{"_thisens"}}
};


static bool_t
is_user_relative(const char *cname)
{
	int	i;

	for (i = 0; i < sizeof (user_rel) / sizeof (user_rel[0]); i++) {
		if (strcmp(cname, user_rel[i].binding) == 0) {
			return (TRUE);
		}
	}
	return (FALSE);
}


static char *
equiv_name(FN_ctx_t *ctx, const char *cname, FN_status_t *status)
{
	FN_composite_name_t	*name;
	FN_string_t		*leading_name;
	FN_composite_name_t	*equiv;
	FN_string_t		*equiv_string;
	const char		*equiv_str;
	char			*equiv_str_dup;
	const char		**leads;
	unsigned int		stat;
	int			i;

	for (i = 0; i < sizeof (user_rel) / sizeof (user_rel[0]); i++) {
		if (strcmp(cname, user_rel[i].binding) == 0) {
			break;
		}
	}
	if ((name = new_cname(cname)) == NULL) {
		return (NULL);
	}
	leads = user_rel[i].leads;	/* array of leading names to try */
	do {
		leading_name = fn_string_from_str((unsigned char *)*leads);
		if (leading_name == NULL) {
			log_mem_failure();
			fn_composite_name_destroy(name);
			return (NULL);
		}
		equiv = prelim_fn_ctx_equivalent_name(ctx, name, leading_name,
		    status);
		fn_string_destroy(leading_name);
	} while (equiv == NULL && *++leads != NULL);

	fn_composite_name_destroy(name);

	if (equiv == NULL) {
		if (transient(status)) {
			logstat(status, "could not find equivalent of", cname);
		}
		return (NULL);
	}
	equiv_string = fn_string_from_composite_name(equiv, &stat);
	fn_composite_name_destroy(equiv);
	if (equiv_string == NULL) {
		log_mem_failure();
		return (NULL);
	}
	equiv_str = (const char *)fn_string_str(equiv_string, &stat);
	if (equiv_str == NULL ||
	    (equiv_str_dup = strdup(equiv_str)) == NULL) {
		log_mem_failure();
		fn_string_destroy(equiv_string);
		return (NULL);
	}
	fn_string_destroy(equiv_string);
	return (equiv_str_dup);
}

#endif	/* XFN1ENV */