root/usr/src/lib/smbsrv/libsmbns/common/smbns_netbios.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 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) smb_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);
}