/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#include "nspr.h"
#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"
#include "nss.h"
#include "cmdutil.h"
#include "nsspki.h"
/* hmmm...*/
#include "pki.h"

#define PKIUTIL_VERSION_STRING "pkiutil version 0.1"

char *progName = NULL;

typedef struct {
    PRBool      raw;
    PRBool      ascii;
    char       *name;
    PRFileDesc *file;
} objOutputMode;

typedef enum {
    PKIUnknown = -1,
    PKICertificate,
    PKIPublicKey,
    PKIPrivateKey,
    PKIAny
} PKIObjectType;

static PKIObjectType
get_object_class(char *type)
{
    if (strcmp(type, "certificate") == 0 || strcmp(type, "cert") == 0 ||
        strcmp(type, "Certificate") == 0 || strcmp(type, "Cert") == 0) {
	return PKICertificate;
    } else if (strcmp(type, "public_key") == 0 || 
               strcmp(type, "PublicKey") == 0) {
	return PKIPublicKey;
    } else if (strcmp(type, "private_key") == 0 || 
               strcmp(type, "PrivateKey") == 0) {
	return PKIPrivateKey;
    } else if (strcmp(type, "all") == 0 || strcmp(type, "any") == 0) {
	return PKIAny;
    }
    fprintf(stderr, "%s: \"%s\" is not a valid PKCS#11 object type.\n",
                     progName, type);
    return PKIUnknown;
}

static PRStatus
print_cert_callback(NSSCertificate *c, void *arg)
{
    int i;
    NSSUTF8 *label;
    NSSItem *id;
    label = NSSCertificate_GetLabel(c);
    printf("%s\n", label);
    nss_ZFreeIf((void*)label);
#if 0
    id = NSSCertificate_GetID(c);
    for (i=0; i<id->size; i++) {
	printf("%c", ((char *)id->data)[i]);
    }
    printf("\n");
#endif
    return PR_SUCCESS;
}

/*  pkiutil commands  */
enum {
    cmd_Add = 0,
    cmd_Dump,
    cmd_List,
    cmd_Version,
    pkiutil_num_commands
};

/*  pkiutil options */
enum {
    opt_Help = 0,
    opt_Ascii,
    opt_ProfileDir,
    opt_TokenName,
    opt_InputFile,
    opt_Nickname,
    opt_OutputFile,
    opt_Binary,
    opt_Trust,
    opt_Type,
    pkiutil_num_options
};

static cmdCommandLineArg pkiutil_commands[] =
{
 { /* cmd_Add         */  'A', "add", CMDNoArg, 0, PR_FALSE, 
                           CMDBIT(opt_Nickname) | CMDBIT(opt_Trust), 
                           CMDBIT(opt_Ascii) | CMDBIT(opt_ProfileDir) 
                           | CMDBIT(opt_TokenName) | CMDBIT(opt_InputFile) 
                           | CMDBIT(opt_Binary) | CMDBIT(opt_Type) },
 { /* cmd_Dump        */   0 , "dump", CMDNoArg, 0, PR_FALSE,
                           CMDBIT(opt_Nickname),
                           CMDBIT(opt_Ascii) | CMDBIT(opt_ProfileDir) 
                           | CMDBIT(opt_TokenName) | CMDBIT(opt_Binary)
                           | CMDBIT(opt_Type) },
 { /* cmd_List        */  'L', "list", CMDNoArg, 0, PR_FALSE, 0, 
                           CMDBIT(opt_Ascii) | CMDBIT(opt_ProfileDir) 
                           | CMDBIT(opt_TokenName) | CMDBIT(opt_Binary)
                           | CMDBIT(opt_Nickname) | CMDBIT(opt_Type) },
 { /* cmd_Version     */  'Y', "version", CMDNoArg, 0, PR_FALSE, 0, 0 }
};

static cmdCommandLineOpt pkiutil_options[] =
{
 { /* opt_Help        */  '?', "help",     CMDNoArg,   0, PR_FALSE },
 { /* opt_Ascii       */  'a', "ascii",    CMDNoArg,   0, PR_FALSE },
 { /* opt_ProfileDir  */  'd', "dbdir",    CMDArgReq,  0, PR_FALSE },
 { /* opt_TokenName   */  'h', "token",    CMDArgReq,  0, PR_FALSE },
 { /* opt_InputFile   */  'i', "infile",   CMDArgReq,  0, PR_FALSE },
 { /* opt_Nickname    */  'n', "nickname", CMDArgReq,  0, PR_FALSE },
 { /* opt_OutputFile  */  'o', "outfile",  CMDArgReq,  0, PR_FALSE },
 { /* opt_Binary      */  'r', "raw",      CMDNoArg,   0, PR_FALSE },
 { /* opt_Trust       */  't', "trust",    CMDArgReq,  0, PR_FALSE },
 { /* opt_Type        */   0 , "type",     CMDArgReq,  0, PR_FALSE }
};

void pkiutil_usage(cmdPrintState *ps, 
                   int num, PRBool cmd, PRBool header, PRBool footer)
{
#define pusg CMD_PrintUsageString
    if (header) {
	pusg(ps, "utility for managing PKCS#11 objects (certs and keys)\n");
    } else if (footer) {
	/*
	printf("certificate trust can be:\n");
	printf(" p - valid peer, P - trusted peer (implies p)\n");
	printf(" c - valid CA\n");
	printf("  T - trusted CA to issue client certs (implies c)\n");
	printf("  C - trusted CA to issue server certs (implies c)\n");
	printf(" u - user cert\n");
	printf(" w - send warning\n");
	*/
    } else if (cmd) {
	switch(num) {
	case cmd_Add:     
	    pusg(ps, "Add an object to the token"); break;
	case cmd_Dump:
	    pusg(ps, "Dump a single object"); break;
	case cmd_List:    
	    pusg(ps, "List objects on the token (-n for single object)"); break;
	case cmd_Version: 
	    pusg(ps, "Report version"); break;
	default:
	    pusg(ps, "Unrecognized command"); break;
	}
    } else {
	switch(num) {
	case opt_Ascii:      
	    pusg(ps, "Use ascii (base-64 encoded) mode for I/O"); break;
	case opt_ProfileDir:    
	    pusg(ps, "Directory containing security databases (def: \".\")"); 
	    break;
	case opt_TokenName:  
	    pusg(ps, "Name of PKCS#11 token to use (def: internal)"); break;
	case opt_InputFile:  
	    pusg(ps, "File for input (def: stdin)"); break;
	case opt_Nickname:   
	    pusg(ps, "Nickname of object"); break;
	case opt_OutputFile: 
	    pusg(ps, "File for output (def: stdout)"); break;
	case opt_Binary:    
	    pusg(ps, "Use raw (binary der-encoded) mode for I/O"); break;
	case opt_Trust:      
	    pusg(ps, "Trust level for certificate"); break;
	case opt_Help: break;
	default:
	    pusg(ps, "Unrecognized option");
	}
    }
}

int 
main(int argc, char **argv)
{
    PRFileDesc     *infile        = NULL;
    PRFileDesc     *outfile       = NULL;
    char           *profiledir       = "./";
#if 0
    secuPWData      pwdata        = { PW_NONE, 0 };
#endif
    int             objclass      = 3; /* ANY */
    NSSTrustDomain *root_cert_td  = NULL;
    char           *rootpath      = NULL;
    char            builtin_name[]= "libnssckbi.so"; /* temporary hardcode */
    PRStatus        rv            = PR_SUCCESS;

    int cmdToRun;
    cmdCommand pkiutil;
    pkiutil.ncmd = pkiutil_num_commands;
    pkiutil.nopt = pkiutil_num_options;
    pkiutil.cmd = pkiutil_commands;
    pkiutil.opt = pkiutil_options;

    progName = strrchr(argv[0], '/');
    progName = progName ? progName+1 : argv[0];

    cmdToRun = CMD_ParseCommandLine(argc, argv, progName, &pkiutil);

#if 0
    { int i, nc;
    for (i=0; i<pkiutil.ncmd; i++)
	printf("%s: %s <%s>\n", pkiutil.cmd[i].s, 
	                        (pkiutil.cmd[i].on) ? "on" : "off",
				pkiutil.cmd[i].arg);
    for (i=0; i<pkiutil.nopt; i++)
	printf("%s: %s <%s>\n", pkiutil.opt[i].s, 
	                        (pkiutil.opt[i].on) ? "on" : "off",
				pkiutil.opt[i].arg);
    }
#endif

    if (pkiutil.opt[opt_Help].on)
	CMD_LongUsage(progName, &pkiutil, pkiutil_usage);

    if (cmdToRun < 0)
	CMD_Usage(progName, &pkiutil);

    /* -d */
    if (pkiutil.opt[opt_ProfileDir].on) {
	profiledir = strdup(pkiutil.opt[opt_ProfileDir].arg);
    }

    /* -i */
    if (pkiutil.opt[opt_InputFile].on) {
	char *fn = pkiutil.opt[opt_InputFile].arg;
	infile = PR_Open(fn, PR_RDONLY, 0660);
    } else {
	infile = PR_STDIN;
    }

    /* -o */
    if (pkiutil.opt[opt_OutputFile].on) {
	char *fn = pkiutil.opt[opt_OutputFile].arg;
	outfile = PR_Open(fn, PR_WRONLY | PR_CREATE_FILE, 0660);
    } else {
	outfile = PR_STDOUT;
    }

    /* --type can be found on many options */
    if (pkiutil.opt[opt_Type].on)
	objclass = get_object_class(pkiutil.opt[opt_Type].arg);
    else if (cmdToRun == cmd_Dump && pkiutil.cmd[cmd_Dump].arg)
	objclass = get_object_class(pkiutil.cmd[cmd_Dump].arg);
    else if (cmdToRun == cmd_List && pkiutil.cmd[cmd_List].arg)
	objclass = get_object_class(pkiutil.cmd[cmd_List].arg);
    else if (cmdToRun == cmd_Add && pkiutil.cmd[cmd_Add].arg)
	objclass = get_object_class(pkiutil.cmd[cmd_Add].arg);
    if (objclass < 0)
	goto done;

    /* --print is an alias for --list --nickname */
    if (cmdToRun == cmd_Dump) cmdToRun = cmd_List;

    /* if list has raw | ascii must have -n.  can't have both raw and ascii */
    if (pkiutil.opt[opt_Binary].on || pkiutil.opt[opt_Ascii].on) {
	if (cmdToRun == cmd_List && !pkiutil.opt[opt_Nickname].on) {
	    fprintf(stderr, "%s: specify a object to output with -n\n", 
	                     progName);
	    CMD_LongUsage(progName, &pkiutil, pkiutil_usage);
	}
    }

    /* initialize */
    PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
    /* NSS_InitReadWrite(profiledir); */
    NSS_NoDB_Init(NULL);

    /* Display version info and exit */
    if (cmdToRun == cmd_Version) {
	printf("%s\nNSS Version %s\n", PKIUTIL_VERSION_STRING, NSS_VERSION);
	goto done;
    }

    /* XXX okay - bootstrap stan by loading the root cert module for testing */
    root_cert_td = NSSTrustDomain_Create(NULL, NULL, NULL, NULL);
    {
	int rootpathlen = strlen(profiledir) + strlen(builtin_name) + 1;
	rootpath = (char *)malloc(rootpathlen);
	memcpy(rootpath, profiledir, strlen(profiledir));
	memcpy(rootpath + strlen(profiledir), 
               builtin_name, strlen(builtin_name));
	rootpath[rootpathlen - 1] = '\0';
    }
    NSSTrustDomain_LoadModule(root_cert_td, "Builtin Root Module", rootpath, 
                              NULL, NULL);

    printf("\n");
    if (pkiutil.opt[opt_Nickname].on) {
	int i;
	NSSCertificate **certs;
	NSSCertificate *cert;
	certs = NSSTrustDomain_FindCertificatesByNickname(root_cert_td,
			pkiutil.opt[opt_Nickname].arg, NULL, 0, NULL);
	i = 0;
	while ((cert = certs[i++]) != NULL) {
	    printf("Found cert:\n");
	    print_cert_callback(cert, NULL);
	}
    } else {
        NSSTrustDomain_TraverseCertificates(root_cert_td, print_cert_callback, 0);
    }

    NSSTrustDomain_Destroy(root_cert_td);

    /* List token objects */
    if (cmdToRun == cmd_List)  {
#if 0
	rv = list_token_objects(slot, objclass, 
	                        pkiutil.opt[opt_Nickname].arg,
	                        pkiutil.opt[opt_Binary].on,
	                        pkiutil.opt[opt_Ascii].on,
	                        outfile, &pwdata);
#endif
	goto done;
    }

#if 0
    /* Import an object into the token. */
    if (cmdToRun == cmd_Add) {
	rv = add_object_to_token(slot, object);
	goto done;
    }
#endif

done:
    if (NSS_Shutdown() != SECSuccess) {
	exit(1);
    }

    return rv;
}
