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