Path: blob/main/sys/contrib/openzfs/lib/libshare/os/linux/smb.c
48546 views
// SPDX-License-Identifier: CDDL-1.01/*2* CDDL HEADER START3*4* The contents of this file are subject to the terms of the5* 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.LICENSE9* or https://opensource.org/licenses/CDDL-1.0.10* See the License for the specific language governing permissions11* and limitations under the License.12*13* When distributing Covered Code, include this CDDL HEADER in each14* file and include the License file at usr/src/OPENSOLARIS.LICENSE.15* If applicable, add the following below this CDDL HEADER, with the16* fields enclosed by brackets "[]" replaced with your own identifying17* information: Portions Copyright [yyyy] [name of copyright owner]18*19* CDDL HEADER END20*/2122/*23* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.24* Copyright (c) 2011,2012 Turbo Fredriksson <[email protected]>, based on nfs.c25* by Gunnar Beutner26* 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 SMB29* shares using the 'net share' command that comes with Samba.30*31* TESTING32* Make sure that samba listens to 'localhost' (127.0.0.1) and that the options33* 'usershare max shares' and 'usershare owner only' have been reviewed/set34* accordingly (see zfs(8) for information).35*36* Once configuration in samba have been done, test that this37* works with the following three commands (in this case, my ZFS38* 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 test43* (root)# net -U root -S 127.0.0.1 usershare delete Test144*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*/4849#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"6364static boolean_t smb_available(void);6566static smb_share_t *smb_shares;67static int smb_disable_share(sa_share_impl_t impl_share);68static boolean_t smb_is_share_active(sa_share_impl_t impl_share);6970/*71* Retrieve the list of SMB shares.72*/73static int74smb_retrieve_shares(void)75{76int rc = SA_OK;77char file_path[PATH_MAX], line[512], *token, *key, *value;78char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL;79char *guest_ok = NULL;80DIR *shares_dir;81FILE *share_file_fp = NULL;82struct dirent *directory;83struct stat eStat;84smb_share_t *shares, *new_shares = NULL;8586/* opendir(), stat() */87shares_dir = opendir(SHARE_DIR);88if (shares_dir == NULL)89return (SA_SYSTEM_ERR);9091/* Go through the directory, looking for shares */92while ((directory = readdir(shares_dir))) {93int fd;9495if (directory->d_name[0] == '.')96continue;9798snprintf(file_path, sizeof (file_path),99"%s/%s", SHARE_DIR, directory->d_name);100101if ((fd = open(file_path, O_RDONLY | O_CLOEXEC)) == -1) {102rc = SA_SYSTEM_ERR;103goto out;104}105106if (fstat(fd, &eStat) == -1) {107close(fd);108rc = SA_SYSTEM_ERR;109goto out;110}111112if (!S_ISREG(eStat.st_mode)) {113close(fd);114continue;115}116117if ((share_file_fp = fdopen(fd, "r")) == NULL) {118close(fd);119rc = SA_SYSTEM_ERR;120goto out;121}122123name = strdup(directory->d_name);124if (name == NULL) {125rc = SA_NO_MEMORY;126goto out;127}128129while (fgets(line, sizeof (line), share_file_fp)) {130if (line[0] == '#')131continue;132133/* Trim trailing new-line character(s). */134while (line[strlen(line) - 1] == '\r' ||135line[strlen(line) - 1] == '\n')136line[strlen(line) - 1] = '\0';137138/* Split the line in two, separated by '=' */139token = strchr(line, '=');140if (token == NULL)141continue;142143key = line;144value = token + 1;145*token = '\0';146147dup_value = strdup(value);148if (dup_value == NULL) {149rc = SA_NO_MEMORY;150goto out;151}152153if (strcmp(key, "path") == 0) {154free(path);155path = dup_value;156} else if (strcmp(key, "comment") == 0) {157free(comment);158comment = dup_value;159} else if (strcmp(key, "guest_ok") == 0) {160free(guest_ok);161guest_ok = dup_value;162} else163free(dup_value);164165dup_value = NULL;166167if (path == NULL || comment == NULL || guest_ok == NULL)168continue; /* Incomplete share definition */169else {170shares = (smb_share_t *)171malloc(sizeof (smb_share_t));172if (shares == NULL) {173rc = SA_NO_MEMORY;174goto out;175}176177(void) strlcpy(shares->name, name,178sizeof (shares->name));179180(void) strlcpy(shares->path, path,181sizeof (shares->path));182183(void) strlcpy(shares->comment, comment,184sizeof (shares->comment));185186shares->guest_ok = atoi(guest_ok);187188shares->next = new_shares;189new_shares = shares;190191free(path);192free(comment);193free(guest_ok);194195path = NULL;196comment = NULL;197guest_ok = NULL;198}199}200201out:202if (share_file_fp != NULL) {203fclose(share_file_fp);204share_file_fp = NULL;205}206207free(name);208free(path);209free(comment);210free(guest_ok);211212name = NULL;213path = NULL;214comment = NULL;215guest_ok = NULL;216}217closedir(shares_dir);218219smb_shares = new_shares;220221return (rc);222}223224/*225* Used internally by smb_enable_share to enable sharing for a single host.226*/227static int228smb_enable_share_one(const char *sharename, const char *sharepath)229{230char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX];231232/* Support ZFS share name regexp '[[:alnum:]_-.: ]' */233strlcpy(name, sharename, sizeof (name));234for (char *itr = name; *itr != '\0'; ++itr)235switch (*itr) {236case '/':237case '-':238case ':':239case ' ':240*itr = '_';241}242243/*244* CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \245* "Comment" "Everyone:F"246*/247snprintf(comment, sizeof (comment), "Comment: %s", sharepath);248249char *argv[] = {250(char *)NET_CMD_PATH,251(char *)"-S",252(char *)NET_CMD_ARG_HOST,253(char *)"usershare",254(char *)"add",255name,256(char *)sharepath,257comment,258(char *)"Everyone:F",259NULL,260};261262if (libzfs_run_process(argv[0], argv, 0) != 0)263return (SA_SYSTEM_ERR);264265/* Reload the share file */266(void) smb_retrieve_shares();267268return (SA_OK);269}270271/*272* Enables SMB sharing for the specified share.273*/274static int275smb_enable_share(sa_share_impl_t impl_share)276{277if (!smb_available())278return (SA_SYSTEM_ERR);279280if (smb_is_share_active(impl_share))281smb_disable_share(impl_share);282283if (impl_share->sa_shareopts == NULL) /* on/off */284return (SA_SYSTEM_ERR);285286if (strcmp(impl_share->sa_shareopts, "off") == 0)287return (SA_OK);288289/* Magic: Enable (i.e., 'create new') share */290return (smb_enable_share_one(impl_share->sa_zfsname,291impl_share->sa_mountpoint));292}293294/*295* Used internally by smb_disable_share to disable sharing for a single host.296*/297static int298smb_disable_share_one(const char *sharename)299{300/* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */301char *argv[] = {302(char *)NET_CMD_PATH,303(char *)"-S",304(char *)NET_CMD_ARG_HOST,305(char *)"usershare",306(char *)"delete",307(char *)sharename,308NULL,309};310311if (libzfs_run_process(argv[0], argv, 0) != 0)312return (SA_SYSTEM_ERR);313else314return (SA_OK);315}316317/*318* Disables SMB sharing for the specified share.319*/320static int321smb_disable_share(sa_share_impl_t impl_share)322{323if (!smb_available()) {324/*325* The share can't possibly be active, so nothing326* needs to be done to disable it.327*/328return (SA_OK);329}330331for (const smb_share_t *i = smb_shares; i != NULL; i = i->next)332if (strcmp(impl_share->sa_mountpoint, i->path) == 0)333return (smb_disable_share_one(i->name));334335return (SA_OK);336}337338/*339* Checks whether the specified SMB share options are syntactically correct.340*/341static int342smb_validate_shareopts(const char *shareopts)343{344/* TODO: Accept 'name' and sec/acl (?) */345if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0))346return (SA_OK);347348return (SA_SYNTAX_ERR);349}350351/*352* Checks whether a share is currently active.353*/354static boolean_t355smb_is_share_active(sa_share_impl_t impl_share)356{357if (!smb_available())358return (B_FALSE);359360/* Retrieve the list of (possible) active shares */361smb_retrieve_shares();362363for (const smb_share_t *i = smb_shares; i != NULL; i = i->next)364if (strcmp(impl_share->sa_mountpoint, i->path) == 0)365return (B_TRUE);366367return (B_FALSE);368}369370static int371smb_update_shares(void)372{373/* Not implemented */374return (0);375}376377const 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,381382.validate_shareopts = smb_validate_shareopts,383.commit_shares = smb_update_shares,384};385386/*387* Provides a convenient wrapper for determining SMB availability388*/389static boolean_t390smb_available(void)391{392static int avail;393394if (!avail) {395struct stat statbuf;396397if (access(NET_CMD_PATH, F_OK) != 0 ||398lstat(SHARE_DIR, &statbuf) != 0 ||399!S_ISDIR(statbuf.st_mode))400avail = -1;401else402avail = 1;403}404405return (avail == 1);406}407408409