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