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