xref: /illumos-gate/usr/src/cmd/fs.d/autofs/ns_fnmount.c (revision 948f2876ce2a3010558f4f6937e16086ebcd36f2)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  *	ns_fnmount.c
24  *
25  *	Copyright (c) 1996 Sun Microsystems Inc
26  *	All Rights Reserved.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <syslog.h>
36 #include <rpc/rpc.h>
37 #include <rpcsvc/nis.h>
38 #include <xfn/xfn.h>
39 #include "automount.h"
40 #include "ns_fnutils.h"
41 
42 
43 /*
44  * The maximum sizes of map names, key names, composite names, and status
45  * descriptions, including the trailing '\0'.
46  */
47 #define	MAPNAMESZ	(size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
48 #define	KEYNAMESZ	(size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
49 #define	COMPNAMESZ	(size_t)(MAPNAMESZ - FNPREFIXLEN + KEYNAMESZ - 2)
50 #define	DESCSZ		(size_t)512
51 
52 
53 /*
54  * Number of the home directory field in NIS+ password tables.
55  */
56 #define	NIS_HOME	5
57 
58 
59 typedef struct mapent	mapent;
60 typedef struct mapline	mapline;
61 
62 
63 /*
64  * The name of an attribute.
65  */
66 static const FN_identifier_t attr_exported = {FN_ID_STRING, 8, "exported"};
67 
68 
69 /*
70  * Given a request by a particular user to mount the name "key" under
71  * map/context "map", and a set of default mount options, return (in
72  * "res") either a list of mapents giving the mounts that need to be
73  * performed, or a symbolic link to be created for a user-relative
74  * context.  If "shallow" is true return, in place of the list of
75  * mapents, a single mapent representing an indirect mount point.
76  *
77  *	void
78  *	getmapent_fn(char *key, char *map, char *opts, uid_t uid,
79  *	      bool_t shallow, getmapent_fn_res *res);
80  */
81 
82 /*
83  * Given a reference, its composite name, default mount options, and a
84  * mapent root, return a list of mapents to mount.  If "shallow" is
85  * true return, in place of the list of mapents, a single mapent
86  * representing an indirect mount point.  The map and key strings are
87  * pieces of the composite name such that:
88  * "FNPREFIX/cname" == "map/key".
89  */
90 static mapent *
91 process_ref(const FN_ref_t *ref, const char *cname, char *map, char *key,
92     char *opts, char *root, bool_t shallow, FN_status_t *status);
93 
94 /*
95  * Traverse the namespace to find a frontier below ref along which
96  * future mounts may need to be triggered.  Add to mapents the
97  * corresponding direct autofs mount points.
98  *     map:	map name for ref
99  *     maplen:	strlen(map)
100  *     mntpnt:	suffix of map where the current mount request begins
101  *		(starts off as "", and grows as we traverse the namespace)
102  *     opts:	default mount options
103  *     status:	passed from above to avoid having to allocate one on each call
104  * Works by calling frontier_aux() on each name bound under ref.
105  * Return the new mapents, or free mapents and return NULL on failure.
106  */
107 static mapent *
108 frontier(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
109     char *mntpnt, char *opts, FN_status_t *status);
110 
111 /*
112  * Called by frontier(), once for each "name" that it finds.  map is
113  * passed unchanged from frontier().  ref is the reference named by
114  * "map/name".  If ref is found to be along the frontier, add the
115  * corresponding direct autofs mount point to mapents.  Otherwise
116  * continue traversing the namespace to find the frontier.  Other
117  * arguments and the return value are as for frontier().
118  */
119 static mapent *
120 frontier_aux(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
121     char *mntpnt, const char *name, char *opts, FN_status_t *status);
122 
123 /*
124  * Given a reference with an address type of ADDR_HOST and its
125  * composite name, check the attr_exported attribute to determine if
126  * the corresponding directory is exported.  Return FALSE on error.
127  */
128 static bool_t
129 exported(const FN_ref_t *ref, const char *cname, FN_status_t *status);
130 
131 /*
132  * Find a reference's address type and, if "data" is not NULL, its
133  * data string.  If there is no address of a known type, set *typep to
134  * NUM_ADDRTYPES; if there are several, stop after finding the first.
135  * Return 0 on success.
136  */
137 static int
138 addr_from_ref(const FN_ref_t *ref, const char *cname, addrtype_t *typep,
139     char *data, size_t datasz);
140 
141 /*
142  * Decode an address's data into a string.  Return 0 on success.
143  */
144 static int
145 str_from_addr(const char *cname, const FN_ref_addr_t *addr, char str[],
146     size_t strsz);
147 
148 /*
149  * Perform a NIS+ query to find a home directory.  The result is a
150  * newly-allocated string, or NULL on error.
151  */
152 static char *
153 nisplus_homedir(const char *nisname);
154 
155 /*
156  * Given a map name and its current length, append "/name".  Return
157  * the new length.  On error, syslog a warning and return 0.
158  */
159 static size_t
160 append_mapname(char *map, size_t maplen, const char *name);
161 
162 /*
163  * Concatenate two strings using the given separator.  The result is a
164  * newly-allocated string, or NULL on error.
165  */
166 static char *
167 concat(const char *s1, char sep, const char *s2);
168 
169 /*
170  * Add the "nosuid" option to a mapent.  Also check for a sneaky
171  * hacker trying to override this option by manually inserting a
172  * multiple mount entry into the XFN namespace.  Return FALSE on error.
173  */
174 static bool_t
175 safe_mapent(mapent *me);
176 
177 /*
178  * Append "nosuid" to a list of options.  The result is a
179  * newly-allocated string, or NULL on error.
180  */
181 static char *
182 safe_opts(const char *opts);
183 
184 /*
185  * Trim comments and trailing whitespace from ml->linebuf, then
186  * unquote it and leave the result in ml.  Return 0 on success.
187  */
188 static int
189 trim_line(mapline *ml);
190 
191 /*
192  * Determine whether ml contains an option string (such as "-ro") and
193  * nothing else.
194  */
195 static bool_t
196 opts_only(const mapline *ml);
197 
198 /*
199  * Allocate a new mapent structure.  The arguments must have been
200  * malloc'ed, and are owned by the mapent; they are freed if
201  * new_mapent() fails.  If any argument is NULL, the call fails and a
202  * memory allocation failure is logged.  A root argument of 'noroot'
203  * indicates that the map_root field does not need to be set (it's
204  * only needed in the first of a list of mapents).
205  */
206 static char *noroot = "[no root]";
207 static mapent *
208 new_mapent(char *root, char *mntpnt, char *fstype, char *mntopts, char *host,
209     char *dir);
210 
211 /*
212  * Determine whether cname is a user-relative binding -- such as "myself" --
213  * in the initial context.
214  */
215 static bool_t
216 is_user_relative(const char *cname);
217 
218 /*
219  * Given the name of a user-relative binding, return an equivalent
220  * name that is not user-relative.
221  */
222 static char *
223 equiv_name(FN_ctx_t *, const char *cname, FN_status_t *);
224 
225 void
226 getmapent_fn(char *key, char *map, char *opts, uid_t uid, bool_t shallow,
227     getmapent_fn_res *res)
228 {
229 	size_t			maplen;
230 	FN_status_t		*status;
231 	FN_ctx_t		*init_ctx = NULL;
232 	int			statcode;
233 	char			cname[COMPNAMESZ];
234 	FN_composite_name_t	*compname;
235 	FN_ref_t		*ref;
236 	char			mapname[MAPNAMESZ];
237 	char			*root;
238 
239 	res->type = FN_NONE;
240 	res->m_or_l.mapents = NULL;
241 
242 	if (init_fn() != 0) {
243 		return;
244 	}
245 
246 	/*
247 	 * For direct mounts, the key is the entire path, and the map
248 	 * name already has the final key component appended.  Split
249 	 * apart the map name and key.  The "root" of the mapent is
250 	 * "/key" for indirect mounts, and "" for direct mounts.
251 	 */
252 	strcpy(mapname, map);
253 	if (key[0] == '/') {
254 		key = strrchr(key, '/') + 1;
255 		*strrchr(mapname, '/') = '\0';
256 		root = strdup("");
257 	} else {
258 		root = concat("", '/', key);
259 	}
260 	map = mapname;
261 	maplen = strlen(map);
262 
263 	if ((maplen - FNPREFIXLEN + strlen(key)) >= COMPNAMESZ) {
264 		if (verbose) {
265 			syslog(LOG_ERR, "name %s/%s too long", map, key);
266 		}
267 		return;
268 	}
269 	if (maplen == FNPREFIXLEN) {
270 		strcpy(cname, key);
271 	} else {
272 		sprintf(cname, "%s/%s", map + FNPREFIXLEN + 1, key);
273 	}
274 
275 	status = fn_status_create();
276 	if (status == NULL) {
277 		if (verbose) {
278 			syslog(LOG_ERR, "Could not create FNS status object");
279 		}
280 		return;
281 	}
282 	init_ctx = _fn_ctx_handle_from_initial_with_uid(uid, 0, status);
283 	if (init_ctx == NULL) {
284 		logstat(status, "", "No initial context");
285 		goto done;
286 	}
287 
288 #ifndef XFN1ENV
289 	if (is_user_relative(cname)) {
290 		res->type = FN_SYMLINK;
291 		res->m_or_l.symlink = equiv_name(init_ctx, cname, status);
292 		goto done;
293 	}
294 #endif
295 
296 	if ((compname = new_cname(cname)) == NULL) {
297 		goto done;
298 	}
299 	ref = fn_ctx_lookup(init_ctx, compname, status);
300 	statcode = fn_status_code(status);
301 	fn_composite_name_destroy(compname);
302 
303 	if (trace > 1 && !shallow) {
304 		trace_prt(1, "  FNS traversal: %s\n", cname);
305 	}
306 
307 	if (ref == NULL) {
308 		if ((statcode != FN_E_NAME_NOT_FOUND) &&
309 		    (statcode != FN_E_NOT_A_CONTEXT)) {
310 			logstat(status, "lookup failed on", cname);
311 		}
312 		goto done;
313 	}
314 
315 	res->type = FN_MAPENTS;
316 	res->m_or_l.mapents =
317 	    process_ref(ref, cname, map, key, opts, root, shallow, status);
318 	fn_ref_destroy(ref);
319 done:
320 	fn_ctx_handle_destroy(init_ctx);
321 	fn_status_destroy(status);
322 }
323 
324 
325 static mapent *
326 process_ref(const FN_ref_t *ref, const char *cname, char *map, char *key,
327     char *opts, char *root, bool_t shallow, FN_status_t *status)
328 {
329 	addrtype_t	addrtype;
330 	mapline		ml;
331 	char		*addrdata = ml.linebuf;
332 	mapent		*mapents;
333 	bool_t		self;
334 	char		*homedir;
335 	size_t		maplen;
336 	char		*colon;
337 	char		*nfshost;
338 	char		*nfsdir;
339 
340 	if ((reftype(ref) < NUM_REFTYPES) &&
341 	    (addr_from_ref(ref, cname, &addrtype, addrdata, LINESZ) == 0)) {
342 
343 		switch (addrtype) {
344 		case ADDR_MOUNT:
345 			if (trim_line(&ml) != 0) {
346 				return (NULL);
347 			}
348 			if (opts_only(&ml)) {
349 				/* parse_entry() can't handle such lines */
350 				if (macro_expand("&", ml.linebuf,
351 				    ml.lineqbuf, LINESZ)) {
352 					syslog(LOG_ERR,
353 					"%s/%s: opts too long (max %d chars)",
354 					FNPREFIX, cname, LINESZ - 1);
355 					return (NULL);
356 				}
357 				opts = ml.linebuf + 1;	/* skip '-' */
358 				goto indirect;
359 			}
360 			mapents = parse_entry(key, map, opts, &ml, NULL, 0,
361 						TRUE);
362 			if (mapents == NULL || !safe_mapent(mapents)) {
363 				free_mapent(mapents);
364 				return (NULL);
365 			}
366 			free(mapents->map_root);
367 			mapents->map_root = root;
368 			break;
369 
370 		case ADDR_HOST:
371 			/*
372 			 * Address is of the form "host:dir".
373 			 * If "dir" is not supplied, it defaults to "/".
374 			 */
375 			colon = strchr(addrdata, ':');
376 			if (colon == NULL || colon[1] == '\0') {
377 				nfsdir = strdup("/");
378 			} else {
379 				*colon = '\0';
380 				nfsdir = strdup(colon + 1);
381 			}
382 			nfshost = strdup(addrdata);
383 			/*
384 			 * If nfshost is the local host, the NFS mount
385 			 * request will be converted to a loopback
386 			 * mount.  Otherwise check that the file system
387 			 * is exported.
388 			 */
389 			if (nfshost != NULL) {
390 				self = self_check(nfshost);
391 				if (!self && !exported(ref, cname, status)) {
392 					if (transient(status)) {
393 						return (NULL);
394 					} else {
395 						goto indirect;
396 					}
397 				}
398 			}
399 			mapents = new_mapent(root, strdup(""), strdup("nfs"),
400 					safe_opts(opts), nfshost, nfsdir);
401 			if (self && !shallow) {
402 				return (mapents);
403 			}
404 			break;
405 
406 		case ADDR_USER:
407 			homedir = strdup(addrdata);
408 			homedir[strcspn(homedir, " \t\r\n")] = '\0';
409 			mapents = new_mapent(root, strdup(""), strdup("lofs"),
410 					strdup(opts), strdup(""), homedir);
411 			break;
412 		case ADDR_USER_NISPLUS:
413 			homedir = nisplus_homedir(addrdata);
414 			mapents = (homedir == NULL)
415 				? NULL
416 				: new_mapent(root, strdup(""), strdup("lofs"),
417 					strdup(opts), strdup(""), homedir);
418 			break;
419 		}
420 
421 		if (mapents == NULL) {
422 			return (NULL);
423 		}
424 		if (shallow) {
425 			mapents->map_root = NULL;	/* don't free "root" */
426 			free_mapent(mapents);
427 			goto indirect;
428 		}
429 
430 		/* "map" => "map/key" */
431 		if ((maplen = append_mapname(map, strlen(map), key)) == 0) {
432 			return (mapents);
433 		}
434 		return (frontier(mapents, ref, map, maplen, map + maplen,
435 				opts, status));
436 	}
437 
438 	/* Ref type wasn't recognized. */
439 
440 indirect:
441 	/* Install an indirect autofs mount point. */
442 	return (new_mapent(root, strdup(""), strdup("autofs"), strdup(opts),
443 				strdup(""), concat(map, '/', key)));
444 }
445 
446 
447 /*
448  * All that this function really does is call frontier_aux() on every
449  * name bound under ref.  The rest is error checking(!)
450  *
451  * The error handling strategy is to reject the entire mount request
452  * (by freeing mapents) if any (potentially) transient error occurs,
453  * and to treat nontransient errors as holes in the affected portions
454  * of the namespace.
455  */
456 static mapent *
457 frontier(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
458     char *mntpnt, char *opts, FN_status_t *status)
459 {
460 	FN_ctx_t		*ctx;
461 	FN_bindinglist_t	*bindings = NULL;
462 	FN_ref_t		*child_ref;
463 	FN_string_t		*child_s;
464 	const char		*child;
465 	unsigned int		statcode;
466 
467 	ctx = fn_ctx_handle_from_ref(ref, XFN2(0) status);
468 	if (ctx == NULL) {
469 		if (fn_status_code(status) != FN_E_NO_SUPPORTED_ADDRESS) {
470 			logstat(status, "from_ref failed for", map);
471 		}
472 		goto checkerr_return;
473 	}
474 
475 	bindings = fn_ctx_list_bindings(ctx, empty_cname, status);
476 	fn_ctx_handle_destroy(ctx);
477 	if (bindings == NULL) {
478 		logstat(status, "list_bindings failed for", map);
479 		goto checkerr_return;
480 	}
481 
482 	while ((child_s = fn_bindinglist_next(bindings, &child_ref, status))
483 			!= NULL) {
484 		child = (const char *)fn_string_str(child_s, &statcode);
485 		if (child == NULL) {
486 			if (verbose) {
487 				syslog(LOG_ERR,
488 					"FNS string error listing %s", map);
489 			}
490 			fn_string_destroy(child_s);
491 			goto err_return;
492 		}
493 		mapents = frontier_aux(mapents, child_ref, map, maplen,
494 					mntpnt, child, opts, status);
495 		fn_string_destroy(child_s);
496 		fn_ref_destroy(child_ref);
497 		if (mapents == NULL) {
498 			goto noerr_return;
499 		}
500 	}
501 	if (fn_status_is_success(status)) {
502 		goto noerr_return;
503 	} else {
504 		logstat(status, "error while listing", map);
505 		/* Fall through to checkerr_return. */
506 	}
507 
508 checkerr_return:
509 	if (!transient(status)) {
510 		goto noerr_return;
511 	}
512 err_return:
513 	free_mapent(mapents);
514 	mapents = NULL;
515 noerr_return:
516 	fn_bindinglist_destroy(bindings XFN1(status));
517 	return (mapents);
518 }
519 
520 
521 static mapent *
522 frontier_aux(mapent *mapents, const FN_ref_t *ref, char *map, size_t maplen,
523     char *mntpnt, const char *name, char *opts, FN_status_t *status)
524 {
525 	addrtype_t	addrtype;
526 	bool_t		at_frontier;
527 	mapent		*me;
528 	size_t		maplen_save = maplen;
529 	char		*cname = map + FNPREFIXLEN + 1;	/* for error msgs */
530 
531 	if (reftype(ref) >= NUM_REFTYPES) {
532 		/*
533 		 * We could instead install an indirect autofs mount point
534 		 * here.  That would allow, for example, a user to be bound
535 		 * beneath a file system.
536 		 */
537 		return (mapents);
538 	}
539 
540 	/* "map" => "map/name" */
541 	if ((maplen = append_mapname(map, maplen, name)) == 0) {
542 		return (mapents);
543 	}
544 	if (trace > 1) {
545 		trace_prt(1, "  FNS traversal: %s/\n", cname);
546 	}
547 
548 	/*
549 	 * If this is an address type that we know how to mount, then
550 	 * we have reached the frontier.
551 	 */
552 	at_frontier = (addr_from_ref(ref, cname, &addrtype, NULL, 0) == 0);
553 	/*
554 	 * For an ADDR_HOST address, treat a non-exported directory as
555 	 * if the address type were not known:  continue searching for
556 	 * exported subdirectories.
557 	 */
558 	if (at_frontier && (addrtype == ADDR_HOST)) {
559 		if (!exported(ref, cname, status)) {
560 			if (transient(status)) {
561 				free_mapent(mapents);
562 				return (NULL);
563 			} else {
564 				at_frontier = FALSE;
565 			}
566 		}
567 	}
568 	/*
569 	 * If we have reached the frontier, install a direct autofs
570 	 * mount point (which will trigger the actual mount if the
571 	 * user steps on it later).  Otherwise, continue traversing
572 	 * the namespace looking for known address types.
573 	 */
574 	if (at_frontier) {
575 		opts = (opts[0] != '\0')
576 			? concat(opts, ',', "direct")
577 			: strdup("direct");
578 		me = new_mapent(noroot, strdup(mntpnt), strdup("autofs"), opts,
579 				strdup(""), strdup(map));
580 		if (me != NULL) {
581 			/* Link new mapent into list (not at the head). */
582 			me->map_next = mapents->map_next;
583 			mapents->map_next = me;
584 		} else {
585 			free_mapent(mapents);
586 			mapents = NULL;
587 		}
588 	} else {
589 		mapents =
590 		    frontier(mapents, ref, map, maplen, mntpnt, opts, status);
591 	}
592 	map[maplen_save] = '\0';	/* "map/name" => "map" */
593 	return (mapents);
594 }
595 
596 
597 static bool_t
598 exported(const FN_ref_t *ref, const char *cname, FN_status_t *status)
599 {
600 	FN_ctx_t		*ctx;
601 	FN_attribute_t		*attr;
602 
603 	ctx = fn_ctx_handle_from_ref(ref, XFN2(0) status);
604 	if (ctx == NULL) {
605 		logstat(status, "from_ref failed for", cname);
606 		return (FALSE);
607 	}
608 	attr = fn_attr_get(ctx, empty_cname, &attr_exported, XFN2(1) status);
609 	fn_ctx_handle_destroy(ctx);
610 
611 	switch (fn_status_code(status)) {
612 	case FN_SUCCESS:
613 		fn_attribute_destroy(attr);
614 		break;
615 	case FN_E_NO_SUCH_ATTRIBUTE:
616 		break;
617 	default:
618 		logstat(status, "could not get attributes for", cname);
619 	}
620 	return (attr != NULL);
621 }
622 
623 
624 static int
625 addr_from_ref(const FN_ref_t *ref, const char *cname, addrtype_t *typep,
626     char *data, size_t datasz)
627 {
628 	const FN_ref_addr_t	*addr;
629 	void			*iter_pos;
630 
631 	addr = fn_ref_first(ref, &iter_pos);
632 	if (addr == NULL) {
633 		if (verbose) {
634 			syslog(LOG_ERR, "FNS ref with no address: %s", cname);
635 		}
636 		return (-1);
637 	}
638 	while (addr != NULL) {
639 		*typep = addrtype(addr);
640 		if (*typep < NUM_ADDRTYPES) {
641 			return ((data != NULL)
642 				? str_from_addr(cname, addr, data, datasz)
643 				: 0);
644 		}
645 		addr = fn_ref_next(ref, &iter_pos);
646 	}
647 	return (-1);
648 }
649 
650 
651 static int
652 str_from_addr(const char *cname, const FN_ref_addr_t *addr, char str[],
653     size_t strsz)
654 {
655 	XDR	xdr;
656 	int	res;
657 
658 	xdrmem_create(&xdr, (caddr_t)fn_ref_addr_data(addr),
659 			fn_ref_addr_length(addr), XDR_DECODE);
660 	if (!xdr_string(&xdr, &str, strsz)) {
661 		if (verbose) {
662 			syslog(LOG_ERR,
663 				"Could not decode FNS address for %s", cname);
664 		}
665 		res = -1;
666 	} else {
667 		res = 0;
668 	}
669 	xdr_destroy(&xdr);
670 	return (res);
671 }
672 
673 
674 static char *
675 nisplus_homedir(const char *nisname)
676 {
677 	nis_result	*res;
678 	void		*val;
679 	size_t		len;
680 	char		*homedir = NULL;
681 
682 	/* NIS+ query to find passwd table entry */
683 	res = nis_list((nis_name)nisname,
684 			FOLLOW_LINKS | FOLLOW_PATH | USE_DGRAM, NULL, NULL);
685 	if (res == NULL) {
686 		if (verbose) {
687 			syslog(LOG_ERR,
688 				"FNS home dir query failed: %s", nisname);
689 		}
690 		return (NULL);
691 	}
692 	if (res->status != NIS_SUCCESS && res->status != NIS_S_SUCCESS) {
693 		if ((res->status != NIS_NOTFOUND) && verbose) {
694 			syslog(LOG_ERR,
695 				"FNS home dir query failed: %s", nisname);
696 		}
697 		goto done;
698 	}
699 	val = ENTRY_VAL(NIS_RES_OBJECT(res), NIS_HOME);
700 	len = ENTRY_LEN(NIS_RES_OBJECT(res), NIS_HOME);
701 
702 	if (len < 1) {
703 		if (verbose) {
704 			syslog(LOG_ERR, "FNS home dir not found: %s", nisname);
705 		}
706 		goto done;
707 	}
708 	homedir = malloc(len + 1);
709 	if (homedir == NULL) {
710 		log_mem_failure();
711 		goto done;
712 	}
713 
714 	strncpy(homedir, (char *)val, len);
715 	homedir[len] = '\0';
716 done:
717 	nis_freeresult(res);
718 	return (homedir);
719 }
720 
721 
722 static size_t
723 append_mapname(char *map, size_t maplen, const char *name)
724 {
725 	size_t namelen = strlen(name);
726 
727 	if (maplen + 1 + namelen >= MAPNAMESZ) {
728 		if (verbose) {
729 			syslog(LOG_ERR, "FNS name %s/%s too long",
730 				map + FNPREFIXLEN + 1, name);
731 		}
732 		return (0);
733 	}
734 	sprintf(map + maplen, "/%s", name);
735 	return (maplen + 1 + namelen);
736 }
737 
738 
739 static char *
740 concat(const char *s1, char sep, const char *s2)
741 {
742 	char *s = malloc(strlen(s1) + 1 + strlen(s2) + 1);
743 
744 	if (s != NULL) {
745 		sprintf(s, "%s%c%s", s1, sep, s2);
746 	}
747 	return (s);
748 }
749 
750 
751 static bool_t
752 safe_mapent(mapent *me)
753 {
754 	char	*opts;
755 
756 	if (me->map_next != NULL) {
757 		/* Multiple mounts don't belong in XFN namespace. */
758 		return (NULL);
759 	}
760 	opts = me->map_mntopts;
761 	me->map_mntopts = safe_opts(opts);
762 	free(opts);
763 	return (me->map_mntopts != NULL);
764 }
765 
766 
767 static char *
768 safe_opts(const char *opts)
769 {
770 	char	*start;
771 	size_t	len;
772 
773 	if (opts[0] == '\0') {
774 		return (strdup(MNTOPT_NOSUID));
775 	}
776 
777 	/* A quick-and-dirty check to see if "nosuid" is already there. */
778 	start = strstr(opts, MNTOPT_NOSUID);
779 	len = sizeof (MNTOPT_NOSUID) - 1;	/* "-1" for trailing '\0' */
780 	if (start != NULL) {
781 		while (start > opts && isspace(*(start - 1))) {
782 			start--;
783 		}
784 		if ((start == opts || *(start - 1) == ',') &&
785 		    opts[len] == ',' || opts[len] == '\0') {
786 			return (strdup(opts));
787 		}
788 	}
789 	return (concat(opts, ',', MNTOPT_NOSUID));
790 }
791 
792 
793 static int
794 trim_line(mapline *ml)
795 {
796 	char	*end;	/* pointer to '\0' at end of linebuf */
797 
798 	end = ml->linebuf + strcspn(ml->linebuf, "#");
799 	while ((end > ml->linebuf) && isspace(end[-1])) {
800 		end--;
801 	}
802 	if (end <= ml->linebuf) {
803 		return (-1);
804 	}
805 	*end = '\0';
806 	unquote(ml->linebuf, ml->lineqbuf);
807 	return (0);
808 }
809 
810 
811 static bool_t
812 opts_only(const mapline *ml)
813 {
814 	const char *s = ml->linebuf;
815 	const char *q = ml->lineqbuf;
816 
817 	if (*s != '-') {
818 		return (FALSE);
819 	}
820 	for (; *s != '\0'; s++, q++) {
821 		if (isspace(*s) && (*q == ' ')) {
822 			return (FALSE);
823 		}
824 	}
825 	return (TRUE);
826 }
827 
828 
829 static mapent *
830 new_mapent(char *root, char *mntpnt, char *fstype, char *mntopts, char *host,
831     char *dir)
832 {
833 	mapent		*me;
834 	struct mapfs	*mfs;
835 	char		*mounter = NULL;
836 
837 	me = calloc(1, sizeof (*me));
838 	mfs = calloc(1, sizeof (*mfs));
839 	if (fstype != NULL) {
840 		mounter = strdup(fstype);
841 	}
842 	if ((mntpnt == NULL) || (fstype == NULL) || (mntopts == NULL) ||
843 	    (host == NULL) || (dir == NULL) || (me == NULL) || (mfs == NULL) ||
844 	    (mounter == NULL) || (root == NULL)) {
845 		log_mem_failure();
846 		free(me);
847 		free(mfs);
848 		free(mounter);
849 		free(root);
850 		free(mntpnt);
851 		free(fstype);
852 		free(mntopts);
853 		free(host);
854 		free(dir);
855 		return (NULL);
856 	}
857 	me->map_root	= (root != noroot) ? root : NULL;
858 	me->map_fstype	= fstype;
859 	me->map_mounter	= mounter;
860 	me->map_mntpnt	= mntpnt;
861 	me->map_mntopts	= mntopts;
862 	me->map_fsw	= NULL;
863 	me->map_fswq    = NULL;
864 	me->map_fs	= mfs;
865 	mfs->mfs_host	= host;
866 	mfs->mfs_dir	= dir;
867 	me->map_mntlevel = -1;
868 	me->map_modified = FALSE;
869 	me->map_faked = FALSE;
870 	me->map_err = 0;		/* MAPENT_NOERR */
871 	return (me);
872 }
873 
874 
875 #ifndef XFN1ENV
876 
877 /*
878  * User-relative bindings in the initial context, and the leading components
879  * of their non-user-relative equivalents.  Leading components are listed in
880  * the order in which they should be tried.  Each list is NULL-terminated
881  * (the compiler generously does this for us).
882  * For "myorgunit", for example, we first check if it is equivalent to
883  * "thisorgunit".  If not, we translate it into "org/<something>".
884  */
885 #define	MAX_LEADS 3
886 
887 static struct {
888 	const char	*binding;
889 	const char	*leads[MAX_LEADS + 1];
890 } user_rel[] = {
891 	{"thisuser",	{"user", "thisorgunit", "org"}},
892 	{"myself",	{"user", "thisorgunit", "org"}},
893 	{"_myself",	{"_user", "_thisorgunit", "_orgunit"}},
894 	{"myorgunit",	{"thisorgunit", "org"}},
895 	{"_myorgunit",	{"_thisorgunit", "_orgunit"}},
896 	{"myens",	{"thisens"}},
897 	{"_myens",	{"_thisens"}}
898 };
899 
900 
901 static bool_t
902 is_user_relative(const char *cname)
903 {
904 	int	i;
905 
906 	for (i = 0; i < sizeof (user_rel) / sizeof (user_rel[0]); i++) {
907 		if (strcmp(cname, user_rel[i].binding) == 0) {
908 			return (TRUE);
909 		}
910 	}
911 	return (FALSE);
912 }
913 
914 
915 static char *
916 equiv_name(FN_ctx_t *ctx, const char *cname, FN_status_t *status)
917 {
918 	FN_composite_name_t	*name;
919 	FN_string_t		*leading_name;
920 	FN_composite_name_t	*equiv;
921 	FN_string_t		*equiv_string;
922 	const char		*equiv_str;
923 	char			*equiv_str_dup;
924 	const char		**leads;
925 	unsigned int		stat;
926 	int			i;
927 
928 	for (i = 0; i < sizeof (user_rel) / sizeof (user_rel[0]); i++) {
929 		if (strcmp(cname, user_rel[i].binding) == 0) {
930 			break;
931 		}
932 	}
933 	if ((name = new_cname(cname)) == NULL) {
934 		return (NULL);
935 	}
936 	leads = user_rel[i].leads;	/* array of leading names to try */
937 	do {
938 		leading_name = fn_string_from_str((unsigned char *)*leads);
939 		if (leading_name == NULL) {
940 			log_mem_failure();
941 			fn_composite_name_destroy(name);
942 			return (NULL);
943 		}
944 		equiv = prelim_fn_ctx_equivalent_name(ctx, name, leading_name,
945 		    status);
946 		fn_string_destroy(leading_name);
947 	} while (equiv == NULL && *++leads != NULL);
948 
949 	fn_composite_name_destroy(name);
950 
951 	if (equiv == NULL) {
952 		if (transient(status)) {
953 			logstat(status, "could not find equivalent of", cname);
954 		}
955 		return (NULL);
956 	}
957 	equiv_string = fn_string_from_composite_name(equiv, &stat);
958 	fn_composite_name_destroy(equiv);
959 	if (equiv_string == NULL) {
960 		log_mem_failure();
961 		return (NULL);
962 	}
963 	equiv_str = (const char *)fn_string_str(equiv_string, &stat);
964 	if (equiv_str == NULL ||
965 	    (equiv_str_dup = strdup(equiv_str)) == NULL) {
966 		log_mem_failure();
967 		fn_string_destroy(equiv_string);
968 		return (NULL);
969 	}
970 	fn_string_destroy(equiv_string);
971 	return (equiv_str_dup);
972 }
973 
974 #endif	/* XFN1ENV */
975