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 <fcntl.h>
54 #include <sys/wait.h>
55 #include <unistd.h>
56 #include <dirent.h>
57 #include <sys/types.h>
58 #include <sys/stat.h>
59 #include <libzfs.h>
60 #include <libshare.h>
61 #include "libshare_impl.h"
62 #include "smb.h"
63 
64 static boolean_t smb_available(void);
65 
66 static sa_fstype_t *smb_fstype;
67 
68 smb_share_t *smb_shares;
69 static int smb_disable_share(sa_share_impl_t impl_share);
70 static boolean_t smb_is_share_active(sa_share_impl_t impl_share);
71 
72 /*
73  * Retrieve the list of SMB shares.
74  */
75 static int
76 smb_retrieve_shares(void)
77 {
78 	int rc = SA_OK;
79 	char file_path[PATH_MAX], line[512], *token, *key, *value;
80 	char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL;
81 	char *guest_ok = NULL;
82 	DIR *shares_dir;
83 	FILE *share_file_fp = NULL;
84 	struct dirent *directory;
85 	struct stat eStat;
86 	smb_share_t *shares, *new_shares = NULL;
87 
88 	/* opendir(), stat() */
89 	shares_dir = opendir(SHARE_DIR);
90 	if (shares_dir == NULL)
91 		return (SA_SYSTEM_ERR);
92 
93 	/* Go through the directory, looking for shares */
94 	while ((directory = readdir(shares_dir))) {
95 		if (directory->d_name[0] == '.')
96 			continue;
97 
98 		snprintf(file_path, sizeof (file_path),
99 		    "%s/%s", SHARE_DIR, directory->d_name);
100 
101 		if (stat(file_path, &eStat) == -1) {
102 			rc = SA_SYSTEM_ERR;
103 			goto out;
104 		}
105 
106 		if (!S_ISREG(eStat.st_mode))
107 			continue;
108 
109 		if ((share_file_fp = fopen(file_path, "re")) == NULL) {
110 			rc = SA_SYSTEM_ERR;
111 			goto out;
112 		}
113 
114 		name = strdup(directory->d_name);
115 		if (name == NULL) {
116 			rc = SA_NO_MEMORY;
117 			goto out;
118 		}
119 
120 		while (fgets(line, sizeof (line), share_file_fp)) {
121 			if (line[0] == '#')
122 				continue;
123 
124 			/* Trim trailing new-line character(s). */
125 			while (line[strlen(line) - 1] == '\r' ||
126 			    line[strlen(line) - 1] == '\n')
127 				line[strlen(line) - 1] = '\0';
128 
129 			/* Split the line in two, separated by '=' */
130 			token = strchr(line, '=');
131 			if (token == NULL)
132 				continue;
133 
134 			key = line;
135 			value = token + 1;
136 			*token = '\0';
137 
138 			dup_value = strdup(value);
139 			if (dup_value == NULL) {
140 				rc = SA_NO_MEMORY;
141 				goto out;
142 			}
143 
144 			if (strcmp(key, "path") == 0) {
145 				free(path);
146 				path = dup_value;
147 			} else if (strcmp(key, "comment") == 0) {
148 				free(comment);
149 				comment = dup_value;
150 			} else if (strcmp(key, "guest_ok") == 0) {
151 				free(guest_ok);
152 				guest_ok = dup_value;
153 			} else
154 				free(dup_value);
155 
156 			dup_value = NULL;
157 
158 			if (path == NULL || comment == NULL || guest_ok == NULL)
159 				continue; /* Incomplete share definition */
160 			else {
161 				shares = (smb_share_t *)
162 				    malloc(sizeof (smb_share_t));
163 				if (shares == NULL) {
164 					rc = SA_NO_MEMORY;
165 					goto out;
166 				}
167 
168 				(void) strlcpy(shares->name, name,
169 				    sizeof (shares->name));
170 
171 				(void) strlcpy(shares->path, path,
172 				    sizeof (shares->path));
173 
174 				(void) strlcpy(shares->comment, comment,
175 				    sizeof (shares->comment));
176 
177 				shares->guest_ok = atoi(guest_ok);
178 
179 				shares->next = new_shares;
180 				new_shares = shares;
181 
182 				free(path);
183 				free(comment);
184 				free(guest_ok);
185 
186 				path = NULL;
187 				comment = NULL;
188 				guest_ok = NULL;
189 			}
190 		}
191 
192 out:
193 		if (share_file_fp != NULL) {
194 			fclose(share_file_fp);
195 			share_file_fp = NULL;
196 		}
197 
198 		free(name);
199 		free(path);
200 		free(comment);
201 		free(guest_ok);
202 
203 		name = NULL;
204 		path = NULL;
205 		comment = NULL;
206 		guest_ok = NULL;
207 	}
208 	closedir(shares_dir);
209 
210 	smb_shares = new_shares;
211 
212 	return (rc);
213 }
214 
215 /*
216  * Used internally by smb_enable_share to enable sharing for a single host.
217  */
218 static int
219 smb_enable_share_one(const char *sharename, const char *sharepath)
220 {
221 	char *argv[10], *pos;
222 	char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX];
223 	int rc;
224 
225 	/* Support ZFS share name regexp '[[:alnum:]_-.: ]' */
226 	strlcpy(name, sharename, sizeof (name));
227 	name [sizeof (name)-1] = '\0';
228 
229 	pos = name;
230 	while (*pos != '\0') {
231 		switch (*pos) {
232 		case '/':
233 		case '-':
234 		case ':':
235 		case ' ':
236 			*pos = '_';
237 		}
238 
239 		++pos;
240 	}
241 
242 	/*
243 	 * CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \
244 	 *      "Comment" "Everyone:F"
245 	 */
246 	snprintf(comment, sizeof (comment), "Comment: %s", sharepath);
247 
248 	argv[0] = NET_CMD_PATH;
249 	argv[1] = (char *)"-S";
250 	argv[2] = NET_CMD_ARG_HOST;
251 	argv[3] = (char *)"usershare";
252 	argv[4] = (char *)"add";
253 	argv[5] = (char *)name;
254 	argv[6] = (char *)sharepath;
255 	argv[7] = (char *)comment;
256 	argv[8] = (char *)"Everyone:F";
257 	argv[9] = NULL;
258 
259 	rc = libzfs_run_process(argv[0], argv, 0);
260 	if (rc < 0)
261 		return (SA_SYSTEM_ERR);
262 
263 	/* Reload the share file */
264 	(void) smb_retrieve_shares();
265 
266 	return (SA_OK);
267 }
268 
269 /*
270  * Enables SMB sharing for the specified share.
271  */
272 static int
273 smb_enable_share(sa_share_impl_t impl_share)
274 {
275 	char *shareopts;
276 
277 	if (!smb_available())
278 		return (SA_SYSTEM_ERR);
279 
280 	if (smb_is_share_active(impl_share))
281 		smb_disable_share(impl_share);
282 
283 	shareopts = FSINFO(impl_share, smb_fstype)->shareopts;
284 	if (shareopts == NULL) /* on/off */
285 		return (SA_SYSTEM_ERR);
286 
287 	if (strcmp(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
299 smb_disable_share_one(const char *sharename)
300 {
301 	int rc;
302 	char *argv[7];
303 
304 	/* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */
305 	argv[0] = NET_CMD_PATH;
306 	argv[1] = (char *)"-S";
307 	argv[2] = NET_CMD_ARG_HOST;
308 	argv[3] = (char *)"usershare";
309 	argv[4] = (char *)"delete";
310 	argv[5] = (char *)sharename;
311 	argv[6] = NULL;
312 
313 	rc = libzfs_run_process(argv[0], argv, 0);
314 	if (rc < 0)
315 		return (SA_SYSTEM_ERR);
316 	else
317 		return (SA_OK);
318 }
319 
320 /*
321  * Disables SMB sharing for the specified share.
322  */
323 static int
324 smb_disable_share(sa_share_impl_t impl_share)
325 {
326 	smb_share_t *shares = smb_shares;
327 
328 	if (!smb_available()) {
329 		/*
330 		 * The share can't possibly be active, so nothing
331 		 * needs to be done to disable it.
332 		 */
333 		return (SA_OK);
334 	}
335 
336 	while (shares != NULL) {
337 		if (strcmp(impl_share->sa_mountpoint, shares->path) == 0)
338 			return (smb_disable_share_one(shares->name));
339 
340 		shares = shares->next;
341 	}
342 
343 	return (SA_OK);
344 }
345 
346 /*
347  * Checks whether the specified SMB share options are syntactically correct.
348  */
349 static int
350 smb_validate_shareopts(const char *shareopts)
351 {
352 	/* TODO: Accept 'name' and sec/acl (?) */
353 	if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0))
354 		return (SA_OK);
355 
356 	return (SA_SYNTAX_ERR);
357 }
358 
359 /*
360  * Checks whether a share is currently active.
361  */
362 static boolean_t
363 smb_is_share_active(sa_share_impl_t impl_share)
364 {
365 	smb_share_t *iter = smb_shares;
366 
367 	if (!smb_available())
368 		return (B_FALSE);
369 
370 	/* Retrieve the list of (possible) active shares */
371 	smb_retrieve_shares();
372 
373 	while (iter != NULL) {
374 		if (strcmp(impl_share->sa_mountpoint, iter->path) == 0)
375 			return (B_TRUE);
376 
377 		iter = iter->next;
378 	}
379 
380 	return (B_FALSE);
381 }
382 
383 /*
384  * Called to update a share's options. A share's options might be out of
385  * date if the share was loaded from disk and the "sharesmb" dataset
386  * property has changed in the meantime. This function also takes care
387  * of re-enabling the share if necessary.
388  */
389 static int
390 smb_update_shareopts(sa_share_impl_t impl_share, const char *shareopts)
391 {
392 	if (!impl_share)
393 		return (SA_SYSTEM_ERR);
394 
395 	FSINFO(impl_share, smb_fstype)->shareopts = (char *)shareopts;
396 	return (SA_OK);
397 }
398 
399 static int
400 smb_update_shares(void)
401 {
402 	/* Not implemented */
403 	return (0);
404 }
405 
406 /*
407  * Clears a share's SMB options. Used by libshare to
408  * clean up shares that are about to be free()'d.
409  */
410 static void
411 smb_clear_shareopts(sa_share_impl_t impl_share)
412 {
413 	FSINFO(impl_share, smb_fstype)->shareopts = NULL;
414 }
415 
416 static const sa_share_ops_t smb_shareops = {
417 	.enable_share = smb_enable_share,
418 	.disable_share = smb_disable_share,
419 	.is_shared = smb_is_share_active,
420 
421 	.validate_shareopts = smb_validate_shareopts,
422 	.update_shareopts = smb_update_shareopts,
423 	.clear_shareopts = smb_clear_shareopts,
424 	.commit_shares = smb_update_shares,
425 };
426 
427 /*
428  * Provides a convenient wrapper for determining SMB availability
429  */
430 static boolean_t
431 smb_available(void)
432 {
433 	struct stat statbuf;
434 
435 	if (lstat(SHARE_DIR, &statbuf) != 0 ||
436 	    !S_ISDIR(statbuf.st_mode))
437 		return (B_FALSE);
438 
439 	if (access(NET_CMD_PATH, F_OK) != 0)
440 		return (B_FALSE);
441 
442 	return (B_TRUE);
443 }
444 
445 /*
446  * Initializes the SMB functionality of libshare.
447  */
448 void
449 libshare_smb_init(void)
450 {
451 	smb_fstype = register_fstype("smb", &smb_shareops);
452 }
453