4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
24 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
25 * Use is subject to license terms.
34 #include <rpcsvc/nis.h>
36 #include "automount.h"
37 #include "ns_fnutils.h"
41 * The maximum sizes of map names, key names, composite names, and status
42 * descriptions, including the trailing '\0'.
44 #define MAPNAMESZ (size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
45 #define KEYNAMESZ (size_t)(AUTOFS_MAXCOMPONENTLEN + 1)
46 #define COMPNAMESZ (size_t)(MAPNAMESZ - FNPREFIXLEN + KEYNAMESZ - 2)
47 #define DESCSZ (size_t)512
49 typedef struct mapent mapent
;
50 typedef struct mapline mapline
;
54 * The name of an attribute.
56 static const FN_identifier_t attr_exported
= {FN_ID_STRING
, 8, "exported"};
60 * Given a request by a particular user to mount the name "key" under
61 * map/context "map", and a set of default mount options, return (in
62 * "res") either a list of mapents giving the mounts that need to be
63 * performed, or a symbolic link to be created for a user-relative
64 * context. If "shallow" is true return, in place of the list of
65 * mapents, a single mapent representing an indirect mount point.
68 * getmapent_fn(char *key, char *map, char *opts, uid_t uid,
69 * bool_t shallow, getmapent_fn_res *res);
73 * Given a reference, its composite name, default mount options, and a
74 * mapent root, return a list of mapents to mount. If "shallow" is
75 * true return, in place of the list of mapents, a single mapent
76 * representing an indirect mount point. The map and key strings are
77 * pieces of the composite name such that:
78 * "FNPREFIX/cname" == "map/key".
81 process_ref(const FN_ref_t
*ref
, const char *cname
, char *map
, char *key
,
82 char *opts
, char *root
, bool_t shallow
, FN_status_t
*status
);
85 * Traverse the namespace to find a frontier below ref along which
86 * future mounts may need to be triggered. Add to mapents the
87 * corresponding direct autofs mount points.
88 * map: map name for ref
90 * mntpnt: suffix of map where the current mount request begins
91 * (starts off as "", and grows as we traverse the namespace)
92 * opts: default mount options
93 * status: passed from above to avoid having to allocate one on each call
94 * Works by calling frontier_aux() on each name bound under ref.
95 * Return the new mapents, or free mapents and return NULL on failure.
98 frontier(mapent
*mapents
, const FN_ref_t
*ref
, char *map
, size_t maplen
,
99 char *mntpnt
, char *opts
, FN_status_t
*status
);
102 * Called by frontier(), once for each "name" that it finds. map is
103 * passed unchanged from frontier(). ref is the reference named by
104 * "map/name". If ref is found to be along the frontier, add the
105 * corresponding direct autofs mount point to mapents. Otherwise
106 * continue traversing the namespace to find the frontier. Other
107 * arguments and the return value are as for frontier().
110 frontier_aux(mapent
*mapents
, const FN_ref_t
*ref
, char *map
, size_t maplen
,
111 char *mntpnt
, const char *name
, char *opts
, FN_status_t
*status
);
114 * Given a reference with an address type of ADDR_HOST and its
115 * composite name, check the attr_exported attribute to determine if
116 * the corresponding directory is exported. Return FALSE on error.
119 exported(const FN_ref_t
*ref
, const char *cname
, FN_status_t
*status
);
122 * Find a reference's address type and, if "data" is not NULL, its
123 * data string. If there is no address of a known type, set *typep to
124 * NUM_ADDRTYPES; if there are several, stop after finding the first.
125 * Return 0 on success.
128 addr_from_ref(const FN_ref_t
*ref
, const char *cname
, addrtype_t
*typep
,
129 char *data
, size_t datasz
);
132 * Decode an address's data into a string. Return 0 on success.
135 str_from_addr(const char *cname
, const FN_ref_addr_t
*addr
, char str
[],
139 * Given a map name and its current length, append "/name". Return
140 * the new length. On error, syslog a warning and return 0.
143 append_mapname(char *map
, size_t maplen
, const char *name
);
146 * Concatenate two strings using the given separator. The result is a
147 * newly-allocated string, or NULL on error.
150 concat(const char *s1
, char sep
, const char *s2
);
153 * Add the "nosuid" option to a mapent. Also check for a sneaky
154 * hacker trying to override this option by manually inserting a
155 * multiple mount entry into the XFN namespace. Return FALSE on error.
158 safe_mapent(mapent
*me
);
161 * Append "nosuid" to a list of options. The result is a
162 * newly-allocated string, or NULL on error.
165 safe_opts(const char *opts
);
168 * Trim comments and trailing whitespace from ml->linebuf, then
169 * unquote it and leave the result in ml. Return 0 on success.
172 trim_line(mapline
*ml
);
175 * Determine whether ml contains an option string (such as "-ro") and
179 opts_only(const mapline
*ml
);
182 * Allocate a new mapent structure. The arguments must have been
183 * malloc'ed, and are owned by the mapent; they are freed if
184 * new_mapent() fails. If any argument is NULL, the call fails and a
185 * memory allocation failure is logged. A root argument of 'noroot'
186 * indicates that the map_root field does not need to be set (it's
187 * only needed in the first of a list of mapents).
189 static char *noroot
= "[no root]";
191 new_mapent(char *root
, char *mntpnt
, char *fstype
, char *mntopts
, char *host
,
195 * Determine whether cname is a user-relative binding -- such as "myself" --
196 * in the initial context.
199 is_user_relative(const char *cname
);
202 * Given the name of a user-relative binding, return an equivalent
203 * name that is not user-relative.
206 equiv_name(FN_ctx_t
*, const char *cname
, FN_status_t
*);
209 getmapent_fn(char *key
, char *map
, char *opts
, uid_t uid
, bool_t shallow
,
210 getmapent_fn_res
*res
)
214 FN_ctx_t
*init_ctx
= NULL
;
216 char cname
[COMPNAMESZ
];
217 FN_composite_name_t
*compname
;
219 char mapname
[MAPNAMESZ
];
223 res
->m_or_l
.mapents
= NULL
;
225 if (init_fn() != 0) {
230 * For direct mounts, the key is the entire path, and the map
231 * name already has the final key component appended. Split
232 * apart the map name and key. The "root" of the mapent is
233 * "/key" for indirect mounts, and "" for direct mounts.
235 strcpy(mapname
, map
);
237 key
= strrchr(key
, '/') + 1;
238 *strrchr(mapname
, '/') = '\0';
241 root
= concat("", '/', key
);
244 maplen
= strlen(map
);
246 if ((maplen
- FNPREFIXLEN
+ strlen(key
)) >= COMPNAMESZ
) {
248 syslog(LOG_ERR
, "name %s/%s too long", map
, key
);
252 if (maplen
== FNPREFIXLEN
) {
255 sprintf(cname
, "%s/%s", map
+ FNPREFIXLEN
+ 1, key
);
258 status
= fn_status_create();
259 if (status
== NULL
) {
261 syslog(LOG_ERR
, "Could not create FNS status object");
265 init_ctx
= _fn_ctx_handle_from_initial_with_uid(uid
, 0, status
);
266 if (init_ctx
== NULL
) {
267 logstat(status
, "", "No initial context");
272 if (is_user_relative(cname
)) {
273 res
->type
= FN_SYMLINK
;
274 res
->m_or_l
.symlink
= equiv_name(init_ctx
, cname
, status
);
279 if ((compname
= new_cname(cname
)) == NULL
) {
282 ref
= fn_ctx_lookup(init_ctx
, compname
, status
);
283 statcode
= fn_status_code(status
);
284 fn_composite_name_destroy(compname
);
286 if (trace
> 1 && !shallow
) {
287 trace_prt(1, " FNS traversal: %s\n", cname
);
291 if ((statcode
!= FN_E_NAME_NOT_FOUND
) &&
292 (statcode
!= FN_E_NOT_A_CONTEXT
)) {
293 logstat(status
, "lookup failed on", cname
);
298 res
->type
= FN_MAPENTS
;
299 res
->m_or_l
.mapents
=
300 process_ref(ref
, cname
, map
, key
, opts
, root
, shallow
, status
);
303 fn_ctx_handle_destroy(init_ctx
);
304 fn_status_destroy(status
);
309 process_ref(const FN_ref_t
*ref
, const char *cname
, char *map
, char *key
,
310 char *opts
, char *root
, bool_t shallow
, FN_status_t
*status
)
314 char *addrdata
= ml
.linebuf
;
323 if ((reftype(ref
) < NUM_REFTYPES
) &&
324 (addr_from_ref(ref
, cname
, &addrtype
, addrdata
, LINESZ
) == 0)) {
328 if (trim_line(&ml
) != 0) {
331 if (opts_only(&ml
)) {
332 /* parse_entry() can't handle such lines */
333 if (macro_expand("&", ml
.linebuf
,
334 ml
.lineqbuf
, LINESZ
)) {
336 "%s/%s: opts too long (max %d chars)",
337 FNPREFIX
, cname
, LINESZ
- 1);
340 opts
= ml
.linebuf
+ 1; /* skip '-' */
343 mapents
= parse_entry(key
, map
, opts
, &ml
, NULL
, 0,
345 if (mapents
== NULL
|| !safe_mapent(mapents
)) {
346 free_mapent(mapents
);
349 free(mapents
->map_root
);
350 mapents
->map_root
= root
;
355 * Address is of the form "host:dir".
356 * If "dir" is not supplied, it defaults to "/".
358 colon
= strchr(addrdata
, ':');
359 if (colon
== NULL
|| colon
[1] == '\0') {
360 nfsdir
= strdup("/");
363 nfsdir
= strdup(colon
+ 1);
365 nfshost
= strdup(addrdata
);
367 * If nfshost is the local host, the NFS mount
368 * request will be converted to a loopback
369 * mount. Otherwise check that the file system
372 if (nfshost
!= NULL
) {
373 self
= self_check(nfshost
);
374 if (!self
&& !exported(ref
, cname
, status
)) {
375 if (transient(status
)) {
382 mapents
= new_mapent(root
, strdup(""), strdup("nfs"),
383 safe_opts(opts
), nfshost
, nfsdir
);
384 if (self
&& !shallow
) {
390 homedir
= strdup(addrdata
);
391 homedir
[strcspn(homedir
, " \t\r\n")] = '\0';
392 mapents
= new_mapent(root
, strdup(""), strdup("lofs"),
393 strdup(opts
), strdup(""), homedir
);
397 if (mapents
== NULL
) {
401 mapents
->map_root
= NULL
; /* don't free "root" */
402 free_mapent(mapents
);
406 /* "map" => "map/key" */
407 if ((maplen
= append_mapname(map
, strlen(map
), key
)) == 0) {
410 return (frontier(mapents
, ref
, map
, maplen
, map
+ maplen
,
414 /* Ref type wasn't recognized. */
417 /* Install an indirect autofs mount point. */
418 return (new_mapent(root
, strdup(""), strdup("autofs"), strdup(opts
),
419 strdup(""), concat(map
, '/', key
)));
424 * All that this function really does is call frontier_aux() on every
425 * name bound under ref. The rest is error checking(!)
427 * The error handling strategy is to reject the entire mount request
428 * (by freeing mapents) if any (potentially) transient error occurs,
429 * and to treat nontransient errors as holes in the affected portions
433 frontier(mapent
*mapents
, const FN_ref_t
*ref
, char *map
, size_t maplen
,
434 char *mntpnt
, char *opts
, FN_status_t
*status
)
437 FN_bindinglist_t
*bindings
= NULL
;
439 FN_string_t
*child_s
;
441 unsigned int statcode
;
443 ctx
= fn_ctx_handle_from_ref(ref
, XFN2(0) status
);
445 if (fn_status_code(status
) != FN_E_NO_SUPPORTED_ADDRESS
) {
446 logstat(status
, "from_ref failed for", map
);
448 goto checkerr_return
;
451 bindings
= fn_ctx_list_bindings(ctx
, empty_cname
, status
);
452 fn_ctx_handle_destroy(ctx
);
453 if (bindings
== NULL
) {
454 logstat(status
, "list_bindings failed for", map
);
455 goto checkerr_return
;
458 while ((child_s
= fn_bindinglist_next(bindings
, &child_ref
, status
))
460 child
= (const char *)fn_string_str(child_s
, &statcode
);
464 "FNS string error listing %s", map
);
466 fn_string_destroy(child_s
);
469 mapents
= frontier_aux(mapents
, child_ref
, map
, maplen
,
470 mntpnt
, child
, opts
, status
);
471 fn_string_destroy(child_s
);
472 fn_ref_destroy(child_ref
);
473 if (mapents
== NULL
) {
477 if (fn_status_is_success(status
)) {
480 logstat(status
, "error while listing", map
);
481 /* Fall through to checkerr_return. */
485 if (!transient(status
)) {
489 free_mapent(mapents
);
492 fn_bindinglist_destroy(bindings
XFN1(status
));
498 frontier_aux(mapent
*mapents
, const FN_ref_t
*ref
, char *map
, size_t maplen
,
499 char *mntpnt
, const char *name
, char *opts
, FN_status_t
*status
)
504 size_t maplen_save
= maplen
;
505 char *cname
= map
+ FNPREFIXLEN
+ 1; /* for error msgs */
507 if (reftype(ref
) >= NUM_REFTYPES
) {
509 * We could instead install an indirect autofs mount point
510 * here. That would allow, for example, a user to be bound
511 * beneath a file system.
516 /* "map" => "map/name" */
517 if ((maplen
= append_mapname(map
, maplen
, name
)) == 0) {
521 trace_prt(1, " FNS traversal: %s/\n", cname
);
525 * If this is an address type that we know how to mount, then
526 * we have reached the frontier.
528 at_frontier
= (addr_from_ref(ref
, cname
, &addrtype
, NULL
, 0) == 0);
530 * For an ADDR_HOST address, treat a non-exported directory as
531 * if the address type were not known: continue searching for
532 * exported subdirectories.
534 if (at_frontier
&& (addrtype
== ADDR_HOST
)) {
535 if (!exported(ref
, cname
, status
)) {
536 if (transient(status
)) {
537 free_mapent(mapents
);
545 * If we have reached the frontier, install a direct autofs
546 * mount point (which will trigger the actual mount if the
547 * user steps on it later). Otherwise, continue traversing
548 * the namespace looking for known address types.
551 opts
= (opts
[0] != '\0')
552 ? concat(opts
, ',', "direct")
554 me
= new_mapent(noroot
, strdup(mntpnt
), strdup("autofs"), opts
,
555 strdup(""), strdup(map
));
557 /* Link new mapent into list (not at the head). */
558 me
->map_next
= mapents
->map_next
;
559 mapents
->map_next
= me
;
561 free_mapent(mapents
);
566 frontier(mapents
, ref
, map
, maplen
, mntpnt
, opts
, status
);
568 map
[maplen_save
] = '\0'; /* "map/name" => "map" */
574 exported(const FN_ref_t
*ref
, const char *cname
, FN_status_t
*status
)
577 FN_attribute_t
*attr
;
579 ctx
= fn_ctx_handle_from_ref(ref
, XFN2(0) status
);
581 logstat(status
, "from_ref failed for", cname
);
584 attr
= fn_attr_get(ctx
, empty_cname
, &attr_exported
, XFN2(1) status
);
585 fn_ctx_handle_destroy(ctx
);
587 switch (fn_status_code(status
)) {
589 fn_attribute_destroy(attr
);
591 case FN_E_NO_SUCH_ATTRIBUTE
:
594 logstat(status
, "could not get attributes for", cname
);
596 return (attr
!= NULL
);
601 addr_from_ref(const FN_ref_t
*ref
, const char *cname
, addrtype_t
*typep
,
602 char *data
, size_t datasz
)
604 const FN_ref_addr_t
*addr
;
607 addr
= fn_ref_first(ref
, &iter_pos
);
610 syslog(LOG_ERR
, "FNS ref with no address: %s", cname
);
614 while (addr
!= NULL
) {
615 *typep
= addrtype(addr
);
616 if (*typep
< NUM_ADDRTYPES
) {
617 return ((data
!= NULL
)
618 ? str_from_addr(cname
, addr
, data
, datasz
)
621 addr
= fn_ref_next(ref
, &iter_pos
);
628 str_from_addr(const char *cname
, const FN_ref_addr_t
*addr
, char str
[],
634 xdrmem_create(&xdr
, (caddr_t
)fn_ref_addr_data(addr
),
635 fn_ref_addr_length(addr
), XDR_DECODE
);
636 if (!xdr_string(&xdr
, &str
, strsz
)) {
639 "Could not decode FNS address for %s", cname
);
650 append_mapname(char *map
, size_t maplen
, const char *name
)
652 size_t namelen
= strlen(name
);
654 if (maplen
+ 1 + namelen
>= MAPNAMESZ
) {
656 syslog(LOG_ERR
, "FNS name %s/%s too long",
657 map
+ FNPREFIXLEN
+ 1, name
);
661 sprintf(map
+ maplen
, "/%s", name
);
662 return (maplen
+ 1 + namelen
);
667 concat(const char *s1
, char sep
, const char *s2
)
669 char *s
= malloc(strlen(s1
) + 1 + strlen(s2
) + 1);
672 sprintf(s
, "%s%c%s", s1
, sep
, s2
);
679 safe_mapent(mapent
*me
)
683 if (me
->map_next
!= NULL
) {
684 /* Multiple mounts don't belong in XFN namespace. */
687 opts
= me
->map_mntopts
;
688 me
->map_mntopts
= safe_opts(opts
);
690 return (me
->map_mntopts
!= NULL
);
695 safe_opts(const char *opts
)
700 if (opts
[0] == '\0') {
701 return (strdup(MNTOPT_NOSUID
));
704 /* A quick-and-dirty check to see if "nosuid" is already there. */
705 start
= strstr(opts
, MNTOPT_NOSUID
);
706 len
= sizeof (MNTOPT_NOSUID
) - 1; /* "-1" for trailing '\0' */
708 while (start
> opts
&& isspace(*(start
- 1))) {
711 if ((start
== opts
|| *(start
- 1) == ',') &&
712 opts
[len
] == ',' || opts
[len
] == '\0') {
713 return (strdup(opts
));
716 return (concat(opts
, ',', MNTOPT_NOSUID
));
721 trim_line(mapline
*ml
)
723 char *end
; /* pointer to '\0' at end of linebuf */
725 end
= ml
->linebuf
+ strcspn(ml
->linebuf
, "#");
726 while ((end
> ml
->linebuf
) && isspace(end
[-1])) {
729 if (end
<= ml
->linebuf
) {
733 unquote(ml
->linebuf
, ml
->lineqbuf
);
739 opts_only(const mapline
*ml
)
741 const char *s
= ml
->linebuf
;
742 const char *q
= ml
->lineqbuf
;
747 for (; *s
!= '\0'; s
++, q
++) {
748 if (isspace(*s
) && (*q
== ' ')) {
757 new_mapent(char *root
, char *mntpnt
, char *fstype
, char *mntopts
, char *host
,
762 char *mounter
= NULL
;
764 me
= calloc(1, sizeof (*me
));
765 mfs
= calloc(1, sizeof (*mfs
));
766 if (fstype
!= NULL
) {
767 mounter
= strdup(fstype
);
769 if ((mntpnt
== NULL
) || (fstype
== NULL
) || (mntopts
== NULL
) ||
770 (host
== NULL
) || (dir
== NULL
) || (me
== NULL
) || (mfs
== NULL
) ||
771 (mounter
== NULL
) || (root
== NULL
)) {
784 me
->map_root
= (root
!= noroot
) ? root
: NULL
;
785 me
->map_fstype
= fstype
;
786 me
->map_mounter
= mounter
;
787 me
->map_mntpnt
= mntpnt
;
788 me
->map_mntopts
= mntopts
;
792 mfs
->mfs_host
= host
;
794 me
->map_mntlevel
= -1;
795 me
->map_modified
= FALSE
;
796 me
->map_faked
= FALSE
;
797 me
->map_err
= 0; /* MAPENT_NOERR */
805 * User-relative bindings in the initial context, and the leading components
806 * of their non-user-relative equivalents. Leading components are listed in
807 * the order in which they should be tried. Each list is NULL-terminated
808 * (the compiler generously does this for us).
809 * For "myorgunit", for example, we first check if it is equivalent to
810 * "thisorgunit". If not, we translate it into "org/<something>".
816 const char *leads
[MAX_LEADS
+ 1];
818 {"thisuser", {"user", "thisorgunit", "org"}},
819 {"myself", {"user", "thisorgunit", "org"}},
820 {"_myself", {"_user", "_thisorgunit", "_orgunit"}},
821 {"myorgunit", {"thisorgunit", "org"}},
822 {"_myorgunit", {"_thisorgunit", "_orgunit"}},
823 {"myens", {"thisens"}},
824 {"_myens", {"_thisens"}}
829 is_user_relative(const char *cname
)
833 for (i
= 0; i
< sizeof (user_rel
) / sizeof (user_rel
[0]); i
++) {
834 if (strcmp(cname
, user_rel
[i
].binding
) == 0) {
843 equiv_name(FN_ctx_t
*ctx
, const char *cname
, FN_status_t
*status
)
845 FN_composite_name_t
*name
;
846 FN_string_t
*leading_name
;
847 FN_composite_name_t
*equiv
;
848 FN_string_t
*equiv_string
;
849 const char *equiv_str
;
855 for (i
= 0; i
< sizeof (user_rel
) / sizeof (user_rel
[0]); i
++) {
856 if (strcmp(cname
, user_rel
[i
].binding
) == 0) {
860 if ((name
= new_cname(cname
)) == NULL
) {
863 leads
= user_rel
[i
].leads
; /* array of leading names to try */
865 leading_name
= fn_string_from_str((unsigned char *)*leads
);
866 if (leading_name
== NULL
) {
868 fn_composite_name_destroy(name
);
871 equiv
= prelim_fn_ctx_equivalent_name(ctx
, name
, leading_name
,
873 fn_string_destroy(leading_name
);
874 } while (equiv
== NULL
&& *++leads
!= NULL
);
876 fn_composite_name_destroy(name
);
879 if (transient(status
)) {
880 logstat(status
, "could not find equivalent of", cname
);
884 equiv_string
= fn_string_from_composite_name(equiv
, &stat
);
885 fn_composite_name_destroy(equiv
);
886 if (equiv_string
== NULL
) {
890 equiv_str
= (const char *)fn_string_str(equiv_string
, &stat
);
891 if (equiv_str
== NULL
||
892 (equiv_str_dup
= strdup(equiv_str
)) == NULL
) {
894 fn_string_destroy(equiv_string
);
897 fn_string_destroy(equiv_string
);
898 return (equiv_str_dup
);