/*
 * Program for managing cert_map db files.  If invoked with no command line
 * arguments, it runs an interactive loop read and executing multiple
 * commands.  If command line arguments are present, it treats those
 * arguments as a single command to execute, then exits.
 *
 * The CERTIFICATE_RIGHTS_MAP logical must be defined as the file specification
 * of the database to open.
 *
 * Author:	David Jones
 * Date:	8-DEC-2001
 * Revised:	17-DEC-2001
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <smg$routines.h>
#include <smgdef.h>		/* VMS screen management functions */
#include <descrip.h>		/* VMS string descriptors */

#include "cert_map.h"
/*
 * Headers for OpenSSL functions.
 */
#ifndef flat_inc		/* all includes in 1 directory, SSLinc */
#define flat_inc
#endif
#include "crypto.h"
#include "pem.h"
#include "asn1.h"

static char *cert_file_default = (char *) 0;

static void set_cf_default ( const char *defspec )
{
    int length;
    length = strlen ( defspec ) + 10;
    if ( cert_file_default ) 
	cert_file_default = realloc ( cert_file_default, length );
    else
	cert_file_default = malloc ( length );

    sprintf ( cert_file_default, "dna=%s", defspec );
}

static int convert_cert_file ( const char *cert_file, 
	struct certmap_db_var *subject, struct certmap_db_var *issuer )
{
    FILE *cf;
    X509 *cert;
    int length;
    unsigned char *pp;

    subject->name_len = subject->serial_len = 0;
    issuer->name_len = issuer->serial_len = 0;

    cf = fopen ( cert_file, "r", cert_file_default );
    if ( cf ) {
	cert = PEM_read_X509 ( cf, NULL, NULL, NULL );
	if ( !cert ) {
	    printf ( "Error reading certificate file\n" );
	    return 0;
	}
	fclose ( cf );
    } else {
	printf ( "Open failure on cert file %s\n", cert_file );
	return 0;
    }

    pp = subject->s;
    length = i2d_X509_NAME ( X509_get_subject_name ( cert ), &pp );
    if ( length < 0 ) {
	printf ( "error extracting subject name in certificate!\n" );
	return 0;
    }
    subject->name_len = length;
    length = i2c_ASN1_INTEGER ( X509_get_serialNumber ( cert ), &pp );
    if ( length < 0 ) {
	printf ( "error extracting serial in certificate!\n" );
	return 0;
    }
    subject->serial_len = length;

    pp = issuer->s;
    length = i2d_X509_NAME ( X509_get_issuer_name ( cert ), &pp );
    if ( length < 0 ) {
	printf ( "error extracting issuer name in certificate!\n" );
	return 0;
    }
    issuer->name_len = length;

    return 1;

}

static void format_db_var ( struct certmap_db_var *subject,
	char *name, int name_size, char *serial, int serial_size )
{
    X509_NAME *xname, *result;
    char *line;
    int status, length, i;
    unsigned char *pp;

    if ( name ) {
	/*
	 * Convert DER-encoding stored in db_var structure to internal
	 * OpenSSL structures, then format as printable text.
	 */
	pp = subject->s;
        xname = X509_NAME_new();
	if ( xname ) {
	    result = d2i_X509_NAME ( &xname, &pp, subject->name_len );
	    if ( result ) {
		line = X509_NAME_oneline ( xname, name, name_size );
	    } else {
		name[0] = '\0';
	    }

	    X509_NAME_free ( xname );
	} else name[0] = '\0';
    }

    if ( serial ) {
	/*
	 * Convert to a hex string.
	 */
	pp = &subject->s[subject->name_len];
	if ( (subject->serial_len*2)+3 < serial_size ) {
	    *serial++ = '0';
	    *serial++ = 'x';
	    *serial = '\0';
	    for ( i = 0; i < subject->serial_len; i++ ) {
		sprintf ( serial, "%02x", pp[i] );
		serial += 2;
	    }
	} else {
	    serial[0] = '\0';
	}
    }
}

static long keyboard, keytable;

static void parse_qualifiers ( char *s, char **qual, int *qual_count )
{
    int i, qcnt;
    qcnt = *qual_count;
    for ( i = 0; s[i]; i++ ) if ( s[i] == '/' ) {
	s[i] = '\0';
	qual[qcnt++] = &s[i+1];
    }
    qual[qcnt] = (char *) 0;
    *qual_count = qcnt;
}

static int qualifier_present ( const char *name, char **qual )
{
    int i, j, length;
    for ( i = 0; qual[i]; i++ ) {
	length = strlen ( qual[i] );
	if ( length == 0 ) continue;
	
	for ( j = 0; j < length; j++ ) {
	    if ( name[j] != _tolower(qual[i][j]) ) break;
	}
	if ( j == length ) return 1;
    }
    return 0;
}

int main ( int argc, char **argv )
{
    char *dbname, *cmd, *issuer;
    unsigned char *pp;
    char errmsg[256], disp_subject[1024], disp_serial[60], id_name[64];
    char *tok[64], *qual[64];
    int status, i, tcnt, qcnt, attrib, id_status, t0_len, cmd_index, matches;
    long rec_num;
    unsigned long identifier, resid;
    static char cmd_line[512];
    static $DESCRIPTOR(cmd_line_dx,cmd_line);
    static $DESCRIPTOR(prompt_dx, "certmap> " );
    static int length, max_len = 511;
    FILE *cf;
    struct certmap_db_rec rec;
    struct certmap_db_var subj, issr;
    X509_NAME *xname;
    void *db, *sctx;
    static char *command[] = { "add", "create", "help", "remove", "show",
	"exit", "set", (char *) 0 };

    dbname = certmap_logical_dbname;
    set_cf_default ( ".pem" );		/* default to .pem files */
    /*
     * Attempt to open the database.
     */
    status = certmap_open ( dbname,  "sys$disk:[].dat", "r+", &db, errmsg );
    if ( (status&1) == 0 ) {
	printf ( "Failed to open %s file:\n  %s\n", dbname, errmsg );
	printf ( "Use 'create' command to create new one\n" );
	db = (void *) 0;	/* ensure marked invalid */
    }
    /*
     * Setup SMG for reading if processing multiple commands.
     */
    if ( argc == 1 ) {
	status = smg$create_virtual_keyboard ( &keyboard, 0, 0, 0 );
	if ( (status&1) == 0 ) {
	    fprintf(stderr,"Error createing SMG virt. keyboard: %d\n", status);
	    return status;
	}
	status = smg$create_key_table ( &keytable );
	if ( (status&1) == 0 ) return status;
	cmd_line_dx.dsc$w_length--;	/* Ensure room to append NULL */
    }
    /*
     * Get commands
     */
    do {
	/*
	 * Get command line and parse into keywords/qualifiers
	 */
	tcnt = 0;
	qcnt = 0;
	if ( argc > 1 ) {
	    /*
	     * Do one-shot command from the shell command line.
	     */
	    for ( i = 1; i < argc; i++ ) {
		parse_qualifiers ( argv[i], qual, &qcnt );
		if ( !argv[i] ) continue;	/* empty */
		tok[tcnt++] = argv[i];
	    }
	} else {
	    length = 0;
	    status = smg$read_composed_line ( &keyboard, &keytable,
		&cmd_line_dx, &prompt_dx, &length, 0 );
	    if ( (status&1) == 0 ) { status = 1; break; }
	    
	    cmd_line[length] = '\0';
	    tok[0] = strtok ( cmd_line, " \t" );
	    if ( !tok[0] ) continue;	/* ignore blank line */
	    while ( (tcnt < 64) && tok[tcnt] ) {
		parse_qualifiers ( tok[tcnt], qual, &qcnt );
		if ( tok[tcnt][0] ) tcnt++;
		tok[tcnt] = strtok ( (char *) 0, " \t" );
	    }
	}
	/* for ( i = 0; i < qcnt; i++ ) 
		printf ( "qual[%d] = '%s'\n",i,qual[i]); */
	/*
	 * Validate keyword.
	 */
	if ( tcnt == 0 ) {
	    cmd_index = -3;
	} else {
	    cmd_index = -1;			/* default to unknown */
	    t0_len = strlen ( tok[0] );
	    for ( i = 0; command[i]; i++ ) {
	        if ( strncmp ( tok[0], command[i], t0_len ) == 0 ) {
		    if ( cmd_index >= 0 ) {
		        cmd_index = -2;	/* Flag as ambiguous */
		        break;
		    } else {
		        cmd_index = i;
		    }
		}
	    }
	}
	/*
	 * Interpret keyword.
	 */
	if ( cmd_index == -1 ) {
	    printf ( "Unknown command: %s\n", tok[0] );
	} else if ( cmd_index == -2 ) {
	    printf ( "Ambiguous command: %s\n", tok[0] );
	} else if ( cmd_index == -3 ) {
	    printf ( "No command on command line.\n" );

	} else if ( strcmp ( command[cmd_index], "create" ) == 0 ) {
	    /*
	     * Create a new certificate database file.
	     */
	    if ( tcnt > 1 ) dbname = tok[1];
	    else dbname = certmap_logical_dbname;

	    status = certmap_create ( dbname,  "sys$disk:[].dat", errmsg );
	    if ( (status&1) == 0 ) {
		printf ( "Create failed:\n   %s\n", errmsg );
		db = (void *) 0;
	    } else {
	        status = certmap_open ( dbname,  "sys$disk:[].dat", "r+", &db, 
			errmsg );
	        if ( (status&1) == 0 ) {
		    printf ( "Open failed (code=%d):\n   %s\n", status, errmsg );
		    db = (void *) 0;
		}
	    }

	} else if ( strcmp ( command[cmd_index], "help" ) == 0 ) {
	    /*
	     * Print basic help.
	     */
	    printf ( "Commands:\n   %s\n   %s\n   %s\n   %s\n   %s\n   %s\n",
		"create [filename]", "add certfile [identifier] [/noissuer]",
		"remove cert-file [identifier]", 
		"show [/default] [cert-file]", 
		"set default defspec", "exit" );

	} else if ( strcmp ( command[cmd_index], "add" ) == 0 ) {
	    /*
	     * Add a new certificate mapping to the database.
	     */
	    if ( tcnt < 2 ) {
		fprintf(stderr, "Must supply certificate file to add\n" );
	    } else if ( !db ) {
		fprintf ( stderr, "No database open\n" ) ;
	    } else {
		/*
		 * Convert identifier name to binary longword.
		 */
		if ( tcnt > 2 ) {
		    status = certmap_asctoid ( tok[2], &identifier,
			&attrib, errmsg );
		    if ( (status&1) == 0 ) {
			printf ( "Bad identifier: %s", errmsg );
			continue;
		    }
		} else identifier = 0;
		/*
		 * Extract subject and issuer from certificate.
		 */
		status = convert_cert_file ( tok[1], &subj, &issr );

		if ( (status&1) == 0 ) {
		    printf ( "Bad cert-file\n" );
		} else {
		    sctx = (void *) 0;

		    format_db_var ( &subj, disp_subject, 
			sizeof(disp_subject)-1, disp_serial, 60 );

		    printf ( "Subject: %s, serial: %s\n", disp_subject,
			disp_serial );

		    if ( qualifier_present ( "noissuer", qual ) ) {
			/* Don't link certificate to it's issuer */
			issr.name_len = 0;
		    }

		    status = certmap_add ( db, &subj, &issr, 
			identifier, &rec_num, errmsg );
		    if ( (status&1) == 0 ) {
			printf ( "add failed:\n   %s\n", errmsg );
		    }
		}
	    }
		
	} else if ( strcmp ( command[cmd_index], "remove" ) == 0 ) {
	    /*
	     * Remove mapping from certificate database.  If no
	     * identifier given, all mappings for specified certificate
	     * are removed.
	     */
	    if ( tcnt < 2 ) {
		fprintf(stderr, "Must supply certificate file to remove\n" );
	    } else if ( !db ) {
		fprintf ( stderr, "No database open\n" ) ;
	    } else {
		if ( tcnt > 2 ) {
		    status = certmap_asctoid ( tok[2], &identifier,
			&attrib, errmsg );
		    if ( (status&1) == 0 ) {
			printf ( "Bad identifier: %s", errmsg );
			continue;
		    }
		} else identifier = 0;
		status = convert_cert_file ( tok[1], &subj, &issr );
		if ( (status&1) == 0 ) {
		    printf ( "Bad cert-file\n" );
		} else {
		    sctx = (void *) 0;
		    format_db_var ( &subj, disp_subject, 
			sizeof(disp_subject)-1,	disp_serial, 60 );

		    printf ( "Subject: %s, serial: %s\n", disp_subject,
			disp_serial );
		    do {
			status = certmap_search ( db, &subj, 
				&issr, &sctx, &rec );
			if ( ((status&1) == 1) && 
			    ((identifier==0) || 
				(identifier==rec.identifier)) ) {
			    /*
			     * Must get record for update before deleting.
			     */
			    int dn_len;
			    status = certmap_get ( db, rec.rec_num, 1,
				&rec );
			    if ( status&1 ) status = certmap_delete ( db );
			    printf ( "Delete %x status: %d\n", rec.rec_num, status );

			}
		    } while ( status&1 );
		    certmap_search_end ( db, &sctx );

		}
	    }

	} else if ( strcmp ( command[cmd_index], "show" ) == 0 ) {
	    /*
	     * Display mappings.
	     */
	    if ( qualifier_present ( "default", qual ) ) {
		printf ( "Default for certificate files: %s\n",
			&cert_file_default[4] );
	    } else if ( tcnt < 2 ) {
		fprintf(stderr, "Must supply certificate file to show\n" );
	    } else if ( !db ) {
		fprintf ( stderr, "No database open\n" ) ;
	    } else {
		status = convert_cert_file ( tok[1], &subj, &issr );
		if ( (status&1) == 0 ) {
		    printf ( "Bad cert-file\n" );
		} else {
		    format_db_var ( &subj, disp_subject,
			sizeof(disp_subject)-1,	disp_serial, 60 );

		    sctx = (void *) 0;
		    printf ( "Certificate decoded, subject: %s, serial: %s\n", 
			disp_subject, disp_serial );
		    matches = 0;
		    do {
			status = certmap_search ( db, &subj, &issr, &sctx, &rec );
			if ( (status&1) == 1 ) {
			    if ( matches == 0 ) printf ( "records:\n" );
			    matches++;
			    id_status = certmap_idtoasc ( rec.identifier,
				id_name, sizeof(id_name), &resid, 
				(int *) 0, (long *) 0, errmsg );
			    printf ( "  #%x, ident: %x (%s)\n", rec.rec_num,
				rec.identifier, 
				(id_status&1) ? id_name : errmsg );
			}
		    } while ( status&1 );
		    certmap_search_end ( db, &sctx );
		    if ( matches == 0 ) printf ( "no records found!\n" );

		}
	    }

	} else if ( strcmp ( command[cmd_index], "set" ) == 0 ) {
	    if ( tcnt < 2 ) {
		fprintf ( stderr, "Missing command option\n" );
	    } else {
		int tlen;
		tlen = strlen ( tok[1] );
		if ( strncmp ( tok[1], "default", tlen ) == 0 ) {
		    if ( tcnt < 3 ) {
			fprintf (stderr, "Must supply new specification\n" );
		    } else {
			set_cf_default ( tok[2] );
		    }
		}
	    }
	} else if ( strcmp ( command[cmd_index], "exit" ) == 0 ) {
	    break;
	} else {
	   fprintf ( stderr, "Unknown command: '%s'\n", tok[0] );
	}
    } while ( argc <= 1 );
    /*
     * Cleanup.
     */
    if ( db ) certmap_close ( db );
    return status;
}

