1/*- 2 * Copyright (c) 1989 Jan-Simon Pendry 3 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine 4 * Copyright (c) 1989, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Jan-Simon Pendry at Imperial College, London. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35#include "am.h" 36 37/* 38 * static copy of the options with 39 * which to play 40 */ 41static struct am_opts fs_static; 42 43static char *opt_host = hostname; 44static char *opt_hostd = hostd; 45static char nullstr[] = ""; 46static char *opt_key = nullstr; 47static char *opt_map = nullstr; 48static char *opt_path = nullstr; 49 50static char *vars[8]; 51 52/* 53 * Length of longest option name 54 */ 55#define NLEN 16 /* conservative */ 56#define S(x) (x) , (sizeof(x)-1) 57static struct opt { 58 char *name; /* Name of the option */ 59 int nlen; /* Length of option name */ 60 char **optp; /* Pointer to option value string */ 61 char **sel_p; /* Pointer to selector value string */ 62} opt_fields[] = { 63 /* Options in something corresponding to frequency of use */ 64 { S("opts"), &fs_static.opt_opts, 0 }, 65 { S("host"), 0, &opt_host }, 66 { S("hostd"), 0, &opt_hostd }, 67 { S("type"), &fs_static.opt_type, 0 }, 68 { S("rhost"), &fs_static.opt_rhost, 0 }, 69 { S("rfs"), &fs_static.opt_rfs, 0 }, 70 { S("fs"), &fs_static.opt_fs, 0 }, 71 { S("key"), 0, &opt_key }, 72 { S("map"), 0, &opt_map }, 73 { S("sublink"), &fs_static.opt_sublink, 0 }, 74 { S("arch"), 0, &arch }, 75 { S("dev"), &fs_static.opt_dev, 0 }, 76 { S("pref"), &fs_static.opt_pref, 0 }, 77 { S("path"), 0, &opt_path }, 78 { S("autodir"), 0, &auto_dir }, 79 { S("delay"), &fs_static.opt_delay, 0 }, 80 { S("domain"), 0, &hostdomain }, 81 { S("karch"), 0, &karch }, 82 { S("cluster"), 0, &cluster }, 83 { S("wire"), 0, &wire }, 84 { S("byte"), 0, &endian }, 85 { S("os"), 0, &op_sys }, 86 { S("remopts"), &fs_static.opt_remopts, 0 }, 87 { S("mount"), &fs_static.opt_mount, 0 }, 88 { S("unmount"), &fs_static.opt_unmount, 0 }, 89 { S("cache"), &fs_static.opt_cache, 0 }, 90 { S("user"), &fs_static.opt_user, 0 }, 91 { S("group"), &fs_static.opt_group, 0 }, 92 { S("var0"), &vars[0], 0 }, 93 { S("var1"), &vars[1], 0 }, 94 { S("var2"), &vars[2], 0 }, 95 { S("var3"), &vars[3], 0 }, 96 { S("var4"), &vars[4], 0 }, 97 { S("var5"), &vars[5], 0 }, 98 { S("var6"), &vars[6], 0 }, 99 { S("var7"), &vars[7], 0 }, 100 { 0, 0, 0, 0 }, 101}; 102 103typedef struct opt_apply opt_apply; 104struct opt_apply { 105 char **opt; 106 char *val; 107}; 108 109/* 110 * Specially expand the remote host name first 111 */ 112static opt_apply rhost_expansion[] = { 113 { &fs_static.opt_rhost, "${host}" }, 114 { 0, 0 }, 115}; 116/* 117 * List of options which need to be expanded 118 * Note that this the order here _may_ be important. 119 */ 120static opt_apply expansions[] = { 121/* { &fs_static.opt_dir, 0 }, */ 122 { &fs_static.opt_sublink, 0 }, 123 { &fs_static.opt_rfs, "${path}" }, 124 { &fs_static.opt_fs, "${autodir}/${rhost}${rfs}" }, 125 { &fs_static.opt_opts, "rw" }, 126 { &fs_static.opt_remopts, "${opts}" }, 127 { &fs_static.opt_mount, 0 }, 128 { &fs_static.opt_unmount, 0 }, 129 { 0, 0 }, 130}; 131 132/* 133 * List of options which need to be free'ed before re-use 134 */ 135static opt_apply to_free[] = { 136 { &fs_static.fs_glob, 0 }, 137 { &fs_static.fs_local, 0 }, 138 { &fs_static.fs_mtab, 0 }, 139/* { &fs_static.opt_dir, 0 }, */ 140 { &fs_static.opt_sublink, 0 }, 141 { &fs_static.opt_rfs, 0 }, 142 { &fs_static.opt_fs, 0 }, 143 { &fs_static.opt_rhost, 0 }, 144 { &fs_static.opt_opts, 0 }, 145 { &fs_static.opt_remopts, 0 }, 146 { &fs_static.opt_mount, 0 }, 147 { &fs_static.opt_unmount, 0 }, 148 { &vars[0], 0 }, 149 { &vars[1], 0 }, 150 { &vars[2], 0 }, 151 { &vars[3], 0 }, 152 { &vars[4], 0 }, 153 { &vars[5], 0 }, 154 { &vars[6], 0 }, 155 { &vars[7], 0 }, 156 { 0, 0 }, 157}; 158 159/* 160 * Skip to next option in the string 161 */ 162static char * 163opt(char **p) 164{ 165 char *cp = *p; 166 char *dp = cp; 167 char *s = cp; 168 169top: 170 while (*cp && *cp != ';') { 171 if (*cp == '\"') { 172 /* 173 * Skip past string 174 */ 175 cp++; 176 while (*cp && *cp != '\"') 177 *dp++ = *cp++; 178 if (*cp) 179 cp++; 180 } else { 181 *dp++ = *cp++; 182 } 183 } 184 185 /* 186 * Skip past any remaining ';'s 187 */ 188 while (*cp == ';') 189 cp++; 190 191 /* 192 * If we have a zero length string 193 * and there are more fields, then 194 * parse the next one. This allows 195 * sequences of empty fields. 196 */ 197 if (*cp && dp == s) 198 goto top; 199 200 *dp = '\0'; 201 202 *p = cp; 203 return s; 204} 205 206static int 207eval_opts(char *opts, char *mapkey) 208{ 209 /* 210 * Fill in the global structure fs_static by 211 * cracking the string opts. opts may be 212 * scribbled on at will. 213 */ 214 char *o = opts; 215 char *f; 216 217 /* 218 * For each user-specified option 219 */ 220 while (*(f = opt(&o))) { 221 struct opt *op; 222 enum vs_opt { OldSyn, SelEQ, SelNE, VarAss } vs_opt; 223 char *eq = strchr(f, '='); 224 char *opt; 225 if (!eq || eq[1] == '\0' || eq == f) { 226 /* 227 * No value, just continue 228 */ 229 plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f); 230 continue; 231 } 232 233 /* 234 * Check what type of operation is happening 235 * !=, =! is SelNE 236 * == is SelEQ 237 * := is VarAss 238 * = is OldSyn (either SelEQ or VarAss) 239 */ 240 if (eq[-1] == '!') { /* != */ 241 vs_opt = SelNE; 242 eq[-1] = '\0'; 243 opt = eq + 1; 244 } else if (eq[-1] == ':') { /* := */ 245 vs_opt = VarAss; 246 eq[-1] = '\0'; 247 opt = eq + 1; 248 } else if (eq[1] == '=') { /* == */ 249 vs_opt = SelEQ; 250 eq[0] = '\0'; 251 opt = eq + 2; 252 } else if (eq[1] == '!') { /* =! */ 253 vs_opt = SelNE; 254 eq[0] = '\0'; 255 opt = eq + 2; 256 } else { /* = */ 257 vs_opt = OldSyn; 258 eq[0] = '\0'; 259 opt = eq + 1; 260 } 261 262 /* 263 * For each recognised option 264 */ 265 for (op = opt_fields; op->name; op++) { 266 /* 267 * Check whether they match 268 */ 269 if (FSTREQ(op->name, f)) { 270 switch (vs_opt) { 271#if 1 /* XXX ancient compat */ 272 case OldSyn: 273 plog(XLOG_WARNING, "key %s: Old syntax selector found: %s=%s", mapkey, f, opt); 274 if (!op->sel_p) { 275 *op->optp = opt; 276 break; 277 } 278 /* fall through ... */ 279#endif 280 case SelEQ: 281 case SelNE: 282 if (op->sel_p && (STREQ(*op->sel_p, opt) == (vs_opt == SelNE))) { 283 plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s", 284 mapkey, 285 op->name, 286 *op->sel_p, 287 vs_opt == SelNE ? "not " : "", 288 opt); 289 return 0; 290 } 291 break; 292 293 case VarAss: 294 if (op->sel_p) { 295 plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", mapkey, op->name); 296 return 0; 297 } 298 *op->optp = opt; 299 break; 300 } 301 break; 302 } 303 } 304 305 if (!op->name) 306 plog(XLOG_USER, "key %s: Unrecognised key/option \"%s\"", mapkey, f); 307 } 308 309 return 1; 310} 311 312/* 313 * Free an option 314 */ 315static void 316free_op(opt_apply *p, int b) 317{ 318 if (*p->opt) { 319 free(*p->opt); 320 *p->opt = 0; 321 } 322} 323 324/* 325 * Normalize slashes in the string. 326 */ 327void 328normalize_slash(char *p) 329{ 330 char *f = strchr(p, '/'); 331 char *f0 = f; 332 if (f) { 333 char *t = f; 334 do { 335 /* assert(*f == '/'); */ 336 if (f == f0 && f[0] == '/' && f[1] == '/') { 337 /* copy double slash iff first */ 338 *t++ = *f++; 339 *t++ = *f++; 340 } else { 341 /* copy a single / across */ 342 *t++ = *f++; 343 } 344 345 /* assert(f[-1] == '/'); */ 346 /* skip past more /'s */ 347 while (*f == '/') 348 f++; 349 350 /* assert(*f != '/'); */ 351 /* keep copying up to next / */ 352 while (*f && *f != '/') { 353 *t++ = *f++; 354 } 355 356 /* assert(*f == 0 || *f == '/'); */ 357 358 } while (*f); 359 *t = 0; /* derived from fix by Steven Glassman */ 360 } 361} 362 363/* 364 * Macro-expand an option. Note that this does not 365 * handle recursive expansions. They will go badly wrong. 366 * If sel is true then old expand selectors, otherwise 367 * don't expand selectors. 368 */ 369static void 370expand_op(opt_apply *p, int sel_p) 371{ 372/* 373 * The BUFSPACE macros checks that there is enough space 374 * left in the expansion buffer. If there isn't then we 375 * give up completely. This is done to avoid crashing the 376 * automounter itself (which would be a bad thing to do). 377 */ 378#define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+PATH_MAX) 379static char expand_error[] = "No space to expand \"%s\""; 380 381 char expbuf[PATH_MAX+1]; 382 char nbuf[NLEN+1]; 383 char *ep = expbuf; 384 char *cp = *p->opt; 385 char *dp; 386#ifdef DEBUG 387 char *cp_orig = *p->opt; 388#endif /* DEBUG */ 389 struct opt *op; 390 391 while ((dp = strchr(cp, '$'))) { 392 char ch; 393 /* 394 * First copy up to the $ 395 */ 396 { int len = dp - cp; 397 if (BUFSPACE(ep, len)) { 398 strncpy(ep, cp, len); 399 ep += len; 400 } else { 401 plog(XLOG_ERROR, expand_error, *p->opt); 402 goto out; 403 } 404 } 405 cp = dp + 1; 406 ch = *cp++; 407 if (ch == '$') { 408 if (BUFSPACE(ep, 1)) { 409 *ep++ = '$'; 410 } else { 411 plog(XLOG_ERROR, expand_error, *p->opt); 412 goto out; 413 } 414 } else if (ch == '{') { 415 /* Expansion... */ 416 enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo; 417 /* 418 * Find closing brace 419 */ 420 char *br_p = strchr(cp, '}'); 421 int len; 422 /* 423 * Check we found it 424 */ 425 if (!br_p) { 426 /* 427 * Just give up 428 */ 429 plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt); 430 goto out; 431 } 432 len = br_p - cp; 433 /* 434 * Figure out which part of the variable to grab. 435 */ 436 if (*cp == '/') { 437 /* 438 * Just take the last component 439 */ 440 todo = E_File; 441 cp++; 442 --len; 443 } else if (br_p[-1] == '/') { 444 /* 445 * Take all but the last component 446 */ 447 todo = E_Dir; 448 --len; 449 } else if (*cp == '.') { 450 /* 451 * Take domain name 452 */ 453 todo = E_Domain; 454 cp++; 455 --len; 456 } else if (br_p[-1] == '.') { 457 /* 458 * Take host name 459 */ 460 todo = E_Host; 461 --len; 462 } else { 463 /* 464 * Take the whole lot 465 */ 466 todo = E_All; 467 } 468 /* 469 * Truncate if too long. Since it won't 470 * match anyway it doesn't matter that 471 * it has been cut short. 472 */ 473 if (len > NLEN) 474 len = NLEN; 475 /* 476 * Put the string into another buffer so 477 * we can do comparisons. 478 */ 479 strncpy(nbuf, cp, len); 480 nbuf[len] = '\0'; 481 /* 482 * Advance cp 483 */ 484 cp = br_p + 1; 485 /* 486 * Search the option array 487 */ 488 for (op = opt_fields; op->name; op++) { 489 /* 490 * Check for match 491 */ 492 if (len == op->nlen && STREQ(op->name, nbuf)) { 493 char xbuf[NLEN+3]; 494 char *val; 495 /* 496 * Found expansion. Copy 497 * the correct value field. 498 */ 499 if (!(!op->sel_p == !sel_p)) { 500 /* 501 * Copy the string across unexpanded 502 */ 503 snprintf(xbuf, sizeof(xbuf), "${%s%s%s}", 504 todo == E_File ? "/" : 505 todo == E_Domain ? "." : "", 506 nbuf, 507 todo == E_Dir ? "/" : 508 todo == E_Host ? "." : ""); 509 val = xbuf; 510 /* 511 * Make sure expansion doesn't 512 * munge the value! 513 */ 514 todo = E_All; 515 } else if (op->sel_p) { 516 val = *op->sel_p; 517 } else { 518 val = *op->optp; 519 } 520 if (val) { 521 /* 522 * Do expansion: 523 * ${/var} means take just the last part 524 * ${var/} means take all but the last part 525 * ${.var} means take all but first part 526 * ${var.} means take just the first part 527 * ${var} means take the whole lot 528 */ 529 int vlen = strlen(val); 530 char *vptr = val; 531 switch (todo) { 532 case E_Dir: 533 vptr = strrchr(val, '/'); 534 if (vptr) 535 vlen = vptr - val; 536 vptr = val; 537 break; 538 case E_File: 539 vptr = strrchr(val, '/'); 540 if (vptr) { 541 vptr++; 542 vlen = strlen(vptr); 543 } else 544 vptr = val; 545 break; 546 case E_Domain: 547 vptr = strchr(val, '.'); 548 if (vptr) { 549 vptr++; 550 vlen = strlen(vptr); 551 } else { 552 vptr = ""; 553 vlen = 0; 554 } 555 break; 556 case E_Host: 557 vptr = strchr(val, '.'); 558 if (vptr) 559 vlen = vptr - val; 560 vptr = val; 561 break; 562 case E_All: 563 break; 564 } 565#ifdef DEBUG 566 /*dlog("Expanding \"%s\" to \"%s\"", nbuf, val);*/ 567#endif /* DEBUG */ 568 if (BUFSPACE(ep, vlen)) { 569 strlcpy(ep, vptr, expbuf + sizeof expbuf - ep); 570 ep += strlen(ep); 571 } else { 572 plog(XLOG_ERROR, expand_error, *p->opt); 573 goto out; 574 } 575 } 576 /* 577 * Done with this variable 578 */ 579 break; 580 } 581 } 582 /* 583 * Check that the search was successful 584 */ 585 if (!op->name) { 586 /* 587 * If it wasn't then scan the 588 * environment for that name 589 * and use any value found 590 */ 591 char *env = getenv(nbuf); 592 if (env) { 593 int vlen = strlen(env); 594 595 if (BUFSPACE(ep, vlen)) { 596 strlcpy(ep, env, expbuf + sizeof expbuf - ep); 597 ep += strlen(ep); 598 } else { 599 plog(XLOG_ERROR, expand_error, *p->opt); 600 goto out; 601 } 602#ifdef DEBUG 603 Debug(D_STR) 604 plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env); 605#endif /* DEBUG */ 606 } else { 607 plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf); 608 } 609 } 610 } else { 611 /* 612 * Error, error 613 */ 614 plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt); 615 } 616 } 617 618out: 619 /* 620 * Handle common case - no expansion 621 */ 622 if (cp == *p->opt) { 623 *p->opt = strdup(cp); 624 } else { 625 /* 626 * Finish off the expansion 627 */ 628 if (BUFSPACE(ep, strlen(cp))) { 629 strlcpy(ep, cp, expbuf + sizeof expbuf - ep); 630 } else { 631 plog(XLOG_ERROR, expand_error, *p->opt); 632 } 633 634 /* 635 * Save the exansion 636 */ 637 *p->opt = strdup(expbuf); 638 } 639 640 normalize_slash(*p->opt); 641 642#ifdef DEBUG 643 Debug(D_STR) { 644 plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig); 645 plog(XLOG_DEBUG, "... is \"%s\"", *p->opt); 646 } 647#endif /* DEBUG */ 648} 649 650/* 651 * Wrapper for expand_op 652 */ 653static void 654expand_opts(opt_apply *p, int sel_p) 655{ 656 if (*p->opt) { 657 expand_op(p, sel_p); 658 } else if (p->val) { 659 /* 660 * Do double expansion, remembering 661 * to free the string from the first 662 * expansion... 663 */ 664 char *s = *p->opt = expand_key(p->val); 665 expand_op(p, sel_p); 666 free(s); 667 } 668} 669 670/* 671 * Apply a function to a list of options 672 */ 673static void 674apply_opts(void (*op)(), opt_apply ppp[], int b) 675{ 676 opt_apply *pp; 677 for (pp = ppp; pp->opt; pp++) 678 (*op)(pp, b); 679} 680 681/* 682 * Free the option table 683 */ 684void 685free_opts(am_opts *fo) 686{ 687 /* 688 * Copy in the structure we are playing with 689 */ 690 fs_static = *fo; 691 692 /* 693 * Free previously allocated memory 694 */ 695 apply_opts(free_op, to_free, FALSE); 696} 697 698/* 699 * Expand lookup key 700 */ 701char * 702expand_key(char *key) 703{ 704 opt_apply oa; 705 706 oa.opt = &key; oa.val = 0; 707 expand_opts(&oa, TRUE); 708 709 return key; 710} 711 712/* 713 * Remove trailing /'s from a string 714 * unless the string is a single / (Steven Glassman) 715 */ 716void 717deslashify(char *s) 718{ 719 if (s && *s) { 720 char *sl = s + strlen(s); 721 while (*--sl == '/' && sl > s) 722 *sl = '\0'; 723 } 724} 725 726int 727eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path, 728 char *key, char *map) 729{ 730 int ok = TRUE; 731 732 free_opts(fo); 733 734 /* 735 * Clear out the option table 736 */ 737 bzero(&fs_static, sizeof(fs_static)); 738 bzero(vars, sizeof(vars)); 739 bzero(fo, sizeof(*fo)); 740 741 /* 742 * Set key, map & path before expansion 743 */ 744 opt_key = key; 745 opt_map = map; 746 opt_path = path; 747 748 /* 749 * Expand global options 750 */ 751 fs_static.fs_glob = expand_key(g_opts); 752 753 /* 754 * Expand local options 755 */ 756 fs_static.fs_local = expand_key(opts); 757 758 /* 759 * Expand default (global) options 760 */ 761 if (!eval_opts(fs_static.fs_glob, key)) 762 ok = FALSE; 763 764 /* 765 * Expand local options 766 */ 767 if (ok && !eval_opts(fs_static.fs_local, key)) 768 ok = FALSE; 769 770 /* 771 * Normalise remote host name. 772 * 1. Expand variables 773 * 2. Normalize relative to host tables 774 * 3. Strip local domains from the remote host 775 * name before using it in other expansions. 776 * This makes mount point names and other things 777 * much shorter, while allowing cross domain 778 * sharing of mount maps. 779 */ 780 apply_opts(expand_opts, rhost_expansion, FALSE); 781 if (ok && fs_static.opt_rhost && *fs_static.opt_rhost) 782 host_normalize(&fs_static.opt_rhost); 783 784 /* 785 * Macro expand the options. 786 * Do this regardless of whether we are accepting 787 * this mount - otherwise nasty things happen 788 * with memory allocation. 789 */ 790 apply_opts(expand_opts, expansions, FALSE); 791 792 /* 793 * Strip trailing slashes from local pathname... 794 */ 795 deslashify(fs_static.opt_fs); 796 797 /* 798 * ok... copy the data back out. 799 */ 800 *fo = fs_static; 801 802 /* 803 * Clear defined options 804 */ 805 opt_key = opt_map = opt_path = nullstr; 806 807 return ok; 808} 809