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