view usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.c @ 10717:fe0545fc3cdd

6612607 CIFS ADS client should use ldap_sasl_interactive_bind_s API 6877755 smbd should not route stderr, stdout to /dev/null 6882701 Wrong error message for attempt to map local user to Windows group, or vice versa 6885105 Potential for deadlock in smb_node_set_delete_on_close() 6881928 smbd core generated when running a script to join domain, set abe properties 6885538 Reduce dependencies on libsmbrdr 6820325 cifs service can't start on multi vlan+ipmp configuration
author Alan Wright <amw@Sun.COM>
date Mon, 05 Oct 2009 11:03:34 -0700
parents 6bebab7d43d5
children 37e5dcdf36d3
line wrap: on
line source

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Main startup code for SMB/NETBIOS and some utility routines
 * for the NETBIOS layer.
 */

#include <sys/tzfile.h>
#include <assert.h>
#include <synch.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <smbns_netbios.h>

#define	SMB_NETBIOS_DUMP_FILE		"netbios"

static netbios_service_t nbtd;

static void smb_netbios_shutdown(void);
static void *smb_netbios_service(void *);
static void smb_netbios_dump(void);

/*
 * Start the NetBIOS services
 */
int
smb_netbios_start(void)
{
	pthread_t	tid;
	pthread_attr_t	attr;
	int		rc;

	if (smb_netbios_cache_init() < 0)
		return (-1);

	(void) pthread_attr_init(&attr);
	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	rc = pthread_create(&tid, &attr, smb_netbios_service, NULL);
	(void) pthread_attr_destroy(&attr);
	return (rc);
}

/*
 * Stop the NetBIOS services
 */
void
smb_netbios_stop(void)
{
	char	fname[MAXPATHLEN];

	smb_netbios_event(NETBIOS_EVENT_STOP);

	(void) snprintf(fname, MAXPATHLEN, "%s/%s",
	    SMB_VARRUN_DIR, SMB_NETBIOS_DUMP_FILE);
	(void) unlink(fname);

}

/*
 * Launch the NetBIOS Name Service, Datagram and Browser services
 * and then sit in a loop providing a 1 second resolution timer.
 * The timer will:
 *	- update the netbios stats file every 10 minutes
 *	- clean the cache every 10 minutes
 */
/*ARGSUSED*/
static void *
smb_netbios_service(void *arg)
{
	static uint32_t	ticks = 0;
	pthread_t	tid;
	int		rc;

	smb_netbios_event(NETBIOS_EVENT_START);

	rc = pthread_create(&tid, NULL, smb_netbios_name_service, NULL);
	if (rc != 0) {
		smb_netbios_shutdown();
		return (NULL);
	}

	smb_netbios_wait(NETBIOS_EVENT_NS_START);
	if (smb_netbios_error()) {
		smb_netbios_shutdown();
		return (NULL);
	}

	smb_netbios_name_config();

	rc = pthread_create(&tid, NULL, smb_netbios_datagram_service, NULL);
	if (rc != 0) {
		smb_netbios_shutdown();
		return (NULL);
	}

	smb_netbios_wait(NETBIOS_EVENT_DGM_START);
	if (smb_netbios_error()) {
		smb_netbios_shutdown();
		return (NULL);
	}

	rc = pthread_create(&tid, NULL, smb_browser_service, NULL);
	if (rc != 0) {
		smb_netbios_shutdown();
		return (NULL);
	}

	smb_netbios_event(NETBIOS_EVENT_TIMER_START);

	for (;;) {
		(void) sleep(1);
		ticks++;

		if (!smb_netbios_running())
			break;

		smb_netbios_datagram_tick();
		smb_netbios_name_tick();

		if ((ticks % 600) == 0) {
			smb_netbios_event(NETBIOS_EVENT_DUMP);
			smb_netbios_cache_clean();
		}
	}

	smb_netbios_event(NETBIOS_EVENT_TIMER_STOP);
	smb_netbios_shutdown();
	return (NULL);
}

static void
smb_netbios_shutdown(void)
{
	(void) pthread_join(nbtd.nbs_browser.s_tid, 0);
	(void) pthread_join(nbtd.nbs_dgm.s_tid, 0);
	(void) pthread_join(nbtd.nbs_ns.s_tid, 0);

	nbtd.nbs_browser.s_tid = 0;
	nbtd.nbs_dgm.s_tid = 0;
	nbtd.nbs_ns.s_tid = 0;

	smb_netbios_cache_fini();

	if (smb_netbios_error()) {
		smb_netbios_event(NETBIOS_EVENT_RESET);
		if (smb_netbios_start() != 0)
			syslog(LOG_ERR, "netbios: restart failed");
	}
}

int
smb_first_level_name_encode(struct name_entry *name,
				unsigned char *out, int max_out)
{
	return (netbios_first_level_name_encode(name->name, name->scope,
	    out, max_out));
}

int
smb_first_level_name_decode(unsigned char *in, struct name_entry *name)
{
	return (netbios_first_level_name_decode((char *)in, (char *)name->name,
	    (char *)name->scope));
}

/*
 * smb_encode_netbios_name
 *
 * Set up the name and scope fields in the destination name_entry structure.
 * The name is padded with spaces to 15 bytes. The suffix is copied into the
 * last byte, i.e. "netbiosname    <suffix>". The scope is copied and folded
 * to uppercase.
 */
void
smb_encode_netbios_name(unsigned char *name, char suffix, unsigned char *scope,
    struct name_entry *dest)
{
	smb_tonetbiosname((char *)name, (char *)dest->name, suffix);

	if (scope) {
		(void) strlcpy((char *)dest->scope, (const char *)scope,
		    sizeof (dest->scope));
	} else {
		(void) smb_config_getstr(SMB_CI_NBSCOPE, (char *)dest->scope,
		    sizeof (dest->scope));
	}

	(void) utf8_strupr((char *)dest->scope);
}

void
smb_init_name_struct(unsigned char *name, char suffix, unsigned char *scope,
    uint32_t ipaddr, unsigned short port, uint32_t attr,
    uint32_t addr_attr, struct name_entry *dest)
{
	bzero(dest, sizeof (struct name_entry));
	smb_encode_netbios_name(name, suffix, scope, dest);

	switch (smb_node_type) {
	case 'H':
		dest->attributes = attr | NAME_ATTR_OWNER_TYPE_HNODE;
		break;
	case 'M':
		dest->attributes = attr | NAME_ATTR_OWNER_TYPE_MNODE;
		break;
	case 'P':
		dest->attributes = attr | NAME_ATTR_OWNER_TYPE_PNODE;
		break;
	case 'B':
	default:
		dest->attributes = attr | NAME_ATTR_OWNER_TYPE_BNODE;
		break;
	}

	dest->addr_list.refresh_ttl = dest->addr_list.ttl =
	    TO_SECONDS(DEFAULT_TTL);

	dest->addr_list.sin.sin_family = AF_INET;
	dest->addr_list.sinlen = sizeof (dest->addr_list.sin);
	dest->addr_list.sin.sin_addr.s_addr = ipaddr;
	dest->addr_list.sin.sin_port = port;
	dest->addr_list.attributes = addr_attr;
	dest->addr_list.forw = dest->addr_list.back = &dest->addr_list;
}

void
smb_netbios_event(netbios_event_t event)
{
	static char *event_msg[] = {
		"startup",
		"shutdown",
		"restart",
		"name service started",
		"name service stopped",
		"datagram service started",
		"datagram service stopped",
		"browser service started",
		"browser service stopped",
		"timer service started",
		"timer service stopped",
		"error",
		"dump"
	};

	(void) mutex_lock(&nbtd.nbs_mtx);

	if (event == NETBIOS_EVENT_DUMP) {
		if (nbtd.nbs_last_event == NULL)
			nbtd.nbs_last_event = event_msg[event];
		smb_netbios_dump();
		(void) mutex_unlock(&nbtd.nbs_mtx);
		return;
	}

	nbtd.nbs_last_event = event_msg[event];
	syslog(LOG_DEBUG, "netbios: %s", nbtd.nbs_last_event);

	switch (nbtd.nbs_state) {
	case NETBIOS_STATE_INIT:
		if (event == NETBIOS_EVENT_START)
			nbtd.nbs_state = NETBIOS_STATE_RUNNING;
		break;

	case NETBIOS_STATE_RUNNING:
		switch (event) {
		case NETBIOS_EVENT_NS_START:
			nbtd.nbs_ns.s_tid = pthread_self();
			nbtd.nbs_ns.s_up = B_TRUE;
			break;
		case NETBIOS_EVENT_NS_STOP:
			nbtd.nbs_ns.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_DGM_START:
			nbtd.nbs_dgm.s_tid = pthread_self();
			nbtd.nbs_dgm.s_up = B_TRUE;
			break;
		case NETBIOS_EVENT_DGM_STOP:
			nbtd.nbs_dgm.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_BROWSER_START:
			nbtd.nbs_browser.s_tid = pthread_self();
			nbtd.nbs_browser.s_up = B_TRUE;
			break;
		case NETBIOS_EVENT_BROWSER_STOP:
			nbtd.nbs_browser.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_TIMER_START:
			nbtd.nbs_timer.s_tid = pthread_self();
			nbtd.nbs_timer.s_up = B_TRUE;
			break;
		case NETBIOS_EVENT_TIMER_STOP:
			nbtd.nbs_timer.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_STOP:
			nbtd.nbs_state = NETBIOS_STATE_CLOSING;
			break;
		case NETBIOS_EVENT_ERROR:
			nbtd.nbs_state = NETBIOS_STATE_ERROR;
			++nbtd.nbs_errors;
			break;
		default:
			break;
		}
		break;

	case NETBIOS_STATE_CLOSING:
	case NETBIOS_STATE_ERROR:
	default:
		switch (event) {
		case NETBIOS_EVENT_NS_STOP:
			nbtd.nbs_ns.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_DGM_STOP:
			nbtd.nbs_dgm.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_BROWSER_STOP:
			nbtd.nbs_browser.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_TIMER_STOP:
			nbtd.nbs_timer.s_up = B_FALSE;
			break;
		case NETBIOS_EVENT_STOP:
			nbtd.nbs_state = NETBIOS_STATE_CLOSING;
			break;
		case NETBIOS_EVENT_RESET:
			nbtd.nbs_state = NETBIOS_STATE_INIT;
			break;
		case NETBIOS_EVENT_ERROR:
			++nbtd.nbs_errors;
			break;
		default:
			break;
		}
		break;
	}

	smb_netbios_dump();
	(void) cond_broadcast(&nbtd.nbs_cv);
	(void) mutex_unlock(&nbtd.nbs_mtx);
}

void
smb_netbios_wait(netbios_event_t event)
{
	boolean_t *svc = NULL;
	boolean_t desired_state;

	(void) mutex_lock(&nbtd.nbs_mtx);

	switch (event) {
	case NETBIOS_EVENT_NS_START:
	case NETBIOS_EVENT_NS_STOP:
		svc = &nbtd.nbs_ns.s_up;
		desired_state =
		    (event == NETBIOS_EVENT_NS_START) ? B_TRUE : B_FALSE;
		break;
	case NETBIOS_EVENT_DGM_START:
	case NETBIOS_EVENT_DGM_STOP:
		svc = &nbtd.nbs_dgm.s_up;
		desired_state =
		    (event == NETBIOS_EVENT_DGM_START) ? B_TRUE : B_FALSE;
		break;
	case NETBIOS_EVENT_BROWSER_START:
	case NETBIOS_EVENT_BROWSER_STOP:
		svc = &nbtd.nbs_browser.s_up;
		desired_state =
		    (event == NETBIOS_EVENT_BROWSER_START) ? B_TRUE : B_FALSE;
		break;
	default:
		(void) mutex_unlock(&nbtd.nbs_mtx);
		return;
	}

	while (*svc != desired_state) {
		if (nbtd.nbs_state != NETBIOS_STATE_RUNNING)
			break;

		(void) cond_wait(&nbtd.nbs_cv, &nbtd.nbs_mtx);
	}

	(void) mutex_unlock(&nbtd.nbs_mtx);
}

void
smb_netbios_sleep(time_t seconds)
{
	timestruc_t reltimeout;

	(void) mutex_lock(&nbtd.nbs_mtx);

	if (nbtd.nbs_state == NETBIOS_STATE_RUNNING) {
		if (seconds == 0)
			seconds  = 1;
		reltimeout.tv_sec = seconds;
		reltimeout.tv_nsec = 0;

		(void) cond_reltimedwait(&nbtd.nbs_cv,
		    &nbtd.nbs_mtx, &reltimeout);
	}

	(void) mutex_unlock(&nbtd.nbs_mtx);
}

boolean_t
smb_netbios_running(void)
{
	boolean_t is_running;

	(void) mutex_lock(&nbtd.nbs_mtx);

	if (nbtd.nbs_state == NETBIOS_STATE_RUNNING)
		is_running = B_TRUE;
	else
		is_running = B_FALSE;

	(void) mutex_unlock(&nbtd.nbs_mtx);
	return (is_running);
}

boolean_t
smb_netbios_error(void)
{
	boolean_t error;

	(void) mutex_lock(&nbtd.nbs_mtx);

	if (nbtd.nbs_state == NETBIOS_STATE_ERROR)
		error = B_TRUE;
	else
		error = B_FALSE;

	(void) mutex_unlock(&nbtd.nbs_mtx);
	return (error);
}

/*
 * Write the service state to /var/run/smb/netbios.
 *
 * This is a private interface.  To update the file use:
 *	smb_netbios_event(NETBIOS_EVENT_DUMP);
 */
static void
smb_netbios_dump(void)
{
	static struct {
		netbios_state_t state;
		char		*text;
	} sm[] = {
		{ NETBIOS_STATE_INIT,		"init" },
		{ NETBIOS_STATE_RUNNING,	"running" },
		{ NETBIOS_STATE_CLOSING,	"closing" },
		{ NETBIOS_STATE_ERROR,		"error" }
	};

	char		fname[MAXPATHLEN];
	FILE		*fp;
	struct passwd	*pwd;
	struct group	*grp;
	uid_t		uid;
	gid_t		gid;
	char		*last_event = "none";
	int		i;

	(void) snprintf(fname, MAXPATHLEN, "%s/%s",
	    SMB_VARRUN_DIR, SMB_NETBIOS_DUMP_FILE);

	if ((fp = fopen(fname, "w")) == NULL)
		return;

	pwd = getpwnam("root");
	grp = getgrnam("sys");
	uid = (pwd == NULL) ? 0 : pwd->pw_uid;
	gid = (grp == NULL) ? 3 : grp->gr_gid;

	(void) lockf(fileno(fp), F_LOCK, 0);
	(void) fchmod(fileno(fp), 0600);
	(void) fchown(fileno(fp), uid, gid);

	if (nbtd.nbs_last_event)
		last_event = nbtd.nbs_last_event;

	for (i = 0; i < sizeof (sm) / sizeof (sm[0]); ++i) {
		if (nbtd.nbs_state == sm[i].state) {
			(void) fprintf(fp,
			    "State             %s  (event: %s, errors: %u)\n",
			    sm[i].text, last_event, nbtd.nbs_errors);
			break;
		}
	}

	(void) fprintf(fp, "Name Service      %-7s  (%u)\n",
	    nbtd.nbs_ns.s_up ? "up" : "down", nbtd.nbs_ns.s_tid);
	(void) fprintf(fp, "Datagram Service  %-7s  (%u)\n",
	    nbtd.nbs_dgm.s_up ? "up" : "down", nbtd.nbs_dgm.s_tid);
	(void) fprintf(fp, "Browser Service   %-7s  (%u)\n",
	    nbtd.nbs_browser.s_up ? "up" : "down", nbtd.nbs_browser.s_tid);
	(void) fprintf(fp, "Timer Service     %-7s  (%u)\n",
	    nbtd.nbs_timer.s_up ? "up" : "down", nbtd.nbs_timer.s_tid);

	smb_netbios_cache_dump(fp);

	(void) lockf(fileno(fp), F_ULOCK, 0);
	(void) fclose(fp);
}