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