/* * 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 #include #include #include #include #include #include #include #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/". */ #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 */