/* * lookup_nisplus.c * * Module for Linux automountd to access a NIS+ automount map */ #include #include #include #include #include #include #include #include #include #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; }