/*
* 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
* 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 2015 Nexenta Systems, Inc. All rights reserved.
*/
/*
*/
#include <errno.h>
#include <synch.h>
#include <stdlib.h>
#include <strings.h>
#include <syslog.h>
#include <thread.h>
#include <pthread.h>
#include <assert.h>
#include <libshare.h>
#include <libzfs.h>
#include <priv_utils.h>
#include <unistd.h>
#include <pwd.h>
#include <signal.h>
#include <dirent.h>
#include <dlfcn.h>
#include <smbsrv/libsmbns.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/smb_share.h>
#include <mlsvc.h>
#include <dfs.h>
typedef struct smb_transient {
char *name;
char *cmnt;
char *path;
char drive;
};
static struct {
char *value;
} cscopt[] = {
{ "disabled", SMB_SHRF_CSC_DISABLED },
{ "manual", SMB_SHRF_CSC_MANUAL },
{ "auto", SMB_SHRF_CSC_AUTO },
{ "vdo", SMB_SHRF_CSC_VDO }
};
/*
* Cache functions and vars
*/
/*
* Cache handle
*
* Shares cache is a hash table.
*
* sc_cache pointer to hash table handle
* sc_state cache state machine values
* sc_mtx protects handle fields
*/
typedef struct smb_shr_cache {
/*
* Cache states
*/
#define SMB_SHR_CACHE_STATE_NONE 0
/*
* Cache lock modes
*/
#define SMB_SHR_CACHE_RDLOCK 0
static uint32_t smb_shr_cache_create(void);
static void smb_shr_cache_destroy(void);
static uint32_t smb_shr_cache_lock(int);
static void smb_shr_cache_unlock(void);
static int smb_shr_cache_count(void);
static smb_share_t *smb_shr_cache_findent(char *);
static void smb_shr_cache_delent(char *);
static void smb_shr_cache_freent(HT_ITEM *);
static boolean_t smb_shr_is_empty(const char *);
static boolean_t smb_shr_is_dot_or_dotdot(const char *);
/*
* sharemgr functions
*/
static void smb_shr_sa_loadgrp(sa_group_t);
static uint32_t smb_shr_sa_loadbyname(char *);
/*
* .ZFS management functions
*/
static void smb_shr_zfs_add(smb_share_t *);
static void smb_shr_zfs_remove(smb_share_t *);
/*
* share publishing
*/
#define SMB_SHR_PUBLISH 0
typedef struct smb_shr_pitem {
char spi_op;
/*
* publish queue states
*/
#define SMB_SHR_PQS_NOQUEUE 0
/*
* share publishing queue
*/
typedef struct smb_shr_pqueue {
static int smb_shr_publisher_start(void);
static void smb_shr_publisher_stop(void);
static void smb_shr_publisher_queue(const char *, const char *, char);
static void *smb_shr_publisher(void *);
static void smb_shr_publisher_flush(list_t *);
static void smb_shr_publish(const char *, const char *);
static void smb_shr_unpublish(const char *, const char *);
/*
*/
static uint32_t smb_shr_add_transient(char *, char *, char *);
static int smb_shr_enable_all_privs(void);
static char **smb_shr_tokenize_cmd(char *);
static void smb_shr_sig_abnormal_term(int);
static void smb_shr_sig_child(int);
/*
* libshare handle and synchronization
*/
typedef struct smb_sa_handle {
/*
* Semaphore held during temporary, process-wide changes
* such as process privileges. It is a seamaphore and
* not a mutex so a child of fork can reset it.
*/
/*
* Creates and initializes the cache and starts the publisher
* thread.
*/
int
smb_shr_start(void)
{
int i;
if (smb_shr_cache_create() != NERR_Success)
return (ENOMEM);
continue;
if (nerr != NERR_Success)
return (ENOMEM);
}
return (smb_shr_publisher_start());
}
void
smb_shr_stop(void)
{
}
}
/*
* Get a handle and exclusive access to the libshare API.
*/
smb_shr_sa_enter(void)
{
if (!smb_sa_handle.sa_in_service) {
return (NULL);
}
}
return (NULL);
}
}
return (smb_sa_handle.sa_handle);
}
/*
* Release exclusive access to the libshare API.
*/
void
smb_shr_sa_exit(void)
{
}
/*
* Return the total number of shares
*/
int
smb_shr_count(void)
{
int n_shares = 0;
}
return (n_shares);
}
/*
* smb_shr_iterinit
*
* Initialize given iterator for traversing hash table.
*/
void
{
}
/*
* smb_shr_iterate
*
* Iterate on the shares in the hash table. The iterator must be initialized
* before the first iteration. On subsequent calls, the iterator must be
* passed unchanged.
*
* Returns NULL on failure or when all shares are visited, otherwise
* returns information of visited share.
*/
{
return (NULL);
}
}
return (share);
}
/*
* Adds the given share to cache, publishes the share in ADS
* if it has an AD container, calls kernel to take a hold on
* the shared file system. If it can't take a hold on the
* shared file system, it's either because shared directory
* does not exist or some other error has occurred, in any
* case the share is removed from the cache.
*
* If the specified share is an autohome share which already
* exists in the cache, just increments the reference count.
*/
{
int rc;
return (ERROR_INVALID_NAME);
return (NERR_InternalError);
if (cached_si) {
cached_si->shr_refcnt++;
} else {
}
return (status);
}
/*
* If share type is STYPE_DISKTREE then the path to the
* share should exist so that we can add the share to cache.
*/
if (rc != 0) {
return (NERR_ItemNotFound);
}
}
return (status);
}
/* don't hold the lock across door call */
/* send the share to kernel */
if (rc == 0) {
return (NERR_Success);
}
}
}
/*
* rc == ENOENT means the shared directory doesn't exist
*/
}
/*
* Removes the specified share from cache, removes it from AD
* if it has an AD container, and calls the kernel to release
* the hold on the shared file system.
*
* If this is an autohome share then decrement the reference
* count. If it reaches 0 then it proceeds with removing steps.
*/
{
return (ERROR_INVALID_NAME);
return (NERR_InternalError);
return (NERR_NetNameNotFound);
}
/* IPC$ share cannot be removed */
return (ERROR_ACCESS_DENIED);
}
if ((--si->shr_refcnt) > 0) {
return (NERR_Success);
}
}
/*
* to remove before cleanup of cache occurs.
*/
/* call kernel to release the hold on the shared file system */
(void) smb_kmod_unshare(shrlist);
}
if (dfsroot)
return (NERR_Success);
}
/*
* Rename a share. Check that the current name exists and the new name
* doesn't exist. The rename is performed by deleting the current share
* definition and creating a new share with the new name.
*/
{
return (ERROR_INVALID_NAME);
return (NERR_InternalError);
return (NERR_NetNameNotFound);
}
/* IPC$ share cannot be renamed */
return (ERROR_ACCESS_DENIED);
}
return (NERR_DuplicateShare);
}
return (status);
}
(void) smb_kmod_unshare(shrlist);
(void) smb_kmod_share(shrlist);
}
}
return (NERR_Success);
}
/*
* Load the information for the specified share into the supplied share
* info structure.
*
* First looks up the cache to see if the specified share exists, if there
* is a miss then it looks up sharemgr.
*/
{
return (NERR_NetNameNotFound);
return (status);
return (status);
}
/*
* Modifies an existing share. Properties that can be modified are:
*
* o comment
* o AD container
* o host access
* o abe
*/
{
return (NERR_InternalError);
return (NERR_NetNameNotFound);
}
/* IPC$ share cannot be modified */
return (ERROR_ACCESS_DENIED);
}
if (adc_changed) {
/* save current container - needed for unpublishing */
sizeof (old_container));
sizeof (si->shr_container));
}
if (access & SMB_SHRF_ACC_NONE)
sizeof (si->shr_access_none));
if (access & SMB_SHRF_ACC_RO)
sizeof (si->shr_access_ro));
if (access & SMB_SHRF_ACC_RW)
sizeof (si->shr_access_rw));
(void) smb_kmod_unshare(shrlist);
(void) smb_kmod_share(shrlist);
}
}
if (adc_changed) {
}
return (NERR_Success);
}
/*
* smb_shr_exists
*
* Returns B_TRUE if the share exists. Otherwise returns B_FALSE
*/
{
return (B_FALSE);
}
return (exists);
}
/*
* If the shared directory does not begin with a /, one will be
* inserted as a prefix. If ipaddr is not zero, then also return
* information about access based on the host level access lists, if
* present. Also return access check if there is an IP address and
* shr_accflags.
*
* The value of smb_chk_hostaccess is checked for an access match.
* -1 is wildcard match
* 0 is no match
* 1 is match
*
* Precedence is none is checked first followed by ro then rw if
* needed. If x is wildcard (< 0) then check to see if the other
* values are a match. If a match, that wins.
*/
{
int none = 0;
int ro = 0;
int rw = 0;
if (!smb_inet_iszero(ipaddr)) {
if ((flag & SMB_SHRF_ACC_NONE) != 0)
if ((flag & SMB_SHRF_ACC_RO) != 0)
if ((flag & SMB_SHRF_ACC_RW) != 0)
/* make first pass to get basic value */
if (none != 0)
else if (ro != 0)
else if (rw != 0)
/* make second pass to handle '*' case */
if (none < 0) {
if (ro > 0)
else if (rw > 0)
} else if (ro < 0) {
if (none > 0)
else if (rw > 0)
} else if (rw < 0) {
if (none > 0)
else if (ro > 0)
}
}
return (acc);
}
/*
* smb_shr_is_special
*
* Special share reserved for interprocess communication (IPC$) or
* remote administration of the server (ADMIN$). Can also refer to
* administrative shares such as C$, D$, E$, and so forth.
*/
int
{
int len;
return (0);
return (0);
return (STYPE_SPECIAL);
return (0);
}
/*
* smb_shr_is_restricted
*
* Check whether or not there is a restriction on a share. Restricted
* shares are generally STYPE_SPECIAL, for example, IPC$. All the
* administration share names are restricted: C$, D$ etc. Returns B_TRUE
* if the share is restricted. Otherwise B_FALSE is returned to indicate
* that there are no restrictions.
*/
{
static char *restricted[] = {
"IPC$"
};
int i;
return (B_FALSE);
for (i = 0; i < sizeof (restricted)/sizeof (restricted[0]); i++) {
return (B_TRUE);
}
return (smb_shr_is_admin(sharename));
}
/*
* smb_shr_is_admin
*
* Check whether or not access to the share should be restricted to
* administrators. This is a bit of a hack because what we're doing
* is checking for the default admin shares: C$, D$ etc.. There are
* other shares that have restrictions: see smb_shr_is_restricted().
*
* Returns B_TRUE if the shares is an admin share. Otherwise B_FALSE
* is returned to indicate that there are no restrictions.
*/
{
return (B_FALSE);
return (B_TRUE);
}
return (B_FALSE);
}
char
{
int i;
return ('\0');
continue;
}
return ('\0');
}
/*
* Returns true if the specified directory is empty,
* otherwise returns false.
*/
static boolean_t
{
return (B_TRUE);
return (B_TRUE);
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Returns true if name is "." or "..", otherwise returns false.
*/
static boolean_t
{
if (*name != '.')
return (B_FALSE);
return (B_TRUE);
return (B_FALSE);
}
/*
* smb_shr_get_realpath
*
* Derive the real path for a share from the path provided by a client.
* For instance, the real path of C:\ may be /cvol or the real path of
*
* clntpath - path provided by the Windows client is in the
* format of <drive letter>:\<dir>
* realpath - path that will be stored as the directory field of
* the smb_share_t structure of the share.
* maxlen - maximum length of the realpath buffer
*
* Return LAN Manager network error code.
*/
{
const char *p;
int len;
++p;
else
p = clntpath;
return (NERR_Success);
}
void
{
int n = 0;
if (--offset > 0)
continue;
if (++n == LMSHARES_PER_REQUEST)
break;
}
}
}
/*
*
* Returns 0 on success. Otherwise non-zero for errors.
*/
int
{
int child_status;
return (-1);
*cmd = '\0';
(void) mutex_lock(&smb_shr_exec_mtx);
case SMB_EXEC_MAP:
break;
case SMB_EXEC_UNMAP:
break;
default:
(void) mutex_unlock(&smb_shr_exec_mtx);
return (-1);
}
(void) mutex_unlock(&smb_shr_exec_mtx);
if (*cmd == '\0')
return (0);
if (smb_proc_takesem() != 0)
return (-1);
return (-1);
}
if (child_pid == 0) {
/* child process */
_exit(-1);
if (smb_shr_enable_all_privs())
_exit(-1);
(void) trim_whitespace(cmd);
free(cmd_tokens[0]);
_exit(-1);
}
}
_exit(-1);
}
/* parent process */
break;
/* continue if waitpid got interrupted by a signal */
errno = 0;
continue;
}
if (WIFEXITED(child_status))
return (WEXITSTATUS(child_status));
return (child_status);
}
/*
* Locking for process-wide settings (i.e. privileges)
*/
void
smb_proc_initsem(void)
{
}
int
smb_proc_takesem(void)
{
return (sema_wait(&smb_proc_sem));
}
void
smb_proc_givesem(void)
{
(void) sema_post(&smb_proc_sem);
}
/*
* ============================================
* ============================================
*/
/*
* Looks up the given share in the cache and return
* the info in 'si'
*/
static uint32_t
{
return (NERR_NetNameNotFound);
}
}
return (status);
}
/*
* Add IPC$ or Admin shares to the cache upon startup.
*/
static uint32_t
{
return (status);
if (cmnt)
if (path)
}
return (status);
}
/*
* ============================================
* Cache management functions
*
* All cache functions are private
* ============================================
*/
/*
* Create the share cache (hash table).
*/
static uint32_t
smb_shr_cache_create(void)
{
switch (smb_shr_cache.sc_state) {
case SMB_SHR_CACHE_STATE_NONE:
MAXNAMELEN, 0);
break;
}
smb_shr_cache.sc_nops = 0;
break;
default:
assert(0);
break;
}
return (status);
}
/*
* Destroy the share cache (hash table).
* destroying the cache.
*/
static void
smb_shr_cache_destroy(void)
{
while (smb_shr_cache.sc_nops > 0)
}
}
/*
* If the cache is in "created" state, lock the cache for read
*
* Whenever a lock is granted, the number of inflight cache
* operations is incremented.
*/
static uint32_t
{
return (NERR_InternalError);
}
/*
* Lock has to be taken outside the mutex otherwise
* there could be a deadlock
*/
if (mode == SMB_SHR_CACHE_RDLOCK)
else
return (NERR_Success);
}
/*
* Decrement the number of inflight operations and then unlock.
*/
static void
smb_shr_cache_unlock(void)
{
}
/*
* Return the total number of shares
*/
static int
smb_shr_cache_count(void)
{
}
/*
* looks up the given share name in the cache and if it
* finds a match returns a pointer to the cached entry.
* Note that since a pointer is returned this function
* MUST be protected by smb_shr_cache_lock/unlock pair
*/
static smb_share_t *
{
(void) smb_strlwr(sharename);
return (NULL);
}
/*
* the cache based on the given iterator.
*
* Calls to this function MUST be protected by
*/
static smb_share_t *
{
} else {
}
return (NULL);
}
/*
* Add the specified share to the cache. Memory needs to be allocated
* for the cache entry and the passed information is copied to the
* allocated space.
*/
static uint32_t
{
return (ERROR_NOT_ENOUGH_MEMORY);
== NULL) {
}
return (status);
}
/*
* Delete the specified share from the cache.
*/
static void
{
(void) smb_strlwr(sharename);
}
/*
* Call back to free the given cache entry.
*/
static void
{
}
/*
* ============================================
* Interfaces to sharemgr
*
* All functions in this section are private
* ============================================
*/
/*
* Load shares from sharemgr
*/
/*ARGSUSED*/
void *
{
char *gstate;
(void) mutex_lock(&smb_shr_exec_mtx);
(void) mutex_unlock(&smb_shr_exec_mtx);
return (NULL);
}
continue;
if (gdisabled)
continue;
}
}
return (NULL);
}
/*
* Load the shares contained in the specified group.
*
* Don't process groups on which the smb protocol is disabled.
* The top level ZFS group won't have the smb protocol enabled
* but sub-groups will.
*
* We will tolerate a limited number of errors and then give
* up on the current group. A typical error might be that the
* shared directory no longer exists.
*/
static void
{
int error_count = 0;
return;
++error_count;
break;
}
break;
}
}
/*
* Load a share definition from sharemgr and add it to the cache.
* If the share is already in the cache then it doesn't do anything.
*
* This function does not report duplicate shares as error since
* a share might have been added by smb_shr_get() while load is
* in progress.
*/
static uint32_t
{
char *sharename;
return (NERR_InternalError);
if (loaded)
return (NERR_Success);
return (status);
}
return (status);
}
return (NERR_Success);
}
static char *
{
return (val);
}
/*
* Read the specified share information from sharemgr and return
* it in the given smb_share_t structure.
*
* Shares read from sharemgr are marked as permanent/persistent.
*/
static uint32_t
{
char *path;
char *rname;
return (NERR_InternalError);
return (NERR_InternalError);
}
}
return (NERR_Success);
sizeof (si->shr_container));
}
}
}
}
}
}
sizeof (si->shr_access_none));
}
sizeof (si->shr_access_ro));
}
sizeof (si->shr_access_rw));
}
return (NERR_Success);
}
/*
* Map a client-side caching (CSC) option to the appropriate share
* flag. Only one option is allowed; an error will be logged if
* multiple options have been specified. We don't need to do anything
* about multiple values here because the SRVSVC will not recognize
* a value containing multiple flags and will return the default value.
*
* If the option value is not recognized, it will be ignored: invalid
* values will typically be caught and rejected by sharemgr.
*/
void
{
int i;
break;
}
}
case 0:
case SMB_SHRF_CSC_DISABLED:
case SMB_SHRF_CSC_MANUAL:
case SMB_SHRF_CSC_AUTO:
case SMB_SHRF_CSC_VDO:
break;
default:
break;
}
}
/*
* Return the option name for the first CSC flag (there should be only
* one) encountered in the share flags.
*/
char *
{
int i;
}
return (NULL);
}
/*
* specified flag based on the property's value.
*/
void
{
else
}
/*
* looks up sharemgr for the given share (resource) and loads
* the definition into cache if lookup is successful
*/
static uint32_t
{
return (NERR_InternalError);
return (NERR_NetNameNotFound);
}
return (NERR_InternalError);
}
return (status);
}
/*
* ============================================
* Share publishing functions
*
* All the functions are private
* ============================================
*/
static void
{
}
static void
{
}
/*
* In domain mode, put a share on the publisher queue.
* This is a no-op if the smb service is in Workgroup mode.
*/
static void
{
return;
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
return;
case SMB_SHR_PQS_READY:
case SMB_SHR_PQS_PUBLISHING:
break;
default:
return;
}
return;
sizeof (item->spi_container));
}
/*
* Publishing won't be activated if the smb service is running in
* Workgroup mode.
*/
static int
smb_shr_publisher_start(void)
{
int rc;
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
return (0);
return (-1);
}
(void) pthread_attr_init(&tattr);
(void) pthread_attr_destroy(&tattr);
return (rc);
}
static void
smb_shr_publisher_stop(void)
{
if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
return;
case SMB_SHR_PQS_READY:
case SMB_SHR_PQS_PUBLISHING:
break;
default:
break;
}
}
/*
* This is the publisher daemon thread. While running, the thread waits
* on a conditional variable until notified that a share needs to be
* [un]published or that the thread should be terminated.
*
* Entries may remain in the outgoing queue if the Active Directory
* service is inaccessible, in which case the thread wakes up every 60
* seconds to retry.
*/
/*ARGSUSED*/
static void *
{
return (NULL);
}
for (;;) {
if (list_is_empty(&publist)) {
} else {
break;
}
}
break;
}
/*
* Transfer queued items to the local list so that
* the mutex can be released.
*/
}
}
}
return (NULL);
}
/*
* Remove items from the specified queue and [un]publish them.
*/
static void
{
return;
}
else
}
}
/*
*/
static void
{
}
}
/*
* If the share path refers to a ZFS file system, add the
* to initialize quota support for the share.
*/
static void
{
int ret;
return;
return;
return;
}
errno = 0;
}
}
/*
* If the share path refers to a ZFS file system, remove the
* to end quota support for the share.
*/
static void
{
int ret;
return;
return;
return;
}
errno = 0;
}
}
/*
* If the share path refers to a ZFS file system, rename the
*/
static void
{
int ret;
return;
return;
return;
}
errno = 0;
}
/*
* Enable all privileges in the inheritable set to execute command.
*/
static int
smb_shr_enable_all_privs(void)
{
pset = priv_allocset();
return (-1);
return (-1);
}
return (-1);
}
return (0);
}
/*
* Tokenizes the command string and returns the list of tokens in an array.
*
* Returns NULL if there are no tokens.
*/
static char **
{
int argc, i;
return (NULL);
return (NULL);
if (*bp == ' ')
++argc;
return (NULL);
}
do {
break;
} while (*value == '\0');
break;
}
/* get the filename of the command from the path */
return (argv);
}
/*
* Expands the command string for the following substitution tokens:
*
* %U - Windows username
* %D - Name of the domain or workgroup of %U
* %h - The server hostname
* %M - The client hostname
* %L - The server NetBIOS name
* %m - The client NetBIOS name. This option is only valid for NetBIOS
* connections (port 139).
* %I - The IP address of the client machine
* %i - The local IP address to which the client is connected
* %S - The name of the share
* %P - The root directory of the share
* %u - The UID of the Unix user
*
* Returns 0 on success. Otherwise -1.
*/
static int
{
int i;
return (-1);
for (i = 1; cmd_toks[i]; i++) {
if (*fmt == '%') {
switch (*sub_chr) {
case 'U':
break;
case 'D':
break;
case 'h':
else
break;
case 'M':
else
break;
case 'L':
NETBIOS_NAME_SZ) != 0)
else
break;
case 'm':
else {
(void) smb_mbstowcs(wbuf,
SMB_PI_MAX_HOST - 1);
SMB_PI_MAX_HOST, OEM_CPG_850) == 0)
}
break;
case 'I':
!= NULL)
else
break;
case 'i':
!= NULL)
else
break;
case 'S':
break;
case 'P':
break;
case 'u':
break;
default:
/* unknown sub char */
break;
}
if (unknown)
} else /* first char of cmd's arg is not '%' char */
for (i = 1; cmd_toks[i]; i++)
return (-1);
}
}
return (0);
}
/*ARGSUSED*/
static void
{
/*
* signal.
*/
_exit(-1);
}
/*ARGSUSED*/
static void
{
/*
* Catch the signal and allow the exit status of the child process
* to be available for reaping.
*/
}
/*
* This is a temporary function which converts the given smb_share_t
* structure to the nvlist format that will be provided by libsharev2
*/
static int
{
char *csc;
int rc = 0;
return (rc);
return (rc);
}
return (rc);
}
/* global share properties */
/* smb protocol properties */
}
if (rc != 0)
else
return (rc);
}