/* * lookup_hesiod.c * * Module for Linux automountd to access automount maps in hesiod filsys * entries. * */ #include #include #include #include #include #include #include #include #include #define MODULE_LOOKUP #include "automount.h" #include "nsswitch.h" #define MAPFMT_DEFAULT "hesiod" #define AMD_MAP_PREFIX "hesiod." #define AMD_MAP_PREFIX_LEN 7 #define MODPREFIX "lookup(hesiod): " #define HESIOD_LEN 512 struct lookup_context { const char *mapname; struct parse_mod *parser; void *hesiod_context; }; static pthread_mutex_t hesiod_mutex = PTHREAD_MUTEX_INITIALIZER; 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) { char buf[MAX_ERR_BUF]; int ret = 0; /* Initialize the resolver. */ res_init(); /* Initialize the hesiod context. */ if (hesiod_init(&(ctxt->hesiod_context)) != 0) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "hesiod_init(): %s", estr); return 1; } /* If a map type isn't explicitly given, parse it as hesiod entries. */ if (!mapfmt) mapfmt = MAPFMT_DEFAULT; if (!strcmp(mapfmt, "amd")) { /* amd formated hesiod maps have a map name */ const char *mapname = argv[0]; if (strncmp(mapname, AMD_MAP_PREFIX, AMD_MAP_PREFIX_LEN)) { hesiod_end(ctxt->hesiod_context); logerr(MODPREFIX "incorrect prefix for hesiod map %s", mapname); return 1; } ctxt->mapname = mapname; argc--; argv++; } if (reinit) { ret = reinit_parse(ctxt->parser, mapfmt, MODPREFIX, argc - 1, argv - 1); if (ret) logerr(MODPREFIX "failed to reinit parse context"); } else { ctxt->parser = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); if (!ctxt->parser) { logerr(MODPREFIX "failed to open parse context"); ret = 1; } } if (ret) hesiod_end(ctxt->hesiod_context); return ret; } /* This initializes a context (persistent non-global data) for queries to this module. */ int lookup_init(const char *mapfmt, int argc, const char *const *argv, void **context) { struct lookup_context *ctxt; char buf[MAX_ERR_BUF]; int ret; *context = NULL; /* If we can't build a context, bail. */ ctxt = malloc(sizeof(struct lookup_context)); if (!ctxt) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } memset(ctxt, 0, sizeof(struct lookup_context)); ret = do_init(mapfmt, argc, argv, ctxt, 0); if (ret) { 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; /* If we can't build a context, bail. */ new = malloc(sizeof(struct lookup_context)); if (!new) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } memset(new, 0, sizeof(struct lookup_context)); new->parser = ctxt->parser; ret = do_init(mapfmt, argc, argv, new, 1); if (ret) { free(new); return 1; } *context = new; hesiod_end(ctxt->hesiod_context); free(ctxt); return 0; } int lookup_read_master(struct master *master, time_t age, void *context) { return NSS_STATUS_UNKNOWN; } int lookup_read_map(struct autofs_point *ap, time_t age, void *context) { ap->entry->current = NULL; master_source_current_signal(ap->entry); return NSS_STATUS_UNKNOWN; } /* * Lookup and act on a filesystem name. In this case, lookup the "filsys" * record in hesiod. If it's an AFS or NFS filesystem, parse it out. If * it's an ERR filesystem, it's an error message we should log. Otherwise, * assume it's something we know how to deal with already (generic). */ 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 **hes_result; char **record, *best_record = NULL, *p; int priority, lowest_priority = INT_MAX; int ret, status; mc = source->mc; status = pthread_mutex_lock(&hesiod_mutex); if (status) fatal(status); hes_result = hesiod_resolve(ctxt->hesiod_context, key, "filsys"); if (!hes_result || !hes_result[0]) { int err = errno; if (!defaults_disable_not_found_message()) { error(ap->logopt, MODPREFIX "key \"%s\" not found in map", key); } status = pthread_mutex_unlock(&hesiod_mutex); if (status) fatal(status); if (err == HES_ER_NOTFOUND) return CHE_MISSING; else return CHE_FAIL; } /* autofs doesn't support falling back to alternate records, so just find the record with the lowest priority and hope it works. -- Aaron Ucko 2002-03-11 */ for (record = hes_result; *record; ++record) { p = strrchr(*record, ' '); if ( p && isdigit(p[1]) ) { priority = atoi(p+1); } else { priority = INT_MAX - 1; } if (priority < lowest_priority) { lowest_priority = priority; best_record = *record; } } cache_writelock(mc); ret = cache_update(mc, source, key, best_record, monotonic_time(NULL)); cache_unlock(mc); if (ret == CHE_FAIL) { hesiod_free_list(ctxt->hesiod_context, hes_result); status = pthread_mutex_unlock(&hesiod_mutex); if (status) fatal(status); return ret; } debug(ap->logopt, MODPREFIX "lookup for \"%s\" gave \"%s\"", key, best_record); hesiod_free_list(ctxt->hesiod_context, hes_result); status = pthread_mutex_unlock(&hesiod_mutex); if (status) fatal(status); return ret; } static int lookup_one_amd(struct autofs_point *ap, struct map_source *source, const char *key, int key_len, struct lookup_context *ctxt) { struct mapent_cache *mc; char *hesiod_base; char **hes_result; char *lkp_key; int status, ret; mc = source->mc; hesiod_base = conf_amd_get_hesiod_base(); if (!hesiod_base) return CHE_FAIL; lkp_key = malloc(key_len + strlen(ctxt->mapname) - 7 + 2); if (!lkp_key) { free(hesiod_base); return CHE_FAIL; } strcpy(lkp_key, key); strcat(lkp_key, "."); strcat(lkp_key, ctxt->mapname + AMD_MAP_PREFIX_LEN); status = pthread_mutex_lock(&hesiod_mutex); if (status) fatal(status); hes_result = hesiod_resolve(ctxt->hesiod_context, lkp_key, hesiod_base); if (!hes_result || !hes_result[0]) { int err = errno; if (err == HES_ER_NOTFOUND) ret = CHE_MISSING; else ret = CHE_FAIL; goto done; } cache_writelock(mc); ret = cache_update(mc, source, lkp_key, *hes_result, monotonic_time(NULL)); cache_unlock(mc); if (hes_result) hesiod_free_list(ctxt->hesiod_context, hes_result); done: free(lkp_key); status = pthread_mutex_unlock(&hesiod_mutex); if (status) fatal(status); return ret; } static int match_amd_key(struct autofs_point *ap, struct map_source *source, const char *key, int key_len, struct lookup_context *ctxt) { char buf[MAX_ERR_BUF]; char *lkp_key; char *prefix; int ret; ret = lookup_one_amd(ap, source, key, key_len, ctxt); if (ret == CHE_OK || ret == CHE_UPDATED) 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_amd(ap, source, match, len, ctxt); free(match); if (ret == CHE_OK || ret == CHE_UPDATED) goto done; } /* Lastly try the wildcard */ ret = lookup_one_amd(ap, source, "*", 1, ctxt); done: free(lkp_key); return ret; } int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; char buf[MAX_ERR_BUF]; struct map_source *source; struct mapent *me; char key[KEY_MAX_LEN + 1]; size_t key_len; char *lkp_key; size_t len; char *mapent; int rv; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); debug(ap->logopt, MODPREFIX "looking up root=\"%s\", name=\"%s\"", ap->path, 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; expandamdent(name, key, NULL); key[key_len] = '\0'; debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); } /* Check if we recorded a mount fail for this key anywhere */ me = lookup_source_mapent(ap, name, 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, name); /* Negative timeout expired for non-existent entry. */ if (sme && !sme->mapent) { if (cache_pop_mapent(sme) == CHE_FAIL) cache_delete(smc, name); } cache_unlock(smc); } } } /* If this is not here the filesystem stays busy, for some reason... */ if (chdir("/")) warn(ap->logopt, MODPREFIX "failed to set working directory to \"/\""); len = key_len; if (!(source->flags & MAP_FLAG_FORMAT_AMD)) lkp_key = strdup(key); else { rv = lookup_one_amd(ap, source, "/defaults", 9, ctxt); if (rv == CHE_FAIL) warn(ap->logopt, MODPREFIX "failed to lookup \"/defaults\" entry"); if (!ap->pref) lkp_key = strdup(key); else { len += strlen(ap->pref); lkp_key = malloc(len + 1); if (lkp_key) { strcpy(lkp_key, ap->pref); strcat(lkp_key, name); } } } if (!lkp_key) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, "malloc: %s", estr); return NSS_STATUS_UNKNOWN; } if (source->flags & MAP_FLAG_FORMAT_AMD) rv = match_amd_key(ap, source, lkp_key, len, ctxt); else rv = lookup_one(ap, source, lkp_key, len, ctxt); if (rv == CHE_FAIL) { free(lkp_key); return NSS_STATUS_UNAVAIL; } me = match_cached_key(ap, MODPREFIX, source, lkp_key); if (!me) { free(lkp_key); return NSS_STATUS_NOTFOUND; } if (!me->mapent) { free(lkp_key); return NSS_STATUS_UNAVAIL; } mapent = strdup(me->mapent); if (!mapent) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, "malloc: %s", estr); free(lkp_key); return NSS_STATUS_UNKNOWN; } debug(ap->logopt, MODPREFIX "%s -> %s", lkp_key, mapent); free(lkp_key); rv = ctxt->parser->parse_mount(ap, key, key_len, mapent, ctxt->parser->context); free(mapent); /* * Unavailable due to error such as module load fail * or out of memory, etc. */ if (rv == 1 || rv == -1) return NSS_STATUS_UNAVAIL; return NSS_STATUS_SUCCESS; } /* This destroys a context for queries to this module. It releases the parser structure (unloading the module) and frees the memory used by the context. */ int lookup_done(void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; int rv = close_parse(ctxt->parser); hesiod_end(ctxt->hesiod_context); free(ctxt); return rv; }