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