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

/*
 * This module provides the high level interface to the LSA RPC functions.
 */

#include <strings.h>
#include <unistd.h>

#include <smbsrv/libsmb.h>
#include <smbsrv/libmlsvc.h>
#include <smbsrv/smbinfo.h>
#include <smbsrv/smb_token.h>

#include <lsalib.h>

static uint32_t lsa_lookup_name_int(char *, uint16_t, smb_account_t *,
    boolean_t);
static uint32_t lsa_lookup_sid_int(smb_sid_t *, smb_account_t *, boolean_t);

static uint32_t lsa_lookup_name_builtin(char *, char *, smb_account_t *);
static uint32_t lsa_lookup_name_domain(char *, smb_account_t *);

static uint32_t lsa_lookup_sid_builtin(smb_sid_t *, smb_account_t *);
static uint32_t lsa_lookup_sid_domain(smb_sid_t *, smb_account_t *);

static uint32_t lsa_list_accounts(mlsvc_handle_t *);
static uint32_t lsa_map_status(uint32_t);

/*
 * Lookup the given account and returns the account information
 * in the passed smb_account_t structure.
 *
 * The lookup is performed in the following order:
 *    well known accounts
 *    local accounts
 *    domain accounts
 *
 * If it's established the given account is well know or local
 * but the lookup fails for some reason, the next step(s) won't be
 * performed.
 *
 * If the name is a domain account, it may refer to a user, group or
 * alias. If it is a local account, its type should be specified
 * in the sid_type parameter. In case the account type is unknown
 * sid_type should be set to SidTypeUnknown.
 *
 * account argument could be either [domain\]name or [domain/]name.
 *
 * Return status:
 *
 *   NT_STATUS_SUCCESS          Account is successfully translated
 *   NT_STATUS_NONE_MAPPED      Couldn't translate the account
 */
uint32_t
lsa_lookup_name(char *account, uint16_t type, smb_account_t *info)
{
        return (lsa_lookup_name_int(account, type, info, B_TRUE));
}

/* Variant that avoids the call out to AD. */
uint32_t
lsa_lookup_lname(char *account, uint16_t type, smb_account_t *info)
{
        return (lsa_lookup_name_int(account, type, info, B_FALSE));
}

uint32_t
lsa_lookup_name_int(char *account, uint16_t type, smb_account_t *info,
    boolean_t try_ad)
{
        char nambuf[SMB_USERNAME_MAXLEN];
        char dombuf[SMB_PI_MAX_DOMAIN];
        char *name, *domain;
        uint32_t status;
        char *slash;

        if (account == NULL)
                return (NT_STATUS_NONE_MAPPED);

        (void) strsubst(account, '/', '\\');
        (void) strcanon(account, "\\");
        /* \john -> john */
        account += strspn(account, "\\");

        if ((slash = strchr(account, '\\')) != NULL) {
                *slash = '\0';
                (void) strlcpy(dombuf, account, sizeof (dombuf));
                (void) strlcpy(nambuf, slash + 1, sizeof (nambuf));
                *slash = '\\';
                name = nambuf;
                domain = dombuf;
        } else {
                name = account;
                domain = NULL;
        }

        status = lsa_lookup_name_builtin(domain, name, info);
        if (status == NT_STATUS_NOT_FOUND) {
                status = smb_sam_lookup_name(domain, name, type, info);
                if (status == NT_STATUS_SUCCESS)
                        return (status);

                if (try_ad && ((domain == NULL) ||
                    (status == NT_STATUS_NOT_FOUND))) {
                        status = lsa_lookup_name_domain(account, info);
                }
        }

        return ((status == NT_STATUS_SUCCESS) ? status : NT_STATUS_NONE_MAPPED);
}

uint32_t
lsa_lookup_sid(smb_sid_t *sid, smb_account_t *info)
{
        return (lsa_lookup_sid_int(sid, info, B_TRUE));
}

/* Variant that avoids the call out to AD. */
uint32_t
lsa_lookup_lsid(smb_sid_t *sid, smb_account_t *info)
{
        return (lsa_lookup_sid_int(sid, info, B_FALSE));
}

static uint32_t
lsa_lookup_sid_int(smb_sid_t *sid, smb_account_t *info, boolean_t try_ad)
{
        uint32_t status;

        if (!smb_sid_isvalid(sid))
                return (NT_STATUS_INVALID_SID);

        status = lsa_lookup_sid_builtin(sid, info);
        if (status == NT_STATUS_NOT_FOUND) {
                status = smb_sam_lookup_sid(sid, info);
                if (try_ad && status == NT_STATUS_NOT_FOUND) {
                        status = lsa_lookup_sid_domain(sid, info);
                }
        }

        return ((status == NT_STATUS_SUCCESS) ? status : NT_STATUS_NONE_MAPPED);
}

/*
 * Obtains the primary domain SID and name from the specified server
 * (domain controller).
 *
 * The requested information will be returned via 'info' argument.
 *
 * Returns NT status codes. (Raw, not LSA-ized)
 */
DWORD
lsa_query_primary_domain_info(char *server, char *domain,
    smb_domain_t *info)
{
        mlsvc_handle_t domain_handle;
        char user[SMB_USERNAME_MAXLEN];
        DWORD status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        status = lsar_open(server, domain, user, &domain_handle);
        if (status != 0)
                return (status);

        status = lsar_query_info_policy(&domain_handle,
            MSLSA_POLICY_PRIMARY_DOMAIN_INFO, info);

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * Obtains the account domain SID and name from the current server
 * (domain controller).
 *
 * The requested information will be returned via 'info' argument.
 *
 * Returns NT status codes. (Raw, not LSA-ized)
 */
DWORD
lsa_query_account_domain_info(char *server, char *domain,
    smb_domain_t *info)
{
        mlsvc_handle_t domain_handle;
        char user[SMB_USERNAME_MAXLEN];
        DWORD status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        status = lsar_open(server, domain, user, &domain_handle);
        if (status != 0)
                return (status);

        status = lsar_query_info_policy(&domain_handle,
            MSLSA_POLICY_ACCOUNT_DOMAIN_INFO, info);

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * lsa_query_dns_domain_info
 *
 * Obtains the DNS domain info from the specified server
 * (domain controller).
 *
 * The requested information will be returned via 'info' argument.
 *
 * Returns NT status codes. (Raw, not LSA-ized)
 */
DWORD
lsa_query_dns_domain_info(char *server, char *domain, smb_domain_t *info)
{
        mlsvc_handle_t domain_handle;
        char user[SMB_USERNAME_MAXLEN];
        DWORD status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        status = lsar_open(server, domain, user, &domain_handle);
        if (status != 0)
                return (status);

        status = lsar_query_info_policy(&domain_handle,
            MSLSA_POLICY_DNS_DOMAIN_INFO, info);

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * Enumerate the trusted domains of  primary domain.
 * This is the basic enumaration call which only returns the
 * NetBIOS name of the domain and its SID.
 *
 * The requested information will be returned via 'info' argument.
 *
 * Returns NT status codes.  (Raw, not LSA-ized)
 */
DWORD
lsa_enum_trusted_domains(char *server, char *domain,
    smb_trusted_domains_t *info)
{
        mlsvc_handle_t domain_handle;
        char user[SMB_USERNAME_MAXLEN];
        DWORD enum_context;
        DWORD status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        status = lsar_open(server, domain, user, &domain_handle);
        if (status != 0)
                return (status);

        enum_context = 0;

        status = lsar_enum_trusted_domains(&domain_handle, &enum_context, info);
        if (status == NT_STATUS_NO_MORE_ENTRIES) {
                /*
                 * STATUS_NO_MORE_ENTRIES indicates that we
                 * have all of the available information.
                 */
                status = NT_STATUS_SUCCESS;
        }

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * Enumerate the trusted domains of the primary domain.
 * This is the extended enumaration call which besides
 * NetBIOS name of the domain and its SID, it will return
 * the FQDN plus some trust information which is not used.
 *
 * The requested information will be returned via 'info' argument.
 *
 * Returns NT status codes. (Raw, not LSA-ized)
 */
DWORD
lsa_enum_trusted_domains_ex(char *server, char *domain,
    smb_trusted_domains_t *info)
{
        mlsvc_handle_t domain_handle;
        char user[SMB_USERNAME_MAXLEN];
        DWORD enum_context;
        DWORD status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        status = lsar_open(server, domain, user, &domain_handle);
        if (status != 0)
                return (status);

        enum_context = 0;

        status = lsar_enum_trusted_domains_ex(&domain_handle, &enum_context,
            info);
        if (status == NT_STATUS_NO_MORE_ENTRIES) {
                /*
                 * STATUS_NO_MORE_ENTRIES indicates that we
                 * have all of the available information.
                 */
                status = NT_STATUS_SUCCESS;
        }

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * Lookup well known accounts table
 *
 * Return status:
 *
 *   NT_STATUS_SUCCESS          Account is translated successfully
 *   NT_STATUS_NOT_FOUND        This is not a well known account
 *   NT_STATUS_NONE_MAPPED      Account is found but domains don't match
 *   NT_STATUS_NO_MEMORY        Memory shortage
 *   NT_STATUS_INTERNAL_ERROR   Internal error/unexpected failure
 */
static uint32_t
lsa_lookup_name_builtin(char *domain, char *name, smb_account_t *info)
{
        smb_wka_t *wka;
        char *wkadom;

        bzero(info, sizeof (smb_account_t));

        if ((wka = smb_wka_lookup_name(name)) == NULL)
                return (NT_STATUS_NOT_FOUND);

        if ((wkadom = smb_wka_get_domain(wka->wka_domidx)) == NULL)
                return (NT_STATUS_INTERNAL_ERROR);

        if ((domain != NULL) && (smb_strcasecmp(domain, wkadom, 0) != 0))
                return (NT_STATUS_NONE_MAPPED);

        info->a_name = strdup(name);
        info->a_sid = smb_sid_dup(wka->wka_binsid);
        info->a_domain = strdup(wkadom);
        info->a_domsid = smb_sid_split(wka->wka_binsid, &info->a_rid);
        info->a_type = wka->wka_type;

        if (!smb_account_validate(info)) {
                smb_account_free(info);
                return (NT_STATUS_NO_MEMORY);
        }

        return (NT_STATUS_SUCCESS);
}

/*
 * Lookup a domain account by its name.
 *
 * The information is returned in the user_info structure.
 * The caller is responsible for allocating and releasing
 * this structure.
 *
 * Returns NT status codes. (LSA-ized)
 */
static uint32_t
lsa_lookup_name_domain(char *account_name, smb_account_t *info)
{
        mlsvc_handle_t domain_handle;
        smb_domainex_t dinfo;
        char user[SMB_USERNAME_MAXLEN];
        uint32_t status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        if (!smb_domain_getinfo(&dinfo))
                return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);

        status = lsar_open(dinfo.d_dci.dc_name, dinfo.d_primary.di_nbname,
            user, &domain_handle);
        if (status != 0)
                return (lsa_map_status(status));

        status = lsar_lookup_names(&domain_handle, account_name, info);

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * lsa_lookup_privs
 *
 * Request the privileges associated with the specified account. In
 * order to get the privileges, we first have to lookup the name on
 * the specified domain controller and obtain the appropriate SID.
 * The SID can then be used to open the account and obtain the
 * account privileges. The results from both the name lookup and the
 * privileges are returned in the user_info structure. The caller is
 * responsible for allocating and releasing this structure.
 *
 * Returns NT status codes. (LSA-ized)
 */
/*ARGSUSED*/
DWORD
lsa_lookup_privs(char *account_name, char *target_name, smb_account_t *ainfo)
{
        mlsvc_handle_t domain_handle;
        smb_domainex_t dinfo;
        char user[SMB_USERNAME_MAXLEN];
        DWORD status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        if (!smb_domain_getinfo(&dinfo))
                return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);

        status = lsar_open(dinfo.d_dci.dc_name, dinfo.d_primary.di_nbname,
            user, &domain_handle);
        if (status != 0)
                return (lsa_map_status(status));

        status = lsa_list_accounts(&domain_handle);
        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * lsa_list_privs
 *
 * List the privileges supported by the specified server.
 * This function is only intended for diagnostics.
 *
 * Returns NT status codes. (LSA-ized)
 */
DWORD
lsa_list_privs(char *server, char *domain)
{
        static char name[128];
        static struct ms_luid luid;
        mlsvc_handle_t domain_handle;
        char user[SMB_USERNAME_MAXLEN];
        DWORD status;
        int rc;
        int i;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        status = lsar_open(server, domain, user, &domain_handle);
        if (status != 0)
                return (lsa_map_status(status));

        for (i = 0; i < 30; ++i) {
                luid.low_part = i;
                rc = lsar_lookup_priv_name(&domain_handle, &luid, name, 128);
                if (rc != 0)
                        continue;

                (void) lsar_lookup_priv_value(&domain_handle, name, &luid);
                (void) lsar_lookup_priv_display_name(&domain_handle, name,
                    name, 128);
        }

        (void) lsar_close(&domain_handle);
        return (NT_STATUS_SUCCESS);
}

/*
 * lsa_list_accounts
 *
 * This function can be used to list the accounts in the specified
 * domain. For now the SIDs are just listed in the system log.
 *
 * Returns NT status
 */
static DWORD
lsa_list_accounts(mlsvc_handle_t *domain_handle)
{
        mlsvc_handle_t account_handle;
        struct mslsa_EnumAccountBuf accounts;
        struct mslsa_sid *sid;
        smb_account_t ainfo;
        DWORD enum_context = 0;
        DWORD status;
        int i;

        bzero(&accounts, sizeof (struct mslsa_EnumAccountBuf));

        do {
                status = lsar_enum_accounts(domain_handle, &enum_context,
                    &accounts);
                if (status != 0)
                        return (status);

                for (i = 0; i < accounts.entries_read; ++i) {
                        sid = accounts.info[i].sid;

                        if (lsar_open_account(domain_handle, sid,
                            &account_handle) == 0) {
                                (void) lsar_enum_privs_account(&account_handle,
                                    &ainfo);
                                (void) lsar_close(&account_handle);
                        }

                        free(accounts.info[i].sid);
                }

                if (accounts.info)
                        free(accounts.info);
        } while (status == 0 && accounts.entries_read != 0);

        return (0);
}

/*
 * Lookup well known accounts table for the given SID
 *
 * Return status:
 *
 *   NT_STATUS_SUCCESS          Account is translated successfully
 *   NT_STATUS_NOT_FOUND        This is not a well known account
 *   NT_STATUS_NO_MEMORY        Memory shortage
 *   NT_STATUS_INTERNAL_ERROR   Internal error/unexpected failure
 */
static uint32_t
lsa_lookup_sid_builtin(smb_sid_t *sid, smb_account_t *ainfo)
{
        smb_wka_t *wka;
        char *wkadom;

        bzero(ainfo, sizeof (smb_account_t));

        if ((wka = smb_wka_lookup_sid(sid)) == NULL)
                return (NT_STATUS_NOT_FOUND);

        if ((wkadom = smb_wka_get_domain(wka->wka_domidx)) == NULL)
                return (NT_STATUS_INTERNAL_ERROR);

        ainfo->a_name = strdup(wka->wka_name);
        ainfo->a_sid = smb_sid_dup(wka->wka_binsid);
        ainfo->a_domain = strdup(wkadom);
        ainfo->a_domsid = smb_sid_split(ainfo->a_sid, &ainfo->a_rid);
        ainfo->a_type = wka->wka_type;

        if (!smb_account_validate(ainfo)) {
                smb_account_free(ainfo);
                return (NT_STATUS_NO_MEMORY);
        }

        return (NT_STATUS_SUCCESS);
}

/*
 * Lookup a domain account by its SID.
 *
 * The information is returned in the user_info structure.
 * The caller is responsible for allocating and releasing
 * this structure.
 *
 * Returns NT status codes. (LSA-ized)
 */
static uint32_t
lsa_lookup_sid_domain(smb_sid_t *sid, smb_account_t *ainfo)
{
        mlsvc_handle_t domain_handle;
        smb_domainex_t dinfo;
        char user[SMB_USERNAME_MAXLEN];
        uint32_t status;

        smb_ipc_get_user(user, SMB_USERNAME_MAXLEN);

        if (!smb_domain_getinfo(&dinfo))
                return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO);

        status = lsar_open(dinfo.d_dci.dc_name, dinfo.d_primary.di_nbname,
            user, &domain_handle);
        if (status != 0)
                return (lsa_map_status(status));

        status = lsar_lookup_sids(&domain_handle, sid, ainfo);

        (void) lsar_close(&domain_handle);
        return (status);
}

/*
 * Most functions that call the local security authority expect
 * only a limited set of status returns.  This function maps the
 * status we get from talking to our domain controller into one
 * that LSA functions can return.  Most common errors become:
 * NT_STATUS_CANT_ACCESS_DOMAIN_INFO (when no DC etc.)
 */
static uint32_t
lsa_map_status(uint32_t status)
{
        switch (status) {
        case NT_STATUS_SUCCESS:
                break;
        case NT_STATUS_INVALID_PARAMETER:       /* rpc bind */
                break;
        case NT_STATUS_NO_MEMORY:
                break;
        case NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND:
        case NT_STATUS_BAD_NETWORK_PATH:        /* get server addr */
        case NT_STATUS_NETWORK_ACCESS_DENIED:   /* authentication */
        case NT_STATUS_BAD_NETWORK_NAME:        /* tree connect */
        case NT_STATUS_ACCESS_DENIED:           /* open pipe */
                status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
                break;
        default:
                status = NT_STATUS_UNSUCCESSFUL;
                break;
        }
        return (status);
}