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 49static boolean_t nfs_available(void); 50static boolean_t exports_available(void); 51 52typedef int (*nfs_shareopt_callback_t)(const char *opt, const char *value, 53 void *cookie); 54 55typedef 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 */ 62static int 63foreach_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 122typedef 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 */ 135static int 136foreach_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 */ 229static int 230foreach_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 */ 248static const char * 249get_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 */ 269static int 270add_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 301static 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 */ 310static int 311get_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 */ 390static int 391get_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 */ 421static int 422nfs_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 */ 451static int 452nfs_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 465static int 466nfs_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 */ 479static int 480nfs_disable_share_impl(sa_share_impl_t impl_share, FILE *tmpfile) 481{ 482 (void) impl_share, (void) tmpfile; 483 return (SA_OK); 484} 485 486static int 487nfs_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 497static boolean_t 498nfs_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 */ 509static int 510nfs_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 525static int 526nfs_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 540static void 541nfs_truncate_shares(void) 542{ 543 if (!exports_available()) 544 return; 545 nfs_reset_shares(ZFS_EXPORTS_LOCK, ZFS_EXPORTS_FILE); 546} 547 548const 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 558static boolean_t 559nfs_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 573static boolean_t 574exports_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