xref: /freebsd/sys/contrib/openzfs/lib/libshare/os/linux/smb.c (revision 9e5787d2284e187abb5b654d924394a65772e004)
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 http://www.opensolaris.org/os/licensing.
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,2012 Turbo Fredriksson <turbo@bayour.com>, based on nfs.c
25  *                         by Gunnar Beutner
26  * Copyright (c) 2019, 2020 by Delphix. All rights reserved.
27  *
28  * This is an addition to the zfs device driver to add, modify and remove SMB
29  * shares using the 'net share' command that comes with Samba.
30  *
31  * TESTING
32  * Make sure that samba listens to 'localhost' (127.0.0.1) and that the options
33  * 'usershare max shares' and 'usershare owner only' have been reviewed/set
34  * accordingly (see zfs(8) for information).
35  *
36  * Once configuration in samba have been done, test that this
37  * works with the following three commands (in this case, my ZFS
38  * filesystem is called 'share/Test1'):
39  *
40  *	(root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \
41  *		"Comment: /share/Test1" "Everyone:F"
42  *	(root)# net usershare list | grep -i test
43  *	(root)# net -U root -S 127.0.0.1 usershare delete Test1
44  *
45  * The first command will create a user share that gives everyone full access.
46  * To limit the access below that, use normal UNIX commands (chmod, chown etc).
47  */
48 
49 #include <time.h>
50 #include <stdlib.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <strings.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 sa_fstype_t *smb_fstype;
68 
69 smb_share_t *smb_shares;
70 static int smb_disable_share(sa_share_impl_t impl_share);
71 static boolean_t smb_is_share_active(sa_share_impl_t impl_share);
72 
73 /*
74  * Retrieve the list of SMB shares.
75  */
76 static int
77 smb_retrieve_shares(void)
78 {
79 	int rc = SA_OK;
80 	char file_path[PATH_MAX], line[512], *token, *key, *value;
81 	char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL;
82 	char *guest_ok = NULL;
83 	DIR *shares_dir;
84 	FILE *share_file_fp = NULL;
85 	struct dirent *directory;
86 	struct stat eStat;
87 	smb_share_t *shares, *new_shares = NULL;
88 
89 	/* opendir(), stat() */
90 	shares_dir = opendir(SHARE_DIR);
91 	if (shares_dir == NULL)
92 		return (SA_SYSTEM_ERR);
93 
94 	/* Go through the directory, looking for shares */
95 	while ((directory = readdir(shares_dir))) {
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 (stat(file_path, &eStat) == -1) {
103 			rc = SA_SYSTEM_ERR;
104 			goto out;
105 		}
106 
107 		if (!S_ISREG(eStat.st_mode))
108 			continue;
109 
110 		if ((share_file_fp = fopen(file_path, "r")) == NULL) {
111 			rc = SA_SYSTEM_ERR;
112 			goto out;
113 		}
114 
115 		name = strdup(directory->d_name);
116 		if (name == NULL) {
117 			rc = SA_NO_MEMORY;
118 			goto out;
119 		}
120 
121 		while (fgets(line, sizeof (line), share_file_fp)) {
122 			if (line[0] == '#')
123 				continue;
124 
125 			/* Trim trailing new-line character(s). */
126 			while (line[strlen(line) - 1] == '\r' ||
127 			    line[strlen(line) - 1] == '\n')
128 				line[strlen(line) - 1] = '\0';
129 
130 			/* Split the line in two, separated by '=' */
131 			token = strchr(line, '=');
132 			if (token == NULL)
133 				continue;
134 
135 			key = line;
136 			value = token + 1;
137 			*token = '\0';
138 
139 			dup_value = strdup(value);
140 			if (dup_value == NULL) {
141 				rc = SA_NO_MEMORY;
142 				goto out;
143 			}
144 
145 			if (strcmp(key, "path") == 0) {
146 				free(path);
147 				path = dup_value;
148 			} else if (strcmp(key, "comment") == 0) {
149 				free(comment);
150 				comment = dup_value;
151 			} else if (strcmp(key, "guest_ok") == 0) {
152 				free(guest_ok);
153 				guest_ok = dup_value;
154 			} else
155 				free(dup_value);
156 
157 			dup_value = NULL;
158 
159 			if (path == NULL || comment == NULL || guest_ok == NULL)
160 				continue; /* Incomplete share definition */
161 			else {
162 				shares = (smb_share_t *)
163 				    malloc(sizeof (smb_share_t));
164 				if (shares == NULL) {
165 					rc = SA_NO_MEMORY;
166 					goto out;
167 				}
168 
169 				(void) strlcpy(shares->name, name,
170 				    sizeof (shares->name));
171 
172 				(void) strlcpy(shares->path, path,
173 				    sizeof (shares->path));
174 
175 				(void) strlcpy(shares->comment, comment,
176 				    sizeof (shares->comment));
177 
178 				shares->guest_ok = atoi(guest_ok);
179 
180 				shares->next = new_shares;
181 				new_shares = shares;
182 
183 				free(path);
184 				free(comment);
185 				free(guest_ok);
186 
187 				path = NULL;
188 				comment = NULL;
189 				guest_ok = NULL;
190 			}
191 		}
192 
193 out:
194 		if (share_file_fp != NULL) {
195 			fclose(share_file_fp);
196 			share_file_fp = NULL;
197 		}
198 
199 		free(name);
200 		free(path);
201 		free(comment);
202 		free(guest_ok);
203 
204 		name = NULL;
205 		path = NULL;
206 		comment = NULL;
207 		guest_ok = NULL;
208 	}
209 	closedir(shares_dir);
210 
211 	smb_shares = new_shares;
212 
213 	return (rc);
214 }
215 
216 /*
217  * Used internally by smb_enable_share to enable sharing for a single host.
218  */
219 static int
220 smb_enable_share_one(const char *sharename, const char *sharepath)
221 {
222 	char *argv[10], *pos;
223 	char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX];
224 	int rc;
225 
226 	/* Support ZFS share name regexp '[[:alnum:]_-.: ]' */
227 	strlcpy(name, sharename, sizeof (name));
228 	name [sizeof (name)-1] = '\0';
229 
230 	pos = name;
231 	while (*pos != '\0') {
232 		switch (*pos) {
233 		case '/':
234 		case '-':
235 		case ':':
236 		case ' ':
237 			*pos = '_';
238 		}
239 
240 		++pos;
241 	}
242 
243 	/*
244 	 * CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \
245 	 *      "Comment" "Everyone:F"
246 	 */
247 	snprintf(comment, sizeof (comment), "Comment: %s", sharepath);
248 
249 	argv[0] = NET_CMD_PATH;
250 	argv[1] = (char *)"-S";
251 	argv[2] = NET_CMD_ARG_HOST;
252 	argv[3] = (char *)"usershare";
253 	argv[4] = (char *)"add";
254 	argv[5] = (char *)name;
255 	argv[6] = (char *)sharepath;
256 	argv[7] = (char *)comment;
257 	argv[8] = "Everyone:F";
258 	argv[9] = NULL;
259 
260 	rc = libzfs_run_process(argv[0], argv, 0);
261 	if (rc < 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
274 smb_enable_share(sa_share_impl_t impl_share)
275 {
276 	char *shareopts;
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 	shareopts = FSINFO(impl_share, smb_fstype)->shareopts;
285 	if (shareopts == NULL) /* on/off */
286 		return (SA_SYSTEM_ERR);
287 
288 	if (strcmp(shareopts, "off") == 0)
289 		return (SA_OK);
290 
291 	/* Magic: Enable (i.e., 'create new') share */
292 	return (smb_enable_share_one(impl_share->sa_zfsname,
293 	    impl_share->sa_mountpoint));
294 }
295 
296 /*
297  * Used internally by smb_disable_share to disable sharing for a single host.
298  */
299 static int
300 smb_disable_share_one(const char *sharename)
301 {
302 	int rc;
303 	char *argv[7];
304 
305 	/* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */
306 	argv[0] = NET_CMD_PATH;
307 	argv[1] = (char *)"-S";
308 	argv[2] = NET_CMD_ARG_HOST;
309 	argv[3] = (char *)"usershare";
310 	argv[4] = (char *)"delete";
311 	argv[5] = strdup(sharename);
312 	argv[6] = NULL;
313 
314 	rc = libzfs_run_process(argv[0], argv, 0);
315 	if (rc < 0)
316 		return (SA_SYSTEM_ERR);
317 	else
318 		return (SA_OK);
319 }
320 
321 /*
322  * Disables SMB sharing for the specified share.
323  */
324 static int
325 smb_disable_share(sa_share_impl_t impl_share)
326 {
327 	smb_share_t *shares = smb_shares;
328 
329 	if (!smb_available()) {
330 		/*
331 		 * The share can't possibly be active, so nothing
332 		 * needs to be done to disable it.
333 		 */
334 		return (SA_OK);
335 	}
336 
337 	while (shares != NULL) {
338 		if (strcmp(impl_share->sa_mountpoint, shares->path) == 0)
339 			return (smb_disable_share_one(shares->name));
340 
341 		shares = shares->next;
342 	}
343 
344 	return (SA_OK);
345 }
346 
347 /*
348  * Checks whether the specified SMB share options are syntactically correct.
349  */
350 static int
351 smb_validate_shareopts(const char *shareopts)
352 {
353 	/* TODO: Accept 'name' and sec/acl (?) */
354 	if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0))
355 		return (SA_OK);
356 
357 	return (SA_SYNTAX_ERR);
358 }
359 
360 /*
361  * Checks whether a share is currently active.
362  */
363 static boolean_t
364 smb_is_share_active(sa_share_impl_t impl_share)
365 {
366 	smb_share_t *iter = smb_shares;
367 
368 	if (!smb_available())
369 		return (B_FALSE);
370 
371 	/* Retrieve the list of (possible) active shares */
372 	smb_retrieve_shares();
373 
374 	while (iter != NULL) {
375 		if (strcmp(impl_share->sa_mountpoint, iter->path) == 0)
376 			return (B_TRUE);
377 
378 		iter = iter->next;
379 	}
380 
381 	return (B_FALSE);
382 }
383 
384 /*
385  * Called to update a share's options. A share's options might be out of
386  * date if the share was loaded from disk and the "sharesmb" dataset
387  * property has changed in the meantime. This function also takes care
388  * of re-enabling the share if necessary.
389  */
390 static int
391 smb_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
392 {
393 	if (!impl_share)
394 		return (SA_SYSTEM_ERR);
395 
396 	FSINFO(impl_share, smb_fstype)->shareopts = (char *)shareopts;
397 	return (SA_OK);
398 }
399 
400 static int
401 smb_update_shares(void)
402 {
403 	/* Not implemented */
404 	return (0);
405 }
406 
407 /*
408  * Clears a share's SMB options. Used by libshare to
409  * clean up shares that are about to be free()'d.
410  */
411 static void
412 smb_clear_shareopts(sa_share_impl_t impl_share)
413 {
414 	FSINFO(impl_share, smb_fstype)->shareopts = NULL;
415 }
416 
417 static const sa_share_ops_t smb_shareops = {
418 	.enable_share = smb_enable_share,
419 	.disable_share = smb_disable_share,
420 	.is_shared = smb_is_share_active,
421 
422 	.validate_shareopts = smb_validate_shareopts,
423 	.update_shareopts = smb_update_shareopts,
424 	.clear_shareopts = smb_clear_shareopts,
425 	.commit_shares = smb_update_shares,
426 };
427 
428 /*
429  * Provides a convenient wrapper for determining SMB availability
430  */
431 static boolean_t
432 smb_available(void)
433 {
434 	struct stat statbuf;
435 
436 	if (lstat(SHARE_DIR, &statbuf) != 0 ||
437 	    !S_ISDIR(statbuf.st_mode))
438 		return (B_FALSE);
439 
440 	if (access(NET_CMD_PATH, F_OK) != 0)
441 		return (B_FALSE);
442 
443 	return (B_TRUE);
444 }
445 
446 /*
447  * Initializes the SMB functionality of libshare.
448  */
449 void
450 libshare_smb_init(void)
451 {
452 	smb_fstype = register_fstype("smb", &smb_shareops);
453 }
454