1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. 24 * Copyright (c) 2011 Gunnar Beutner 25 * Copyright (c) 2012 Cyril Plisko. All rights reserved. 26 * Copyright (c) 2019, 2020 by Delphix. All rights reserved. 27 */ 28 29 #include <dirent.h> 30 #include <stdio.h> 31 #include <string.h> 32 #include <strings.h> 33 #include <errno.h> 34 #include <sys/file.h> 35 #include <sys/stat.h> 36 #include <sys/types.h> 37 #include <sys/wait.h> 38 #include <unistd.h> 39 #include <libzfs.h> 40 #include <libshare.h> 41 #include "libshare_impl.h" 42 #include "nfs.h" 43 44 #define FILE_HEADER "# !!! DO NOT EDIT THIS FILE MANUALLY !!!\n\n" 45 #define ZFS_EXPORTS_DIR "/etc/exports.d" 46 #define ZFS_EXPORTS_FILE ZFS_EXPORTS_DIR"/zfs.exports" 47 #define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock" 48 49 static sa_fstype_t *nfs_fstype; 50 51 typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value, 52 void *cookie); 53 54 typedef int (*nfs_host_callback_t)(const char *sharepath, const char *filename, 55 const char *host, const char *security, const char *access, void *cookie); 56 57 static int nfs_lock_fd = -1; 58 59 /* 60 * The nfs_exports_[lock|unlock] is used to guard against conconcurrent 61 * updates to the exports file. Each protocol is responsible for 62 * providing the necessary locking to ensure consistency. 63 */ 64 static int 65 nfs_exports_lock(void) 66 { 67 nfs_lock_fd = open(ZFS_EXPORTS_LOCK, 68 O_RDWR | O_CREAT, 0600); 69 if (nfs_lock_fd == -1) { 70 fprintf(stderr, "failed to lock %s: %s\n", 71 ZFS_EXPORTS_LOCK, strerror(errno)); 72 return (errno); 73 } 74 if (flock(nfs_lock_fd, LOCK_EX) != 0) { 75 fprintf(stderr, "failed to lock %s: %s\n", 76 ZFS_EXPORTS_LOCK, strerror(errno)); 77 return (errno); 78 } 79 return (0); 80 } 81 82 static void 83 nfs_exports_unlock(void) 84 { 85 verify(nfs_lock_fd > 0); 86 87 if (flock(nfs_lock_fd, LOCK_UN) != 0) { 88 fprintf(stderr, "failed to unlock %s: %s\n", 89 ZFS_EXPORTS_LOCK, strerror(errno)); 90 } 91 close(nfs_lock_fd); 92 nfs_lock_fd = -1; 93 } 94 95 /* 96 * Invokes the specified callback function for each Solaris share option 97 * listed in the specified string. 98 */ 99 static int 100 foreach_nfs_shareopt(const char *shareopts, 101 nfs_shareopt_callback_t callback, void *cookie) 102 { 103 char *shareopts_dup, *opt, *cur, *value; 104 int was_nul, error; 105 106 if (shareopts == NULL) 107 return (SA_OK); 108 109 if (strcmp(shareopts, "on") == 0) 110 shareopts = "rw,crossmnt"; 111 112 shareopts_dup = strdup(shareopts); 113 114 115 if (shareopts_dup == NULL) 116 return (SA_NO_MEMORY); 117 118 opt = shareopts_dup; 119 was_nul = 0; 120 121 while (1) { 122 cur = opt; 123 124 while (*cur != ',' && *cur != '\0') 125 cur++; 126 127 if (*cur == '\0') 128 was_nul = 1; 129 130 *cur = '\0'; 131 132 if (cur > opt) { 133 value = strchr(opt, '='); 134 135 if (value != NULL) { 136 *value = '\0'; 137 value++; 138 } 139 140 error = callback(opt, value, cookie); 141 142 if (error != SA_OK) { 143 free(shareopts_dup); 144 return (error); 145 } 146 } 147 148 opt = cur + 1; 149 150 if (was_nul) 151 break; 152 } 153 154 free(shareopts_dup); 155 156 return (SA_OK); 157 } 158 159 typedef struct nfs_host_cookie_s { 160 nfs_host_callback_t callback; 161 const char *sharepath; 162 void *cookie; 163 const char *filename; 164 const char *security; 165 } nfs_host_cookie_t; 166 167 /* 168 * Helper function for foreach_nfs_host. This function checks whether the 169 * current share option is a host specification and invokes a callback 170 * function with information about the host. 171 */ 172 static int 173 foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie) 174 { 175 int error; 176 const char *access; 177 char *host_dup, *host, *next; 178 nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie; 179 180 #ifdef DEBUG 181 fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value); 182 #endif 183 184 if (strcmp(opt, "sec") == 0) 185 udata->security = value; 186 187 if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) { 188 if (value == NULL) 189 value = "*"; 190 191 access = opt; 192 193 host_dup = strdup(value); 194 195 if (host_dup == NULL) 196 return (SA_NO_MEMORY); 197 198 host = host_dup; 199 200 do { 201 next = strchr(host, ':'); 202 if (next != NULL) { 203 *next = '\0'; 204 next++; 205 } 206 207 error = udata->callback(udata->filename, 208 udata->sharepath, host, udata->security, 209 access, udata->cookie); 210 211 if (error != SA_OK) { 212 free(host_dup); 213 214 return (error); 215 } 216 217 host = next; 218 } while (host != NULL); 219 220 free(host_dup); 221 } 222 223 return (SA_OK); 224 } 225 226 /* 227 * Invokes a callback function for all NFS hosts that are set for a share. 228 */ 229 static int 230 foreach_nfs_host(sa_share_impl_t impl_share, char *filename, 231 nfs_host_callback_t callback, void *cookie) 232 { 233 nfs_host_cookie_t udata; 234 char *shareopts; 235 236 udata.callback = callback; 237 udata.sharepath = impl_share->sa_mountpoint; 238 udata.cookie = cookie; 239 udata.filename = filename; 240 udata.security = "sys"; 241 242 shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; 243 244 return (foreach_nfs_shareopt(shareopts, foreach_nfs_host_cb, 245 &udata)); 246 } 247 248 /* 249 * Converts a Solaris NFS host specification to its Linux equivalent. 250 */ 251 static int 252 get_linux_hostspec(const char *solaris_hostspec, char **plinux_hostspec) 253 { 254 /* 255 * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host 256 * wildcards (e.g. *.example.org). 257 */ 258 if (solaris_hostspec[0] == '@') { 259 /* 260 * Solaris host specifier, e.g. @192.168.0.0/16; we just need 261 * to skip the @ in this case 262 */ 263 *plinux_hostspec = strdup(solaris_hostspec + 1); 264 } else { 265 *plinux_hostspec = strdup(solaris_hostspec); 266 } 267 268 if (*plinux_hostspec == NULL) { 269 return (SA_NO_MEMORY); 270 } 271 272 return (SA_OK); 273 } 274 275 /* 276 * Adds a Linux share option to an array of NFS options. 277 */ 278 static int 279 add_linux_shareopt(char **plinux_opts, const char *key, const char *value) 280 { 281 size_t len = 0; 282 char *new_linux_opts; 283 284 if (*plinux_opts != NULL) 285 len = strlen(*plinux_opts); 286 287 new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) + 288 (value ? 1 + strlen(value) : 0) + 1); 289 290 if (new_linux_opts == NULL) 291 return (SA_NO_MEMORY); 292 293 new_linux_opts[len] = '\0'; 294 295 if (len > 0) 296 strcat(new_linux_opts, ","); 297 298 strcat(new_linux_opts, key); 299 300 if (value != NULL) { 301 strcat(new_linux_opts, "="); 302 strcat(new_linux_opts, value); 303 } 304 305 *plinux_opts = new_linux_opts; 306 307 return (SA_OK); 308 } 309 310 /* 311 * Validates and converts a single Solaris share option to its Linux 312 * equivalent. 313 */ 314 static int 315 get_linux_shareopts_cb(const char *key, const char *value, void *cookie) 316 { 317 char **plinux_opts = (char **)cookie; 318 319 /* host-specific options, these are taken care of elsewhere */ 320 if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0 || 321 strcmp(key, "sec") == 0) 322 return (SA_OK); 323 324 if (strcmp(key, "anon") == 0) 325 key = "anonuid"; 326 327 if (strcmp(key, "root_mapping") == 0) { 328 (void) add_linux_shareopt(plinux_opts, "root_squash", NULL); 329 key = "anonuid"; 330 } 331 332 if (strcmp(key, "nosub") == 0) 333 key = "subtree_check"; 334 335 if (strcmp(key, "insecure") != 0 && strcmp(key, "secure") != 0 && 336 strcmp(key, "async") != 0 && strcmp(key, "sync") != 0 && 337 strcmp(key, "no_wdelay") != 0 && strcmp(key, "wdelay") != 0 && 338 strcmp(key, "nohide") != 0 && strcmp(key, "hide") != 0 && 339 strcmp(key, "crossmnt") != 0 && 340 strcmp(key, "no_subtree_check") != 0 && 341 strcmp(key, "subtree_check") != 0 && 342 strcmp(key, "insecure_locks") != 0 && 343 strcmp(key, "secure_locks") != 0 && 344 strcmp(key, "no_auth_nlm") != 0 && strcmp(key, "auth_nlm") != 0 && 345 strcmp(key, "no_acl") != 0 && strcmp(key, "mountpoint") != 0 && 346 strcmp(key, "mp") != 0 && strcmp(key, "fsuid") != 0 && 347 strcmp(key, "refer") != 0 && strcmp(key, "replicas") != 0 && 348 strcmp(key, "root_squash") != 0 && 349 strcmp(key, "no_root_squash") != 0 && 350 strcmp(key, "all_squash") != 0 && 351 strcmp(key, "no_all_squash") != 0 && strcmp(key, "fsid") != 0 && 352 strcmp(key, "anonuid") != 0 && strcmp(key, "anongid") != 0) { 353 return (SA_SYNTAX_ERR); 354 } 355 356 (void) add_linux_shareopt(plinux_opts, key, value); 357 358 return (SA_OK); 359 } 360 361 /* 362 * Takes a string containing Solaris share options (e.g. "sync,no_acl") and 363 * converts them to a NULL-terminated array of Linux NFS options. 364 */ 365 static int 366 get_linux_shareopts(const char *shareopts, char **plinux_opts) 367 { 368 int error; 369 370 assert(plinux_opts != NULL); 371 372 *plinux_opts = NULL; 373 374 /* no_subtree_check - Default as of nfs-utils v1.1.0 */ 375 (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL); 376 377 /* mountpoint - Restrict exports to ZFS mountpoints */ 378 (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL); 379 380 error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, 381 plinux_opts); 382 383 if (error != SA_OK) { 384 free(*plinux_opts); 385 *plinux_opts = NULL; 386 } 387 388 return (error); 389 } 390 391 static char * 392 nfs_init_tmpfile(void) 393 { 394 char *tmpfile = NULL; 395 396 if (asprintf(&tmpfile, "%s%s", ZFS_EXPORTS_FILE, ".XXXXXXXX") == -1) { 397 fprintf(stderr, "Unable to allocate temporary file\n"); 398 return (NULL); 399 } 400 401 int fd = mkstemp(tmpfile); 402 if (fd == -1) { 403 fprintf(stderr, "Unable to create temporary file: %s", 404 strerror(errno)); 405 free(tmpfile); 406 return (NULL); 407 } 408 close(fd); 409 return (tmpfile); 410 } 411 412 static int 413 nfs_fini_tmpfile(char *tmpfile) 414 { 415 if (rename(tmpfile, ZFS_EXPORTS_FILE) == -1) { 416 fprintf(stderr, "Unable to rename %s: %s\n", tmpfile, 417 strerror(errno)); 418 unlink(tmpfile); 419 free(tmpfile); 420 return (SA_SYSTEM_ERR); 421 } 422 free(tmpfile); 423 return (SA_OK); 424 } 425 426 /* 427 * This function populates an entry into /etc/exports.d/zfs.exports. 428 * This file is consumed by the linux nfs server so that zfs shares are 429 * automatically exported upon boot or whenever the nfs server restarts. 430 */ 431 static int 432 nfs_add_entry(const char *filename, const char *sharepath, 433 const char *host, const char *security, const char *access_opts, 434 void *pcookie) 435 { 436 int error; 437 char *linuxhost; 438 const char *linux_opts = (const char *)pcookie; 439 440 error = get_linux_hostspec(host, &linuxhost); 441 if (error != SA_OK) 442 return (error); 443 444 if (linux_opts == NULL) 445 linux_opts = ""; 446 447 FILE *fp = fopen(filename, "a+"); 448 if (fp == NULL) { 449 fprintf(stderr, "failed to open %s file: %s", filename, 450 strerror(errno)); 451 free(linuxhost); 452 return (SA_SYSTEM_ERR); 453 } 454 455 if (fprintf(fp, "%s %s(sec=%s,%s,%s)\n", sharepath, linuxhost, 456 security, access_opts, linux_opts) < 0) { 457 fprintf(stderr, "failed to write to %s\n", filename); 458 free(linuxhost); 459 fclose(fp); 460 return (SA_SYSTEM_ERR); 461 } 462 463 free(linuxhost); 464 if (fclose(fp) != 0) { 465 fprintf(stderr, "Unable to close file %s: %s\n", 466 filename, strerror(errno)); 467 return (SA_SYSTEM_ERR); 468 } 469 return (SA_OK); 470 } 471 472 /* 473 * This function copies all entries from the exports file to "filename", 474 * omitting any entries for the specified mountpoint. 475 */ 476 static int 477 nfs_copy_entries(char *filename, const char *mountpoint) 478 { 479 char *buf = NULL; 480 size_t buflen = 0; 481 int error = SA_OK; 482 483 /* 484 * If the file doesn't exist then there is nothing more 485 * we need to do. 486 */ 487 FILE *oldfp = fopen(ZFS_EXPORTS_FILE, "r"); 488 if (oldfp == NULL) 489 return (SA_OK); 490 491 FILE *newfp = fopen(filename, "w+"); 492 fputs(FILE_HEADER, newfp); 493 while ((getline(&buf, &buflen, oldfp)) != -1) { 494 char *space = NULL; 495 496 if (buf[0] == '\n' || buf[0] == '#') 497 continue; 498 499 if ((space = strchr(buf, ' ')) != NULL) { 500 int mountpoint_len = strlen(mountpoint); 501 502 if (space - buf == mountpoint_len && 503 strncmp(mountpoint, buf, mountpoint_len) == 0) { 504 continue; 505 } 506 } 507 fputs(buf, newfp); 508 } 509 510 if (oldfp != NULL && ferror(oldfp) != 0) { 511 error = ferror(oldfp); 512 } 513 if (error == 0 && ferror(newfp) != 0) { 514 error = ferror(newfp); 515 } 516 517 free(buf); 518 if (fclose(newfp) != 0) { 519 fprintf(stderr, "Unable to close file %s: %s\n", 520 filename, strerror(errno)); 521 error = error != 0 ? error : SA_SYSTEM_ERR; 522 } 523 fclose(oldfp); 524 525 return (error); 526 } 527 528 /* 529 * Enables NFS sharing for the specified share. 530 */ 531 static int 532 nfs_enable_share(sa_share_impl_t impl_share) 533 { 534 char *shareopts, *linux_opts; 535 char *filename = NULL; 536 int error; 537 538 if ((filename = nfs_init_tmpfile()) == NULL) 539 return (SA_SYSTEM_ERR); 540 541 error = nfs_exports_lock(); 542 if (error != 0) { 543 unlink(filename); 544 free(filename); 545 return (error); 546 } 547 548 error = nfs_copy_entries(filename, impl_share->sa_mountpoint); 549 if (error != SA_OK) { 550 unlink(filename); 551 free(filename); 552 nfs_exports_unlock(); 553 return (error); 554 } 555 556 shareopts = FSINFO(impl_share, nfs_fstype)->shareopts; 557 error = get_linux_shareopts(shareopts, &linux_opts); 558 if (error != SA_OK) { 559 unlink(filename); 560 free(filename); 561 nfs_exports_unlock(); 562 return (error); 563 } 564 565 error = foreach_nfs_host(impl_share, filename, nfs_add_entry, 566 linux_opts); 567 free(linux_opts); 568 if (error == 0) { 569 error = nfs_fini_tmpfile(filename); 570 } else { 571 unlink(filename); 572 free(filename); 573 } 574 nfs_exports_unlock(); 575 return (error); 576 } 577 578 /* 579 * Disables NFS sharing for the specified share. 580 */ 581 static int 582 nfs_disable_share(sa_share_impl_t impl_share) 583 { 584 int error; 585 char *filename = NULL; 586 587 if ((filename = nfs_init_tmpfile()) == NULL) 588 return (SA_SYSTEM_ERR); 589 590 error = nfs_exports_lock(); 591 if (error != 0) { 592 unlink(filename); 593 free(filename); 594 return (error); 595 } 596 597 error = nfs_copy_entries(filename, impl_share->sa_mountpoint); 598 if (error != SA_OK) { 599 unlink(filename); 600 free(filename); 601 nfs_exports_unlock(); 602 return (error); 603 } 604 error = nfs_fini_tmpfile(filename); 605 nfs_exports_unlock(); 606 return (error); 607 } 608 609 static boolean_t 610 nfs_is_shared(sa_share_impl_t impl_share) 611 { 612 size_t buflen = 0; 613 char *buf = NULL; 614 615 FILE *fp = fopen(ZFS_EXPORTS_FILE, "r"); 616 if (fp == NULL) { 617 return (B_FALSE); 618 } 619 while ((getline(&buf, &buflen, fp)) != -1) { 620 char *space = NULL; 621 622 if ((space = strchr(buf, ' ')) != NULL) { 623 int mountpoint_len = strlen(impl_share->sa_mountpoint); 624 625 if (space - buf == mountpoint_len && 626 strncmp(impl_share->sa_mountpoint, buf, 627 mountpoint_len) == 0) { 628 fclose(fp); 629 free(buf); 630 return (B_TRUE); 631 } 632 } 633 } 634 free(buf); 635 fclose(fp); 636 return (B_FALSE); 637 } 638 639 /* 640 * Checks whether the specified NFS share options are syntactically correct. 641 */ 642 static int 643 nfs_validate_shareopts(const char *shareopts) 644 { 645 char *linux_opts; 646 int error; 647 648 error = get_linux_shareopts(shareopts, &linux_opts); 649 650 if (error != SA_OK) 651 return (error); 652 653 free(linux_opts); 654 return (SA_OK); 655 } 656 657 static int 658 nfs_update_shareopts(sa_share_impl_t impl_share, const char *shareopts) 659 { 660 FSINFO(impl_share, nfs_fstype)->shareopts = (char *)shareopts; 661 return (SA_OK); 662 } 663 664 /* 665 * Clears a share's NFS options. Used by libshare to 666 * clean up shares that are about to be free()'d. 667 */ 668 static void 669 nfs_clear_shareopts(sa_share_impl_t impl_share) 670 { 671 FSINFO(impl_share, nfs_fstype)->shareopts = NULL; 672 } 673 674 static int 675 nfs_commit_shares(void) 676 { 677 char *argv[] = { 678 "/usr/sbin/exportfs", 679 "-ra", 680 NULL 681 }; 682 683 return (libzfs_run_process(argv[0], argv, 0)); 684 } 685 686 static const sa_share_ops_t nfs_shareops = { 687 .enable_share = nfs_enable_share, 688 .disable_share = nfs_disable_share, 689 .is_shared = nfs_is_shared, 690 691 .validate_shareopts = nfs_validate_shareopts, 692 .update_shareopts = nfs_update_shareopts, 693 .clear_shareopts = nfs_clear_shareopts, 694 .commit_shares = nfs_commit_shares, 695 }; 696 697 /* 698 * Initializes the NFS functionality of libshare. 699 */ 700 void 701 libshare_nfs_init(void) 702 { 703 struct stat sb; 704 705 nfs_fstype = register_fstype("nfs", &nfs_shareops); 706 707 if (stat(ZFS_EXPORTS_DIR, &sb) < 0 && 708 mkdir(ZFS_EXPORTS_DIR, 0755) < 0) { 709 fprintf(stderr, "failed to create %s: %s\n", 710 ZFS_EXPORTS_DIR, strerror(errno)); 711 } 712 } 713