/*
 * $Source: /afs/nada.kth.se/home/staff/bg/Work/krb4-0.0/src/admin/RCS/kdb_util.c,v $
 * $Author: bg $
 *
 * Copyright 1987, 1988 by the Massachusetts Institute of Technology.
 * 
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 *
 * Kerberos database manipulation utility. This program allows you to
 * dump a kerberos database to an ascii readable file and load this
 * file into the database. Read locking of the database is done during a
 * dump operation. NO LOCKING is done during a load operation. Loads
 * should happen with other processes shutdown. 
 *
 * Written July 9, 1987 by Jeffrey I. Schiller
 */

#include "adm_locl.h"

static char *prog; 

Principal aprinc;

static des_cblock master_key, new_master_key;
static des_key_schedule master_key_schedule, new_master_key_schedule;

#define zaptime(foo) bzero((char *)(foo), sizeof(*(foo)))

/* cv_key is a procedure which takes a principle and changes its key, 
   either for a new method of encrypting the keys, or a new master key.
   if cv_key is null no transformation of key is done (other than net byte
   order). */

struct callback_args {
    void (*cv_key)();
    FILE *output_file;
};

static void
print_time(FILE *file, time_t timeval)
{
    struct tm *tm;
    tm = gmtime(&timeval);
    fprintf(file, " %04d%02d%02d%02d%02d",
            tm->tm_year < 1900 ? tm->tm_year + 1900: tm->tm_year,
            tm->tm_mon + 1,
            tm->tm_mday,
            tm->tm_hour,
            tm->tm_min);
}

static long
time_explode(char *cp)
{
    char wbuf[5];
    struct tm tp;
    long maketime(struct tm *tp, int local);
    int local;

    zaptime(&tp);			/* clear out the struct */
    
    if (strlen(cp) > 10) {		/* new format */
	(void) strncpy(wbuf, cp, 4);
	wbuf[4] = 0;
	tp.tm_year = atoi(wbuf);
	cp += 4;			/* step over the year */
	local = 0;			/* GMT */
    } else {				/* old format: local time, 
					   year is 2 digits, assuming 19xx */
	wbuf[0] = *cp++;
	wbuf[1] = *cp++;
	wbuf[2] = 0;
	tp.tm_year = 1900 + atoi(wbuf);
	local = 1;			/* local */
    }

    wbuf[0] = *cp++;
    wbuf[1] = *cp++;
    wbuf[2] = 0;
    tp.tm_mon = atoi(wbuf)-1;

    wbuf[0] = *cp++;
    wbuf[1] = *cp++;
    tp.tm_mday = atoi(wbuf);
    
    wbuf[0] = *cp++;
    wbuf[1] = *cp++;
    tp.tm_hour = atoi(wbuf);
    
    wbuf[0] = *cp++;
    wbuf[1] = *cp++;
    tp.tm_min = atoi(wbuf);


    return(maketime(&tp, local));
}

static int
dump_db_1(char *arg, Principal *principal)
{	    /* replace null strings with "*" */
    struct callback_args *a = (struct callback_args *)arg;
    
    if (principal->instance[0] == '\0') {
	principal->instance[0] = '*';
	principal->instance[1] = '\0';
    }
    if (principal->mod_name[0] == '\0') {
	principal->mod_name[0] = '*';
	principal->mod_name[1] = '\0';
    }
    if (principal->mod_instance[0] == '\0') {
	principal->mod_instance[0] = '*';
	principal->mod_instance[1] = '\0';
    }
    if (a->cv_key != NULL) {
	(*a->cv_key) (principal);
    }
    fprintf(a->output_file, "%s %s %d %d %d %d %x %x",
	    principal->name,
	    principal->instance,
	    principal->max_life,
	    principal->kdc_key_ver,
	    principal->key_version,
	    principal->attributes,
	    (int)htonl (principal->key_low),
	    (int)htonl (principal->key_high));
    print_time(a->output_file, principal->exp_date);
    print_time(a->output_file, principal->mod_date);
    fprintf(a->output_file, " %s %s\n",
	    principal->mod_name,
	    principal->mod_instance);
    return 0;
}

static int
dump_db (char *db_file, FILE *output_file, void (*cv_key) (Principal *))
{
    struct callback_args a;

    a.cv_key = cv_key;
    a.output_file = output_file;
    
    kerb_db_iterate (dump_db_1, (char *)&a);
    return fflush(output_file);
}

static void
load_db (char *db_file, FILE *input_file)
{
    char    exp_date_str[50];
    char    mod_date_str[50];
    int     temp1, temp2, temp3;
    int code;
    char *temp_db_file;
    temp1 = strlen(db_file)+2;
    temp_db_file = malloc (temp1);
    strcpy(temp_db_file, db_file);
    strcat(temp_db_file, "~");

    /* Create the database */
    if ((code = kerb_db_create(temp_db_file)) != 0) {
	fprintf(stderr, "Couldn't create temp database %s: %s\n",
		temp_db_file, k_strerror(code));
	exit(1);
    }
    kerb_db_set_name(temp_db_file);
    for (;;) {			/* explicit break on eof from fscanf */
        u_long key_lo, key_hi;	/* Must match format string */
	bzero((char *)&aprinc, sizeof(aprinc));
	if (fscanf(input_file,
		   "%s %s %d %d %d %hd %lx %lx %s %s %s %s\n",
		   aprinc.name,
		   aprinc.instance,
		   &temp1,
		   &temp2,
		   &temp3,
		   &aprinc.attributes,
		   &key_lo,
		   &key_hi,
		   exp_date_str,
		   mod_date_str,
		   aprinc.mod_name,
		   aprinc.mod_instance) == EOF)
	    break;
	aprinc.key_low = ntohl (key_lo);
	aprinc.key_high = ntohl (key_hi);
	aprinc.max_life = (unsigned char) temp1;
	aprinc.kdc_key_ver = (unsigned char) temp2;
	aprinc.key_version = (unsigned char) temp3;
	aprinc.exp_date = time_explode(exp_date_str);
	aprinc.mod_date = time_explode(mod_date_str);
	if (aprinc.instance[0] == '*')
	    aprinc.instance[0] = '\0';
	if (aprinc.mod_name[0] == '*')
	    aprinc.mod_name[0] = '\0';
	if (aprinc.mod_instance[0] == '*')
	    aprinc.mod_instance[0] = '\0';
	if (kerb_db_put_principal(&aprinc, 1) != 1) {
	    fprintf(stderr, "Couldn't store %s.%s: %s; load aborted\n",
		    aprinc.name, aprinc.instance,
		    k_strerror(errno));
	    exit(1);
	};
    }
    if ((code = kerb_db_rename(temp_db_file, db_file)) != 0)
	perror("database rename failed");
    (void) fclose(input_file);
    free(temp_db_file);
}

/*ARGSUSED*/
static void
update_ok_file (char *file_name)
{
    /* handle slave locking/failure stuff */
    char *file_ok;
    int fd;
    static char ok[]=".dump_ok";

    if ((file_ok = (char *)malloc(strlen(file_name) + strlen(ok) + 1))
	== NULL) {
	fprintf(stderr, "kdb_util: out of memory.\n");
	(void) fflush (stderr);
	perror ("malloc");
	exit (1);
    }
    strcpy(file_ok, file_name);
    strcat(file_ok, ok);
    if ((fd = open(file_ok, O_WRONLY|O_CREAT|O_TRUNC, 0400)) < 0) {
	fprintf(stderr, "Error creating 'ok' file, '%s'", file_ok);
	perror("");
	(void) fflush (stderr);
	exit (1);
    }
    free(file_ok);
    close(fd);
}

static void
convert_key_new_master (Principal *p)
{
  des_cblock key;

  /* leave null keys alone */
  if ((p->key_low == 0) && (p->key_high == 0)) return;

  /* move current key to des_cblock for encryption, special case master key
     since that's changing */
  if ((strncmp (p->name, KERB_M_NAME, ANAME_SZ) == 0) &&
      (strncmp (p->instance, KERB_M_INST, INST_SZ) == 0)) {
    bcopy((char *)new_master_key, (char *) key, sizeof (des_cblock));
    (p->key_version)++;
  } else {
    bcopy((char *)&(p->key_low), (char *)key, 4);
    bcopy((char *)&(p->key_high), (char *) (((long *) key) + 1), 4);
    kdb_encrypt_key (&key, &key, &master_key, master_key_schedule, DES_DECRYPT);
  }

  kdb_encrypt_key (&key, &key, &new_master_key, new_master_key_schedule, DES_ENCRYPT);

  bcopy((char *)key, (char *)&(p->key_low), 4);
  bcopy((char *)(((long *) key) + 1), (char *)&(p->key_high), 4);
  bzero((char *)key, sizeof (key));  /* a little paranoia ... */

  (p->kdc_key_ver)++;
}

static void
clear_secrets (void)
{
  bzero((char *)master_key, sizeof (des_cblock));
  bzero((char *)master_key_schedule, sizeof (des_key_schedule));
  bzero((char *)new_master_key, sizeof (des_cblock));
  bzero((char *)new_master_key_schedule, sizeof (des_key_schedule));
}

static void
convert_new_master_key (char *db_file, FILE *out)
{

  printf ("\n\nEnter the CURRENT master key.");
  if (kdb_get_master_key (TRUE, &master_key, master_key_schedule) != 0) {
    fprintf (stderr, "%s: Couldn't get master key.\n", prog);
    clear_secrets ();
    exit (-1);
  }

  if (kdb_verify_master_key (&master_key, master_key_schedule, stderr) < 0) {
    clear_secrets ();
    exit (-1);
  }

  printf ("\n\nNow enter the NEW master key.  Do not forget it!!");
  if (kdb_get_master_key (TRUE, &new_master_key, new_master_key_schedule) != 0) {
    fprintf (stderr, "%s: Couldn't get new master key.\n", prog);
    clear_secrets ();
    exit (-1);
  }

  dump_db (db_file, out, convert_key_new_master);
}

static void
convert_key_old_db (Principal *p)
{
  des_cblock key;

 /* leave null keys alone */
  if ((p->key_low == 0) && (p->key_high == 0)) return;

  bcopy((char *)&(p->key_low), (char *)key, 4);
  bcopy((char *)&(p->key_high), (char *)(((long *) key) + 1), 4);

#ifndef NOENCRYPTION
  des_pcbc_encrypt((des_cblock *)key,(des_cblock *)key,
	(long)sizeof(des_cblock),master_key_schedule,
	(des_cblock *)master_key_schedule, DES_DECRYPT);
#endif

  /* make new key, new style */
  kdb_encrypt_key (&key, &key, &master_key, master_key_schedule, DES_ENCRYPT);

  bcopy((char *)key, (char *)&(p->key_low), 4);
  bcopy((char *)(((long *) key) + 1), (char *)&(p->key_high), 4);
  bzero((char *)key, sizeof (key));  /* a little paranoia ... */
}

static void
convert_old_format_db (char *db_file, FILE *out)
{
  des_cblock key_from_db;
  Principal principal_data[1];
  int n, more;

  if (kdb_get_master_key (TRUE, &master_key, master_key_schedule) != 0L) {
    fprintf (stderr, "%s: Couldn't get master key.\n", prog);
    clear_secrets();
    exit (-1);
  }

  /* can't call kdb_verify_master_key because this is an old style db */
  /* lookup the master key version */
  n = kerb_get_principal(KERB_M_NAME, KERB_M_INST, principal_data,
			 1 /* only one please */, &more);
  if ((n != 1) || more) {
    fprintf(stderr, "verify_master_key: Kerberos error on master key lookup, %d found.\n", n);
    exit (-1);
  }

  /* set up the master key */
  fprintf(stderr, "Current Kerberos master key version is %d.\n",
	  principal_data[0].kdc_key_ver);

  /*
   * now use the master key to decrypt (old style) the key in the db, had better
   * be the same! 
   */
  bcopy((char *)&principal_data[0].key_low, (char *)key_from_db, 4);
  bcopy((char *)&principal_data[0].key_high,
	(char *)(((long *) key_from_db) + 1), 4);
#ifndef NOENCRYPTION
  des_pcbc_encrypt(&key_from_db,&key_from_db,(long)sizeof(key_from_db),
	master_key_schedule,(des_cblock *)master_key_schedule, DES_DECRYPT);
#endif
  /* the decrypted database key had better equal the master key */
  n = bcmp((char *) master_key, (char *) key_from_db,
	   sizeof(master_key));
  bzero((char *)key_from_db, sizeof(key_from_db));

  if (n) {
    fprintf(stderr, "\n\07\07verify_master_key: Invalid master key, ");
    fprintf(stderr, "does not match database.\n");
    exit (-1);
  }
    
  fprintf(stderr, "Master key verified.\n");
  (void) fflush(stderr);

  dump_db (db_file, out, convert_key_old_db);
}

int
main(int argc, char **argv)
{
    FILE   *file;
    enum {
	OP_LOAD,
	OP_DUMP,
	OP_SLAVE_DUMP,
	OP_NEW_MASTER,
	OP_CONVERT_OLD_DB,
    }       op;
    char *file_name;
    char *db_name;
    prog = argv[0];
    
    if (argc != 3 && argc != 4) {
	fprintf(stderr, "Usage: %s operation file-name [database name].\n",
		argv[0]);
	exit(1);
    }
    if (argc == 3)
	db_name = DBM_FILE;
    else
	db_name = argv[3];

    if (kerb_db_set_name (db_name) != 0) {
	perror("Can't open database");
	exit(1);
    }
    
    if (!strcmp(argv[1], "load"))
	op = OP_LOAD;
    else if (!strcmp(argv[1], "dump"))
	op = OP_DUMP;
    else if (!strcmp(argv[1], "slave_dump"))
        op = OP_SLAVE_DUMP;
    else if (!strcmp(argv[1], "new_master_key"))
        op = OP_NEW_MASTER;
    else if (!strcmp(argv[1], "convert_old_db"))
        op = OP_CONVERT_OLD_DB;
    else {
	fprintf(stderr,
	    "%s: %s is an invalid operation.\n", prog, argv[1]);
	fprintf(stderr,
	    "%s: Valid operations are \"dump\", \"slave_dump\",", argv[0]);
	fprintf(stderr,
		"\"load\", \"new_master_key\", and \"convert_old_db\".\n");
	exit(1);
    }

    file_name = argv[2];
    file = fopen(file_name, op == OP_LOAD ? "r" : "w");
    if (file == NULL) {
	fprintf(stderr, "%s: Unable to open %s\n", prog, argv[2]);
	(void) fflush(stderr);
	perror("open");
	exit(1);
    }

    switch (op) {
    case OP_DUMP:
      if ((dump_db (db_name, file, (void (*)()) 0) == EOF) ||
	  (fclose(file) == EOF)) {
	  fprintf(stderr, "error on file %s:", file_name);
	  perror("");
	  exit(1);
      }
      break;
    case OP_SLAVE_DUMP:
      if ((dump_db (db_name, file, (void (*)()) 0) == EOF) ||
	  (fclose(file) == EOF)) {
	  fprintf(stderr, "error on file %s:", file_name);
	  perror("");
	  exit(1);
      }
      update_ok_file (file_name);
      break;
    case OP_LOAD:
      load_db (db_name, file);
      break;
    case OP_NEW_MASTER:
      convert_new_master_key (db_name, file);
      printf("Don't forget to do a `kdb_util load %s' to reload the database!\n", file_name);
      break;
    case OP_CONVERT_OLD_DB:
      convert_old_format_db (db_name, file);
      printf("Don't forget to do a `kdb_util load %s' to reload the database!\n", file_name);      
      break;
    }
    exit(0);
  }
