smb_init.c revision 55bf511df53aad0fdb7eb3fa349f0308cc05234c
/*
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/strsubr.h>
#include <sys/socketvar.h>
#include <sys/modctl.h>
#include <sys/cred.h>
#include <sys/ioccom.h>
#include <sys/priv.h>
#include <sys/policy.h>
#include <smbsrv/smb_incl.h>
#include <smbsrv/mlsvc.h>
#include <smbsrv/smb_door_svc.h>
#include <smbsrv/smb_ioctl.h>
#include <smbsrv/smb_kproto.h>
/*
* DDI entry points.
*/
static int smb_drv_attach(dev_info_t *, ddi_attach_cmd_t);
static int smb_drv_detach(dev_info_t *, ddi_detach_cmd_t);
static int smb_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int smb_drv_open(dev_t *, int, int, cred_t *);
static int smb_drv_close(dev_t, int, int, cred_t *);
static int smb_drv_busy(void);
static int smb_drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
/*
* module linkage info for the kernel
*/
static struct cb_ops cbops = {
smb_drv_open, /* cb_open */
smb_drv_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
smb_drv_ioctl, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_streamtab */
D_MP, /* cb_flag */
CB_REV, /* cb_rev */
nodev, /* cb_aread */
nodev, /* cb_awrite */
};
static struct dev_ops devops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
smb_drv_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
smb_drv_attach, /* devo_attach */
smb_drv_detach, /* devo_detach */
nodev, /* devo_reset */
&cbops, /* devo_cb_ops */
NULL, /* devo_bus_ops */
NULL, /* devo_power */
};
static struct modldrv modldrv = {
&mod_driverops, /* drv_modops */
"CIFS Server Protocol %I%", /* drv_linkinfo */
&devops,
};
static struct modlinkage modlinkage = {
MODREV_1, /* revision of the module, must be: MODREV_1 */
&modldrv, /* ptr to linkage structures */
NULL,
};
static int smb_info_init(struct smb_info *si);
static void smb_info_fini(struct smb_info *si);
extern int smb_fsop_start(void);
extern void smb_fsop_stop(void);
extern int nt_mapk_start(void);
extern void nt_mapk_stop(void);
extern int smb_get_kconfig(smb_kmod_cfg_t *cfg);
extern void smb_notify_change_daemon(smb_thread_t *thread, void *arg);
extern void smb_nbt_daemon(smb_thread_t *thread, void *arg);
extern void smb_tcp_daemon(smb_thread_t *thread, void *arg);
extern void smb_timers(smb_thread_t *thread, void *arg);
extern void smb_session_worker(void *arg);
extern int smb_maxbufsize;
extern time_t smb_oplock_timeout;
/* Debug logging level: 0=Disabled, 1=Quiet, 2=Verbose */
int smbsrv_debug_level;
struct smb_info smb_info;
static dev_info_t *smb_drv_dip = NULL;
static kmutex_t smb_drv_opencount_lock;
static int smb_drv_opencount = 0;
/*
* Kstat smb_info statistics.
*/
static struct smbinfo_stats {
kstat_named_t state;
kstat_named_t open_files;
kstat_named_t open_trees;
kstat_named_t open_users;
} smbinfo_stats = {
{ "state", KSTAT_DATA_UINT32 },
{ "open_files", KSTAT_DATA_UINT32 },
{ "connections", KSTAT_DATA_UINT32 },
{ "sessions", KSTAT_DATA_UINT32 }
};
static int smb_kstat_init(void);
static void smb_kstat_fini(void);
static int smb_kstat_update_info(kstat_t *ksp, int rw);
extern void smb_initialize_dispatch_kstat(void);
extern void smb_remove_dispatch_kstat(void);
static kstat_t *smbinfo_ksp = NULL;
/*
* SMB pseudo-driver entry points
*/
int
_init(void)
{
int rc;
mutex_init(&smb_drv_opencount_lock, NULL, MUTEX_DRIVER, NULL);
if ((rc = mod_install(&modlinkage)) != 0) {
mutex_destroy(&smb_drv_opencount_lock);
cmn_err(CE_NOTE, "init: %d\n", rc);
return (rc);
}
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int rc;
mutex_enter(&smb_drv_opencount_lock);
if (smb_drv_busy()) {
mutex_exit(&smb_drv_opencount_lock);
return (EBUSY);
}
mutex_exit(&smb_drv_opencount_lock);
if ((rc = mod_remove(&modlinkage)) == 0)
mutex_destroy(&smb_drv_opencount_lock);
return (rc);
}
/*
* DDI entry points.
*/
/* ARGSUSED */
static int
smb_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
ulong_t instance = getminor((dev_t)arg);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = smb_drv_dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)instance;
return (DDI_SUCCESS);
default:
break;
}
return (DDI_FAILURE);
}
static int
smb_drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (ddi_get_instance(dip) != 0) {
/* we only allow instance 0 to attach */
return (DDI_FAILURE);
}
smb_drv_dip = dip;
/* create the minor node */
if (ddi_create_minor_node(dip, "smbsrv", S_IFCHR, 0,
DDI_PSEUDO, 0) != DDI_SUCCESS) {
cmn_err(CE_WARN, "smb_drv_attach: failed creating minor node");
ddi_remove_minor_node(dip, NULL);
return (DDI_FAILURE);
}
if (smb_service_init() != 0) {
ddi_remove_minor_node(dip, NULL);
cmn_err(CE_WARN, "smb_drv_attach: failed to initialize "
"SMB service");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
smb_drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
mutex_enter(&smb_drv_opencount_lock);
/*
* Service state value is not protected by a lock in this case but
* it shouldn't be possible for the service state machine to transition
* TO a busy state at a time when smb_drv_busy() would return false.
*/
if (smb_drv_busy() || smb_svcstate_sm_busy()) {
mutex_exit(&smb_drv_opencount_lock);
return (DDI_FAILURE);
}
mutex_exit(&smb_drv_opencount_lock);
smb_service_fini();
smb_drv_dip = NULL;
ddi_remove_minor_node(dip, NULL);
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
smb_drv_ioctl(dev_t drv, int cmd, intptr_t argp, int flag, cred_t *cred,
int *retval)
{
int gmtoff;
int door_id;
switch (cmd) {
case SMB_IOC_GMTOFF:
if (ddi_copyin((int *)argp, &gmtoff, sizeof (int), flag))
return (EFAULT);
(void) smb_set_gmtoff((uint32_t)gmtoff);
break;
case SMB_IOC_CONFIG:
#if 0
smb_svcstate_event(SMB_SVCEVT_CONFIG, NULL);
#endif
break;
case SMB_IOC_WINPIPE:
if (ddi_copyin((int *)argp, &door_id, sizeof (int), flag))
return (EFAULT);
if (smb_winpipe_open(door_id) != 0)
return (EPIPE);
break;
default:
break;
}
return (0);
}
/* ARGSUSED */
static int
smb_drv_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
int rc = 0;
/*
* Only allow one open at a time
*/
mutex_enter(&smb_drv_opencount_lock);
if (smb_drv_busy()) {
mutex_exit(&smb_drv_opencount_lock);
return (EBUSY);
}
smb_drv_opencount++;
mutex_exit(&smb_drv_opencount_lock);
/*
* Check caller's privileges.
*/
if (secpolicy_smb(credp) != 0) {
mutex_enter(&smb_drv_opencount_lock);
smb_drv_opencount--;
mutex_exit(&smb_drv_opencount_lock);
return (EPERM);
}
/*
* Start SMB service state machine
*/
rc = smb_svcstate_sm_start(&smb_info.si_svc_sm_ctx);
if (rc != 0) {
mutex_enter(&smb_drv_opencount_lock);
smb_drv_opencount--;
mutex_exit(&smb_drv_opencount_lock);
return (rc);
}
return (0);
}
/* ARGSUSED */
static int
smb_drv_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
mutex_enter(&smb_drv_opencount_lock);
if (!smb_drv_busy()) {
mutex_exit(&smb_drv_opencount_lock);
return (0);
}
mutex_exit(&smb_drv_opencount_lock);
smb_svcstate_event(SMB_SVCEVT_CLOSE, NULL);
mutex_enter(&smb_drv_opencount_lock);
smb_drv_opencount--;
mutex_exit(&smb_drv_opencount_lock);
return (0);
}
/*
* Convenience function - must be called with smb_drv_opencount_lock held.
*/
static int
smb_drv_busy(void)
{
ASSERT(mutex_owned(&smb_drv_opencount_lock));
return (smb_drv_opencount);
}
/*
* SMB Service initialization and startup functions
*/
int
smb_service_init(void)
{
int rc;
rc = smb_info_init(&smb_info);
if (rc != 0) {
return (rc);
}
rc = smb_svcstate_sm_init(&smb_info.si_svc_sm_ctx);
if (rc != 0) {
smb_info_fini(&smb_info);
return (rc);
}
rc = smb_kstat_init();
if (rc != 0) {
smb_kstat_fini();
return (rc);
}
smb_winpipe_init();
return (0);
}
void
smb_service_fini(void)
{
smb_winpipe_fini();
smb_kstat_fini();
smb_svcstate_sm_fini(&smb_info.si_svc_sm_ctx);
smb_info_fini(&smb_info);
}
/*
* Progress bits for smb_info.si_open_progress. For use only by
* smb_service_open/smb_service_close.
*/
#define SMB_FS_STARTED 0x01
#define LMSHRD_KCLIENT_STARTED 0x02
#define SMB_KDOOR_CLNT_STARTED 0x04
#define SMB_KDOOR_SRV_STARTED 0x08
#define SMB_THREADS_STARTED 0x10
int
smb_service_open(struct smb_info *si)
{
int rc;
int size; /* XXX TEMPORARY (remove when kconfig is removed) */
/* Track progress so we can cleanup from a partial failure */
si->si_open_progress = 0;
si->si_connect_progress = 0;
/* XXX TEMPORARY */
if (smb_get_kconfig(&si->si) == 0) {
if (si->si.skc_sync_enable)
smb_set_stability(1);
if (si->si.skc_flush_required)
smb_commit_required(0);
if (si->si.skc_maxconnections == 0)
si->si.skc_maxconnections = 0xFFFFFFFF;
size = si->si.skc_maxbufsize;
if (size != 0) {
if (size < 37 || size > 64)
size = 37;
smb_maxbufsize = SMB_NT_MAXBUF(size);
}
/*
* XXX should not override configuration.
* For now, this disables server side
* signing regardless of configuration.
*/
si->si.skc_signing_enable = 0;
si->si.skc_signing_required = 0;
si->si.skc_signing_check = 0;
smb_correct_keep_alive_values(si->si.skc_keepalive);
/*
* XXX The following code was pulled from smb_oplock_init.
* It should be combined with with the config process if
* this info will be stored with the configuration or with
* the smb_fsop_start function if the data will be stored
* in the root of the fs.
*/
/*
* XXX oplock enable flag.
* Should be stored in extended attribute in root of fs
* or a ZFS user-defined property.
*/
if (si->si.skc_oplock_enable == 0) {
cmn_err(CE_NOTE, "SmbOplocks: disabled");
}
smb_oplock_timeout = si->si.skc_oplock_timeout;
/*
* XXX oplock timeout. Can a customer configure this?
*/
if (si->si.skc_oplock_timeout < OPLOCK_MIN_TIMEOUT)
smb_oplock_timeout = OPLOCK_MIN_TIMEOUT;
} else {
return (EIO); /* XXX Errno? */
}
if ((rc = smb_fsop_start()) != 0) {
return (rc);
}
si->si_open_progress |= SMB_FS_STARTED;
if ((rc = lmshrd_kclient_start()) != 0) {
return (rc);
}
si->si_open_progress |= LMSHRD_KCLIENT_STARTED;
if ((rc = smb_kdoor_clnt_start()) != 0) {
return (rc);
}
si->si_open_progress |= SMB_KDOOR_CLNT_STARTED;
if ((rc = smb_kdoor_srv_start()) != 0) {
return (rc);
}
si->si_open_progress |= SMB_KDOOR_SRV_STARTED;
if ((rc = smb_service_start_threads(si)) != 0) {
return (rc);
}
si->si_open_progress |= SMB_THREADS_STARTED;
return (0);
}
void
smb_service_close(struct smb_info *si)
{
if (si->si_open_progress & SMB_THREADS_STARTED)
smb_service_stop_threads(si);
if (si->si_open_progress & SMB_KDOOR_SRV_STARTED)
smb_kdoor_srv_stop();
if (si->si_open_progress & SMB_KDOOR_CLNT_STARTED)
smb_kdoor_clnt_stop();
if (si->si_open_progress & LMSHRD_KCLIENT_STARTED)
lmshrd_kclient_stop();
if (si->si_open_progress & SMB_FS_STARTED)
smb_fsop_stop();
}
/*
* Start the Netbios and TCP services.
*
* Awaken arguments are not known until thread starts.
*
* XXX We give up the NET_MAC_AWARE privilege because it keeps us from
* re-opening the connection when there are leftover TCP connections in
* TCPS_TIME_WAIT state. There seem to be some security ramifications
* around reestablishing a connection while possessing the NET_MAC_AWARE
* privilege.
*
* This approach may cause problems when we try to support zones. An
* alternative would be to retry the connection setup for a fixed period
* of time until the stale connections clear up but that implies we
* would be offline for a couple minutes every time the service is
* restarted with active connections.
*/
int
smb_service_connect(struct smb_info *si)
{
int rc1, rc2;
if ((rc1 = setpflags(NET_MAC_AWARE, 0, CRED())) != 0) {
cmn_err(CE_WARN, "Cannot remove NET_MAC_AWARE privilege");
smb_svcstate_event(SMB_SVCEVT_DISCONNECT, (uintptr_t)rc1);
return (rc1);
}
rc1 = smb_thread_start(&si->si_nbt_daemon);
rc2 = smb_thread_start(&si->si_tcp_daemon);
if (rc2 != 0)
rc1 = rc2;
return (rc1);
}
void
smb_service_disconnect(struct smb_info *si)
{
smb_thread_stop(&si->si_nbt_daemon);
smb_thread_stop(&si->si_tcp_daemon);
}
/*
* Start any service-related kernel threads except for the NBT and TCP
* daemon threads. Those service daemon threads are handled separately.
*
* Returns 0 for success, non-zero for failure. If failure is returned the
* caller should call smb_service_stop_threads to cleanup any threads that
* were successfully started.
*/
int
smb_service_start_threads(struct smb_info *si)
{
int rval;
si->thread_pool = taskq_create(
"smb_workers",
si->si.skc_maxworkers,
SMB_WORKER_PRIORITY,
si->si.skc_maxworkers,
INT_MAX,
TASKQ_DYNAMIC|TASKQ_PREPOPULATE);
ASSERT(si->thread_pool != NULL);
rval = smb_thread_start(&si->si_thread_notify_change);
if (rval != 0)
return (rval);
rval = smb_thread_start(&si->si_thread_timers);
if (rval != 0) {
smb_thread_stop(&si->si_thread_notify_change);
return (rval);
}
return (0);
}
void
smb_service_stop_threads(struct smb_info *si)
{
smb_thread_stop(&si->si_thread_timers);
smb_thread_stop(&si->si_thread_notify_change);
taskq_destroy(si->thread_pool);
}
static int
smb_info_init(struct smb_info *si)
{
int i;
bzero(si, sizeof (smb_info));
for (i = 0; i <= SMBND_HASH_MASK; i++) {
smb_llist_constructor(&si->node_hash_table[i],
sizeof (smb_node_t), offsetof(smb_node_t, n_lnd));
}
smb_llist_constructor(&si->si_vfs_list,
sizeof (smb_vfs_t), offsetof(smb_vfs_t, sv_lnd));
smb_slist_constructor(&si->si_ncr_list, sizeof (smb_request_t),
offsetof(smb_request_t, sr_ncr.nc_lnd));
smb_slist_constructor(&si->si_nce_list, sizeof (smb_request_t),
offsetof(smb_request_t, sr_ncr.nc_lnd));
si->si_cache_vfs = kmem_cache_create("smb_vfs_cache",
sizeof (smb_vfs_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_request = kmem_cache_create("smb_request_cache",
sizeof (smb_request_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_session = kmem_cache_create("smb_session_cache",
sizeof (smb_session_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_user = kmem_cache_create("smb_user_cache",
sizeof (smb_user_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_tree = kmem_cache_create("smb_tree_cache",
sizeof (smb_tree_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_ofile = kmem_cache_create("smb_ofile_cache",
sizeof (smb_ofile_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_odir = kmem_cache_create("smb_odir_cache",
sizeof (smb_odir_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
si->si_cache_node = kmem_cache_create("smb_smb_node_cache",
sizeof (smb_node_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
smb_thread_init(&si->si_nbt_daemon, "smb_nbt_daemon", smb_nbt_daemon,
si, NULL, NULL);
smb_thread_init(&si->si_tcp_daemon, "smb_tcp_daemon", smb_tcp_daemon,
si, NULL, NULL);
smb_thread_init(&si->si_thread_notify_change,
"smb_notify_change_daemon", smb_notify_change_daemon, &smb_info,
NULL, NULL);
smb_thread_init(&si->si_thread_timers, "smb_timers", smb_timers,
si, NULL, NULL);
return (0);
}
static void
smb_info_fini(struct smb_info *si)
{
int i;
for (i = 0; i <= SMBND_HASH_MASK; i++) {
smb_node_t *node;
/*
* The following sequence is just intended for sanity check.
* This will have to be modified when the code goes into
* production.
*
* The SMB node hash table should be emtpy at this point. If the
* hash table is not empty all the nodes remaining are displayed
* (it should help figure out what actions led to this state)
* and "oops" will be set to B_TRUE which will trigger the
* ASSERT that follows.
*
* The reason why SMB nodes are still remaining in the hash
* table is problably due to a mismatch between calls to
* smb_node_lookup() and smb_node_release(). You must track that
* down.
*
* Now if you are reading this comment because you actually hit
* the ASSERT, the temptation to ignore it is going to be very
* strong. To help you make the right decision you should know
* that when the ASSERT happened a message containing you SunID
* has been sent to cifsgate. By now it has been logged into a
* special database.
*
* You are being watched...
*/
node = smb_llist_head(&si->node_hash_table[i]);
ASSERT(node == NULL);
}
for (i = 0; i <= SMBND_HASH_MASK; i++) {
smb_llist_destructor(&si->node_hash_table[i]);
}
smb_llist_destructor(&si->si_vfs_list);
kmem_cache_destroy(si->si_cache_vfs);
kmem_cache_destroy(si->si_cache_request);
kmem_cache_destroy(si->si_cache_session);
kmem_cache_destroy(si->si_cache_user);
kmem_cache_destroy(si->si_cache_tree);
kmem_cache_destroy(si->si_cache_ofile);
kmem_cache_destroy(si->si_cache_odir);
kmem_cache_destroy(si->si_cache_node);
smb_thread_destroy(&si->si_nbt_daemon);
smb_thread_destroy(&si->si_tcp_daemon);
smb_thread_destroy(&si->si_thread_notify_change);
smb_thread_destroy(&si->si_thread_timers);
}
static int
smb_kstat_init()
{
/* create and initialize smb kstats - smb_info stats */
smbinfo_ksp = kstat_create("smb", 0, "smb_info", "misc",
KSTAT_TYPE_NAMED, sizeof (smbinfo_stats) / sizeof (kstat_named_t),
KSTAT_FLAG_VIRTUAL);
if (smbinfo_ksp) {
smbinfo_ksp->ks_data = (void *) &smbinfo_stats;
smbinfo_ksp->ks_update = smb_kstat_update_info;
kstat_install(smbinfo_ksp);
}
/* create and initialize smb kstats - smb_dispatch stats */
smb_initialize_dispatch_kstat();
return (0);
}
static void
smb_kstat_fini()
{
if (smbinfo_ksp != NULL) {
kstat_delete(smbinfo_ksp);
smbinfo_ksp = NULL;
}
smb_remove_dispatch_kstat();
}
/* ARGSUSED */
static int
smb_kstat_update_info(kstat_t *ksp, int rw)
{
if (rw == KSTAT_WRITE) {
return (EACCES);
} else {
smbinfo_stats.state.value.ui32 =
smb_info.si_svc_sm_ctx.ssc_state;
smbinfo_stats.open_files.value.ui32 = smb_info.open_files;
smbinfo_stats.open_trees.value.ui32 = smb_info.open_trees;
smbinfo_stats.open_users.value.ui32 = smb_info.open_users;
}
return (0);
}