/*
 * 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 2021 RackTop Systems, Inc.
 */


#include <stdio.h>
#include <libintl.h>
#include <stdlib.h>
#include <strings.h>
#include <err.h>
#include <ads/dsgetdc.h>
#include <smb/nterror.h>
#include <uuid/uuid.h>


static void dclist_usage(void);
static int cmd_dclist(char *);
static void dcname_usage(void);
static int cmd_dcname(char *);
static void dsgetdc_usage(void);
static int cmd_dsgetdc(char *);
static void dsgetdcname_usage(void);
static int cmd_dsgetdcname(char *);
static void kick_usage(void);
static int cmd_kick(char *);
static void help(void);

typedef int cmd_fn_t (char *);
typedef void cmd_usage_t (void);


static struct commands {
	const char	*name;	/* name of subcommand */
	cmd_fn_t	*fn;	/* pointer to subcommand handler function */
	cmd_usage_t	*usage;	/* pointer to subcommand help function */
	int		optreq; /* does this have a required optval */
} commands[] = {
	{"dclist", cmd_dclist, dclist_usage, 0},
	{"dcname", cmd_dcname, dcname_usage, 0},
	{"dsgetdc", cmd_dsgetdc, dsgetdc_usage, 0},
	{"dsgetdcname", cmd_dsgetdcname, dsgetdcname_usage, 0},
	{"kick", cmd_kick, kick_usage, 0},
	{NULL, NULL, NULL, 0}
};


/*
 * lookupcmd
 */
static struct commands *
lookupcmd(const char *name)
{
	struct commands *cmd;

	for (cmd = commands; cmd->name; cmd++) {
		if (strcasecmp(cmd->name, name) == 0)
			return (cmd);
	}
	return (NULL);
}

/*
 * dclist
 */
static void
dclist_usage(void)
{
	(void) printf(gettext("usage: nltest dclist... \n"));
	exit(1);
}

/* ARGSUSED */
static int
cmd_dclist(char *optval)
{
	(void) printf("cmd_dclist() \n");
	return (0);
}

/*
 * dcname
 */
static void
dcname_usage(void)
{
	(void) printf(gettext("usage: nltest dcname... \n"));
	exit(1);
}

/* ARGSUSED */
static int
cmd_dcname(char *optval)
{
	(void) printf("cmd_dcname() \n");
	return (0);
}

/*
 * dsgetdc
 */
static void
dsgetdc_usage(void)
{
	(void) printf(gettext("usage: nltest dsgetdc... \n"));
	exit(1);
}

/* ARGSUSED */
static int
cmd_dsgetdc(char *optval)
{
	(void) printf("cmd_dsgetdc() \n");
	return (0);
}

/*
 * dsgetdcname
 */
static void
dsgetdcname_usage(void)
{
	(void) printf(gettext("usage: nltest dsgetdcname domainname \n"));
	exit(1);
}

static int
cmd_dsgetdcname(char *domname)
{
	char uuid_buf[UUID_PRINTABLE_STRING_LENGTH];
	int err = 0;
	char *atype;
	DOMAIN_CONTROLLER_INFO *dcinfo;

	if (domname != NULL)
		(void) printf("  Domain name supplied:  %s \n", domname);

	err = DsGetDcName(NULL, domname, NULL, NULL, 0, &dcinfo);

	switch (err) {
	case 0:
		break;
	case ERROR_NO_SUCH_DOMAIN:
		(void) printf("Domain controller not found.\n");
		(void) printf("See: /var/run/idmap/discovery.log\n");
		exit(1);
	default:
		(void) printf("Unexpected error %d\n", err);
		exit(1);
	}

	switch (dcinfo->DomainControllerAddressType) {
	case DS_INET_ADDRESS:
		atype = "inet";
		break;
	case DS_NETBIOS_ADDRESS:
		atype = "netbios";
		break;
	default:
		atype = "?";
		break;
	}

	uuid_unparse(dcinfo->DomainGuid, uuid_buf);

	(void) printf("Data Returned from DsGetDcName() call: \n");
	(void) printf("  DC Name:  %s \n", dcinfo->DomainControllerName);
	(void) printf("  DC Addr:  %s \n", dcinfo->DomainControllerAddress);
	(void) printf("  DC Addr Type:  %s \n", atype);
	(void) printf("  Domain Name:  %s \n", dcinfo->DomainName);
	(void) printf("  Domain GUID:  %s \n", uuid_buf);
	(void) printf("  DNS Forest Name:  %s \n", dcinfo->DnsForestName);
	(void) printf("  Flags:  0x%x \n", dcinfo->Flags);
	(void) printf("  DC Site Name:  %s \n", dcinfo->DcSiteName);
	(void) printf("  Client Site Name:  %s \n", dcinfo->ClientSiteName);

	DsFreeDcInfo(dcinfo);

	return (0);
}

/*
 * kick
 */
static void
kick_usage(void)
{
	(void) printf(gettext("usage: nltest /KICK \n"));
	exit(1);
}


static int
cmd_kick(char *domname)
{
	int flags = 0;
	int result;

	result = _DsForceRediscovery(domname, flags);

	return (result);
}

/*
 * help functions
 */

static void
help(void)
{
	(void) printf("\n");
	/*
	 * TODO: We may want to revise this help text.  It's basically
	 * a copy-paste from:
	 *   http://technet.microsoft.com/en-us/library/cc731935.aspx
	 */
	(void) printf(gettext("usage: %s /subcommand\n"),
	    (char *)getexecname());
	(void) printf(gettext("where subcommands are:\n"
#if 0	/* not yet */
	    " dclist        Lists all domain controllers in the domain.\n"
	    " dcname        Lists the PDC or PDC emulator.\n"
	    " dsgetdc       Queries DNS server for list of DCs and"
	    " their IP addresses and contacts each DC to check"
	    " for connectivity.\n"
#endif
	    " dsgetdcname   returns the name of a domain controller in a"
	    " specified domain\n"
	    " help          display help on specified subcommand\n"
	    " kick          trigger domain controller re-discovery\n"
	    "\n"));
	exit(1);
}

int
main(int argc, char *argv[])
{
	struct commands *cmd;
	int err = 0;
	char *option_cmd = NULL;
	char *arg;
	char *p;
	char *optname;
	char *optval = NULL;
	int i;
	int optind = 1;

	/*
	 * Parse options.
	 */
	while (optind < argc) {
		arg = argv[optind];
		optname = NULL;
		optval = NULL;

		/* Is this an option? */
		if (arg[0] == '/') {
			optname = arg + 1;
			optind++;

			/*
			 * May have  /optname:value
			 */
			if ((p = strchr(optname, ':')) != NULL) {
				*p++ = '\0';
				optval = p;
			}
		} else if (arg[0] == '-' && arg[1] == '-') {
			optname = arg + 2;
			optind++;

			/*
			 * May have  --optname=value
			 */
			if ((p = strchr(optname, '=')) != NULL) {
				*p++ = '\0';
				optval = p;
			}
		} else {
			/* Not an option.  Stop parsing. */
			break;
		}

		/*
		 * Handle each optname (and maybe its optval)
		 * Might put this logic in a table of options.
		 * (including a flag for "optval required",
		 * so that check could be factored out)
		 */
		for (cmd = commands; cmd->name; cmd++) {
			if (!strcasecmp(optname, cmd->name)) {
				/* cmd->name  requires an optval */
				if (optval == NULL && optind < argc)
					optval = argv[optind++];

				if (optval == NULL && cmd->optreq > 0) {
					(void) fprintf(stderr,
					    "%s: option %s requires a value\n",
					    argv[0], optname);
					return (1);
				}
				option_cmd = optname;
			}
		}
	}

	/*
	 * Handle remaining non-option arguments
	 */
	for (i = optind; i < argc; i++) {
		(void) printf("arg: %s\n", argv[i]);
	}

	if (option_cmd == NULL)
		help();

	cmd = lookupcmd(option_cmd);
	if (cmd == NULL)
		err = 1;
	else
		err = cmd->fn(optval);

	return (err);
}