xref: /netbsd-src/external/bsd/am-utils/dist/amd/opts.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: opts.c,v 1.1.1.2 2009/03/20 20:26:50 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997-2009 Erez Zadok
5  * Copyright (c) 1989 Jan-Simon Pendry
6  * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
7  * Copyright (c) 1989 The Regents of the University of California.
8  * All rights reserved.
9  *
10  * This code is derived from software contributed to Berkeley by
11  * Jan-Simon Pendry at Imperial College, London.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. All advertising materials mentioning features or use of this software
22  *    must display the following acknowledgment:
23  *      This product includes software developed by the University of
24  *      California, Berkeley and its contributors.
25  * 4. Neither the name of the University nor the names of its contributors
26  *    may be used to endorse or promote products derived from this software
27  *    without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39  * SUCH DAMAGE.
40  *
41  *
42  * File: am-utils/amd/opts.c
43  *
44  */
45 
46 #ifdef HAVE_CONFIG_H
47 # include <config.h>
48 #endif /* HAVE_CONFIG_H */
49 #include <am_defs.h>
50 #include <amd.h>
51 
52 /*
53  * MACROS:
54  */
55 #define	NLEN	16	/* Length of longest option name (conservative) */
56 #define S(x) (x) , (sizeof(x)-1)
57 /*
58  * The BUFSPACE macros checks that there is enough space
59  * left in the expansion buffer.  If there isn't then we
60  * give up completely.  This is done to avoid crashing the
61  * automounter itself (which would be a bad thing to do).
62  */
63 #define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+MAXPATHLEN)
64 
65 /*
66  * TYPEDEFS:
67  */
68 typedef int (*IntFuncPtr) (char *);
69 typedef struct opt_apply opt_apply;
70 enum vs_opt { SelEQ, SelNE, VarAss };
71 
72 /*
73  * STRUCTURES
74  */
75 struct opt {
76   char *name;			/* Name of the option */
77   int nlen;			/* Length of option name */
78   char **optp;			/* Pointer to option value string */
79   char **sel_p;			/* Pointer to selector value string */
80   int (*fxn_p)(char *);		/* Pointer to boolean function */
81   int case_insensitive;		/* How to do selector comparisons */
82 };
83 
84 struct opt_apply {
85   char **opt;
86   char *val;
87 };
88 
89 struct functable {
90   char *name;
91   IntFuncPtr func;
92 };
93 
94 /*
95  * FORWARD DEFINITION:
96  */
97 static int f_in_network(char *);
98 static int f_xhost(char *);
99 static int f_netgrp(char *);
100 static int f_netgrpd(char *);
101 static int f_exists(char *);
102 static int f_false(char *);
103 static int f_true(char *);
104 static inline char *expand_options(char *key);
105 
106 /*
107  * STATICS:
108  */
109 static char NullStr[] = "<NULL>";
110 static char nullstr[] = "";
111 static char *opt_dkey = NullStr;
112 static char *opt_host = nullstr; /* XXX: was the global hostname */
113 static char *opt_hostd = hostd;
114 static char *opt_key = nullstr;
115 static char *opt_keyd = nullstr;
116 static char *opt_map = nullstr;
117 static char *opt_path = nullstr;
118 char uid_str[SIZEOF_UID_STR], gid_str[SIZEOF_GID_STR];
119 char *opt_uid = uid_str;
120 char *opt_gid = gid_str;
121 static char *vars[8];
122 static char *literal_dollar = "$"; /* ${dollar}: a literal '$' in maps */
123 
124 /*
125  * GLOBALS
126  */
127 static struct am_opts fs_static;      /* copy of the options to play with */
128 
129 
130 /*
131  * Options in some order corresponding to frequency of use so that
132  * first-match algorithm is sped up.
133  */
134 static struct opt opt_fields[] = {
135   /* Name and length.
136 	Option str.		Selector str.	boolean fxn.	case sensitive */
137   { S("opts"),
138        &fs_static.opt_opts,	0,		0, 		FALSE	},
139   { S("host"),
140 	0,			&opt_host,	0,		TRUE	},
141   { S("hostd"),
142 	0,			&opt_hostd,	0,		TRUE	},
143   { S("type"),
144 	&fs_static.opt_type,	0,		0,		FALSE	},
145   { S("rhost"),
146 	&fs_static.opt_rhost,	0,		0,		TRUE	},
147   { S("rfs"),
148 	&fs_static.opt_rfs,	0,		0,		FALSE	},
149   { S("fs"),
150 	&fs_static.opt_fs,	0,		0,		FALSE	},
151   { S("key"),
152 	0,			&opt_key,	0,		FALSE	},
153   { S("map"),
154 	0,			&opt_map,	0,		FALSE	},
155   { S("sublink"),
156 	&fs_static.opt_sublink,	0,		0,		FALSE	},
157   { S("arch"),
158 	0,			&gopt.arch,	0,		TRUE	},
159   { S("dev"),
160 	&fs_static.opt_dev,	0,		0,		FALSE	},
161   { S("pref"),
162 	&fs_static.opt_pref,	0,		0,		FALSE	},
163   { S("path"),
164 	0,			&opt_path,	0,		FALSE	},
165   { S("autodir"),
166 	0,			&gopt.auto_dir,	0,		FALSE	},
167   { S("delay"),
168 	&fs_static.opt_delay,	0,		0,		FALSE	},
169   { S("domain"),
170 	0,			&hostdomain,	0,		TRUE	},
171   { S("karch"),
172 	0,			&gopt.karch,	0,		TRUE	},
173   { S("cluster"),
174 	0,			&gopt.cluster,	0,		TRUE	},
175   { S("wire"),
176 	0,			0,		f_in_network,	TRUE	},
177   { S("network"),
178 	0,			0,		f_in_network,	TRUE	},
179   { S("netnumber"),
180 	0,			0,		f_in_network,	TRUE	},
181   { S("byte"),
182 	0,			&endian,	0,		TRUE	},
183   { S("os"),
184 	0,			&gopt.op_sys,	0,		TRUE	},
185   { S("osver"),
186 	0,			&gopt.op_sys_ver,	0,	TRUE	},
187   { S("full_os"),
188 	0,			&gopt.op_sys_full,	0,	TRUE	},
189   { S("vendor"),
190 	0,			&gopt.op_sys_vendor,	0,	TRUE	},
191   { S("remopts"),
192 	&fs_static.opt_remopts,	0,		0,		FALSE	},
193   { S("mount"),
194 	&fs_static.opt_mount,	0,		0,		FALSE	},
195   { S("unmount"),
196 	&fs_static.opt_unmount,	0,		0,		FALSE	},
197   { S("umount"),
198 	&fs_static.opt_umount,	0,		0,		FALSE	},
199   { S("cache"),
200 	&fs_static.opt_cache,	0,		0,		FALSE	},
201   { S("user"),
202 	&fs_static.opt_user,	0,		0,		FALSE	},
203   { S("group"),
204 	&fs_static.opt_group,	0,		0,		FALSE	},
205   { S(".key"),
206 	0,			&opt_dkey,	0,		FALSE	},
207   { S("key."),
208 	0,			&opt_keyd,	0,		FALSE	},
209   { S("maptype"),
210 	&fs_static.opt_maptype,	0,		0,		FALSE	},
211   { S("cachedir"),
212 	&fs_static.opt_cachedir, 0,		0,		FALSE	},
213   { S("addopts"),
214 	&fs_static.opt_addopts,	0,		0, 		FALSE	},
215   { S("uid"),
216 	0,			&opt_uid,	0,		FALSE	},
217   { S("gid"),
218 	0,			&opt_gid,	0, 		FALSE	},
219   { S("mount_type"),
220 	&fs_static.opt_mount_type, 0,		0,		FALSE	},
221   { S("dollar"),
222 	&literal_dollar,	0,		0,		FALSE	},
223   { S("var0"),
224 	&vars[0],		0,		0,		FALSE	},
225   { S("var1"),
226 	&vars[1],		0,		0,		FALSE	},
227   { S("var2"),
228 	&vars[2],		0,		0,		FALSE	},
229   { S("var3"),
230 	&vars[3],		0,		0,		FALSE	},
231   { S("var4"),
232 	&vars[4],		0,		0,		FALSE	},
233   { S("var5"),
234 	&vars[5],		0,		0,		FALSE	},
235   { S("var6"),
236 	&vars[6],		0,		0,		FALSE	},
237   { S("var7"),
238 	&vars[7],		0,		0,		FALSE	},
239   { 0, 0, 0, 0, 0, FALSE },
240 };
241 
242 static struct functable functable[] = {
243   { "in_network",	f_in_network },
244   { "xhost",		f_xhost },
245   { "netgrp",		f_netgrp },
246   { "netgrpd",		f_netgrpd },
247   { "exists",		f_exists },
248   { "false",		f_false },
249   { "true",		f_true },
250   { 0, 0 },
251 };
252 
253 /*
254  * Specially expand the remote host name first
255  */
256 static opt_apply rhost_expansion[] =
257 {
258   {&fs_static.opt_rhost, "${host}"},
259   {0, 0},
260 };
261 
262 /*
263  * List of options which need to be expanded
264  * Note that the order here _may_ be important.
265  */
266 static opt_apply expansions[] =
267 {
268   {&fs_static.opt_sublink, 0},
269   {&fs_static.opt_rfs, "${path}"},
270   {&fs_static.opt_fs, "${autodir}/${rhost}${rfs}"},
271   {&fs_static.opt_opts, "rw"},
272   {&fs_static.opt_remopts, "${opts}"},
273   {&fs_static.opt_mount, 0},
274   {&fs_static.opt_unmount, 0},
275   {&fs_static.opt_umount, 0},
276   {&fs_static.opt_cachedir, 0},
277   {&fs_static.opt_addopts, 0},
278   {0, 0},
279 };
280 
281 /*
282  * List of options which need to be free'ed before re-use
283  */
284 static opt_apply to_free[] =
285 {
286   {&fs_static.fs_glob, 0},
287   {&fs_static.fs_local, 0},
288   {&fs_static.fs_mtab, 0},
289   {&fs_static.opt_sublink, 0},
290   {&fs_static.opt_rfs, 0},
291   {&fs_static.opt_fs, 0},
292   {&fs_static.opt_rhost, 0},
293   {&fs_static.opt_opts, 0},
294   {&fs_static.opt_remopts, 0},
295   {&fs_static.opt_mount, 0},
296   {&fs_static.opt_unmount, 0},
297   {&fs_static.opt_umount, 0},
298   {&fs_static.opt_cachedir, 0},
299   {&fs_static.opt_addopts, 0},
300   {&vars[0], 0},
301   {&vars[1], 0},
302   {&vars[2], 0},
303   {&vars[3], 0},
304   {&vars[4], 0},
305   {&vars[5], 0},
306   {&vars[6], 0},
307   {&vars[7], 0},
308   {0, 0},
309 };
310 
311 
312 /*
313  * expand backslash escape sequences
314  * (escaped slash is handled separately in normalize_slash)
315  */
316 static char
317 backslash(char **p)
318 {
319   char c;
320 
321   if ((*p)[1] == '\0') {
322     plog(XLOG_USER, "Empty backslash escape");
323     return **p;
324   }
325 
326   if (**p == '\\') {
327     (*p)++;
328     switch (**p) {
329     case 'g':
330       c = '\007';		/* Bell */
331       break;
332     case 'b':
333       c = '\010';		/* Backspace */
334       break;
335     case 't':
336       c = '\011';		/* Horizontal Tab */
337       break;
338     case 'n':
339       c = '\012';		/* New Line */
340       break;
341     case 'v':
342       c = '\013';		/* Vertical Tab */
343       break;
344     case 'f':
345       c = '\014';		/* Form Feed */
346       break;
347     case 'r':
348       c = '\015';		/* Carriage Return */
349       break;
350     case 'e':
351       c = '\033';		/* Escape */
352       break;
353     case '0':
354     case '1':
355     case '2':
356     case '3':
357     case '4':
358     case '5':
359     case '6':
360     case '7':
361       {
362 	int cnt, val, ch;
363 
364 	for (cnt = 0, val = 0; cnt < 3; cnt++) {
365 	  ch = *(*p)++;
366 	  if (ch < '0' || ch > '7') {
367 	    (*p)--;
368 	    break;
369 	  }
370 	  val = (val << 3) | (ch - '0');
371 	}
372 
373 	if ((val & 0xffffff00) != 0)
374 	  plog(XLOG_USER,
375 	       "Too large character constant %u\n",
376 	       val);
377 	c = (char) val;
378 	--(*p);
379       }
380       break;
381 
382     default:
383       c = **p;
384       break;
385     }
386   } else
387     c = **p;
388 
389   return c;
390 }
391 
392 
393 /*
394  * Skip to next option in the string
395  */
396 static char *
397 opt(char **p)
398 {
399   char *cp = *p;
400   char *dp = cp;
401   char *s = cp;
402 
403 top:
404   while (*cp && *cp != ';') {
405     if (*cp == '"') {
406       /*
407        * Skip past string
408        */
409       for (cp++; *cp && *cp != '"'; cp++)
410 	if (*cp == '\\')
411 	  *dp++ = backslash(&cp);
412 	else
413 	  *dp++ = *cp;
414       if (*cp)
415 	cp++;
416     } else {
417       *dp++ = *cp++;
418     }
419   }
420 
421   /*
422    * Skip past any remaining ';'s
423    */
424   while (*cp == ';')
425     cp++;
426 
427   /*
428    * If we have a zero length string
429    * and there are more fields, then
430    * parse the next one.  This allows
431    * sequences of empty fields.
432    */
433   if (*cp && dp == s)
434     goto top;
435 
436   *dp = '\0';
437 
438   *p = cp;
439   return s;
440 }
441 
442 
443 /*
444  * These routines add a new style of selector; function-style boolean
445  * operators.  To add new ones, just define functions as in true, false,
446  * exists (below) and add them to the functable, above.
447  *
448  * Usage example: Some people have X11R5 local, some go to a server. I do
449  * this:
450  *
451  *    *       exists(/usr/pkg/${key});type:=link;fs:=/usr/pkg/${key} || \
452  *            -type:=nfs;rfs=/usr/pkg/${key} \
453  *            rhost:=server1 \
454  *            rhost:=server2
455  *
456  * -Rens Troost <rens@imsi.com>
457  */
458 static IntFuncPtr
459 functable_lookup(char *key)
460 {
461   struct functable *fp;
462 
463   for (fp = functable; fp->name; fp++)
464     if (FSTREQ(fp->name, key))
465         return (fp->func);
466   return (IntFuncPtr) NULL;
467 }
468 
469 
470 /*
471  * Fill in the global structure fs_static by
472  * cracking the string opts.  opts may be
473  * scribbled on at will.  Does NOT evaluate options.
474  * Returns 0 on error, 1 if no syntax errors were discovered.
475  */
476 static int
477 split_opts(char *opts, char *mapkey)
478 {
479   char *o = opts;
480   char *f;
481 
482   /*
483    * For each user-specified option
484    */
485   for (f = opt(&o); *f; f = opt(&o)) {
486     struct opt *op;
487     char *eq = strchr(f, '=');
488     char *opt = NULL;
489 
490     if (!eq)
491       continue;
492 
493     if (*(eq-1) == '!' ||
494 	eq[1] == '=' ||
495 	eq[1] == '!') {	/* != or == or =! */
496       continue;			/* we don't care about selectors */
497     }
498 
499     if (*(eq-1) == ':') {	/* := */
500       *(eq-1) = '\0';
501     } else {
502       /* old style assignment */
503       eq[0] = '\0';
504     }
505     opt = eq + 1;
506 
507     /*
508      * For each recognized option
509      */
510     for (op = opt_fields; op->name; op++) {
511       /*
512        * Check whether they match
513        */
514       if (FSTREQ(op->name, f)) {
515 	if (op->sel_p) {
516 	  plog(XLOG_USER, "key %s: Can't assign to a selector (%s)",
517 	       mapkey, op->name);
518 	  return 0;
519 	}
520 	*op->optp = opt;	/* actual assignment into fs_static */
521 	break;			/* break out of for loop */
522       }	/* end of "if (FSTREQ(op->name, f))" statement  */
523     } /* end of "for (op = opt_fields..." statement  */
524 
525     if (!op->name)
526       plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f);
527   }
528 
529   return 1;
530 }
531 
532 
533 /*
534  * Just evaluate selectors, which were split by split_opts.
535  * Returns 0 on error or no match, 1 if matched.
536  */
537 static int
538 eval_selectors(char *opts, char *mapkey)
539 {
540   char *o, *old_o;
541   char *f;
542   int ret = 0;
543 
544   o = old_o = strdup(opts);
545 
546   /*
547    * For each user-specified option
548    */
549   for (f = opt(&o); *f; f = opt(&o)) {
550     struct opt *op;
551     enum vs_opt vs_opt;
552     char *eq = strchr(f, '=');
553     char *fx;
554     IntFuncPtr func;
555     char *opt = NULL;
556     char *arg;
557 
558     if (!eq) {
559       /*
560        * No value, is it a function call?
561        */
562       arg = strchr(f, '(');
563 
564       if (!arg || arg[1] == '\0' || arg == f) {
565 	/*
566 	 * No, just continue
567 	 */
568 	plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
569 	continue;
570       }
571 
572       /* null-terminate the argument  */
573       *arg++ = '\0';
574       fx = strchr(arg, ')');
575       if (!arg || fx == arg) {
576 	plog(XLOG_USER, "key %s: Malformed function in \"%s\"", mapkey, f);
577 	continue;
578       }
579       *fx = '\0';
580 
581       if (f[0] == '!') {
582 	vs_opt = SelNE;
583 	f++;
584       } else {
585 	vs_opt = SelEQ;
586       }
587       /*
588        * look up f in functable and pass it arg.
589        * func must return 0 on failure, and 1 on success.
590        */
591       if ((func = functable_lookup(f))) {
592 	int funok;
593 
594 	/* this allocates memory, don't forget to free */
595 	arg = expand_options(arg);
596 	funok = func(arg);
597 	XFREE(arg);
598 
599 	if (vs_opt == SelNE)
600 	  funok = !funok;
601 	if (!funok)
602 	  goto out;
603 
604 	continue;
605       } else {
606 	plog(XLOG_USER, "key %s: unknown function \"%s\"", mapkey, f);
607 	goto out;
608       }
609     } else {
610       if (eq[1] == '\0' || eq == f) {
611 	/* misformed selector */
612 	plog(XLOG_USER, "key %s: Bad selector \"%s\"", mapkey, f);
613 	continue;
614       }
615     }
616 
617     /*
618      * Check what type of operation is happening
619      * !=, =!  is SelNE
620      * == is SelEQ
621      * =, := is VarAss
622      */
623     if (*(eq-1) == '!') {	/* != */
624       vs_opt = SelNE;
625       *(eq-1) = '\0';
626       opt = eq + 1;
627     } else if (*(eq-1) == ':') {	/* := */
628       continue;
629     } else if (eq[1] == '=') {	/* == */
630       vs_opt = SelEQ;
631       eq[0] = '\0';
632       opt = eq + 2;
633     } else if (eq[1] == '!') {	/* =! */
634       vs_opt = SelNE;
635       eq[0] = '\0';
636       opt = eq + 2;
637     } else {
638       /* old style assignment */
639       continue;
640     }
641 
642     /*
643      * For each recognized option
644      */
645     for (op = opt_fields; op->name; op++) {
646       /*
647        * Check whether they match
648        */
649       if (FSTREQ(op->name, f)) {
650 	opt = expand_options(opt);
651 
652 	if (op->sel_p != NULL) {
653 	  int selok;
654 	  if (op->case_insensitive) {
655 	    selok = STRCEQ(*op->sel_p, opt);
656 	  } else {
657 	    selok = STREQ(*op->sel_p, opt);
658 	  }
659 	  if (vs_opt == SelNE)
660 	    selok = !selok;
661 	  if (!selok) {
662 	    plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
663 		 mapkey,
664 		 op->name,
665 		 *op->sel_p,
666 		 vs_opt == SelNE ? "mis" : "",
667 		 opt);
668 	    XFREE(opt);
669 	    goto out;
670 	  }
671 	  XFREE(opt);
672 	}
673 	/* check if to apply a function */
674 	if (op->fxn_p) {
675 	  int funok;
676 
677 	  funok = op->fxn_p(opt);
678 	  if (vs_opt == SelNE)
679 	    funok = !funok;
680 	  if (!funok) {
681 	    plog(XLOG_MAP, "key %s: map function %s did not %smatch %s",
682 		 mapkey,
683 		 op->name,
684 		 vs_opt == SelNE ? "mis" : "",
685 		 opt);
686 	    XFREE(opt);
687 	    goto out;
688 	  }
689 	  XFREE(opt);
690 	}
691 	break;			/* break out of for loop */
692       }
693     }
694 
695     if (!op->name)
696       plog(XLOG_USER, "key %s: Unrecognized key/option \"%s\"", mapkey, f);
697   }
698 
699   /* all is ok */
700   ret = 1;
701 
702  out:
703   free(old_o);
704   return ret;
705 }
706 
707 
708 /*
709  * Skip to next option in the string, but don't scribble over the string.
710  * However, *p gets repointed to the start of the next string past ';'.
711  */
712 static char *
713 opt_no_scribble(char **p)
714 {
715   char *cp = *p;
716   char *dp = cp;
717   char *s = cp;
718 
719 top:
720   while (*cp && *cp != ';') {
721     if (*cp == '\"') {
722       /*
723        * Skip past string
724        */
725       cp++;
726       while (*cp && *cp != '\"')
727 	*dp++ = *cp++;
728       if (*cp)
729 	cp++;
730     } else {
731       *dp++ = *cp++;
732     }
733   }
734 
735   /*
736    * Skip past any remaining ';'s
737    */
738   while (*cp == ';')
739     cp++;
740 
741   /*
742    * If we have a zero length string
743    * and there are more fields, then
744    * parse the next one.  This allows
745    * sequences of empty fields.
746    */
747   if (*cp && dp == s)
748     goto top;
749 
750   *p = cp;
751   return s;
752 }
753 
754 
755 /*
756  * Strip any selectors from a string.  Selectors are all assumed to be
757  * first in the string.  This is used for the new /defaults method which will
758  * use selectors as well.
759  */
760 char *
761 strip_selectors(char *opts, char *mapkey)
762 {
763   /*
764    * Fill in the global structure fs_static by
765    * cracking the string opts.  opts may be
766    * scribbled on at will.
767    */
768   char *o = opts;
769   char *oo = opts;
770   char *f;
771 
772   /*
773    * Scan options.  Note that the opt() function scribbles on the opt string.
774    */
775   while (*(f = opt_no_scribble(&o))) {
776     enum vs_opt vs_opt = VarAss;
777     char *eq = strchr(f, '=');
778 
779     if (!eq || eq[1] == '\0' || eq == f) {
780       /*
781        * No option or assignment?  Return as is.
782        */
783       plog(XLOG_USER, "key %s: No option or assignment in \"%s\"", mapkey, f);
784       return o;
785     }
786     /*
787      * Check what type of operation is happening
788      * !=, =!  is SelNE
789      * == is SelEQ
790      * := is VarAss
791      */
792     if (*(eq-1) == '!') {	/* != */
793       vs_opt = SelNE;
794     } else if (*(eq-1) == ':') {	/* := */
795       vs_opt = VarAss;
796     } else if (eq[1] == '=') {	/* == */
797       vs_opt = SelEQ;
798     } else if (eq[1] == '!') {	/* =! */
799       vs_opt = SelNE;
800     }
801     switch (vs_opt) {
802     case SelEQ:
803     case SelNE:
804       /* Skip this selector, maybe there's another one following it */
805       plog(XLOG_USER, "skipping selector to \"%s\"", o);
806       /* store previous match. it may have been the first assignment */
807       oo = o;
808       break;
809 
810     case VarAss:
811       /* found the first assignment, return the string starting with it */
812       dlog("found first assignment past selectors \"%s\"", o);
813       return oo;
814     }
815   }
816 
817   /* return the same string by default. should not happen. */
818   return oo;
819 }
820 
821 
822 /*****************************************************************************
823  *** BOOLEAN FUNCTIONS (return 0 if false, 1 if true):                     ***
824  *****************************************************************************/
825 
826 /* test if arg is any of this host's network names or numbers */
827 static int
828 f_in_network(char *arg)
829 {
830   int status;
831 
832   if (!arg)
833     return 0;
834 
835   status = is_network_member(arg);
836   dlog("%s is %son a local network", arg, (status ? "" : "not "));
837   return status;
838 }
839 
840 
841 /*
842  * Test if arg is any of this host's names or aliases (CNAMES).
843  * Note: this function compares against the fully expanded host name (hostd).
844  * XXX: maybe we also need to compare against the stripped host name?
845  */
846 static int
847 f_xhost(char *arg)
848 {
849   struct hostent *hp;
850   char **cp;
851 
852   if (!arg)
853     return 0;
854 
855   /* simple test: does it match main host name? */
856   if (STREQ(arg, opt_hostd))
857     return 1;
858 
859   /* now find all of the names of "arg" and compare against opt_hostd */
860   hp = gethostbyname(arg);
861   if (hp == NULL) {
862 #ifdef HAVE_HSTRERROR
863     plog(XLOG_ERROR, "gethostbyname xhost(%s): %s", arg, hstrerror(h_errno));
864 #else /* not HAVE_HSTRERROR */
865     plog(XLOG_ERROR, "gethostbyname xhost(%s): h_errno %d", arg, h_errno);
866 #endif /* not HAVE_HSTRERROR */
867     return 0;
868   }
869   /* check primary name */
870   if (hp->h_name) {
871     dlog("xhost: compare %s==%s", hp->h_name, opt_hostd);
872     if (STREQ(hp->h_name, opt_hostd)) {
873       plog(XLOG_INFO, "xhost(%s): matched h_name %s", arg, hp->h_name);
874       return 1;
875     }
876   }
877   /* check all aliases, if any */
878   if (hp->h_aliases == NULL) {
879     dlog("gethostbyname(%s) has no aliases", arg);
880     return 0;
881   }
882   cp = hp->h_aliases;
883   while (*cp) {
884     dlog("xhost: compare alias %s==%s", *cp, opt_hostd);
885     if (STREQ(*cp, opt_hostd)) {
886       plog(XLOG_INFO, "xhost(%s): matched alias %s", arg, *cp);
887       return 1;
888     }
889     cp++;
890   }
891   /* nothing matched */
892   return 0;
893 }
894 
895 
896 /* test if this host (short hostname form) is in netgroup (arg) */
897 static int
898 f_netgrp(char *arg)
899 {
900   int status;
901   char *ptr, *nhost;
902 
903   if ((ptr = strchr(arg, ',')) != NULL) {
904     *ptr = '\0';
905     nhost = ptr + 1;
906   } else {
907     nhost = opt_host;
908   }
909   status = innetgr(arg, nhost, NULL, NULL);
910   dlog("netgrp = %s status = %d host = %s", arg, status, nhost);
911   if (ptr)
912     *ptr = ',';
913   return status;
914 }
915 
916 
917 /* test if this host (fully-qualified name) is in netgroup (arg) */
918 static int
919 f_netgrpd(char *arg)
920 {
921   int status;
922   char *ptr, *nhost;
923 
924   if ((ptr = strchr(arg, ',')) != NULL) {
925     *ptr = '\0';
926     nhost = ptr + 1;
927   } else {
928     nhost = opt_hostd;
929   }
930   status = innetgr(arg, nhost, NULL, NULL);
931   dlog("netgrp = %s status = %d hostd = %s", arg, status, nhost);
932   if (ptr)
933     *ptr = ',';
934   return status;
935 }
936 
937 
938 /* test if file (arg) exists via lstat */
939 static int
940 f_exists(char *arg)
941 {
942   struct stat buf;
943 
944   if (lstat(arg, &buf) < 0)
945     return (0);
946   else
947     return (1);
948 }
949 
950 
951 /* always false */
952 static int
953 f_false(char *arg)
954 {
955   return (0);
956 }
957 
958 
959 /* always true */
960 static int
961 f_true(char *arg)
962 {
963   return (1);
964 }
965 
966 
967 /*
968  * Free an option
969  */
970 static void
971 free_op(opt_apply *p, int b)
972 {
973   if (*p->opt) {
974     XFREE(*p->opt);
975   }
976 }
977 
978 
979 /*
980  * Normalize slashes in the string.
981  */
982 void
983 normalize_slash(char *p)
984 {
985   char *f, *f0;
986 
987   if (!(gopt.flags & CFM_NORMALIZE_SLASHES))
988     return;
989 
990   f0 = f = strchr(p, '/');
991   if (f) {
992     char *t = f;
993     do {
994       /* assert(*f == '/'); */
995       if (f == f0 && f[0] == '/' && f[1] == '/') {
996 	/* copy double slash iff first */
997 	*t++ = *f++;
998 	*t++ = *f++;
999       } else {
1000 	/* copy a single / across */
1001 	*t++ = *f++;
1002       }
1003 
1004       /* assert(f[-1] == '/'); */
1005       /* skip past more /'s */
1006       while (*f == '/')
1007 	f++;
1008 
1009       /* assert(*f != '/'); */
1010       /* keep copying up to next / */
1011       while (*f && *f != '/') {
1012 	/* support escaped slashes '\/' */
1013 	if (f[0] == '\\' && f[1] == '/')
1014 	  f++;			/* skip backslash */
1015 	*t++ = *f++;
1016       }
1017 
1018       /* assert(*f == 0 || *f == '/'); */
1019 
1020     } while (*f);
1021     *t = '\0';			/* derived from fix by Steven Glassman */
1022   }
1023 }
1024 
1025 
1026 /*
1027  * Macro-expand an option.  Note that this does not
1028  * handle recursive expansions.  They will go badly wrong.
1029  * If sel_p is true then old expand selectors, otherwise
1030  * don't expand selectors.
1031  */
1032 static char *
1033 expand_op(char *opt, int sel_p)
1034 {
1035 #define EXPAND_ERROR "No space to expand \"%s\""
1036   char expbuf[MAXPATHLEN + 1];
1037   char nbuf[NLEN + 1];
1038   char *ep = expbuf;
1039   char *cp = opt;
1040   char *dp;
1041   struct opt *op;
1042   char *cp_orig = opt;
1043 
1044   while ((dp = strchr(cp, '$'))) {
1045     char ch;
1046     /*
1047      * First copy up to the $
1048      */
1049     {
1050       int len = dp - cp;
1051 
1052       if (len > 0) {
1053 	if (BUFSPACE(ep, len)) {
1054 	  /*
1055 	   * We use strncpy (not xstrlcpy) because 'ep' relies on its
1056 	   * semantics.  BUFSPACE guarantees that ep can hold len.
1057 	   */
1058 	  strncpy(ep, cp, len);
1059 	  ep += len;
1060 	} else {
1061 	  plog(XLOG_ERROR, EXPAND_ERROR, opt);
1062 	  goto out;
1063 	}
1064       }
1065     }
1066 
1067     cp = dp + 1;
1068     ch = *cp++;
1069     if (ch == '$') {
1070       if (BUFSPACE(ep, 1)) {
1071 	*ep++ = '$';
1072       } else {
1073 	plog(XLOG_ERROR, EXPAND_ERROR, opt);
1074 	goto out;
1075       }
1076     } else if (ch == '{') {
1077       /* Expansion... */
1078       enum {
1079 	E_All, E_Dir, E_File, E_Domain, E_Host
1080       } todo;
1081       /*
1082        * Find closing brace
1083        */
1084       char *br_p = strchr(cp, '}');
1085       int len;
1086 
1087       /*
1088        * Check we found it
1089        */
1090       if (!br_p) {
1091 	/*
1092 	 * Just give up
1093 	 */
1094 	plog(XLOG_USER, "No closing '}' in \"%s\"", opt);
1095 	goto out;
1096       }
1097       len = br_p - cp;
1098 
1099       /*
1100        * Figure out which part of the variable to grab.
1101        */
1102       if (*cp == '/') {
1103 	/*
1104 	 * Just take the last component
1105 	 */
1106 	todo = E_File;
1107 	cp++;
1108 	--len;
1109       } else if (*(br_p-1) == '/') {
1110 	/*
1111 	 * Take all but the last component
1112 	 */
1113 	todo = E_Dir;
1114 	--len;
1115       } else if (*cp == '.') {
1116 	/*
1117 	 * Take domain name
1118 	 */
1119 	todo = E_Domain;
1120 	cp++;
1121 	--len;
1122       } else if (*(br_p-1) == '.') {
1123 	/*
1124 	 * Take host name
1125 	 */
1126 	todo = E_Host;
1127 	--len;
1128       } else {
1129 	/*
1130 	 * Take the whole lot
1131 	 */
1132 	todo = E_All;
1133       }
1134 
1135       /*
1136        * Truncate if too long.  Since it won't
1137        * match anyway it doesn't matter that
1138        * it has been cut short.
1139        */
1140       if (len > NLEN)
1141 	len = NLEN;
1142 
1143       /*
1144        * Put the string into another buffer so
1145        * we can do comparisons.
1146        *
1147        * We use strncpy here (not xstrlcpy) because the dest is meant
1148        * to be truncated and we don't want to log it as an error.  The
1149        * use of the BUFSPACE macro above guarantees the safe use of
1150        * strncpy with nbuf.
1151        */
1152       strncpy(nbuf, cp, len);
1153       nbuf[len] = '\0';
1154 
1155       /*
1156        * Advance cp
1157        */
1158       cp = br_p + 1;
1159 
1160       /*
1161        * Search the option array
1162        */
1163       for (op = opt_fields; op->name; op++) {
1164 	/*
1165 	 * Check for match
1166 	 */
1167 	if (len == op->nlen && STREQ(op->name, nbuf)) {
1168 	  char xbuf[NLEN + 3];
1169 	  char *val;
1170 	  /*
1171 	   * Found expansion.  Copy
1172 	   * the correct value field.
1173 	   */
1174 	  if (!(!op->sel_p == !sel_p)) {
1175 	    /*
1176 	     * Copy the string across unexpanded
1177 	     */
1178 	    xsnprintf(xbuf, sizeof(xbuf), "${%s%s%s}",
1179 		      todo == E_File ? "/" :
1180 		      todo == E_Domain ? "." : "",
1181 		      nbuf,
1182 		      todo == E_Dir ? "/" :
1183 		      todo == E_Host ? "." : "");
1184 	    val = xbuf;
1185 	    /*
1186 	     * Make sure expansion doesn't
1187 	     * munge the value!
1188 	     */
1189 	    todo = E_All;
1190 	  } else if (op->sel_p) {
1191 	    val = *op->sel_p;
1192 	  } else {
1193 	    val = *op->optp;
1194 	  }
1195 
1196 	  if (val) {
1197 	    /*
1198 	     * Do expansion:
1199 	     * ${/var} means take just the last part
1200 	     * ${var/} means take all but the last part
1201 	     * ${.var} means take all but first part
1202 	     * ${var.} means take just the first part
1203 	     * ${var} means take the whole lot
1204 	     */
1205 	    int vlen = strlen(val);
1206 	    char *vptr = val;
1207 	    switch (todo) {
1208 	    case E_Dir:
1209 	      vptr = strrchr(val, '/');
1210 	      if (vptr)
1211 		vlen = vptr - val;
1212 	      vptr = val;
1213 	      break;
1214 	    case E_File:
1215 	      vptr = strrchr(val, '/');
1216 	      if (vptr) {
1217 		vptr++;
1218 		vlen = strlen(vptr);
1219 	      } else
1220 		vptr = val;
1221 	      break;
1222 	    case E_Domain:
1223 	      vptr = strchr(val, '.');
1224 	      if (vptr) {
1225 		vptr++;
1226 		vlen = strlen(vptr);
1227 	      } else {
1228 		vptr = "";
1229 		vlen = 0;
1230 	      }
1231 	      break;
1232 	    case E_Host:
1233 	      vptr = strchr(val, '.');
1234 	      if (vptr)
1235 		vlen = vptr - val;
1236 	      vptr = val;
1237 	      break;
1238 	    case E_All:
1239 	      break;
1240 	    }
1241 
1242 	    if (BUFSPACE(ep, vlen+1)) {
1243 	      /*
1244 	       * Don't call xstrlcpy() to truncate a string here.  It causes
1245 	       * spurious xstrlcpy() syslog() errors.  Use memcpy() and
1246 	       * explicitly terminate the string.
1247 	       */
1248 	      memcpy(ep, vptr, vlen+1);
1249 	      ep += vlen;
1250 	      *ep = '\0';
1251 	    } else {
1252 	      plog(XLOG_ERROR, EXPAND_ERROR, opt);
1253 	      goto out;
1254 	    }
1255 	  }
1256 	  /*
1257 	   * Done with this variable
1258 	   */
1259 	  break;
1260 	}
1261       }
1262 
1263       /*
1264        * Check that the search was successful
1265        */
1266       if (!op->name) {
1267 	/*
1268 	 * If it wasn't then scan the
1269 	 * environment for that name
1270 	 * and use any value found
1271 	 */
1272 	char *env = getenv(nbuf);
1273 
1274 	if (env) {
1275 	  int vlen = strlen(env);
1276 
1277 	  if (BUFSPACE(ep, vlen+1)) {
1278 	    xstrlcpy(ep, env, vlen+1);
1279 	    ep += vlen;
1280 	  } else {
1281 	    plog(XLOG_ERROR, EXPAND_ERROR, opt);
1282 	    goto out;
1283 	  }
1284 	  if (amuDebug(D_STR))
1285 	    plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
1286 	} else {
1287 	  plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
1288 	}
1289       }
1290     } else {
1291       /*
1292        * Error, error
1293        */
1294       plog(XLOG_USER, "Unknown $ sequence in \"%s\"", opt);
1295     }
1296   }
1297 
1298 out:
1299   /*
1300    * Handle common case - no expansion
1301    */
1302   if (cp == opt) {
1303     opt = strdup(cp);
1304   } else {
1305     /*
1306      * Finish off the expansion
1307      */
1308     int vlen = strlen(cp);
1309     if (BUFSPACE(ep, vlen+1)) {
1310       xstrlcpy(ep, cp, vlen+1);
1311       /* ep += vlen; */
1312     } else {
1313       plog(XLOG_ERROR, EXPAND_ERROR, opt);
1314     }
1315 
1316     /*
1317      * Save the expansion
1318      */
1319     opt = strdup(expbuf);
1320   }
1321 
1322   normalize_slash(opt);
1323 
1324   if (amuDebug(D_STR)) {
1325     plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
1326     plog(XLOG_DEBUG, "......... is \"%s\"", opt);
1327   }
1328   return opt;
1329 }
1330 
1331 
1332 /*
1333  * Wrapper for expand_op
1334  */
1335 static void
1336 expand_opts(opt_apply *p, int sel_p)
1337 {
1338   if (*p->opt) {
1339     *p->opt = expand_op(*p->opt, sel_p);
1340   } else if (p->val) {
1341     /*
1342      * Do double expansion, remembering
1343      * to free the string from the first
1344      * expansion...
1345      */
1346     char *s = expand_op(p->val, TRUE);
1347     *p->opt = expand_op(s, sel_p);
1348     XFREE(s);
1349   }
1350 }
1351 
1352 
1353 /*
1354  * Apply a function to a list of options
1355  */
1356 static void
1357 apply_opts(void (*op) (opt_apply *, int), opt_apply ppp[], int b)
1358 {
1359   opt_apply *pp;
1360 
1361   for (pp = ppp; pp->opt; pp++)
1362     (*op) (pp, b);
1363 }
1364 
1365 
1366 /*
1367  * Free the option table
1368  */
1369 void
1370 free_opts(am_opts *fo)
1371 {
1372   /*
1373    * Copy in the structure we are playing with
1374    */
1375   fs_static = *fo;
1376 
1377   /*
1378    * Free previously allocated memory
1379    */
1380   apply_opts(free_op, to_free, FALSE);
1381 }
1382 
1383 
1384 /*
1385  * Expand selectors (variables that cannot be assigned to or overridden)
1386  */
1387 char *
1388 expand_selectors(char *key)
1389 {
1390   return expand_op(key, TRUE);
1391 }
1392 
1393 
1394 /*
1395  * Expand options (i.e. non-selectors, see above for definition)
1396  */
1397 static inline char *
1398 expand_options(char *key)
1399 {
1400   return expand_op(key, FALSE);
1401 }
1402 
1403 
1404 /*
1405  * Remove trailing /'s from a string
1406  * unless the string is a single / (Steven Glassman)
1407  * or unless it is two slashes // (Kevin D. Bond)
1408  * or unless amd.conf says not to touch slashes.
1409  */
1410 void
1411 deslashify(char *s)
1412 {
1413   if (!(gopt.flags & CFM_NORMALIZE_SLASHES))
1414     return;
1415 
1416   if (s && *s) {
1417     char *sl = s + strlen(s);
1418 
1419     while (*--sl == '/' && sl > s)
1420       *sl = '\0';
1421   }
1422 }
1423 
1424 
1425 int
1426 eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, char *key, char *map)
1427 {
1428   int ok = TRUE;
1429 
1430   free_opts(fo);
1431 
1432   /*
1433    * Clear out the option table
1434    */
1435   memset((voidp) &fs_static, 0, sizeof(fs_static));
1436   memset((voidp) vars, 0, sizeof(vars));
1437   memset((voidp) fo, 0, sizeof(*fo));
1438 
1439   /* set hostname */
1440   opt_host = (char *) am_get_hostname();
1441 
1442   /*
1443    * Set key, map & path before expansion
1444    */
1445   opt_key = key;
1446   opt_map = map;
1447   opt_path = path;
1448 
1449   opt_dkey = strchr(key, '.');
1450   if (!opt_dkey) {
1451     opt_dkey = NullStr;
1452     opt_keyd = key;
1453   } else {
1454     opt_keyd = strnsave(key, opt_dkey - key);
1455     opt_dkey++;
1456     if (*opt_dkey == '\0')	/* check for 'host.' */
1457       opt_dkey = NullStr;
1458   }
1459 
1460   /*
1461    * Expand global options
1462    */
1463   fs_static.fs_glob = expand_selectors(g_opts);
1464 
1465   /*
1466    * Expand local options
1467    */
1468   fs_static.fs_local = expand_selectors(opts);
1469 
1470   /* break global options into fs_static fields */
1471   if ((ok = split_opts(fs_static.fs_glob, key))) {
1472     dlog("global split_opts ok");
1473     /*
1474      * evaluate local selectors
1475      */
1476     if ((ok = eval_selectors(fs_static.fs_local, key))) {
1477       dlog("local eval_selectors ok");
1478       /* if the local selectors matched, then do the local overrides */
1479       ok = split_opts(fs_static.fs_local, key);
1480       if (ok)
1481 	dlog("local split_opts ok");
1482     }
1483   }
1484 
1485   /*
1486    * Normalize remote host name.
1487    * 1.  Expand variables
1488    * 2.  Normalize relative to host tables
1489    * 3.  Strip local domains from the remote host
1490    *     name before using it in other expansions.
1491    *     This makes mount point names and other things
1492    *     much shorter, while allowing cross domain
1493    *     sharing of mount maps.
1494    */
1495   apply_opts(expand_opts, rhost_expansion, FALSE);
1496   if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
1497     host_normalize(&fs_static.opt_rhost);
1498 
1499   /*
1500    * Macro expand the options.
1501    * Do this regardless of whether we are accepting
1502    * this mount - otherwise nasty things happen
1503    * with memory allocation.
1504    */
1505   apply_opts(expand_opts, expansions, FALSE);
1506 
1507   /*
1508    * Strip trailing slashes from local pathname...
1509    */
1510   deslashify(fs_static.opt_fs);
1511 
1512   /*
1513    * ok... copy the data back out.
1514    */
1515   *fo = fs_static;
1516 
1517   /*
1518    * Clear defined options
1519    */
1520   if (opt_keyd != key && opt_keyd != nullstr)
1521     XFREE(opt_keyd);
1522   opt_keyd = nullstr;
1523   opt_dkey = NullStr;
1524   opt_key = opt_map = opt_path = nullstr;
1525 
1526   return ok;
1527 }
1528