]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/amd/amd/opts.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / amd / amd / opts.c
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  */
66 typedef int (*IntFuncPtr) (char *);
67 typedef struct opt_apply opt_apply;
68 enum vs_opt { SelEQ, SelNE, VarAss };
69
70 /*
71  * STRUCTURES
72  */
73 struct 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
82 struct opt_apply {
83   char **opt;
84   char *val;
85 };
86
87 struct functable {
88   char *name;
89   IntFuncPtr func;
90 };
91
92 /*
93  * FORWARD DEFINITION:
94  */
95 static int f_in_network(char *);
96 static int f_xhost(char *);
97 static int f_netgrp(char *);
98 static int f_netgrpd(char *);
99 static int f_exists(char *);
100 static int f_false(char *);
101 static int f_true(char *);
102 static inline char *expand_options(char *key);
103
104 /*
105  * STATICS:
106  */
107 static char NullStr[] = "<NULL>";
108 static char nullstr[] = "";
109 static char *opt_dkey = NullStr;
110 static char *opt_host = nullstr; /* XXX: was the global hostname */
111 static char *opt_hostd = hostd;
112 static char *opt_key = nullstr;
113 static char *opt_keyd = nullstr;
114 static char *opt_map = nullstr;
115 static char *opt_path = nullstr;
116 char uid_str[SIZEOF_UID_STR], gid_str[SIZEOF_GID_STR];
117 char *opt_uid = uid_str;
118 char *opt_gid = gid_str;
119 static char *vars[8];
120 static char *literal_dollar = "$"; /* ${dollar}: a literal '$' in maps */
121
122 /*
123  * GLOBALS
124  */
125 static 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  */
132 static 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
240 static 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  */
254 static 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  */
264 static 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  */
282 static 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  */
314 static char
315 backslash(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  */
394 static char *
395 opt(char **p)
396 {
397   char *cp = *p;
398   char *dp = cp;
399   char *s = cp;
400
401 top:
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  */
456 static IntFuncPtr
457 functable_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  */
474 static int
475 split_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  */
535 static int
536 eval_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  */
710 static char *
711 opt_no_scribble(char **p)
712 {
713   char *cp = *p;
714   char *dp = cp;
715   char *s = cp;
716
717 top:
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  */
758 char *
759 strip_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 */
825 static int
826 f_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  */
844 static int
845 f_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) */
895 static int
896 f_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) */
916 static int
917 f_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 */
937 static int
938 f_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 */
950 static int
951 f_false(char *arg)
952 {
953   return (0);
954 }
955
956
957 /* always true */
958 static int
959 f_true(char *arg)
960 {
961   return (1);
962 }
963
964
965 /*
966  * Free an option
967  */
968 static void
969 free_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  */
980 void
981 normalize_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  */
1030 static char *
1031 expand_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
1290 out:
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  */
1327 static void
1328 expand_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  */
1348 static void
1349 apply_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  */
1361 void
1362 free_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  */
1379 char *
1380 expand_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  */
1389 static inline char *
1390 expand_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  */
1402 void
1403 deslashify(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
1417 int
1418 eval_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 }