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 <[email protected]>, 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
smb_retrieve_shares(void)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
smb_enable_share_one(const char * sharename,const char * sharepath)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
smb_enable_share(sa_share_impl_t impl_share)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
smb_disable_share_one(const char * sharename)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
smb_disable_share(sa_share_impl_t impl_share)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
smb_validate_shareopts(const char * shareopts)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
smb_is_share_active(sa_share_impl_t impl_share)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
smb_update_shareopts(sa_share_impl_t impl_share,const char * shareopts)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
smb_update_shares(void)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
smb_clear_shareopts(sa_share_impl_t impl_share)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
smb_available(void)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
libshare_smb_init(void)450 libshare_smb_init(void)
451 {
452 smb_fstype = register_fstype("smb", &smb_shareops);
453 }
454