/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2022 RackTop Systems, Inc.
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <rpc/rpc.h>
#include <sys/uuid.h>
#include <smb/ntstatus.h>
#include <synch.h>
#include <thread.h>
#include <arpa/inet.h>
#include <uuid/uuid.h>

#include "idmapd.h"
#include "libadutils.h"
#include "dsgetdc.h"
#include "ads_priv.h"

void adspriv_program_1(struct svc_req *, register SVCXPRT *);

SVCXPRT *dcl_xprt = NULL;

void
init_dc_locator(void)
{
	int	connmaxrec = 32 * 1024;

	dcl_xprt = svc_door_create(adspriv_program_1,
	    ADSPRIV_PROGRAM, ADSPRIV_V1, connmaxrec);
	if (dcl_xprt == NULL) {
		syslog(LOG_ERR, "unable to create door RPC service");
		return;
	}

	if (!svc_control(dcl_xprt, SVCSET_CONNMAXREC, &connmaxrec)) {
		syslog(LOG_ERR, "unable to limit RPC request size");
	}
}

void
fini_dc_locator(void)
{
	if (dcl_xprt != NULL)
		svc_destroy(dcl_xprt);
}

/*
 * Functions called by the (generated) adspriv_srv.c
 */

/* ARGSUSED */
bool_t
adspriv_null_1_svc(void *result, struct svc_req *rqstp)
{
	return (TRUE);
}

/* ARGSUSED */
bool_t
adspriv_forcerediscovery_1_svc(
	DsForceRediscoveryArgs args,
	int *res,
	struct svc_req *sreq)
{
	/* Ignoring args for now. */

	idmap_cfg_force_rediscovery();
	*res = 0;

	return (TRUE);
}


/* ARGSUSED */
bool_t
adspriv_getdcname_1_svc(
	DsGetDcNameArgs args,
	DsGetDcNameRes *res,
	struct svc_req *sreq)
{
	uuid_t uuid;
	adspriv_dcinfo *dci;
	idmap_pg_config_t *pgcfg;
	ad_disc_ds_t *ds;
	char *s;

	/* Init */
	(void) memset(res, 0, sizeof (*res));
	res->status = 0;
	dci = &res->DsGetDcNameRes_u.res0;

	if (args.Flags & DS_FORCE_REDISCOVERY)
		idmap_cfg_force_rediscovery();

	/*
	 * We normally should wait if discovery is running.
	 * Sort of mis-using the background flag as a way to
	 * skip the wait, until we really do background disc.
	 */
	if ((args.Flags & DS_BACKGROUND_ONLY) == 0) {
		timespec_t tv = { 15, 0 };
		int rc = 0;
		int waited = 0;

		(void) mutex_lock(&_idmapdstate.addisc_lk);

		if (_idmapdstate.addisc_st != 0)
			idmapdlog(LOG_DEBUG, "getdcname wait begin");

		while (_idmapdstate.addisc_st != 0) {
			waited++;
			rc = cond_reltimedwait(&_idmapdstate.addisc_cv,
			    &_idmapdstate.addisc_lk, &tv);
			if (rc == ETIME)
				break;
		}
		(void) mutex_unlock(&_idmapdstate.addisc_lk);

		if (rc == ETIME) {
			/* Caller will replace this with DC not found. */
			idmapdlog(LOG_ERR, "getdcname timeout");
			res->status = NT_STATUS_CANT_WAIT;
			return (TRUE);
		}
		if (waited) {
			idmapdlog(LOG_DEBUG, "getdcname wait done");
		}
	}

	RDLOCK_CONFIG();
	pgcfg = &_idmapdstate.cfg->pgcfg;

	if (pgcfg->domain_name == NULL) {
		res->status = NT_STATUS_INVALID_SERVER_STATE;
		goto out;
	}

	if (args.DomainName != NULL && args.DomainName[0] != '\0' &&
	    0 != strcasecmp(args.DomainName, pgcfg->domain_name)) {
		/*
		 * They asked for a specific domain not our primary,
		 * which is not supported (and not needed).
		 */
		res->status = NT_STATUS_NO_SUCH_DOMAIN;
		goto out;
	}

	if ((ds = pgcfg->domain_controller) == NULL) {
		res->status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
		goto out;
	}

	dci->dci_DcName = strdup(ds->host);
	if (dci->dci_DcName == NULL) {
		res->status = NT_STATUS_NO_MEMORY;
		goto out;
	}

	dci->dci_DcAddr = calloc(1, INET6_ADDRSTRLEN);
	if (dci->dci_DcAddr == NULL) {
		res->status = NT_STATUS_NO_MEMORY;
		goto out;
	}
	if (ad_disc_getnameinfo(dci->dci_DcAddr, INET6_ADDRSTRLEN,
	    &ds->addr) == 0)
		dci->dci_AddrType = DS_INET_ADDRESS;

	if ((s = pgcfg->domain_guid) != NULL &&
	    0 == uuid_parse(s, uuid)) {
		(void) memcpy(dci->dci_guid, uuid, sizeof (uuid));
	}

	if ((s = pgcfg->domain_name) != NULL) {
		dci->dci_DomainName = strdup(s);
		if (dci->dci_DomainName == NULL) {
			res->status = NT_STATUS_NO_MEMORY;
			goto out;
		}
	}

	if ((s = pgcfg->forest_name) != NULL) {
		dci->dci_DnsForestName = strdup(s);
		if (dci->dci_DnsForestName == NULL) {
			res->status = NT_STATUS_NO_MEMORY;
			goto out;
		}
	}

	dci->dci_Flags = ds->flags;
	dci->dci_DcSiteName = strdup(ds->site);
	if (dci->dci_DcSiteName == NULL) {
		res->status = NT_STATUS_NO_MEMORY;
		goto out;
	}

	if ((s = pgcfg->site_name) != NULL) {
		dci->dci_ClientSiteName = strdup(s);
		if (dci->dci_ClientSiteName == NULL) {
			res->status = NT_STATUS_NO_MEMORY;
			goto out;
		}
	}

	/* Address in binary form too. */
	(void) memcpy(&dci->dci_sockaddr,
	    &ds->addr, ADSPRIV_SOCKADDR_LEN);

out:
	UNLOCK_CONFIG();

	if (res->status != 0) {
		/* Caller will free only if status == 0 */
		xdr_free(xdr_adspriv_dcinfo, (char *)dci);
	}

	return (TRUE);
}

/* ARGSUSED */
int
adspriv_program_1_freeresult(SVCXPRT *xprt, xdrproc_t fun, caddr_t res)
{
	xdr_free(fun, res);
	return (TRUE);
}
