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