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