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 * 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 if (directory->d_name[0] == '.') 94 continue; 95 96 snprintf(file_path, sizeof (file_path), 97 "%s/%s", SHARE_DIR, directory->d_name); 98 99 if (stat(file_path, &eStat) == -1) { 100 rc = SA_SYSTEM_ERR; 101 goto out; 102 } 103 104 if (!S_ISREG(eStat.st_mode)) 105 continue; 106 107 if ((share_file_fp = fopen(file_path, "re")) == NULL) { 108 rc = SA_SYSTEM_ERR; 109 goto out; 110 } 111 112 name = strdup(directory->d_name); 113 if (name == NULL) { 114 rc = SA_NO_MEMORY; 115 goto out; 116 } 117 118 while (fgets(line, sizeof (line), share_file_fp)) { 119 if (line[0] == '#') 120 continue; 121 122 /* Trim trailing new-line character(s). */ 123 while (line[strlen(line) - 1] == '\r' || 124 line[strlen(line) - 1] == '\n') 125 line[strlen(line) - 1] = '\0'; 126 127 /* Split the line in two, separated by '=' */ 128 token = strchr(line, '='); 129 if (token == NULL) 130 continue; 131 132 key = line; 133 value = token + 1; 134 *token = '\0'; 135 136 dup_value = strdup(value); 137 if (dup_value == NULL) { 138 rc = SA_NO_MEMORY; 139 goto out; 140 } 141 142 if (strcmp(key, "path") == 0) { 143 free(path); 144 path = dup_value; 145 } else if (strcmp(key, "comment") == 0) { 146 free(comment); 147 comment = dup_value; 148 } else if (strcmp(key, "guest_ok") == 0) { 149 free(guest_ok); 150 guest_ok = dup_value; 151 } else 152 free(dup_value); 153 154 dup_value = NULL; 155 156 if (path == NULL || comment == NULL || guest_ok == NULL) 157 continue; /* Incomplete share definition */ 158 else { 159 shares = (smb_share_t *) 160 malloc(sizeof (smb_share_t)); 161 if (shares == NULL) { 162 rc = SA_NO_MEMORY; 163 goto out; 164 } 165 166 (void) strlcpy(shares->name, name, 167 sizeof (shares->name)); 168 169 (void) strlcpy(shares->path, path, 170 sizeof (shares->path)); 171 172 (void) strlcpy(shares->comment, comment, 173 sizeof (shares->comment)); 174 175 shares->guest_ok = atoi(guest_ok); 176 177 shares->next = new_shares; 178 new_shares = shares; 179 180 free(path); 181 free(comment); 182 free(guest_ok); 183 184 path = NULL; 185 comment = NULL; 186 guest_ok = NULL; 187 } 188 } 189 190 out: 191 if (share_file_fp != NULL) { 192 fclose(share_file_fp); 193 share_file_fp = NULL; 194 } 195 196 free(name); 197 free(path); 198 free(comment); 199 free(guest_ok); 200 201 name = NULL; 202 path = NULL; 203 comment = NULL; 204 guest_ok = NULL; 205 } 206 closedir(shares_dir); 207 208 smb_shares = new_shares; 209 210 return (rc); 211 } 212 213 /* 214 * Used internally by smb_enable_share to enable sharing for a single host. 215 */ 216 static int 217 smb_enable_share_one(const char *sharename, const char *sharepath) 218 { 219 char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX]; 220 221 /* Support ZFS share name regexp '[[:alnum:]_-.: ]' */ 222 strlcpy(name, sharename, sizeof (name)); 223 for (char *itr = name; *itr != '\0'; ++itr) 224 switch (*itr) { 225 case '/': 226 case '-': 227 case ':': 228 case ' ': 229 *itr = '_'; 230 } 231 232 /* 233 * CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \ 234 * "Comment" "Everyone:F" 235 */ 236 snprintf(comment, sizeof (comment), "Comment: %s", sharepath); 237 238 char *argv[] = { 239 (char *)NET_CMD_PATH, 240 (char *)"-S", 241 (char *)NET_CMD_ARG_HOST, 242 (char *)"usershare", 243 (char *)"add", 244 name, 245 (char *)sharepath, 246 comment, 247 (char *)"Everyone:F", 248 NULL, 249 }; 250 251 if (libzfs_run_process(argv[0], argv, 0) != 0) 252 return (SA_SYSTEM_ERR); 253 254 /* Reload the share file */ 255 (void) smb_retrieve_shares(); 256 257 return (SA_OK); 258 } 259 260 /* 261 * Enables SMB sharing for the specified share. 262 */ 263 static int 264 smb_enable_share(sa_share_impl_t impl_share) 265 { 266 if (!smb_available()) 267 return (SA_SYSTEM_ERR); 268 269 if (smb_is_share_active(impl_share)) 270 smb_disable_share(impl_share); 271 272 if (impl_share->sa_shareopts == NULL) /* on/off */ 273 return (SA_SYSTEM_ERR); 274 275 if (strcmp(impl_share->sa_shareopts, "off") == 0) 276 return (SA_OK); 277 278 /* Magic: Enable (i.e., 'create new') share */ 279 return (smb_enable_share_one(impl_share->sa_zfsname, 280 impl_share->sa_mountpoint)); 281 } 282 283 /* 284 * Used internally by smb_disable_share to disable sharing for a single host. 285 */ 286 static int 287 smb_disable_share_one(const char *sharename) 288 { 289 /* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */ 290 char *argv[] = { 291 (char *)NET_CMD_PATH, 292 (char *)"-S", 293 (char *)NET_CMD_ARG_HOST, 294 (char *)"usershare", 295 (char *)"delete", 296 (char *)sharename, 297 NULL, 298 }; 299 300 if (libzfs_run_process(argv[0], argv, 0) != 0) 301 return (SA_SYSTEM_ERR); 302 else 303 return (SA_OK); 304 } 305 306 /* 307 * Disables SMB sharing for the specified share. 308 */ 309 static int 310 smb_disable_share(sa_share_impl_t impl_share) 311 { 312 if (!smb_available()) { 313 /* 314 * The share can't possibly be active, so nothing 315 * needs to be done to disable it. 316 */ 317 return (SA_OK); 318 } 319 320 for (const smb_share_t *i = smb_shares; i != NULL; i = i->next) 321 if (strcmp(impl_share->sa_mountpoint, i->path) == 0) 322 return (smb_disable_share_one(i->name)); 323 324 return (SA_OK); 325 } 326 327 /* 328 * Checks whether the specified SMB share options are syntactically correct. 329 */ 330 static int 331 smb_validate_shareopts(const char *shareopts) 332 { 333 /* TODO: Accept 'name' and sec/acl (?) */ 334 if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0)) 335 return (SA_OK); 336 337 return (SA_SYNTAX_ERR); 338 } 339 340 /* 341 * Checks whether a share is currently active. 342 */ 343 static boolean_t 344 smb_is_share_active(sa_share_impl_t impl_share) 345 { 346 if (!smb_available()) 347 return (B_FALSE); 348 349 /* Retrieve the list of (possible) active shares */ 350 smb_retrieve_shares(); 351 352 for (const smb_share_t *i = smb_shares; i != NULL; i = i->next) 353 if (strcmp(impl_share->sa_mountpoint, i->path) == 0) 354 return (B_TRUE); 355 356 return (B_FALSE); 357 } 358 359 static int 360 smb_update_shares(void) 361 { 362 /* Not implemented */ 363 return (0); 364 } 365 366 const sa_fstype_t libshare_smb_type = { 367 .enable_share = smb_enable_share, 368 .disable_share = smb_disable_share, 369 .is_shared = smb_is_share_active, 370 371 .validate_shareopts = smb_validate_shareopts, 372 .commit_shares = smb_update_shares, 373 }; 374 375 /* 376 * Provides a convenient wrapper for determining SMB availability 377 */ 378 static boolean_t 379 smb_available(void) 380 { 381 static int avail; 382 383 if (!avail) { 384 struct stat statbuf; 385 386 if (access(NET_CMD_PATH, F_OK) != 0 || 387 lstat(SHARE_DIR, &statbuf) != 0 || 388 !S_ISDIR(statbuf.st_mode)) 389 avail = -1; 390 else 391 avail = 1; 392 } 393 394 return (avail == 1); 395 } 396