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