/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This file comprises the main driver for this tool.
 * Upon parsing the command verbs from user input, it
 * branches to the appropriate modules to perform the
 * requested task.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <libgen.h>
#include <errno.h>
#include <cryptoutil.h>
#include <security/cryptoki.h>
#include "common.h"

/*
 * The verbcmd construct allows genericizing information about a verb so
 * that it is easier to manipulate.  Makes parsing code easier to read,
 * fix, and extend with new verbs.
 */
typedef struct verbcmd_s {
	char	*verb;
	int	(*action)(int, char *[]);
	int	mode;
	char	*synopsis;
} verbcmd;

/* External declarations for supported verb actions. */
extern int	pk_setpin(int argc, char *argv[]);
extern int	pk_list(int argc, char *argv[]);
extern int	pk_delete(int argc, char *argv[]);
extern int	pk_import(int argc, char *argv[]);
extern int	pk_export(int argc, char *argv[]);
extern int	pk_tokens(int argc, char *argv[]);

/* Forward declarations for "built-in" verb actions. */
static int	pk_help(int argc, char *argv[]);

/* Command structure for verbs and their actions.  Do NOT i18n/l10n. */
static verbcmd	cmds[] = {
	{ "tokens",	pk_tokens,	0,	"tokens" },
	{ "setpin",	pk_setpin,	0,
	    "setpin [token=<token>[:<manuf>[:<serial>]]]" },
	{ "list",	pk_list,	0,
	    "list [token=<token>[:<manuf>[:<serial>]]] "
	    "[objtype=private|public|both] [label=<label>]" },
	{ "delete",	pk_delete,	0,
	    "delete [token=<token>[:<manuf>[:<serial>]]] "
	    "{ [objtype=private|public|both] [label=<label>] }" },
	{ "import",	pk_import,	0,
	    "import [token=<token>[:<manuf>[:<serial>]]] infile=<file>" },
	{ "export",	pk_export,	0,
	    "export [token=<token>[:<manuf>[:<serial>]]] outfile=<file>" },
	{ "-?",		pk_help,	0,	"help\t(help and usage)" },
};
static int	num_cmds = sizeof (cmds) / sizeof (verbcmd);

static char	*prog;
static void	usage(void);

/*
 * Usage information.  This function must be updated when new verbs or
 * options are added.
 */
static void
usage(void)
{
	int	i;

	cryptodebug("inside usage");

	/* Display this block only in command-line mode. */
	(void) fprintf(stdout, gettext("Usage:\n"));
	(void) fprintf(stdout, gettext("\t%s -?\t(help and usage)\n"), prog);
	(void) fprintf(stdout, gettext("\t%s subcommand [options...]\n"), prog);
	(void) fprintf(stdout, gettext("where subcommands may be:\n"));

	/* Display only those verbs that match the current tool mode. */
	for (i = 0; i < num_cmds; i++) {
		/* Do NOT i18n/l10n. */
		(void) fprintf(stdout, "\t%s\n", cmds[i].synopsis);
	}
}

/*
 * Provide help, in the form of displaying the usage.
 */
static int
pk_help(int argc, char *argv[])
/* ARGSUSED */
{
	cryptodebug("inside pk_help");

	usage();
	return (0);
}

/*
 * MAIN() -- where all the action is
 */
int
main(int argc, char *argv[], char *envp[])
/* ARGSUSED2 */
{
	int	i, found = -1;
	int	rv;
	int	pk_argc = 0;
	char	**pk_argv = NULL;
	int	save_errno = 0;

	/* Set up for i18n/l10n. */
	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D. */
#define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it isn't. */
#endif
	(void) textdomain(TEXT_DOMAIN);

	/* Get program base name and move pointer over 0th arg. */
	prog = basename(argv[0]);
	argv++, argc--;

	/* Set up for debug and error output. */
	cryptodebug_init(prog);

	if (argc == 0) {
		usage();
		return (1);
	}

	/* Check for help options.  For CLIP-compliance. */
	if (argc == 1 && argv[0][0] == '-') {
		switch (argv[0][1]) {
		case '?':
			return (pk_help(argc, argv));
		default:
			usage();
			return (1);
		}
	}

	/* Always turns off Metaslot so that we can see softtoken. */
	cryptodebug("disabling Metaslot");
	if (setenv("METASLOT_ENABLED", "false", 1) < 0) {
		save_errno = errno;
		cryptoerror(LOG_STDERR,
		    gettext("Disabling Metaslot failed (%s)."),
		    strerror(save_errno));
		return (1);
	}

	/* Begin parsing command line. */
	cryptodebug("begin parsing command line");
	pk_argc = argc;
	pk_argv = argv;

	/* Check for valid verb (or an abbreviation of it). */
	found = -1;
	for (i = 0; i < num_cmds; i++) {
		if (strcmp(cmds[i].verb, pk_argv[0]) == 0) {
			if (found < 0) {
				cryptodebug("found cmd %s", cmds[i].verb);
				found = i;
				break;
			} else {
				cryptodebug("also found cmd %s, skipping",
				    cmds[i].verb);
			}
		}
	}
	/* Stop here if no valid verb found. */
	if (found < 0) {
		cryptoerror(LOG_STDERR, gettext("Invalid verb: %s"),
		    pk_argv[0]);
		return (1);
	}

	/* Get to work! */
	cryptodebug("begin executing cmd action");
	rv = (*cmds[found].action)(pk_argc, pk_argv);
	cryptodebug("end executing cmd action");
	switch (rv) {
	case PK_ERR_NONE:
		cryptodebug("subcommand succeeded");
		break;		/* Command succeeded, do nothing. */
	case PK_ERR_USAGE:
		cryptodebug("usage error detected");
		usage();
		break;
	case PK_ERR_QUIT:
		cryptodebug("quit command received");
		exit(0);
		/* NOTREACHED */
	case PK_ERR_PK11:
		cryptoerror(LOG_STDERR, "%s",
		    gettext("Command failed due to PKCS#11 error."));
		break;
	case PK_ERR_SYSTEM:
		cryptoerror(LOG_STDERR, "%s",
		    gettext("Command failed due to system error."));
		break;
	case PK_ERR_OPENSSL:
		cryptoerror(LOG_STDERR, "%s",
		    gettext("Command failed due to OpenSSL error."));
		break;
	default:
		cryptoerror(LOG_STDERR, "%s (%d).",
		    gettext("Unknown error value"), rv);
		break;
	}
	return (rv);
}