]>
Commit | Line | Data |
---|---|---|
645fb9cc TF |
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 <turbo@bayour.com>, based on nfs.c | |
25 | * by Gunnar Beutner | |
26 | * | |
27 | * This is an addition to the zfs device driver to add, modify and remove SMB | |
28 | * shares using the 'net share' command that comes with Samba. | |
29 | ||
30 | * TESTING | |
31 | * Make sure that samba listens to 'localhost' (127.0.0.1) and that the options | |
32 | * 'usershare max shares' and 'usershare owner only' have been rewied/set | |
33 | * accordingly (see zfs(8) for information). | |
34 | * | |
35 | * Once configuration in samba have been done, test that this | |
36 | * works with the following three commands (in this case, my ZFS | |
37 | * filesystem is called 'share/Test1'): | |
38 | * | |
39 | * (root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \ | |
40 | * "Comment: /share/Test1" "Everyone:F" | |
41 | * (root)# net usershare list | grep -i test | |
42 | * (root)# net -U root -S 127.0.0.1 usershare delete Test1 | |
43 | * | |
44 | * The first command will create a user share that gives everyone full access. | |
45 | * To limit the access below that, use normal UNIX commands (chmod, chown etc). | |
46 | */ | |
47 | ||
48 | #include <time.h> | |
49 | #include <stdlib.h> | |
50 | #include <stdio.h> | |
51 | #include <strings.h> | |
52 | #include <fcntl.h> | |
53 | #include <sys/wait.h> | |
54 | #include <unistd.h> | |
55 | #include <dirent.h> | |
56 | #include <sys/types.h> | |
57 | #include <sys/stat.h> | |
58 | #include <libzfs.h> | |
59 | #include <libshare.h> | |
60 | #include "libshare_impl.h" | |
61 | #include "smb.h" | |
62 | ||
63 | static boolean_t smb_available(void); | |
64 | ||
65 | static sa_fstype_t *smb_fstype; | |
66 | ||
67 | /** | |
68 | * Retrieve the list of SMB shares. | |
69 | */ | |
70 | static int | |
71 | smb_retrieve_shares(void) | |
72 | { | |
73 | int rc = SA_OK; | |
74 | char file_path[PATH_MAX], line[512], *token, *key, *value; | |
75 | char *dup_value, *path = NULL, *comment = NULL, *name = NULL; | |
76 | char *guest_ok = NULL; | |
77 | DIR *shares_dir; | |
78 | FILE *share_file_fp = NULL; | |
79 | struct dirent *directory; | |
80 | struct stat eStat; | |
81 | smb_share_t *shares, *new_shares = NULL; | |
82 | ||
83 | /* opendir(), stat() */ | |
84 | shares_dir = opendir(SHARE_DIR); | |
85 | if (shares_dir == NULL) | |
86 | return SA_SYSTEM_ERR; | |
87 | ||
88 | /* Go through the directory, looking for shares */ | |
89 | while ((directory = readdir(shares_dir))) { | |
90 | if (directory->d_name[0] == '.') | |
91 | continue; | |
92 | ||
93 | snprintf(file_path, sizeof (file_path), | |
94 | "%s/%s", SHARE_DIR, directory->d_name); | |
95 | ||
96 | if (stat(file_path, &eStat) == -1) { | |
97 | rc = SA_SYSTEM_ERR; | |
98 | goto out; | |
99 | } | |
100 | ||
101 | if (!S_ISREG(eStat.st_mode)) | |
102 | continue; | |
103 | ||
104 | if ((share_file_fp = fopen(file_path, "r")) == NULL) { | |
105 | rc = SA_SYSTEM_ERR; | |
106 | goto out; | |
107 | } | |
108 | ||
109 | name = strdup(directory->d_name); | |
110 | if (name == NULL) { | |
111 | rc = SA_NO_MEMORY; | |
112 | goto out; | |
113 | } | |
114 | ||
115 | while (fgets(line, sizeof(line), share_file_fp)) { | |
116 | if (line[0] == '#') | |
117 | continue; | |
118 | ||
119 | /* Trim trailing new-line character(s). */ | |
120 | while (line[strlen(line) - 1] == '\r' || | |
121 | line[strlen(line) - 1] == '\n') | |
122 | line[strlen(line) - 1] = '\0'; | |
123 | ||
124 | /* Split the line in two, separated by '=' */ | |
125 | token = strchr(line, '='); | |
126 | if (token == NULL) | |
127 | continue; | |
128 | ||
129 | key = line; | |
130 | value = token + 1; | |
131 | *token = '\0'; | |
132 | ||
133 | dup_value = strdup(value); | |
134 | if (dup_value == NULL) { | |
135 | rc = SA_NO_MEMORY; | |
136 | goto out; | |
137 | } | |
138 | ||
139 | if (strcmp(key, "path") == 0) | |
140 | path = dup_value; | |
141 | if (strcmp(key, "comment") == 0) | |
142 | comment = dup_value; | |
143 | if (strcmp(key, "guest_ok") == 0) | |
144 | guest_ok = dup_value; | |
145 | ||
146 | if (path == NULL || comment == NULL || guest_ok == NULL) | |
147 | continue; /* Incomplete share definition */ | |
148 | else { | |
149 | shares = (smb_share_t *) | |
150 | malloc(sizeof (smb_share_t)); | |
151 | if (shares == NULL) { | |
152 | rc = SA_NO_MEMORY; | |
153 | goto out; | |
154 | } | |
155 | ||
156 | strncpy(shares->name, name, | |
157 | sizeof (shares->name)); | |
158 | shares->name [sizeof(shares->name)-1] = '\0'; | |
159 | ||
160 | strncpy(shares->path, path, | |
161 | sizeof (shares->path)); | |
162 | shares->path [sizeof(shares->path)-1] = '\0'; | |
163 | ||
164 | strncpy(shares->comment, comment, | |
165 | sizeof (shares->comment)); | |
166 | shares->comment[sizeof(shares->comment)-1]='\0'; | |
167 | ||
168 | shares->guest_ok = atoi(guest_ok); | |
169 | ||
170 | shares->next = new_shares; | |
171 | new_shares = shares; | |
172 | ||
173 | name = NULL; | |
174 | path = NULL; | |
175 | comment = NULL; | |
176 | guest_ok = NULL; | |
177 | } | |
178 | } | |
179 | ||
180 | out: | |
181 | if (share_file_fp != NULL) | |
182 | fclose(share_file_fp); | |
183 | ||
184 | free(name); | |
185 | free(path); | |
186 | free(comment); | |
187 | free(guest_ok); | |
188 | } | |
189 | closedir(shares_dir); | |
190 | ||
191 | smb_shares = new_shares; | |
192 | ||
193 | return rc; | |
194 | } | |
195 | ||
196 | /** | |
197 | * Used internally by smb_enable_share to enable sharing for a single host. | |
198 | */ | |
199 | static int | |
200 | smb_enable_share_one(const char *sharename, const char *sharepath) | |
201 | { | |
202 | char *argv[10], *pos; | |
203 | char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX]; | |
204 | int rc; | |
205 | ||
206 | /* Support ZFS share name regexp '[[:alnum:]_-.: ]' */ | |
207 | strncpy(name, sharename, sizeof(name)); | |
208 | name [sizeof(name)-1] = '\0'; | |
209 | ||
210 | pos = name; | |
211 | while (*pos != '\0') { | |
212 | switch (*pos) { | |
213 | case '/': | |
214 | case '-': | |
215 | case ':': | |
216 | case ' ': | |
217 | *pos = '_'; | |
218 | } | |
219 | ||
220 | ++pos; | |
221 | } | |
222 | ||
223 | /* CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \ | |
224 | * "Comment" "Everyone:F" */ | |
225 | snprintf(comment, sizeof(comment), "Comment: %s", sharepath); | |
226 | ||
227 | argv[0] = NET_CMD_PATH; | |
228 | argv[1] = (char*)"-S"; | |
229 | argv[2] = NET_CMD_ARG_HOST; | |
230 | argv[3] = (char*)"usershare"; | |
231 | argv[4] = (char*)"add"; | |
232 | argv[5] = (char*)name; | |
233 | argv[6] = (char*)sharepath; | |
234 | argv[7] = (char*)comment; | |
235 | argv[8] = "Everyone:F"; | |
236 | argv[9] = NULL; | |
237 | ||
238 | rc = libzfs_run_process(argv[0], argv, 0); | |
239 | if (rc < 0) | |
240 | return SA_SYSTEM_ERR; | |
241 | ||
242 | /* Reload the share file */ | |
243 | (void) smb_retrieve_shares(); | |
244 | ||
245 | return SA_OK; | |
246 | } | |
247 | ||
248 | /** | |
249 | * Enables SMB sharing for the specified share. | |
250 | */ | |
251 | static int | |
252 | smb_enable_share(sa_share_impl_t impl_share) | |
253 | { | |
254 | char *shareopts; | |
255 | ||
256 | if (!smb_available()) | |
257 | return SA_SYSTEM_ERR; | |
258 | ||
259 | shareopts = FSINFO(impl_share, smb_fstype)->shareopts; | |
260 | if (shareopts == NULL) /* on/off */ | |
261 | return SA_SYSTEM_ERR; | |
262 | ||
263 | if (strcmp(shareopts, "off") == 0) | |
264 | return SA_OK; | |
265 | ||
266 | /* Magic: Enable (i.e., 'create new') share */ | |
267 | return smb_enable_share_one(impl_share->dataset, impl_share->sharepath); | |
268 | } | |
269 | ||
270 | /** | |
271 | * Used internally by smb_disable_share to disable sharing for a single host. | |
272 | */ | |
273 | static int | |
274 | smb_disable_share_one(const char *sharename) | |
275 | { | |
276 | int rc; | |
277 | char *argv[7]; | |
278 | ||
279 | /* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */ | |
280 | argv[0] = NET_CMD_PATH; | |
281 | argv[1] = (char*)"-S"; | |
282 | argv[2] = NET_CMD_ARG_HOST; | |
283 | argv[3] = (char*)"usershare"; | |
284 | argv[4] = (char*)"delete"; | |
285 | argv[5] = strdup(sharename); | |
286 | argv[6] = NULL; | |
287 | ||
288 | rc = libzfs_run_process(argv[0], argv, 0); | |
289 | if (rc < 0) | |
290 | return SA_SYSTEM_ERR; | |
291 | else | |
292 | return SA_OK; | |
293 | } | |
294 | ||
295 | /** | |
296 | * Disables SMB sharing for the specified share. | |
297 | */ | |
298 | static int | |
299 | smb_disable_share(sa_share_impl_t impl_share) | |
300 | { | |
301 | smb_share_t *shares = smb_shares; | |
302 | ||
303 | if (!smb_available()) { | |
304 | /* | |
305 | * The share can't possibly be active, so nothing | |
306 | * needs to be done to disable it. | |
307 | */ | |
308 | return SA_OK; | |
309 | } | |
310 | ||
311 | while (shares != NULL) { | |
312 | if (strcmp(impl_share->sharepath, shares->path) == 0) | |
313 | return smb_disable_share_one(shares->name); | |
314 | ||
315 | shares = shares->next; | |
316 | } | |
317 | ||
318 | return SA_OK; | |
319 | } | |
320 | ||
321 | /** | |
322 | * Checks whether the specified SMB share options are syntactically correct. | |
323 | */ | |
324 | static int | |
325 | smb_validate_shareopts(const char *shareopts) | |
326 | { | |
327 | /* TODO: Accept 'name' and sec/acl (?) */ | |
328 | if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0)) | |
329 | return SA_OK; | |
330 | ||
331 | return SA_SYNTAX_ERR; | |
332 | } | |
333 | ||
334 | /** | |
335 | * Checks whether a share is currently active. | |
336 | */ | |
337 | static boolean_t | |
338 | smb_is_share_active(sa_share_impl_t impl_share) | |
339 | { | |
340 | if (!smb_available()) | |
341 | return B_FALSE; | |
342 | ||
343 | /* Retrieve the list of (possible) active shares */ | |
344 | smb_retrieve_shares(); | |
345 | ||
346 | while (smb_shares != NULL) { | |
347 | if (strcmp(impl_share->sharepath, smb_shares->path) == 0) | |
348 | return B_TRUE; | |
349 | ||
350 | smb_shares = smb_shares->next; | |
351 | } | |
352 | ||
353 | return B_FALSE; | |
354 | } | |
355 | ||
356 | /** | |
357 | * Called to update a share's options. A share's options might be out of | |
358 | * date if the share was loaded from disk and the "sharesmb" dataset | |
359 | * property has changed in the meantime. This function also takes care | |
360 | * of re-enabling the share if necessary. | |
361 | */ | |
362 | static int | |
363 | smb_update_shareopts(sa_share_impl_t impl_share, const char *resource, | |
364 | const char *shareopts) | |
365 | { | |
366 | char *shareopts_dup; | |
367 | boolean_t needs_reshare = B_FALSE; | |
368 | char *old_shareopts; | |
369 | ||
370 | if(!impl_share) | |
371 | return SA_SYSTEM_ERR; | |
372 | ||
373 | FSINFO(impl_share, smb_fstype)->active = | |
374 | smb_is_share_active(impl_share); | |
375 | ||
376 | old_shareopts = FSINFO(impl_share, smb_fstype)->shareopts; | |
377 | ||
378 | if (FSINFO(impl_share, smb_fstype)->active && old_shareopts != NULL && | |
379 | strcmp(old_shareopts, shareopts) != 0) { | |
380 | needs_reshare = B_TRUE; | |
381 | smb_disable_share(impl_share); | |
382 | } | |
383 | ||
384 | shareopts_dup = strdup(shareopts); | |
385 | ||
386 | if (shareopts_dup == NULL) | |
387 | return SA_NO_MEMORY; | |
388 | ||
389 | if (old_shareopts != NULL) | |
390 | free(old_shareopts); | |
391 | ||
392 | FSINFO(impl_share, smb_fstype)->shareopts = shareopts_dup; | |
393 | ||
394 | if (needs_reshare) | |
395 | smb_enable_share(impl_share); | |
396 | ||
397 | return SA_OK; | |
398 | } | |
399 | ||
400 | /** | |
401 | * Clears a share's SMB options. Used by libshare to | |
402 | * clean up shares that are about to be free()'d. | |
403 | */ | |
404 | static void | |
405 | smb_clear_shareopts(sa_share_impl_t impl_share) | |
406 | { | |
407 | free(FSINFO(impl_share, smb_fstype)->shareopts); | |
408 | FSINFO(impl_share, smb_fstype)->shareopts = NULL; | |
409 | } | |
410 | ||
411 | static const sa_share_ops_t smb_shareops = { | |
412 | .enable_share = smb_enable_share, | |
413 | .disable_share = smb_disable_share, | |
414 | ||
415 | .validate_shareopts = smb_validate_shareopts, | |
416 | .update_shareopts = smb_update_shareopts, | |
417 | .clear_shareopts = smb_clear_shareopts, | |
418 | }; | |
419 | ||
420 | /* | |
421 | * Provides a convenient wrapper for determining SMB availability | |
422 | */ | |
423 | static boolean_t | |
424 | smb_available(void) | |
425 | { | |
c06d4368 AX |
426 | struct stat statbuf; |
427 | ||
428 | if (lstat(SHARE_DIR, &statbuf) != 0 || | |
429 | !S_ISDIR(statbuf.st_mode)) | |
430 | return B_FALSE; | |
431 | ||
432 | if (access(NET_CMD_PATH, F_OK) != 0) | |
433 | return B_FALSE; | |
434 | ||
645fb9cc TF |
435 | return B_TRUE; |
436 | } | |
437 | ||
438 | /** | |
439 | * Initializes the SMB functionality of libshare. | |
440 | */ | |
441 | void | |
442 | libshare_smb_init(void) | |
443 | { | |
444 | smb_fstype = register_fstype("smb", &smb_shareops); | |
445 | } |