blob: 6430b890bbb97e9688b6f7e59e0737a7b7f03290 [file] [log] [blame]
/*
* 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) {
logmsg(MODPREFIX "NIS+ domain not set");
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);
free(lkp_key);
if (!mapent)
return NSS_STATUS_TRYAGAIN;
master_source_current_wait(ap->entry);
ap->entry->current = source;
debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent);
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;
}