xref: /freebsd/sys/contrib/openzfs/lib/libzfs/os/linux/libzfs_share_smb.c (revision 8a62a2a5659d1839d8799b4274c04469d7f17c78)
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,2012 Turbo Fredriksson <turbo@bayour.com>, based on nfs.c
26  *                         by Gunnar Beutner
27  * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
28  *
29  * This is an addition to the zfs device driver to add, modify and remove SMB
30  * shares using the 'net share' command that comes with Samba.
31  *
32  * TESTING
33  * Make sure that samba listens to 'localhost' (127.0.0.1) and that the options
34  * 'usershare max shares' and 'usershare owner only' have been reviewed/set
35  * accordingly (see zfs(8) for information).
36  *
37  * Once configuration in samba have been done, test that this
38  * works with the following three commands (in this case, my ZFS
39  * filesystem is called 'share/Test1'):
40  *
41  *	(root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \
42  *		"Comment: /share/Test1" "Everyone:F"
43  *	(root)# net usershare list | grep -i test
44  *	(root)# net -U root -S 127.0.0.1 usershare delete Test1
45  *
46  * The first command will create a user share that gives everyone full access.
47  * To limit the access below that, use normal UNIX commands (chmod, chown etc).
48  */
49 
50 #include <time.h>
51 #include <stdlib.h>
52 #include <stdio.h>
53 #include <string.h>
54 #include <fcntl.h>
55 #include <sys/wait.h>
56 #include <unistd.h>
57 #include <dirent.h>
58 #include <sys/types.h>
59 #include <sys/stat.h>
60 #include <libzfs.h>
61 #include "../../libzfs_share.h"
62 
63 static boolean_t smb_available(void);
64 
65 static smb_share_t *smb_shares;
66 static int smb_disable_share(sa_share_impl_t impl_share);
67 static boolean_t smb_is_share_active(sa_share_impl_t impl_share);
68 
69 /*
70  * Retrieve the list of SMB shares.
71  */
72 static int
smb_retrieve_shares(void)73 smb_retrieve_shares(void)
74 {
75 	int rc = SA_OK;
76 	char file_path[PATH_MAX], line[512], *token, *key, *value;
77 	char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL;
78 	char *guest_ok = NULL;
79 	DIR *shares_dir;
80 	FILE *share_file_fp = NULL;
81 	struct dirent *directory;
82 	struct stat eStat;
83 	smb_share_t *shares, *new_shares = NULL;
84 
85 	/* opendir(), stat() */
86 	shares_dir = opendir(SMB_SHARE_DIR);
87 	if (shares_dir == NULL)
88 		return (SA_SYSTEM_ERR);
89 
90 	/* Go through the directory, looking for shares */
91 	while ((directory = readdir(shares_dir))) {
92 		int fd;
93 
94 		if (directory->d_name[0] == '.')
95 			continue;
96 
97 		snprintf(file_path, sizeof (file_path),
98 		    "%s/%s", SMB_SHARE_DIR, directory->d_name);
99 
100 		if ((fd = open(file_path, O_RDONLY | O_CLOEXEC)) == -1) {
101 			rc = SA_SYSTEM_ERR;
102 			goto out;
103 		}
104 
105 		if (fstat(fd, &eStat) == -1) {
106 			close(fd);
107 			rc = SA_SYSTEM_ERR;
108 			goto out;
109 		}
110 
111 		if (!S_ISREG(eStat.st_mode)) {
112 			close(fd);
113 			continue;
114 		}
115 
116 		if ((share_file_fp = fdopen(fd, "r")) == NULL) {
117 			close(fd);
118 			rc = SA_SYSTEM_ERR;
119 			goto out;
120 		}
121 
122 		name = strdup(directory->d_name);
123 		if (name == NULL) {
124 			rc = SA_NO_MEMORY;
125 			goto out;
126 		}
127 
128 		while (fgets(line, sizeof (line), share_file_fp)) {
129 			if (line[0] == '#')
130 				continue;
131 
132 			/* Trim trailing new-line character(s). */
133 			while (line[strlen(line) - 1] == '\r' ||
134 			    line[strlen(line) - 1] == '\n')
135 				line[strlen(line) - 1] = '\0';
136 
137 			/* Split the line in two, separated by '=' */
138 			token = strchr(line, '=');
139 			if (token == NULL)
140 				continue;
141 
142 			key = line;
143 			value = token + 1;
144 			*token = '\0';
145 
146 			dup_value = strdup(value);
147 			if (dup_value == NULL) {
148 				rc = SA_NO_MEMORY;
149 				goto out;
150 			}
151 
152 			if (strcmp(key, "path") == 0) {
153 				free(path);
154 				path = dup_value;
155 			} else if (strcmp(key, "comment") == 0) {
156 				free(comment);
157 				comment = dup_value;
158 			} else if (strcmp(key, "guest_ok") == 0) {
159 				free(guest_ok);
160 				guest_ok = dup_value;
161 			} else
162 				free(dup_value);
163 
164 			dup_value = NULL;
165 
166 			if (path == NULL || comment == NULL || guest_ok == NULL)
167 				continue; /* Incomplete share definition */
168 			else {
169 				shares = (smb_share_t *)
170 				    malloc(sizeof (smb_share_t));
171 				if (shares == NULL) {
172 					rc = SA_NO_MEMORY;
173 					goto out;
174 				}
175 
176 				(void) strlcpy(shares->name, name,
177 				    sizeof (shares->name));
178 
179 				(void) strlcpy(shares->path, path,
180 				    sizeof (shares->path));
181 
182 				(void) strlcpy(shares->comment, comment,
183 				    sizeof (shares->comment));
184 
185 				shares->guest_ok = atoi(guest_ok);
186 
187 				shares->next = new_shares;
188 				new_shares = shares;
189 
190 				free(path);
191 				free(comment);
192 				free(guest_ok);
193 
194 				path = NULL;
195 				comment = NULL;
196 				guest_ok = NULL;
197 			}
198 		}
199 
200 out:
201 		if (share_file_fp != NULL) {
202 			fclose(share_file_fp);
203 			share_file_fp = NULL;
204 		}
205 
206 		free(name);
207 		free(path);
208 		free(comment);
209 		free(guest_ok);
210 
211 		name = NULL;
212 		path = NULL;
213 		comment = NULL;
214 		guest_ok = NULL;
215 	}
216 	closedir(shares_dir);
217 
218 	smb_shares = new_shares;
219 
220 	return (rc);
221 }
222 
223 /*
224  * Used internally by smb_enable_share to enable sharing for a single host.
225  */
226 static int
smb_enable_share_one(const char * sharename,const char * sharepath)227 smb_enable_share_one(const char *sharename, const char *sharepath)
228 {
229 	char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX];
230 
231 	/* Support ZFS share name regexp '[[:alnum:]_-.: ]' */
232 	strlcpy(name, sharename, sizeof (name));
233 	for (char *itr = name; *itr != '\0'; ++itr)
234 		switch (*itr) {
235 		case '/':
236 		case '-':
237 		case ':':
238 		case ' ':
239 			*itr = '_';
240 		}
241 
242 	/*
243 	 * CMD: net -S SMB_NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \
244 	 *      "Comment" "Everyone:F"
245 	 */
246 	snprintf(comment, sizeof (comment), "Comment: %s", sharepath);
247 
248 	char *argv[] = {
249 		(char *)SMB_NET_CMD_PATH,
250 		(char *)"-S",
251 		(char *)SMB_NET_CMD_ARG_HOST,
252 		(char *)"usershare",
253 		(char *)"add",
254 		name,
255 		(char *)sharepath,
256 		comment,
257 		(char *)"Everyone:F",
258 		NULL,
259 	};
260 
261 	if (libzfs_run_process(argv[0], argv, 0) != 0)
262 		return (SA_SYSTEM_ERR);
263 
264 	/* Reload the share file */
265 	(void) smb_retrieve_shares();
266 
267 	return (SA_OK);
268 }
269 
270 /*
271  * Enables SMB sharing for the specified share.
272  */
273 static int
smb_enable_share(sa_share_impl_t impl_share)274 smb_enable_share(sa_share_impl_t impl_share)
275 {
276 	if (!smb_available())
277 		return (SA_SYSTEM_ERR);
278 
279 	if (smb_is_share_active(impl_share))
280 		smb_disable_share(impl_share);
281 
282 	if (impl_share->sa_shareopts == NULL) /* on/off */
283 		return (SA_SYSTEM_ERR);
284 
285 	if (strcmp(impl_share->sa_shareopts, "off") == 0)
286 		return (SA_OK);
287 
288 	/* Magic: Enable (i.e., 'create new') share */
289 	return (smb_enable_share_one(impl_share->sa_zfsname,
290 	    impl_share->sa_mountpoint));
291 }
292 
293 /*
294  * Used internally by smb_disable_share to disable sharing for a single host.
295  */
296 static int
smb_disable_share_one(const char * sharename)297 smb_disable_share_one(const char *sharename)
298 {
299 	/* CMD: net -S SMB_NET_CMD_ARG_HOST usershare delete Test1 */
300 	char *argv[] = {
301 		(char *)SMB_NET_CMD_PATH,
302 		(char *)"-S",
303 		(char *)SMB_NET_CMD_ARG_HOST,
304 		(char *)"usershare",
305 		(char *)"delete",
306 		(char *)sharename,
307 		NULL,
308 	};
309 
310 	if (libzfs_run_process(argv[0], argv, 0) != 0)
311 		return (SA_SYSTEM_ERR);
312 	else
313 		return (SA_OK);
314 }
315 
316 /*
317  * Disables SMB sharing for the specified share.
318  */
319 static int
smb_disable_share(sa_share_impl_t impl_share)320 smb_disable_share(sa_share_impl_t impl_share)
321 {
322 	if (!smb_available()) {
323 		/*
324 		 * The share can't possibly be active, so nothing
325 		 * needs to be done to disable it.
326 		 */
327 		return (SA_OK);
328 	}
329 
330 	for (const smb_share_t *i = smb_shares; i != NULL; i = i->next)
331 		if (strcmp(impl_share->sa_mountpoint, i->path) == 0)
332 			return (smb_disable_share_one(i->name));
333 
334 	return (SA_OK);
335 }
336 
337 /*
338  * Checks whether the specified SMB share options are syntactically correct.
339  */
340 static int
smb_validate_shareopts(const char * shareopts)341 smb_validate_shareopts(const char *shareopts)
342 {
343 	/* TODO: Accept 'name' and sec/acl (?) */
344 	if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0))
345 		return (SA_OK);
346 
347 	return (SA_SYNTAX_ERR);
348 }
349 
350 /*
351  * Checks whether a share is currently active.
352  */
353 static boolean_t
smb_is_share_active(sa_share_impl_t impl_share)354 smb_is_share_active(sa_share_impl_t impl_share)
355 {
356 	if (!smb_available())
357 		return (B_FALSE);
358 
359 	/* Retrieve the list of (possible) active shares */
360 	smb_retrieve_shares();
361 
362 	for (const smb_share_t *i = smb_shares; i != NULL; i = i->next)
363 		if (strcmp(impl_share->sa_mountpoint, i->path) == 0)
364 			return (B_TRUE);
365 
366 	return (B_FALSE);
367 }
368 
369 static int
smb_update_shares(void)370 smb_update_shares(void)
371 {
372 	/* Not implemented */
373 	return (0);
374 }
375 
376 const sa_fstype_t libshare_smb_type = {
377 	.enable_share = smb_enable_share,
378 	.disable_share = smb_disable_share,
379 	.is_shared = smb_is_share_active,
380 
381 	.validate_shareopts = smb_validate_shareopts,
382 	.commit_shares = smb_update_shares,
383 };
384 
385 /*
386  * Provides a convenient wrapper for determining SMB availability
387  */
388 static boolean_t
smb_available(void)389 smb_available(void)
390 {
391 	static int avail;
392 
393 	if (!avail) {
394 		struct stat statbuf;
395 
396 		if (access(SMB_NET_CMD_PATH, F_OK) != 0 ||
397 		    lstat(SMB_SHARE_DIR, &statbuf) != 0 ||
398 		    !S_ISDIR(statbuf.st_mode))
399 			avail = -1;
400 		else
401 			avail = 1;
402 	}
403 
404 	return (avail == 1);
405 }
406