468 lines
10 KiB
C
468 lines
10 KiB
C
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
*
|
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
* or http://www.opensolaris.org/os/licensing.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
*
|
|
* When distributing Covered Code, include this CDDL HEADER in each
|
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
* If applicable, add the following below this CDDL HEADER, with the
|
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
*
|
|
* CDDL HEADER END
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
|
|
* Copyright (c) 2011,2012 Turbo Fredriksson <turbo@bayour.com>, based on nfs.c
|
|
* by Gunnar Beutner
|
|
*
|
|
* This is an addition to the zfs device driver to add, modify and remove SMB
|
|
* shares using the 'net share' command that comes with Samba.
|
|
*
|
|
* TESTING
|
|
* Make sure that samba listens to 'localhost' (127.0.0.1) and that the options
|
|
* 'usershare max shares' and 'usershare owner only' have been reviewed/set
|
|
* accordingly (see zfs(8) for information).
|
|
*
|
|
* Once configuration in samba have been done, test that this
|
|
* works with the following three commands (in this case, my ZFS
|
|
* filesystem is called 'share/Test1'):
|
|
*
|
|
* (root)# net -U root -S 127.0.0.1 usershare add Test1 /share/Test1 \
|
|
* "Comment: /share/Test1" "Everyone:F"
|
|
* (root)# net usershare list | grep -i test
|
|
* (root)# net -U root -S 127.0.0.1 usershare delete Test1
|
|
*
|
|
* The first command will create a user share that gives everyone full access.
|
|
* To limit the access below that, use normal UNIX commands (chmod, chown etc).
|
|
*/
|
|
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <libzfs.h>
|
|
#include <libshare.h>
|
|
#include "libshare_impl.h"
|
|
#include "smb.h"
|
|
|
|
static boolean_t smb_available(void);
|
|
|
|
static sa_fstype_t *smb_fstype;
|
|
|
|
smb_share_t *smb_shares;
|
|
|
|
/*
|
|
* Retrieve the list of SMB shares.
|
|
*/
|
|
static int
|
|
smb_retrieve_shares(void)
|
|
{
|
|
int rc = SA_OK;
|
|
char file_path[PATH_MAX], line[512], *token, *key, *value;
|
|
char *dup_value = NULL, *path = NULL, *comment = NULL, *name = NULL;
|
|
char *guest_ok = NULL;
|
|
DIR *shares_dir;
|
|
FILE *share_file_fp = NULL;
|
|
struct dirent *directory;
|
|
struct stat eStat;
|
|
smb_share_t *shares, *new_shares = NULL;
|
|
|
|
/* opendir(), stat() */
|
|
shares_dir = opendir(SHARE_DIR);
|
|
if (shares_dir == NULL)
|
|
return (SA_SYSTEM_ERR);
|
|
|
|
/* Go through the directory, looking for shares */
|
|
while ((directory = readdir(shares_dir))) {
|
|
if (directory->d_name[0] == '.')
|
|
continue;
|
|
|
|
snprintf(file_path, sizeof (file_path),
|
|
"%s/%s", SHARE_DIR, directory->d_name);
|
|
|
|
if (stat(file_path, &eStat) == -1) {
|
|
rc = SA_SYSTEM_ERR;
|
|
goto out;
|
|
}
|
|
|
|
if (!S_ISREG(eStat.st_mode))
|
|
continue;
|
|
|
|
if ((share_file_fp = fopen(file_path, "r")) == NULL) {
|
|
rc = SA_SYSTEM_ERR;
|
|
goto out;
|
|
}
|
|
|
|
name = strdup(directory->d_name);
|
|
if (name == NULL) {
|
|
rc = SA_NO_MEMORY;
|
|
goto out;
|
|
}
|
|
|
|
while (fgets(line, sizeof (line), share_file_fp)) {
|
|
if (line[0] == '#')
|
|
continue;
|
|
|
|
/* Trim trailing new-line character(s). */
|
|
while (line[strlen(line) - 1] == '\r' ||
|
|
line[strlen(line) - 1] == '\n')
|
|
line[strlen(line) - 1] = '\0';
|
|
|
|
/* Split the line in two, separated by '=' */
|
|
token = strchr(line, '=');
|
|
if (token == NULL)
|
|
continue;
|
|
|
|
key = line;
|
|
value = token + 1;
|
|
*token = '\0';
|
|
|
|
dup_value = strdup(value);
|
|
if (dup_value == NULL) {
|
|
rc = SA_NO_MEMORY;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp(key, "path") == 0) {
|
|
free(path);
|
|
path = dup_value;
|
|
} else if (strcmp(key, "comment") == 0) {
|
|
free(comment);
|
|
comment = dup_value;
|
|
} else if (strcmp(key, "guest_ok") == 0) {
|
|
free(guest_ok);
|
|
guest_ok = dup_value;
|
|
} else
|
|
free(dup_value);
|
|
|
|
dup_value = NULL;
|
|
|
|
if (path == NULL || comment == NULL || guest_ok == NULL)
|
|
continue; /* Incomplete share definition */
|
|
else {
|
|
shares = (smb_share_t *)
|
|
malloc(sizeof (smb_share_t));
|
|
if (shares == NULL) {
|
|
rc = SA_NO_MEMORY;
|
|
goto out;
|
|
}
|
|
|
|
(void) strlcpy(shares->name, name,
|
|
sizeof (shares->name));
|
|
|
|
(void) strlcpy(shares->path, path,
|
|
sizeof (shares->path));
|
|
|
|
(void) strlcpy(shares->comment, comment,
|
|
sizeof (shares->comment));
|
|
|
|
shares->guest_ok = atoi(guest_ok);
|
|
|
|
shares->next = new_shares;
|
|
new_shares = shares;
|
|
|
|
free(path);
|
|
free(comment);
|
|
free(guest_ok);
|
|
|
|
path = NULL;
|
|
comment = NULL;
|
|
guest_ok = NULL;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (share_file_fp != NULL) {
|
|
fclose(share_file_fp);
|
|
share_file_fp = NULL;
|
|
}
|
|
|
|
free(name);
|
|
free(path);
|
|
free(comment);
|
|
free(guest_ok);
|
|
|
|
name = NULL;
|
|
path = NULL;
|
|
comment = NULL;
|
|
guest_ok = NULL;
|
|
}
|
|
closedir(shares_dir);
|
|
|
|
smb_shares = new_shares;
|
|
|
|
return (rc);
|
|
}
|
|
|
|
/*
|
|
* Used internally by smb_enable_share to enable sharing for a single host.
|
|
*/
|
|
static int
|
|
smb_enable_share_one(const char *sharename, const char *sharepath)
|
|
{
|
|
char *argv[10], *pos;
|
|
char name[SMB_NAME_MAX], comment[SMB_COMMENT_MAX];
|
|
int rc;
|
|
|
|
/* Support ZFS share name regexp '[[:alnum:]_-.: ]' */
|
|
strlcpy(name, sharename, sizeof (name));
|
|
name [sizeof (name)-1] = '\0';
|
|
|
|
pos = name;
|
|
while (*pos != '\0') {
|
|
switch (*pos) {
|
|
case '/':
|
|
case '-':
|
|
case ':':
|
|
case ' ':
|
|
*pos = '_';
|
|
}
|
|
|
|
++pos;
|
|
}
|
|
|
|
/*
|
|
* CMD: net -S NET_CMD_ARG_HOST usershare add Test1 /share/Test1 \
|
|
* "Comment" "Everyone:F"
|
|
*/
|
|
snprintf(comment, sizeof (comment), "Comment: %s", sharepath);
|
|
|
|
argv[0] = NET_CMD_PATH;
|
|
argv[1] = (char *)"-S";
|
|
argv[2] = NET_CMD_ARG_HOST;
|
|
argv[3] = (char *)"usershare";
|
|
argv[4] = (char *)"add";
|
|
argv[5] = (char *)name;
|
|
argv[6] = (char *)sharepath;
|
|
argv[7] = (char *)comment;
|
|
argv[8] = "Everyone:F";
|
|
argv[9] = NULL;
|
|
|
|
rc = libzfs_run_process(argv[0], argv, 0);
|
|
if (rc < 0)
|
|
return (SA_SYSTEM_ERR);
|
|
|
|
/* Reload the share file */
|
|
(void) smb_retrieve_shares();
|
|
|
|
return (SA_OK);
|
|
}
|
|
|
|
/*
|
|
* Enables SMB sharing for the specified share.
|
|
*/
|
|
static int
|
|
smb_enable_share(sa_share_impl_t impl_share)
|
|
{
|
|
char *shareopts;
|
|
|
|
if (!smb_available())
|
|
return (SA_SYSTEM_ERR);
|
|
|
|
shareopts = FSINFO(impl_share, smb_fstype)->shareopts;
|
|
if (shareopts == NULL) /* on/off */
|
|
return (SA_SYSTEM_ERR);
|
|
|
|
if (strcmp(shareopts, "off") == 0)
|
|
return (SA_OK);
|
|
|
|
/* Magic: Enable (i.e., 'create new') share */
|
|
return (smb_enable_share_one(impl_share->dataset,
|
|
impl_share->sharepath));
|
|
}
|
|
|
|
/*
|
|
* Used internally by smb_disable_share to disable sharing for a single host.
|
|
*/
|
|
static int
|
|
smb_disable_share_one(const char *sharename)
|
|
{
|
|
int rc;
|
|
char *argv[7];
|
|
|
|
/* CMD: net -S NET_CMD_ARG_HOST usershare delete Test1 */
|
|
argv[0] = NET_CMD_PATH;
|
|
argv[1] = (char *)"-S";
|
|
argv[2] = NET_CMD_ARG_HOST;
|
|
argv[3] = (char *)"usershare";
|
|
argv[4] = (char *)"delete";
|
|
argv[5] = strdup(sharename);
|
|
argv[6] = NULL;
|
|
|
|
rc = libzfs_run_process(argv[0], argv, 0);
|
|
if (rc < 0)
|
|
return (SA_SYSTEM_ERR);
|
|
else
|
|
return (SA_OK);
|
|
}
|
|
|
|
/*
|
|
* Disables SMB sharing for the specified share.
|
|
*/
|
|
static int
|
|
smb_disable_share(sa_share_impl_t impl_share)
|
|
{
|
|
smb_share_t *shares = smb_shares;
|
|
|
|
if (!smb_available()) {
|
|
/*
|
|
* The share can't possibly be active, so nothing
|
|
* needs to be done to disable it.
|
|
*/
|
|
return (SA_OK);
|
|
}
|
|
|
|
while (shares != NULL) {
|
|
if (strcmp(impl_share->sharepath, shares->path) == 0)
|
|
return (smb_disable_share_one(shares->name));
|
|
|
|
shares = shares->next;
|
|
}
|
|
|
|
return (SA_OK);
|
|
}
|
|
|
|
/*
|
|
* Checks whether the specified SMB share options are syntactically correct.
|
|
*/
|
|
static int
|
|
smb_validate_shareopts(const char *shareopts)
|
|
{
|
|
/* TODO: Accept 'name' and sec/acl (?) */
|
|
if ((strcmp(shareopts, "off") == 0) || (strcmp(shareopts, "on") == 0))
|
|
return (SA_OK);
|
|
|
|
return (SA_SYNTAX_ERR);
|
|
}
|
|
|
|
/*
|
|
* Checks whether a share is currently active.
|
|
*/
|
|
static boolean_t
|
|
smb_is_share_active(sa_share_impl_t impl_share)
|
|
{
|
|
smb_share_t *iter = smb_shares;
|
|
|
|
if (!smb_available())
|
|
return (B_FALSE);
|
|
|
|
/* Retrieve the list of (possible) active shares */
|
|
smb_retrieve_shares();
|
|
|
|
while (iter != NULL) {
|
|
if (strcmp(impl_share->sharepath, iter->path) == 0)
|
|
return (B_TRUE);
|
|
|
|
iter = iter->next;
|
|
}
|
|
|
|
return (B_FALSE);
|
|
}
|
|
|
|
/*
|
|
* Called to update a share's options. A share's options might be out of
|
|
* date if the share was loaded from disk and the "sharesmb" dataset
|
|
* property has changed in the meantime. This function also takes care
|
|
* of re-enabling the share if necessary.
|
|
*/
|
|
static int
|
|
smb_update_shareopts(sa_share_impl_t impl_share, const char *resource,
|
|
const char *shareopts)
|
|
{
|
|
char *shareopts_dup;
|
|
boolean_t needs_reshare = B_FALSE;
|
|
char *old_shareopts;
|
|
|
|
if (!impl_share)
|
|
return (SA_SYSTEM_ERR);
|
|
|
|
FSINFO(impl_share, smb_fstype)->active =
|
|
smb_is_share_active(impl_share);
|
|
|
|
old_shareopts = FSINFO(impl_share, smb_fstype)->shareopts;
|
|
|
|
if (FSINFO(impl_share, smb_fstype)->active && old_shareopts != NULL &&
|
|
strcmp(old_shareopts, shareopts) != 0) {
|
|
needs_reshare = B_TRUE;
|
|
smb_disable_share(impl_share);
|
|
}
|
|
|
|
shareopts_dup = strdup(shareopts);
|
|
|
|
if (shareopts_dup == NULL)
|
|
return (SA_NO_MEMORY);
|
|
|
|
if (old_shareopts != NULL)
|
|
free(old_shareopts);
|
|
|
|
FSINFO(impl_share, smb_fstype)->shareopts = shareopts_dup;
|
|
|
|
if (needs_reshare)
|
|
smb_enable_share(impl_share);
|
|
|
|
return (SA_OK);
|
|
}
|
|
|
|
/*
|
|
* Clears a share's SMB options. Used by libshare to
|
|
* clean up shares that are about to be free()'d.
|
|
*/
|
|
static void
|
|
smb_clear_shareopts(sa_share_impl_t impl_share)
|
|
{
|
|
free(FSINFO(impl_share, smb_fstype)->shareopts);
|
|
FSINFO(impl_share, smb_fstype)->shareopts = NULL;
|
|
}
|
|
|
|
static const sa_share_ops_t smb_shareops = {
|
|
.enable_share = smb_enable_share,
|
|
.disable_share = smb_disable_share,
|
|
|
|
.validate_shareopts = smb_validate_shareopts,
|
|
.update_shareopts = smb_update_shareopts,
|
|
.clear_shareopts = smb_clear_shareopts,
|
|
};
|
|
|
|
/*
|
|
* Provides a convenient wrapper for determining SMB availability
|
|
*/
|
|
static boolean_t
|
|
smb_available(void)
|
|
{
|
|
struct stat statbuf;
|
|
|
|
if (lstat(SHARE_DIR, &statbuf) != 0 ||
|
|
!S_ISDIR(statbuf.st_mode))
|
|
return (B_FALSE);
|
|
|
|
if (access(NET_CMD_PATH, F_OK) != 0)
|
|
return (B_FALSE);
|
|
|
|
return (B_TRUE);
|
|
}
|
|
|
|
/*
|
|
* Initializes the SMB functionality of libshare.
|
|
*/
|
|
void
|
|
libshare_smb_init(void)
|
|
{
|
|
smb_fstype = register_fstype("smb", &smb_shareops);
|
|
}
|