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
backslash(char ** p)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 *
opt(char ** p)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
functable_lookup(char * key)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
split_opts(char * opts,char * mapkey)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
eval_selectors(char * opts,char * mapkey)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 *
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 if (*p->opt) {
972 XFREE(*p->opt);
973 }
974 }
975
976
977 /*
978 * Normalize slashes in the string.
979 */
980 void
normalize_slash(char * p)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 *
expand_op(char * opt,int sel_p)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
expand_opts(opt_apply * p,int sel_p)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
apply_opts(void (* op)(opt_apply *,int),opt_apply ppp[],int b)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
free_opts(am_opts * fo)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 *
expand_selectors(char * key)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 *
expand_options(char * key)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
deslashify(char * s)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
eval_fs_opts(am_opts * fo,char * opts,char * g_opts,char * path,char * key,char * map)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 }
1520