/*
 * 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);
}