1 // SPDX-License-Identifier: CDDL-1.0 2 /* 3 * CDDL HEADER START 4 * 5 * The contents of this file are subject to the terms of the 6 * Common Development and Distribution License (the "License"). 7 * You may not use this file except in compliance with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or https://opensource.org/licenses/CDDL-1.0. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. 25 * Copyright (c) 2011 Gunnar Beutner 26 * Copyright (c) 2012 Cyril Plisko. All rights reserved. 27 * Copyright (c) 2019, 2022 by Delphix. All rights reserved. 28 */ 29 30 #include <dirent.h> 31 #include <stdio.h> 32 #include <string.h> 33 #include <errno.h> 34 #include <fcntl.h> 35 #include <sys/file.h> 36 #include <sys/stat.h> 37 #include <sys/types.h> 38 #include <sys/wait.h> 39 #include <unistd.h> 40 #include <libzfs.h> 41 #include <libshare.h> 42 #include "libshare_impl.h" 43 #include "nfs.h" 44 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 50 static boolean_t nfs_available(void); 51 static boolean_t exports_available(void); 52 53 typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value, 54 void *cookie); 55 56 typedef int (*nfs_host_callback_t)(FILE *tmpfile, const char *sharepath, 57 const char *host, const char *security, const char *access, void *cookie); 58 59 /* 60 * Invokes the specified callback function for each Solaris share option 61 * listed in the specified string. 62 */ 63 static int 64 foreach_nfs_shareopt(const char *shareopts, 65 nfs_shareopt_callback_t callback, void *cookie) 66 { 67 char *shareopts_dup, *opt, *cur, *value; 68 int was_nul, error; 69 70 if (shareopts == NULL) 71 return (SA_OK); 72 73 if (strcmp(shareopts, "on") == 0) 74 shareopts = "rw,crossmnt"; 75 76 shareopts_dup = strdup(shareopts); 77 78 79 if (shareopts_dup == NULL) 80 return (SA_NO_MEMORY); 81 82 opt = shareopts_dup; 83 was_nul = 0; 84 85 while (1) { 86 cur = opt; 87 88 while (*cur != ',' && *cur != '\0') 89 cur++; 90 91 if (*cur == '\0') 92 was_nul = 1; 93 94 *cur = '\0'; 95 96 if (cur > opt) { 97 value = strchr(opt, '='); 98 99 if (value != NULL) { 100 *value = '\0'; 101 value++; 102 } 103 104 error = callback(opt, value, cookie); 105 106 if (error != SA_OK) { 107 free(shareopts_dup); 108 return (error); 109 } 110 } 111 112 opt = cur + 1; 113 114 if (was_nul) 115 break; 116 } 117 118 free(shareopts_dup); 119 120 return (SA_OK); 121 } 122 123 typedef struct nfs_host_cookie_s { 124 nfs_host_callback_t callback; 125 const char *sharepath; 126 void *cookie; 127 FILE *tmpfile; 128 const char *security; 129 } nfs_host_cookie_t; 130 131 /* 132 * Helper function for foreach_nfs_host. This function checks whether the 133 * current share option is a host specification and invokes a callback 134 * function with information about the host. 135 */ 136 static int 137 foreach_nfs_host_cb(const char *opt, const char *value, void *pcookie) 138 { 139 int error; 140 const char *access; 141 char *host_dup, *host, *next, *v6Literal; 142 nfs_host_cookie_t *udata = (nfs_host_cookie_t *)pcookie; 143 int cidr_len; 144 145 #ifdef DEBUG 146 fprintf(stderr, "foreach_nfs_host_cb: key=%s, value=%s\n", opt, value); 147 #endif 148 149 if (strcmp(opt, "sec") == 0) 150 udata->security = value; 151 152 if (strcmp(opt, "rw") == 0 || strcmp(opt, "ro") == 0) { 153 if (value == NULL) 154 value = "*"; 155 156 access = opt; 157 158 host_dup = strdup(value); 159 160 if (host_dup == NULL) 161 return (SA_NO_MEMORY); 162 163 host = host_dup; 164 165 do { 166 if (*host == '[') { 167 host++; 168 v6Literal = strchr(host, ']'); 169 if (v6Literal == NULL) { 170 free(host_dup); 171 return (SA_SYNTAX_ERR); 172 } 173 if (v6Literal[1] == '\0') { 174 *v6Literal = '\0'; 175 next = NULL; 176 } else if (v6Literal[1] == '/') { 177 next = strchr(v6Literal + 2, ':'); 178 if (next == NULL) { 179 cidr_len = 180 strlen(v6Literal + 1); 181 memmove(v6Literal, 182 v6Literal + 1, 183 cidr_len); 184 v6Literal[cidr_len] = '\0'; 185 } else { 186 cidr_len = next - v6Literal - 1; 187 memmove(v6Literal, 188 v6Literal + 1, 189 cidr_len); 190 v6Literal[cidr_len] = '\0'; 191 next++; 192 } 193 } else if (v6Literal[1] == ':') { 194 *v6Literal = '\0'; 195 next = v6Literal + 2; 196 } else { 197 free(host_dup); 198 return (SA_SYNTAX_ERR); 199 } 200 } else { 201 next = strchr(host, ':'); 202 if (next != NULL) { 203 *next = '\0'; 204 next++; 205 } 206 } 207 208 error = udata->callback(udata->tmpfile, 209 udata->sharepath, host, udata->security, 210 access, udata->cookie); 211 212 if (error != SA_OK) { 213 free(host_dup); 214 215 return (error); 216 } 217 218 host = next; 219 } while (host != NULL); 220 221 free(host_dup); 222 } 223 224 return (SA_OK); 225 } 226 227 /* 228 * Invokes a callback function for all NFS hosts that are set for a share. 229 */ 230 static int 231 foreach_nfs_host(sa_share_impl_t impl_share, FILE *tmpfile, 232 nfs_host_callback_t callback, void *cookie) 233 { 234 nfs_host_cookie_t udata; 235 236 udata.callback = callback; 237 udata.sharepath = impl_share->sa_mountpoint; 238 udata.cookie = cookie; 239 udata.tmpfile = tmpfile; 240 udata.security = "sys"; 241 242 return (foreach_nfs_shareopt(impl_share->sa_shareopts, 243 foreach_nfs_host_cb, &udata)); 244 } 245 246 /* 247 * Converts a Solaris NFS host specification to its Linux equivalent. 248 */ 249 static const char * 250 get_linux_hostspec(const char *solaris_hostspec) 251 { 252 /* 253 * For now we just support CIDR masks (e.g. @192.168.0.0/16) and host 254 * wildcards (e.g. *.example.org). 255 */ 256 if (solaris_hostspec[0] == '@') { 257 /* 258 * Solaris host specifier, e.g. @192.168.0.0/16; we just need 259 * to skip the @ in this case 260 */ 261 return (solaris_hostspec + 1); 262 } else { 263 return (solaris_hostspec); 264 } 265 } 266 267 /* 268 * Adds a Linux share option to an array of NFS options. 269 */ 270 static int 271 add_linux_shareopt(char **plinux_opts, const char *key, const char *value) 272 { 273 size_t len = 0; 274 char *new_linux_opts; 275 276 if (*plinux_opts != NULL) 277 len = strlen(*plinux_opts); 278 279 new_linux_opts = realloc(*plinux_opts, len + 1 + strlen(key) + 280 (value ? 1 + strlen(value) : 0) + 1); 281 282 if (new_linux_opts == NULL) 283 return (SA_NO_MEMORY); 284 285 new_linux_opts[len] = '\0'; 286 287 if (len > 0) 288 strcat(new_linux_opts, ","); 289 290 strcat(new_linux_opts, key); 291 292 if (value != NULL) { 293 strcat(new_linux_opts, "="); 294 strcat(new_linux_opts, value); 295 } 296 297 *plinux_opts = new_linux_opts; 298 299 return (SA_OK); 300 } 301 302 static int string_cmp(const void *lhs, const void *rhs) { 303 const char *const *l = lhs, *const *r = rhs; 304 return (strcmp(*l, *r)); 305 } 306 307 /* 308 * Validates and converts a single Solaris share option to its Linux 309 * equivalent. 310 */ 311 static int 312 get_linux_shareopts_cb(const char *key, const char *value, void *cookie) 313 { 314 /* This list must remain sorted, since we bsearch() it */ 315 static const char *const valid_keys[] = { "all_squash", "anongid", 316 "anonuid", "async", "auth_nlm", "crossmnt", "fsid", "fsuid", "hide", 317 "insecure", "insecure_locks", "mountpoint", "mp", "no_acl", 318 "no_all_squash", "no_auth_nlm", "no_root_squash", 319 "no_subtree_check", "no_wdelay", "nohide", "refer", "replicas", 320 "root_squash", "secure", "secure_locks", "subtree_check", "sync", 321 "wdelay" }; 322 323 char **plinux_opts = (char **)cookie; 324 char *host, *val_dup, *literal, *next; 325 326 if (strcmp(key, "sec") == 0) 327 return (SA_OK); 328 329 if (strcmp(key, "ro") == 0 || strcmp(key, "rw") == 0) { 330 if (value == NULL || strlen(value) == 0) 331 return (SA_OK); 332 val_dup = strdup(value); 333 host = val_dup; 334 if (host == NULL) 335 return (SA_NO_MEMORY); 336 do { 337 if (*host == '[') { 338 host++; 339 literal = strchr(host, ']'); 340 if (literal == NULL) { 341 free(val_dup); 342 return (SA_SYNTAX_ERR); 343 } 344 if (literal[1] == '\0') 345 next = NULL; 346 else if (literal[1] == '/') { 347 next = strchr(literal + 2, ':'); 348 if (next != NULL) 349 ++next; 350 } else if (literal[1] == ':') 351 next = literal + 2; 352 else { 353 free(val_dup); 354 return (SA_SYNTAX_ERR); 355 } 356 } else { 357 next = strchr(host, ':'); 358 if (next != NULL) 359 ++next; 360 } 361 host = next; 362 } while (host != NULL); 363 free(val_dup); 364 return (SA_OK); 365 } 366 367 if (strcmp(key, "anon") == 0) 368 key = "anonuid"; 369 370 if (strcmp(key, "root_mapping") == 0) { 371 (void) add_linux_shareopt(plinux_opts, "root_squash", NULL); 372 key = "anonuid"; 373 } 374 375 if (strcmp(key, "nosub") == 0) 376 key = "subtree_check"; 377 378 if (bsearch(&key, valid_keys, ARRAY_SIZE(valid_keys), 379 sizeof (*valid_keys), string_cmp) == NULL) 380 return (SA_SYNTAX_ERR); 381 382 (void) add_linux_shareopt(plinux_opts, key, value); 383 384 return (SA_OK); 385 } 386 387 /* 388 * Takes a string containing Solaris share options (e.g. "sync,no_acl") and 389 * converts them to a NULL-terminated array of Linux NFS options. 390 */ 391 static int 392 get_linux_shareopts(const char *shareopts, char **plinux_opts) 393 { 394 int error; 395 396 assert(plinux_opts != NULL); 397 398 *plinux_opts = NULL; 399 400 /* no_subtree_check - Default as of nfs-utils v1.1.0 */ 401 (void) add_linux_shareopt(plinux_opts, "no_subtree_check", NULL); 402 403 /* mountpoint - Restrict exports to ZFS mountpoints */ 404 (void) add_linux_shareopt(plinux_opts, "mountpoint", NULL); 405 406 error = foreach_nfs_shareopt(shareopts, get_linux_shareopts_cb, 407 plinux_opts); 408 409 if (error != SA_OK) { 410 free(*plinux_opts); 411 *plinux_opts = NULL; 412 } 413 414 return (error); 415 } 416 417 /* 418 * This function populates an entry into /etc/exports.d/zfs.exports. 419 * This file is consumed by the linux nfs server so that zfs shares are 420 * automatically exported upon boot or whenever the nfs server restarts. 421 */ 422 static int 423 nfs_add_entry(FILE *tmpfile, const char *sharepath, 424 const char *host, const char *security, const char *access_opts, 425 void *pcookie) 426 { 427 const char *linux_opts = (const char *)pcookie; 428 429 if (linux_opts == NULL) 430 linux_opts = ""; 431 432 boolean_t need_free; 433 char *mp; 434 int rc = nfs_escape_mountpoint(sharepath, &mp, &need_free); 435 if (rc != SA_OK) 436 return (rc); 437 if (fprintf(tmpfile, "%s %s(sec=%s,%s,%s)\n", mp, 438 get_linux_hostspec(host), security, access_opts, 439 linux_opts) < 0) { 440 fprintf(stderr, "failed to write to temporary file\n"); 441 rc = SA_SYSTEM_ERR; 442 } 443 444 if (need_free) 445 free(mp); 446 return (rc); 447 } 448 449 /* 450 * Enables NFS sharing for the specified share. 451 */ 452 static int 453 nfs_enable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile) 454 { 455 char *linux_opts = NULL; 456 int error = get_linux_shareopts(impl_share->sa_shareopts, &linux_opts); 457 if (error != SA_OK) 458 return (error); 459 460 error = foreach_nfs_host(impl_share, tmpfile, nfs_add_entry, 461 linux_opts); 462 free(linux_opts); 463 return (error); 464 } 465 466 static int 467 nfs_enable_share(sa_share_impl_t impl_share) 468 { 469 if (!nfs_available()) 470 return (SA_SYSTEM_ERR); 471 472 return (nfs_toggle_share( 473 ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share, 474 nfs_enable_share_impl)); 475 } 476 477 /* 478 * Disables NFS sharing for the specified share. 479 */ 480 static int 481 nfs_disable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile) 482 { 483 (void) impl_share, (void) tmpfile; 484 return (SA_OK); 485 } 486 487 static int 488 nfs_disable_share(sa_share_impl_t impl_share) 489 { 490 if (!nfs_available()) 491 return (SA_OK); 492 493 return (nfs_toggle_share( 494 ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE, ZFS_EXPORTS_DIR, impl_share, 495 nfs_disable_share_impl)); 496 } 497 498 static boolean_t 499 nfs_is_shared(sa_share_impl_t impl_share) 500 { 501 if (!nfs_available()) 502 return (SA_SYSTEM_ERR); 503 504 return (nfs_is_shared_impl(ZFS_EXPORTS_FILE, impl_share)); 505 } 506 507 /* 508 * Checks whether the specified NFS share options are syntactically correct. 509 */ 510 static int 511 nfs_validate_shareopts(const char *shareopts) 512 { 513 char *linux_opts = NULL; 514 515 if (strlen(shareopts) == 0) 516 return (SA_SYNTAX_ERR); 517 518 int error = get_linux_shareopts(shareopts, &linux_opts); 519 if (error != SA_OK) 520 return (error); 521 522 free(linux_opts); 523 return (SA_OK); 524 } 525 526 static int 527 nfs_commit_shares(void) 528 { 529 if (!nfs_available()) 530 return (SA_SYSTEM_ERR); 531 532 char *argv[] = { 533 (char *)"/usr/sbin/exportfs", 534 (char *)"-ra", 535 NULL 536 }; 537 538 return (libzfs_run_process(argv[0], argv, 0)); 539 } 540 541 static void 542 nfs_truncate_shares(void) 543 { 544 if (!exports_available()) 545 return; 546 nfs_reset_shares(ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE); 547 } 548 549 const sa_fstype_t libshare_nfs_type = { 550 .enable_share = nfs_enable_share, 551 .disable_share = nfs_disable_share, 552 .is_shared = nfs_is_shared, 553 554 .validate_shareopts = nfs_validate_shareopts, 555 .commit_shares = nfs_commit_shares, 556 .truncate_shares = nfs_truncate_shares, 557 }; 558 559 static boolean_t 560 nfs_available(void) 561 { 562 static int avail; 563 564 if (!avail) { 565 if (access("/usr/sbin/exportfs", F_OK) != 0) 566 avail = -1; 567 else 568 avail = 1; 569 } 570 571 return (avail == 1); 572 } 573 574 static boolean_t 575 exports_available(void) 576 { 577 static int avail; 578 579 if (!avail) { 580 if (access(ZFS_EXPORTS_DIR, F_OK) != 0) 581 avail = -1; 582 else 583 avail = 1; 584 } 585 586 return (avail == 1); 587 } 588