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