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