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
smb_retrieve_shares(void)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
smb_enable_share_one(const char * sharename,const char * sharepath)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
smb_enable_share(sa_share_impl_t impl_share)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
smb_disable_share_one(const char * sharename)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
smb_disable_share(sa_share_impl_t impl_share)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
smb_validate_shareopts(const char * shareopts)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
smb_is_share_active(sa_share_impl_t impl_share)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
smb_update_shares(void)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
smb_available(void)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