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