Blob Blame History Raw
/*
 * lookup_nisplus.c
 *
 * Module for Linux automountd to access a NIS+ automount map
 */

#include <stdio.h>
#include <malloc.h>
#include <sys/param.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <rpc/xdr.h>
#include <rpcsvc/nis.h>

#define MODULE_LOOKUP
#include "automount.h"
#include "nsswitch.h"

#define MAPFMT_DEFAULT "sun"

#define MODPREFIX "lookup(nisplus): "

struct lookup_context {
	const char *domainname;
	const char *mapname;
	struct parse_mod *parse;
};

int lookup_version = AUTOFS_LOOKUP_VERSION;	/* Required by protocol */

static int do_init(const char *mapfmt,
		   int argc, const char *const *argv,
		   struct lookup_context *ctxt, unsigned int reinit)
{
	int ret = 0;

	if (argc < 1) {
		logmsg(MODPREFIX "No map name");
		ret = 1;
		goto out;
	}
	ctxt->mapname = argv[0];

	/* 
	 * nis_local_directory () returns a pointer to a static buffer.
	 * We don't need to copy or free it.
	 */
	ctxt->domainname = nis_local_directory();
	if (!ctxt->domainname || !strcmp(ctxt->domainname, "(none).")) {
		ret = 1;
		goto out;
	}

	if (!mapfmt)
		mapfmt = MAPFMT_DEFAULT;

	if (reinit) {
		ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc, argv);
		if (ret)
			logmsg(MODPREFIX "failed to reinit parse context");
	} else {
		ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1);
		if (!ctxt->parse) {
			logerr(MODPREFIX "failed to open parse context");
			ret = 1;
		}
	}
out:
	return ret;
}

int lookup_init(const char *mapfmt,
		int argc, const char *const *argv, void **context)
{
	struct lookup_context *ctxt;
	char buf[MAX_ERR_BUF];

	*context = NULL;

	ctxt = malloc(sizeof(struct lookup_context));
	if (!ctxt) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "%s", estr);
		return 1;
	}
	memset(ctxt, 0, sizeof(struct lookup_context));

	if (do_init(mapfmt, argc, argv, ctxt, 0)) {
		free(ctxt);
		return 1;
	}

	*context = ctxt;

	return 0;
}

int lookup_reinit(const char *mapfmt,
		  int argc, const char *const *argv, void **context)
{
	struct lookup_context *ctxt = (struct lookup_context *) *context;
	struct lookup_context *new;
	char buf[MAX_ERR_BUF];
	int ret;

	new = malloc(sizeof(struct lookup_context));
	if (!new) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "%s", estr);
		return 1;
	}
	memset(new, 0, sizeof(struct lookup_context));

	new->parse = ctxt->parse;
	ret = do_init(mapfmt, argc, argv, new, 1);
	if (ret) {
		free(new);
		return 1;
	}

	*context = new;

	free(ctxt);

	return 0;
}

int lookup_read_master(struct master *master, time_t age, void *context)
{
	struct lookup_context *ctxt = (struct lookup_context *) context;
	unsigned int timeout = master->default_timeout;
	unsigned int logging = master->default_logging;
	unsigned int logopt =  master->logopt;
	char *tablename;
	nis_result *result;
	nis_object *this;
	unsigned int current, result_count;
	char *path, *ent;
	char *buffer;
	char buf[MAX_ERR_BUF];
	int cur_state, len;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state);
	tablename = malloc(strlen(ctxt->mapname) + strlen(ctxt->domainname) + 20);
	if (!tablename) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "malloc: %s", estr);
		pthread_setcancelstate(cur_state, NULL);
		return NSS_STATUS_UNKNOWN;
	}
	sprintf(tablename, "%s.org_dir.%s", ctxt->mapname, ctxt->domainname);

	/* check that the table exists */
	result = nis_lookup(tablename, FOLLOW_PATH | FOLLOW_LINKS);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		int status = result->status;
		nis_freeresult(result);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		if (status == NIS_UNAVAIL || status == NIS_FAIL)
			return NSS_STATUS_UNAVAIL;
		else {
			crit(logopt,
			     MODPREFIX "couldn't locate nis+ table %s",
			     ctxt->mapname);
			return NSS_STATUS_NOTFOUND;
		}
	}

	sprintf(tablename, "[],%s.org_dir.%s", ctxt->mapname, ctxt->domainname);

	result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		nis_freeresult(result);
		crit(logopt,
		     MODPREFIX "couldn't enumrate nis+ map %s", ctxt->mapname);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		return NSS_STATUS_UNAVAIL;
	}

	current = 0;
	result_count = NIS_RES_NUMOBJ(result);

	while (result_count--) {
		this = &result->objects.objects_val[current++];
		path = ENTRY_VAL(this, 0);
		/*
		 * Ignore keys beginning with '+' as plus map
		 * inclusion is only valid in file maps.
		 */
		if (*path == '+')
			continue;

		ent = ENTRY_VAL(this, 1);

		len = ENTRY_LEN(this, 0) + 1 + ENTRY_LEN(this, 1) + 2;
		buffer = malloc(len);
		if (!buffer) {
			logerr(MODPREFIX "could not malloc parse buffer");
			continue;
		}
		memset(buffer, 0, len);

		strcat(buffer, path);
		strcat(buffer, " ");
		strcat(buffer, ent);

		master_parse_entry(buffer, timeout, logging, age);

		free(buffer);
	}

	nis_freeresult(result);
	free(tablename);
	pthread_setcancelstate(cur_state, NULL);

	return NSS_STATUS_SUCCESS;
}

int lookup_read_map(struct autofs_point *ap, time_t age, void *context)
{
	struct lookup_context *ctxt = (struct lookup_context *) context;
	struct map_source *source;
	struct mapent_cache *mc;
	char *tablename;
	nis_result *result;
	nis_object *this;
	unsigned int current, result_count;
	char *key, *mapent;
	char buf[MAX_ERR_BUF];
	int cur_state;

	source = ap->entry->current;
	ap->entry->current = NULL;
	master_source_current_signal(ap->entry);

	/*
	 * If we don't need to create directories (or don't need
	 * to read an amd cache:=all map) then there's no use
	 * reading the map. We always need to read the whole map
	 * for direct mounts in order to mount the triggers.
	 */
	if (ap->type != LKP_DIRECT &&
	    !(ap->flags & (MOUNT_FLAG_GHOST|MOUNT_FLAG_AMD_CACHE_ALL))) {
		debug(ap->logopt, "map read not needed, so not done");
		return NSS_STATUS_SUCCESS;
	}

	mc = source->mc;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state);
	tablename = malloc(strlen(ctxt->mapname) + strlen(ctxt->domainname) + 20);
	if (!tablename) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "malloc: %s", estr);
		pthread_setcancelstate(cur_state, NULL);
		return NSS_STATUS_UNAVAIL;
	}
	sprintf(tablename, "%s.org_dir.%s", ctxt->mapname, ctxt->domainname);

	/* check that the table exists */
	result = nis_lookup(tablename, FOLLOW_PATH | FOLLOW_LINKS);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		nis_freeresult(result);
		crit(ap->logopt,
		     MODPREFIX "couldn't locate nis+ table %s", ctxt->mapname);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		return NSS_STATUS_NOTFOUND;
	}

	sprintf(tablename, "[],%s.org_dir.%s", ctxt->mapname, ctxt->domainname);

	result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		nis_freeresult(result);
		crit(ap->logopt,
		     MODPREFIX "couldn't enumrate nis+ map %s", ctxt->mapname);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		return NSS_STATUS_UNAVAIL;
	}

	current = 0;
	result_count = NIS_RES_NUMOBJ(result);

	while (result_count--) {
		char *s_key;
		size_t len;

		this = &result->objects.objects_val[current++];
		key = ENTRY_VAL(this, 0);
		len = ENTRY_LEN(this, 0);

		/*
		 * Ignore keys beginning with '+' as plus map
		 * inclusion is only valid in file maps.
		 */
		if (*key == '+')
			continue;

		if (!(source->flags & MAP_FLAG_FORMAT_AMD))
			s_key = sanitize_path(key, len, ap->type, ap->logopt);
		else {
			if (!strcmp(key, "/defaults")) {
				mapent = ENTRY_VAL(this, 1);
				cache_writelock(mc);
				cache_update(mc, source, key, mapent, age);
				cache_unlock(mc);
				continue;
			}
			/* Don't fail on "/" in key => type == 0 */
			s_key = sanitize_path(key, len, 0, ap->logopt);
		}
		if (!s_key)
			continue;

		mapent = ENTRY_VAL(this, 1);

		cache_writelock(mc);
		cache_update(mc, source, s_key, mapent, age);
		cache_unlock(mc);

		free(s_key);
	}

	nis_freeresult(result);

	source->age = age;

	free(tablename);
	pthread_setcancelstate(cur_state, NULL);

	return NSS_STATUS_SUCCESS;
}

static int lookup_one(struct autofs_point *ap,
		      struct map_source *source,
		      const char *key, int key_len,
		      struct lookup_context *ctxt)
{
	struct mapent_cache *mc;
	char *tablename;
	nis_result *result;
	nis_object *this;
	char *mapent;
	time_t age = monotonic_time(NULL);
	int ret, cur_state;
	char buf[MAX_ERR_BUF];

	mc = source->mc;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state);
	tablename = malloc(strlen(key) + strlen(ctxt->mapname) +
			   strlen(ctxt->domainname) + 20);
	if (!tablename) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "malloc: %s", estr);
		pthread_setcancelstate(cur_state, NULL);
		return -1;
	}
	sprintf(tablename, "[key=%s],%s.org_dir.%s", key, ctxt->mapname,
		ctxt->domainname);

	result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		nis_error rs = result->status;
		nis_freeresult(result);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		if (rs == NIS_NOTFOUND ||
		    rs == NIS_S_NOTFOUND ||
		    rs == NIS_PARTIAL)
			return CHE_MISSING;

		return -rs;
	}

	
	this = NIS_RES_OBJECT(result);
	mapent = ENTRY_VAL(this, 1);
	cache_writelock(mc);
	ret = cache_update(mc, source, key, mapent, age);
	cache_unlock(mc);

	nis_freeresult(result);
	free(tablename);
	pthread_setcancelstate(cur_state, NULL);

	return ret;
}

static int match_key(struct autofs_point *ap,
		     struct map_source *source,
		     const char *key, int key_len,
		     struct lookup_context *ctxt)
{
	unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD;
	char buf[MAX_ERR_BUF];
	char *lkp_key;
	char *prefix;
	int ret;

	ret = lookup_one(ap, source, key, key_len, ctxt);
	if (ret < 0)
		return ret;
	if (ret == CHE_OK || ret == CHE_UPDATED || is_amd_format)
		return ret;

	lkp_key = strdup(key);
	if (!lkp_key) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		error(ap->logopt, MODPREFIX "strdup: %s", estr);
		return CHE_FAIL;
	}

	ret = CHE_MISSING;

	/*
	 * Now strip successive directory components and try a
	 * match against map entries ending with a wildcard and
	 * finally try the wilcard entry itself.
	 */
	while ((prefix = strrchr(lkp_key, '/'))) {
		char *match;
		size_t len;
		*prefix = '\0';
		len = strlen(lkp_key) + 3;
		match = malloc(len);
		if (!match) {
			char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
			error(ap->logopt, MODPREFIX "malloc: %s", estr);
			ret = CHE_FAIL;
			goto done;
		}
		len--;
		strcpy(match, lkp_key);
		strcat(match, "/*");
		ret = lookup_one(ap, source, match, len, ctxt);
		free(match);
		if (ret < 0)
			goto done;
		if (ret == CHE_OK || ret == CHE_UPDATED)
			goto done;
	}
done:
	free(lkp_key);
	return ret;
}

static int lookup_wild(struct autofs_point *ap,
		       struct map_source *source, struct lookup_context *ctxt)
{
	struct mapent_cache *mc;
	char *tablename;
	nis_result *result;
	nis_object *this;
	char *mapent;
	time_t age = monotonic_time(NULL);
	int ret, cur_state;
	char buf[MAX_ERR_BUF];

	mc = source->mc;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state);
	tablename = malloc(strlen(ctxt->mapname) + strlen(ctxt->domainname) + 20);
	if (!tablename) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "malloc: %s", estr);
		pthread_setcancelstate(cur_state, NULL);
		return -1;
	}
	sprintf(tablename, "[key=*],%s.org_dir.%s", ctxt->mapname,
		ctxt->domainname);

	result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		nis_error rs = result->status;
		nis_freeresult(result);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		if (rs == NIS_NOTFOUND ||
		    rs == NIS_S_NOTFOUND ||
		    rs == NIS_PARTIAL)
			return CHE_MISSING;

		return -rs;
	}

	this = NIS_RES_OBJECT(result);
	mapent = ENTRY_VAL(this, 1);
	cache_writelock(mc);
	ret = cache_update(mc, source, "*", mapent, age);
	cache_unlock(mc);

	nis_freeresult(result);
	free(tablename);
	pthread_setcancelstate(cur_state, NULL);

	return ret;
}

static int lookup_amd_defaults(struct autofs_point *ap,
			       struct map_source *source,
			       struct lookup_context *ctxt)
{
	struct mapent_cache *mc = source->mc;
	char *tablename;
	nis_result *result;
	nis_object *this;
	char *mapent;
	char buf[MAX_ERR_BUF];
	int cur_state;
	int ret;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state);
	tablename = malloc(9 + strlen(ctxt->mapname) +
			   strlen(ctxt->domainname) + 20);
	if (!tablename) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		logerr(MODPREFIX "malloc: %s", estr);
		pthread_setcancelstate(cur_state, NULL);
		return CHE_FAIL;
	}
	sprintf(tablename, "[key=/defaults],%s.org_dir.%s",
		ctxt->mapname, ctxt->domainname);

	result = nis_list(tablename, FOLLOW_PATH | FOLLOW_LINKS, NULL, NULL);
	if (result->status != NIS_SUCCESS && result->status != NIS_S_SUCCESS) {
		nis_error rs = result->status;
		nis_freeresult(result);
		free(tablename);
		pthread_setcancelstate(cur_state, NULL);
		if (rs == NIS_NOTFOUND ||
		    rs == NIS_S_NOTFOUND ||
		    rs == NIS_PARTIAL)
			return CHE_MISSING;

		return -rs;
	}

	this = NIS_RES_OBJECT(result);
	mapent = ENTRY_VAL(this, 1);

	cache_writelock(mc);
	ret = cache_update(mc, source, "/defaults", mapent, monotonic_time(NULL));
	cache_unlock(mc);

	nis_freeresult(result);
	free(tablename);
	pthread_setcancelstate(cur_state, NULL);

	return ret;
}

static int check_map_indirect(struct autofs_point *ap,
			      struct map_source *source,
			      char *key, int key_len,
			      struct lookup_context *ctxt)
{
	unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD;
	struct mapent_cache *mc;
	struct mapent *me, *exists;
	time_t now = monotonic_time(NULL);
	time_t t_last_read;
	int ret = 0;

	mc = source->mc;

	if (is_amd_format) {
		/* Check for a /defaults entry to update the map source */
		if (lookup_amd_defaults(ap, source, ctxt) == CHE_FAIL) {
			warn(ap->logopt, MODPREFIX
			     "error getting /defaults from map %s",
			     ctxt->mapname);
		}
	}

	/* check map and if change is detected re-read map */
	ret = match_key(ap, source, key, key_len, ctxt);
	if (ret == CHE_FAIL)
		return NSS_STATUS_NOTFOUND;

	if (ret < 0) {
		/*
		 * If the server is down and the entry exists in the cache
		 * and belongs to this map return success and use the entry.
		 */
		cache_readlock(mc);
		if (source->flags & MAP_FLAG_FORMAT_AMD)
			exists = match_cached_key(ap, MODPREFIX, source, key);
		else
			exists = cache_lookup(mc, key);
		if (exists && exists->source == source) {
			cache_unlock(mc);
			return NSS_STATUS_SUCCESS;
		}
		cache_unlock(mc);

		warn(ap->logopt,
		     MODPREFIX "lookup for %s failed: %s",
		     key, nis_sperrno(-ret));

		return NSS_STATUS_UNAVAIL;
	}

	cache_writelock(mc);
	t_last_read = ap->exp_runfreq + 1;
	me = cache_lookup_first(mc);
	while (me) {
		if (me->source == source) {
			t_last_read = now - me->age;
			break;
		}
		me = cache_lookup_next(mc, me);
	}
	if (is_amd_format)
		exists = match_cached_key(ap, MODPREFIX, source, key);
	else
		exists = cache_lookup_distinct(mc, key);
	exists = cache_lookup_distinct(mc, key);
	/* Not found in the map but found in the cache */
	if (exists && exists->source == source && ret & CHE_MISSING) {
		if (exists->mapent) {
			free(exists->mapent);
			exists->mapent = NULL;
			source->stale = 1;
			exists->status = 0;
		}
	}
	cache_unlock(mc);

	if (t_last_read > ap->exp_runfreq && ret & CHE_UPDATED)
		source->stale = 1;

	if (ret == CHE_MISSING) {
		int wild = CHE_MISSING;
		struct mapent *we;

		wild = lookup_wild(ap, source, ctxt);
		/*
		 * Check for map change and update as needed for
		 * following cache lookup.
		*/
		cache_writelock(mc);
		we = cache_lookup_distinct(mc, "*");
		if (we) {
			/* Wildcard entry existed and is now gone */
			if (we->source == source && wild & CHE_MISSING) {
				cache_delete(mc, "*");
				source->stale = 1;
			}
		} else {
			/* Wildcard not in map but now is */
			if (wild & (CHE_OK | CHE_UPDATED))
				source->stale = 1;
		}
		cache_unlock(mc);

		if (wild & (CHE_UPDATED | CHE_OK))
			return NSS_STATUS_SUCCESS;
	}

	if (ret == CHE_MISSING)
		return NSS_STATUS_NOTFOUND;

	return NSS_STATUS_SUCCESS;
}

int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context)
{
	struct lookup_context *ctxt = (struct lookup_context *) context;
	struct map_source *source;
	struct mapent_cache *mc;
	char key[KEY_MAX_LEN + 1];
	int key_len;
	char *lkp_key;
	char *mapent = NULL;
	int mapent_len;
	struct mapent *me;
	char buf[MAX_ERR_BUF];
	int status;
	int ret = 1;

	source = ap->entry->current;
	ap->entry->current = NULL;
	master_source_current_signal(ap->entry);

	mc = source->mc;

	debug(ap->logopt, MODPREFIX "looking up %s", name);

	if (!(source->flags & MAP_FLAG_FORMAT_AMD)) {
		key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name);
		if (key_len > KEY_MAX_LEN)
			return NSS_STATUS_NOTFOUND;
	} else {
		key_len = expandamdent(name, NULL, NULL);
		if (key_len > KEY_MAX_LEN)
			return NSS_STATUS_NOTFOUND;
		memset(key, 0, KEY_MAX_LEN + 1);
		expandamdent(name, key, NULL);
		debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key);
	}

	/* Check if we recorded a mount fail for this key anywhere */
	me = lookup_source_mapent(ap, key, LKP_DISTINCT);
	if (me) {
		if (me->status >= monotonic_time(NULL)) {
			cache_unlock(me->mc);
			return NSS_STATUS_NOTFOUND;
		} else {
			struct mapent_cache *smc = me->mc;
			struct mapent *sme;

			if (me->mapent)
				cache_unlock(smc);
			else {
				cache_unlock(smc);
				cache_writelock(smc);
				sme = cache_lookup_distinct(smc, key);
				/* Negative timeout expired for non-existent entry. */
				if (sme && !sme->mapent) {
					if (cache_pop_mapent(sme) == CHE_FAIL)
						cache_delete(smc, key);
				}
				cache_unlock(smc);
			}
		}
	}

	/*
	 * We can't check the direct mount map as if it's not in
	 * the map cache already we never get a mount lookup, so
	 * we never know about it.
	 */
	if (ap->type == LKP_INDIRECT && *key != '/') {
		cache_readlock(mc);
		me = cache_lookup_distinct(mc, key);
		if (me && me->multi)
			lkp_key = strdup(me->multi->key);
		else if (!ap->pref)
			lkp_key = strdup(key);
		else {
			lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1);
			if (lkp_key) {
				strcpy(lkp_key, ap->pref);
				strcat(lkp_key, key);
			}
		}
		cache_unlock(mc);

		if (!lkp_key) {
			char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
			error(ap->logopt, MODPREFIX "malloc: %s", estr);
			return NSS_STATUS_UNKNOWN;
		}

		status = check_map_indirect(ap, source,
					    lkp_key, strlen(lkp_key), ctxt);
		free(lkp_key);
		if (status)
			return status;
	}

	/*
	 * We can't take the writelock for direct mounts. If we're
	 * starting up or trying to re-connect to an existing direct
	 * mount we could be iterating through the map entries with
	 * the readlock held. But we don't need to update the cache
	 * when we're starting up so just take the readlock in that
	 * case.
	 */
	if (ap->flags & MOUNT_FLAG_REMOUNT)
		cache_readlock(mc);
	else
		cache_writelock(mc);

	if (!ap->pref)
		lkp_key = strdup(key);
	else {
		lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1);
		if (lkp_key) {
			strcpy(lkp_key, ap->pref);
			strcat(lkp_key, key);
		}
	}

	if (!lkp_key) {
		char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
		error(ap->logopt, MODPREFIX "malloc: %s", estr);
		cache_unlock(mc);
		return NSS_STATUS_UNKNOWN;
	}

	me = match_cached_key(ap, MODPREFIX, source, lkp_key);
	/* Stale mapent => check for entry in alternate source or wildcard */
	if (me && !me->mapent) {
		while ((me = cache_lookup_key_next(me)))
			if (me->source == source)
				break;
		if (!me)
			me = cache_lookup_distinct(mc, "*");
	}
	if (me && me->mapent) {
		/*
		 * If this is a lookup add wildcard match for later validation
		 * checks and negative cache lookups.
		 */
		if (!(ap->flags & MOUNT_FLAG_REMOUNT) &&
		    ap->type == LKP_INDIRECT && *me->key == '*') {
			ret = cache_update(mc, source, key, me->mapent, me->age);
			if (!(ret & (CHE_OK | CHE_UPDATED)))
				me = NULL;
		}
		if (me && (me->source == source || *me->key == '/')) {
			mapent_len = strlen(me->mapent);
			mapent = malloc(mapent_len + 1);
			if (mapent)
				strcpy(mapent, me->mapent);
		}
	}
	cache_unlock(mc);

	if (!me) {
		free(lkp_key);
		return NSS_STATUS_NOTFOUND;
	}

	if (!mapent) {
		free(lkp_key);
		return NSS_STATUS_TRYAGAIN;
	}

	debug(ap->logopt, MODPREFIX "%s -> %s", lkp_key, mapent);

	free(lkp_key);

	master_source_current_wait(ap->entry);
	ap->entry->current = source;

	ret = ctxt->parse->parse_mount(ap, key, key_len,
				       mapent, ctxt->parse->context);
	if (ret) {
		free(mapent);

		/* Don't update negative cache when re-connecting */
		if (ap->flags & MOUNT_FLAG_REMOUNT)
			return NSS_STATUS_TRYAGAIN;
		cache_writelock(mc);
		cache_update_negative(mc, source, key, ap->negative_timeout);
		cache_unlock(mc);
		return NSS_STATUS_TRYAGAIN;
	}
	free(mapent);

	return NSS_STATUS_SUCCESS;
}

int lookup_done(void *context)
{
	struct lookup_context *ctxt = (struct lookup_context *) context;
	int rv = close_parse(ctxt->parse);
	free(ctxt);
	return rv;
}