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 */
62typedef int (*IntFuncPtr) (char *);
63typedef struct opt_apply opt_apply;
64enum vs_opt { SelEQ, SelNE, VarAss };
65
66/*
67 * STRUCTURES
68 */
69struct 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
78struct opt_apply {
79  char **opt;
80  char *val;
81};
82
83struct functable {
84  char *name;
85  IntFuncPtr func;
86};
87
88/*
89 * FORWARD DEFINITION:
90 */
91static int f_in_network(char *);
92static int f_xhost(char *);
93static int f_netgrp(char *);
94static int f_netgrpd(char *);
95static int f_exists(char *);
96static int f_false(char *);
97static int f_true(char *);
98static inline char *expand_options(char *key);
99
100/*
101 * STATICS:
102 */
103static char NullStr[] = "<NULL>";
104static char nullstr[] = "";
105static char *opt_dkey = NullStr;
106static char *opt_host = nullstr; /* XXX: was the global hostname */
107static char *opt_hostd = hostd;
108static char *opt_key = nullstr;
109static char *opt_keyd = nullstr;
110static char *opt_map = nullstr;
111static char *opt_path = nullstr;
112char uid_str[SIZEOF_UID_STR], gid_str[SIZEOF_GID_STR];
113char *opt_uid = uid_str;
114char *opt_gid = gid_str;
115static char *vars[8];
116static char *literal_dollar = "$"; /* ${dollar}: a literal '$' in maps */
117
118/*
119 * GLOBALS
120 */
121static 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 */
128static 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
236static 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 */
250static 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 */
260static 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 */
278static 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 */
310static char
311backslash(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 */
390static char *
391opt(char **p)
392{
393  char *cp = *p;
394  char *dp = cp;
395  char *s = cp;
396
397top:
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 */
452static IntFuncPtr
453functable_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 */
470static int
471split_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 */
531static int
532eval_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 */
708static char *
709opt_no_scribble(char **p)
710{
711  char *cp = *p;
712  char *dp = cp;
713  char *s = cp;
714
715top:
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 */
756char *
757strip_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 */
823static int
824f_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 */
842static int
843f_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) */
893static int
894f_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) */
914static int
915f_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 */
935static int
936f_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 */
948static int
949f_false(char *arg)
950{
951  return (0);
952}
953
954
955/* always true */
956static int
957f_true(char *arg)
958{
959  return (1);
960}
961
962
963/*
964 * Free an option
965 */
966static void
967free_op(opt_apply *p, int b)
968{
969  XFREE(*p->opt);
970}
971
972
973/*
974 * Normalize slashes in the string.
975 */
976void
977normalize_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 */
1026static char *
1027expand_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
1292out:
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 */
1329static void
1330expand_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 */
1350static void
1351apply_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 */
1363void
1364free_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
1377am_opts *
1378copy_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 */
1420char *
1421expand_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 */
1430static inline char *
1431expand_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 */
1443void
1444deslashify(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
1458int
1459eval_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