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