/*
 * tests/verify/kdb5_verify.c
 *
 * Copyright 1990,1991 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 * 
 *
 * Edit a KDC database.
 */

#include "k5-int.h"
#include "com_err.h"
#include <ss/ss.h>
#include <stdio.h>

#define REALM_SEP	'@'
#define REALM_SEP_STR	"@"

struct mblock {
    krb5_deltat max_life;
    krb5_deltat max_rlife;
    krb5_timestamp expiration;
    krb5_flags flags;
    krb5_kvno mkvno;
} mblock = {				/* XXX */
    KRB5_KDB_MAX_LIFE,
    KRB5_KDB_MAX_RLIFE,
    KRB5_KDB_EXPIRATION,
    KRB5_KDB_DEF_FLAGS,
    0
};

int set_dbname_help (krb5_context, char *, char *);

static void
usage(who, status)
char *who;
int status;
{
    fprintf(stderr,
	    "usage: %s -p prefix -n num_to_check [-d dbpathname] [-r realmname]\n",
	    who);
    fprintf(stderr, "\t [-D depth] [-k enctype] [-M mkeyname]\n");

    exit(status);
}

krb5_keyblock master_keyblock;
krb5_principal master_princ;
krb5_db_entry master_entry;
krb5_encrypt_block master_encblock;
krb5_pointer master_random;
char *str_master_princ;

static char *progname;
static char *cur_realm = 0;
static char *mkey_name = 0;
static char *mkey_password = 0;
static krb5_boolean manual_mkey = FALSE;


int check_princ (krb5_context, char *);

int
main(argc, argv)
    int argc;
    char *argv[];
{
    extern char *optarg;	
    int optchar, i, n;
    char tmp[4096], tmp2[BUFSIZ], *str_princ;

    krb5_context context;
    krb5_error_code retval;
    char *dbname = 0;
    int enctypedone = 0;
    int num_to_check;
    char principal_string[BUFSIZ];
    char *suffix = 0;
    int depth, errors;

    krb5_init_context(&context);

    if (strrchr(argv[0], '/'))
	argv[0] = strrchr(argv[0], '/')+1;

    progname = argv[0];

    memset(principal_string, 0, sizeof(principal_string));
    num_to_check = 0;
    depth = 1;

    while ((optchar = getopt(argc, argv, "D:P:p:n:d:r:R:k:M:e:m")) != -1) {
	switch(optchar) {
	case 'D':
	    depth = atoi(optarg);       /* how deep to go */
	    break;
        case 'P':		/* Only used for testing!!! */
	    mkey_password = optarg;
	    break;
	case 'p':                       /* prefix name to check */
	    strncpy(principal_string, optarg, sizeof(principal_string) - 1);
	    principal_string[sizeof(principal_string) - 1] = '\0';
	    suffix = principal_string + strlen(principal_string);
	    break;
       case 'n':                        /* how many to check */
	    num_to_check = atoi(optarg);
	    break;
	case 'd':			/* set db name */
	    dbname = optarg;
	    break;
	case 'r':
	    cur_realm = optarg;
	    break;
	case 'k':
	    master_keyblock.enctype = atoi(optarg);
	    enctypedone++;
	    break;
	case 'M':			/* master key name in DB */
	    mkey_name = optarg;
	    break;
	case 'm':
	    manual_mkey = TRUE;
	    break;
	case '?':
	default:
	    usage(progname, 1);
	    /*NOTREACHED*/
	}
    }

    if (!(num_to_check && suffix)) usage(progname, 1);

    if (!enctypedone)
	master_keyblock.enctype = DEFAULT_KDC_ENCTYPE;

    if (!krb5_c_valid_enctype(master_keyblock.enctype)) {
	com_err(progname, KRB5_PROG_ETYPE_NOSUPP,
		"while setting up enctype %d", master_keyblock.enctype);
	exit(1);
    }

    krb5_use_enctype(context, &master_encblock, master_keyblock.enctype);

    if (!dbname)
	dbname = DEFAULT_KDB_FILE;	/* XXX? */

    if (!cur_realm) {
	if ((retval = krb5_get_default_realm(context, &cur_realm))) {
	    com_err(progname, retval, "while retrieving default realm name");
	    exit(1);
	}	    
    }
    if ((retval = set_dbname_help(context, progname, dbname)))
	exit(retval);

    errors = 0;

    fprintf(stdout, "\nChecking ");

    for (n = 1; n <= num_to_check; n++) {
      /* build the new principal name */
      /* we can't pick random names because we need to generate all the names 
	 again given a prefix and count to test the db lib and kdb */
      (void) sprintf(suffix, "%d", n);
      (void) sprintf(tmp, "%s-DEPTH-1", principal_string);
      str_princ = tmp;
      if (check_princ(context, str_princ)) errors++;

      for (i = 2; i <= depth; i++) {
	(void) sprintf(tmp2, "/%s-DEPTH-%d", principal_string, i);
	tmp2[sizeof(tmp2) - 1] = '\0';
	strncat(tmp, tmp2, sizeof(tmp) - 1 - strlen(tmp));
	str_princ = tmp;
	if (check_princ(context, str_princ)) errors++;
      }
    }

    if (errors)
      fprintf(stdout, "\n%d errors/principals failed.\n", errors);
    else
      fprintf(stdout, "\nNo errors.\n");

    krb5_finish_random_key(context, &master_encblock, &master_random);
    krb5_finish_key(context, &master_encblock);

    retval = krb5_db_fini(context);
    memset((char *)master_keyblock.contents, 0, (size_t) master_keyblock.length);
    if (retval && retval != KRB5_KDB_DBNOTINITED) {
	com_err(progname, retval, "while closing database");
	exit(1);
    }

    if (str_master_princ) {
	krb5_free_unparsed_name(context, str_master_princ);
    }
    krb5_free_context(context);
    exit(0);
}

int
check_princ(context, str_princ)
    krb5_context context;
    char * str_princ;
{
    krb5_error_code retval;
    krb5_db_entry kdbe;
    krb5_keyblock pwd_key, db_key;
    krb5_data pwd, salt;
    krb5_principal princ;
    krb5_boolean more;
    int nprincs = 1;
    /* char *str_mod_name; */
    char princ_name[4096];

    sprintf(princ_name, "%s@%s", str_princ, cur_realm);

    fprintf(stderr, "\t%s ...\n", princ_name);

    if ((retval = krb5_parse_name(context, princ_name, &princ))) {
      com_err(progname, retval, "while parsing '%s'", princ_name);
      goto out;
    }

    pwd.data = princ_name;  /* must be able to regenerate */
    pwd.length = strlen(princ_name);

    if ((retval = krb5_principal2salt(context, princ, &salt))) {
	com_err(progname, retval, "while converting principal to salt for '%s'", princ_name);
	krb5_free_principal(context, princ);
	goto out;
    }

    if ((retval = krb5_string_to_key(context, &master_encblock, 
				    &pwd_key, &pwd, &salt))) {
	com_err(progname, retval, "while converting password to key for '%s'", 
		princ_name);
	krb5_free_data_contents(context, &salt);
	krb5_free_principal(context, princ);
	goto out;
    }
    krb5_free_data_contents(context, &salt);

    if ((retval = krb5_db_get_principal(context, princ, &kdbe, 
					&nprincs, &more))) {
      com_err(progname, retval, "while attempting to verify principal's existence");
      krb5_free_principal(context, princ);
      goto out;
    }
    krb5_free_principal(context, princ);

    if (nprincs != 1) {
      com_err(progname, 0, "Found %d entries db entry for %s.\n", nprincs,
	      princ_name);
      goto errout;
    }

    if ((retval = krb5_dbekd_decrypt_key_data(context, &master_keyblock, 
				       	     kdbe.key_data, &db_key, NULL))) {
	com_err(progname, retval, "while decrypting key for '%s'", princ_name);
	goto errout;
    }

    if ((pwd_key.enctype != db_key.enctype) ||
	(pwd_key.length != db_key.length)) {
      fprintf (stderr, "\tKey types do not agree (%d expected, %d from db)\n",
	       pwd_key.enctype, db_key.enctype);
errout:
      krb5_db_free_principal(context, &kdbe, nprincs);
      return(-1);
    }
    else {
      if (memcmp((char *)pwd_key.contents, (char *) db_key.contents, 
		 (size_t) pwd_key.length)) {
	fprintf(stderr, "\t key did not match stored value for %s\n", 
		princ_name);
	goto errout;
      }
    }

    free((char *)pwd_key.contents);
    free((char *)db_key.contents);

    if (kdbe.key_data[0].key_data_kvno != 1) {
      fprintf(stderr,"\tkvno did not match stored value for %s.\n", princ_name);
      goto errout;
    }

    if (kdbe.max_life != mblock.max_life) {
      fprintf(stderr, "\tmax life did not match stored value for %s.\n", 
	      princ_name);
      goto errout;
    }

    if (kdbe.max_renewable_life != mblock.max_rlife) {
      fprintf(stderr, 
	      "\tmax renewable life did not match stored value for %s.\n",
	      princ_name);
      goto errout;
    }

    if (kdbe.expiration != mblock.expiration) {
      fprintf(stderr, "\texpiration time did not match stored value for %s.\n",
	      princ_name);
      goto errout;
    }

/*
    if ((retval = krb5_unparse_name(context, kdbe.mod_name, &str_mod_name)))
      com_err(progname, retval, "while unparsing mode name");
    else {
      if (strcmp(str_mod_name, str_master_princ) != 0) {
	fprintf(stderr, "\tmod name isn't the master princ (%s not %s).\n",
		str_mod_name, str_master_princ);
	free(str_mod_name);
	goto errout;
      }
      else free(str_mod_name);
    }
*/

    if (kdbe.attributes != mblock.flags) {
      fprintf(stderr, "\tAttributes did not match stored value for %s.\n",
	      princ_name);
      goto errout;
    }

    out:
    krb5_db_free_principal(context, &kdbe, nprincs);

    return(0);
}

int
set_dbname_help(context, pname, dbname)
    krb5_context context;
    char *pname;
    char *dbname;
{
    krb5_error_code retval;
    int nentries;
    krb5_boolean more;
    krb5_data pwd, scratch;

    if ((retval = krb5_db_set_name(context, dbname))) {
	com_err(pname, retval, "while setting active database to '%s'",
		dbname);
	return(1);
    }
    /* assemble & parse the master key name */

    if ((retval = krb5_db_setup_mkey_name(context, mkey_name, cur_realm, 0,
					 &master_princ))) {
	com_err(pname, retval, "while setting up master key name");
	return(1);
    }
    if (mkey_password) {
	pwd.data = mkey_password;
	pwd.length = strlen(mkey_password);
	retval = krb5_principal2salt(context, master_princ, &scratch);
	if (retval) {
	    com_err(pname, retval, "while calculated master key salt");
	    return(1);
	}
	if ((retval = krb5_string_to_key(context, &master_encblock, 
				    &master_keyblock, &pwd, &scratch))) {
	    com_err(pname, retval,
		    "while transforming master key from password");
	    return(1);
	}
	free(scratch.data);
    } else {
	if ((retval = krb5_db_fetch_mkey(context, master_princ,
					 master_keyblock.enctype,
					manual_mkey, FALSE, (char *) NULL, 0,
					&master_keyblock))) {
	    com_err(pname, retval, "while reading master key");
	    return(1);
	}
    }
    if ((retval = krb5_db_init(context ))) {
	com_err(pname, retval, "while initializing database");
	return(1);
    }
    if ((retval = krb5_db_verify_master_key(context, master_princ, 
					    &master_keyblock))) {
	com_err(pname, retval, "while verifying master key");
	(void) krb5_db_fini(context);
	return(1);
    }
    nentries = 1;
    if ((retval = krb5_db_get_principal(context, master_princ, &master_entry, 
				       &nentries, &more))) {
	com_err(pname, retval, "while retrieving master entry");
	(void) krb5_db_fini(context);
	return(1);
    } else if (more) {
	com_err(pname, KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE,
		"while retrieving master entry");
	(void) krb5_db_fini(context);
	return(1);
    } else if (!nentries) {
	com_err(pname, KRB5_KDB_NOENTRY, "while retrieving master entry");
	(void) krb5_db_fini(context);
	return(1);
    }

    if ((retval = krb5_unparse_name(context, master_princ, 
				    &str_master_princ))) {
      com_err(pname, retval, "while unparsing master principal");
      krb5_db_fini(context);
      return(1);
    }

    if ((retval = krb5_process_key(context,
				  &master_encblock, &master_keyblock))) {
	com_err(pname, retval, "while processing master key");
	(void) krb5_db_fini(context);
	return(1);
    }
    if ((retval = krb5_init_random_key(context,
				      &master_encblock, &master_keyblock,
				      &master_random))) {
	com_err(pname, retval, "while initializing random key generator");
	krb5_finish_key(context, &master_encblock);
	(void) krb5_db_fini(context);
	return(1);
    }
    mblock.max_life = master_entry.max_life;
    mblock.max_rlife = master_entry.max_renewable_life;
    mblock.expiration = master_entry.expiration;
    /* don't set flags, master has some extra restrictions */
    mblock.mkvno = master_entry.key_data[0].key_data_kvno;

    krb5_db_free_principal(context, &master_entry, nentries);
    return 0;
}

