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