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 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 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 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 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 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 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 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 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 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