root/usr/src/lib/smbsrv/libmlsvc/common/srvsvc_clnt.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) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 */

/*
 * Server Service (srvsvc) client side RPC library interface. The
 * srvsvc interface allows a client to query a server for information
 * on shares, sessions, connections and files on the server. Some
 * functions are available via anonymous IPC while others require
 * administrator privilege. Also, some functions return NT status
 * values while others return Win32 errors codes.
 */

#include <sys/errno.h>
#include <sys/tzfile.h>
#include <stdio.h>
#include <time.h>
#include <strings.h>
#include <unistd.h>

#include <smbsrv/libsmb.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/smbinfo.h>
#include <smbsrv/ndl/srvsvc.ndl>

/*
 * Information level for NetShareGetInfo.
 */
DWORD srvsvc_info_level = 1;

/*
 * Bind to the the SRVSVC.
 *
 * If username argument is NULL, an anonymous connection will be established.
 * Otherwise, an authenticated connection will be established.
 */
static int
srvsvc_open(char *server, char *domain, char *username, mlsvc_handle_t *handle)
{
        smb_domainex_t di;

        if (server == NULL || domain == NULL) {
                if (!smb_domain_getinfo(&di))
                        return (-1);

                server = di.d_dci.dc_name;
                domain = di.d_primary.di_nbname;
        }

        if (username == NULL)
                username = MLSVC_ANON_USER;

        if (ndr_rpc_bind(handle, server, domain, username, "SRVSVC") != 0)
                return (-1);

        return (0);
}

/*
 * Unbind the SRVSVC connection.
 */
static void
srvsvc_close(mlsvc_handle_t *handle)
{
        ndr_rpc_unbind(handle);
}

/*
 * This is a client side routine for NetShareGetInfo.
 * Levels 0 and 1 work with an anonymous connection but
 * level 2 requires administrator access.
 */
int
srvsvc_net_share_get_info(char *server, char *domain, char *netname)
{
        struct mlsm_NetShareGetInfo arg;
        mlsvc_handle_t handle;
        int rc;
        int opnum;
        struct mslm_NetShareInfo_0 *info0;
        struct mslm_NetShareInfo_1 *info1;
        struct mslm_NetShareInfo_2 *info2;
        int len;
        char user[SMB_USERNAME_MAXLEN];

        if (netname == NULL)
                return (-1);

        if (srvsvc_info_level == 2)
                smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        if (srvsvc_open(server, domain, user, &handle) != 0)
                return (-1);

        opnum = SRVSVC_OPNUM_NetShareGetInfo;
        bzero(&arg, sizeof (struct mlsm_NetShareGetInfo));

        len = strlen(server) + 4;
        arg.servername = ndr_rpc_malloc(&handle, len);
        if (arg.servername == NULL) {
                srvsvc_close(&handle);
                return (-1);
        }

        (void) snprintf((char *)arg.servername, len, "\\\\%s", server);
        arg.netname = (LPTSTR)netname;
        arg.level = srvsvc_info_level; /* share information level */

        rc = ndr_rpc_call(&handle, opnum, &arg);
        if ((rc != 0) || (arg.status != 0)) {
                srvsvc_close(&handle);
                return (-1);
        }

        switch (arg.result.switch_value) {
        case 0:
                info0 = arg.result.ru.info0;
                smb_tracef("srvsvc shi0_netname=%s", info0->shi0_netname);
                break;

        case 1:
                info1 = arg.result.ru.info1;
                smb_tracef("srvsvc shi1_netname=%s", info1->shi1_netname);
                smb_tracef("srvsvc shi1_type=%u", info1->shi1_type);

                if (info1->shi1_comment)
                        smb_tracef("srvsvc shi1_comment=%s",
                            info1->shi1_comment);
                break;

        case 2:
                info2 = arg.result.ru.info2;
                smb_tracef("srvsvc shi2_netname=%s", info2->shi2_netname);
                smb_tracef("srvsvc shi2_type=%u", info2->shi2_type);

                if (info2->shi2_comment)
                        smb_tracef("srvsvc shi2_comment=%s",
                            info2->shi2_comment);

                smb_tracef("srvsvc shi2_perms=%d", info2->shi2_permissions);
                smb_tracef("srvsvc shi2_max_use=%d", info2->shi2_max_uses);
                smb_tracef("srvsvc shi2_cur_use=%d", info2->shi2_current_uses);

                if (info2->shi2_path)
                        smb_tracef("srvsvc shi2_path=%s", info2->shi2_path);

                if (info2->shi2_passwd)
                        smb_tracef("srvsvc shi2_passwd=%s", info2->shi2_passwd);
                break;

        default:
                smb_tracef("srvsvc: unknown level");
                break;
        }

        srvsvc_close(&handle);
        return (0);
}

/*
 * This is a client side routine for NetSessionEnum.
 * NetSessionEnum requires administrator rights.
 */
int
srvsvc_net_session_enum(char *server, char *domain, char *netname)
{
        struct mslm_NetSessionEnum arg;
        mlsvc_handle_t handle;
        int rc;
        int opnum;
        struct mslm_infonres infonres;
        struct mslm_SESSION_INFO_1 *nsi1;
        int len;
        char user[SMB_USERNAME_MAXLEN];

        if (netname == NULL)
                return (-1);

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        rc = srvsvc_open(server, domain, user, &handle);
        if (rc != 0)
                return (-1);

        opnum = SRVSVC_OPNUM_NetSessionEnum;
        bzero(&arg, sizeof (struct mslm_NetSessionEnum));

        len = strlen(server) + 4;
        arg.servername = ndr_rpc_malloc(&handle, len);
        if (arg.servername == NULL) {
                srvsvc_close(&handle);
                return (-1);
        }

        (void) snprintf((char *)arg.servername, len, "\\\\%s", server);
        infonres.entriesread = 0;
        infonres.entries = 0;
        arg.level = 1;
        arg.result.level = 1;
        arg.result.bufptr.p = &infonres;
        arg.resume_handle = 0;
        arg.pref_max_len = 0xFFFFFFFF;

        rc = ndr_rpc_call(&handle, opnum, &arg);
        if ((rc != 0) || (arg.status != 0)) {
                srvsvc_close(&handle);
                return (-1);
        }

        /* Only the first session info is dereferenced. */
        nsi1 = ((struct mslm_infonres *)arg.result.bufptr.p)->entries;

        smb_tracef("srvsvc switch_value=%d", arg.level);
        smb_tracef("srvsvc sesi1_cname=%s", nsi1->sesi1_cname);
        smb_tracef("srvsvc sesi1_uname=%s", nsi1->sesi1_uname);
        smb_tracef("srvsvc sesi1_nopens=%u", nsi1->sesi1_nopens);
        smb_tracef("srvsvc sesi1_time=%u", nsi1->sesi1_time);
        smb_tracef("srvsvc sesi1_itime=%u", nsi1->sesi1_itime);
        smb_tracef("srvsvc sesi1_uflags=%u", nsi1->sesi1_uflags);

        srvsvc_close(&handle);
        return (0);
}

/*
 * This is a client side routine for NetConnectEnum.
 * NetConnectEnum requires administrator rights.
 * Level 0 and level 1 requests are supported.
 */
int
srvsvc_net_connect_enum(char *server, char *domain, char *netname, int level)
{
        struct mslm_NetConnectEnum arg;
        mlsvc_handle_t handle;
        int rc;
        int opnum;
        struct mslm_NetConnectInfo1 info1;
        struct mslm_NetConnectInfo0 info0;
        struct mslm_NetConnectInfoBuf1 *cib1;
        int len;
        char user[SMB_USERNAME_MAXLEN];

        if (netname == NULL)
                return (-1);

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        rc = srvsvc_open(server, domain, user, &handle);
        if (rc != 0)
                return (-1);

        opnum = SRVSVC_OPNUM_NetConnectEnum;
        bzero(&arg, sizeof (struct mslm_NetConnectEnum));

        len = strlen(server) + 4;
        arg.servername = ndr_rpc_malloc(&handle, len);
        if (arg.servername == NULL) {
                srvsvc_close(&handle);
                return (-1);
        }

        (void) snprintf((char *)arg.servername, len, "\\\\%s", server);
        arg.qualifier = (LPTSTR)netname;

        switch (level) {
        case 0:
                arg.info.level = 0;
                arg.info.switch_value = 0;
                arg.info.ru.info0 = &info0;
                info0.entries_read = 0;
                info0.ci0 = 0;
                break;
        case 1:
                arg.info.level = 1;
                arg.info.switch_value = 1;
                arg.info.ru.info1 = &info1;
                info1.entries_read = 0;
                info1.ci1 = 0;
                break;
        default:
                srvsvc_close(&handle);
                return (-1);
        }

        arg.resume_handle = 0;
        arg.pref_max_len = 0xFFFFFFFF;

        rc = ndr_rpc_call(&handle, opnum, &arg);
        if ((rc != 0) || (arg.status != 0)) {
                srvsvc_close(&handle);
                return (-1);
        }

        smb_tracef("srvsvc switch_value=%d", arg.info.switch_value);

        switch (level) {
        case 0:
                if (arg.info.ru.info0 && arg.info.ru.info0->ci0) {
                        smb_tracef("srvsvc coni0_id=%x",
                            arg.info.ru.info0->ci0->coni0_id);
                }
                break;
        case 1:
                if (arg.info.ru.info1 && arg.info.ru.info1->ci1) {
                        cib1 = arg.info.ru.info1->ci1;

                        smb_tracef("srvsvc coni_uname=%s",
                            cib1->coni1_username ?
                            (char *)cib1->coni1_username : "(null)");
                        smb_tracef("srvsvc coni1_netname=%s",
                            cib1->coni1_netname ?
                            (char *)cib1->coni1_netname : "(null)");
                        smb_tracef("srvsvc coni1_nopens=%u",
                            cib1->coni1_num_opens);
                        smb_tracef("srvsvc coni1_time=%u", cib1->coni1_time);
                        smb_tracef("srvsvc coni1_num_users=%u",
                            cib1->coni1_num_users);
                }
                break;

        default:
                smb_tracef("srvsvc: unknown level");
                break;
        }

        srvsvc_close(&handle);
        return (0);
}

/*
 * Compare the time here with the remote time on the server
 * and report clock skew.
 */
void
srvsvc_timecheck(char *server, char *domain)
{
        char                    hostname[MAXHOSTNAMELEN];
        struct timeval          dc_tv;
        struct tm               dc_tm;
        struct tm               *tm;
        time_t                  tnow;
        time_t                  tdiff;
        int                     priority;

        if (srvsvc_net_remote_tod(server, domain, &dc_tv, &dc_tm) < 0) {
                syslog(LOG_DEBUG, "srvsvc_net_remote_tod failed");
                return;
        }

        tnow = time(NULL);

        if (tnow > dc_tv.tv_sec)
                tdiff = (tnow - dc_tv.tv_sec) / SECSPERMIN;
        else
                tdiff = (dc_tv.tv_sec - tnow) / SECSPERMIN;

        if (tdiff != 0) {
                (void) strlcpy(hostname, "localhost", MAXHOSTNAMELEN);
                (void) gethostname(hostname, MAXHOSTNAMELEN);

                priority = (tdiff > 2) ? LOG_NOTICE : LOG_DEBUG;
                syslog(priority, "DC [%s] clock skew detected: %u minutes",
                    server, tdiff);

                tm = gmtime(&dc_tv.tv_sec);
                syslog(priority, "%-8s  UTC: %s", server, asctime(tm));
                tm = gmtime(&tnow);
                syslog(priority, "%-8s  UTC: %s", hostname, asctime(tm));
        }
}

/*
 * Synchronize the local system clock with the domain controller.
 */
void
srvsvc_timesync(void)
{
        smb_domainex_t di;
        struct timeval tv;
        struct tm tm;
        time_t tsecs;

        if (!smb_domain_getinfo(&di))
                return;

        if (srvsvc_net_remote_tod(di.d_dci.dc_name, di.d_primary.di_nbname,
            &tv, &tm) != 0)
                return;

        if (settimeofday(&tv, 0))
                smb_tracef("unable to set system time");

        tsecs = time(0);
        (void) localtime_r(&tsecs, &tm);
        smb_tracef("SrvsvcTimeSync %s", ctime((time_t *)&tv.tv_sec));
}

/*
 * NetRemoteTOD to get the current GMT time from a Windows NT server.
 */
int
srvsvc_gettime(unsigned long *t)
{
        smb_domainex_t di;
        struct timeval tv;
        struct tm tm;

        if (!smb_domain_getinfo(&di))
                return (-1);

        if (srvsvc_net_remote_tod(di.d_dci.dc_name, di.d_primary.di_nbname,
            &tv, &tm) != 0)
                return (-1);

        *t = tv.tv_sec;
        return (0);
}

/*
 * This is a client side routine for NetRemoteTOD, which gets the time
 * and date from a remote system. The time information is returned in
 * the timeval and tm.
 *
 * typedef struct _TIME_OF_DAY_INFO {
 *      DWORD tod_elapsedt;  // seconds since 00:00:00 January 1 1970 GMT
 *      DWORD tod_msecs;     // arbitrary milliseconds (since reset)
 *      DWORD tod_hours;     // current hour [0-23]
 *      DWORD tod_mins;      // current minute [0-59]
 *      DWORD tod_secs;      // current second [0-59]
 *      DWORD tod_hunds;     // current hundredth (0.01) second [0-99]
 *      LONG tod_timezone;   // time zone of the server
 *      DWORD tod_tinterval; // clock tick time interval
 *      DWORD tod_day;       // day of the month [1-31]
 *      DWORD tod_month;     // month of the year [1-12]
 *      DWORD tod_year;      // current year
 *      DWORD tod_weekday;   // day of the week since sunday [0-6]
 * } TIME_OF_DAY_INFO;
 *
 * The time zone of the server is calculated in minutes from Greenwich
 * Mean Time (GMT). For time zones west of Greenwich, the value is
 * positive; for time zones east of Greenwich, the value is negative.
 * A value of -1 indicates that the time zone is undefined.
 *
 * The clock tick value represents a resolution of one ten-thousandth
 * (0.0001) second.
 */
int
srvsvc_net_remote_tod(char *server, char *domain, struct timeval *tv,
    struct tm *tm)
{
        struct mslm_NetRemoteTOD        arg;
        struct mslm_TIME_OF_DAY_INFO    *tod;
        mlsvc_handle_t                  handle;
        int                             rc;
        int                             opnum;
        int                             len;
        char                            user[SMB_USERNAME_MAXLEN];

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        rc = srvsvc_open(server, domain, user, &handle);
        if (rc != 0)
                return (-1);

        opnum = SRVSVC_OPNUM_NetRemoteTOD;
        bzero(&arg, sizeof (struct mslm_NetRemoteTOD));

        len = strlen(server) + 4;
        arg.servername = ndr_rpc_malloc(&handle, len);
        if (arg.servername == NULL) {
                srvsvc_close(&handle);
                return (-1);
        }

        (void) snprintf((char *)arg.servername, len, "\\\\%s", server);

        rc = ndr_rpc_call(&handle, opnum, &arg);
        if ((rc != 0) || (arg.status != 0)) {
                srvsvc_close(&handle);
                return (-1);
        }

        /*
         * We're assigning milliseconds to microseconds
         * here but the value's not really relevant.
         */
        tod = arg.bufptr;

        if (tv) {
                tv->tv_sec = tod->tod_elapsedt;
                tv->tv_usec = tod->tod_msecs;
        }

        if (tm) {
                tm->tm_sec = tod->tod_secs;
                tm->tm_min = tod->tod_mins;
                tm->tm_hour = tod->tod_hours;
                tm->tm_mday = tod->tod_day;
                tm->tm_mon = tod->tod_month - 1;
                tm->tm_year = tod->tod_year - 1900;
                tm->tm_wday = tod->tod_weekday;
        }

        srvsvc_close(&handle);
        return (0);
}