/*
 * lib/kdb/kdb_cpw.c
 *
 * Copyright 1995 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.
 * 
 */

/*
 * Copyright (C) 1998 by the FundsXpress, INC.
 * 
 * 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 FundsXpress. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  FundsXpress makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "k5-int.h"
#include "krb5/adm.h"
#include <stdio.h>
#include <errno.h>

static int
get_key_data_kvno(context, count, data)
    krb5_context	  context;
    int			  count;
    krb5_key_data	* data;
{
    int i, kvno;
    /* Find last key version number */
    for (kvno = i = 0; i < count; i++) {
	if (kvno < data[i].key_data_kvno) {
	    kvno = data[i].key_data_kvno;
	}
    }
    return(kvno);
}

static void
cleanup_key_data(context, count, data)
    krb5_context	  context;
    int			  count;
    krb5_key_data	* data;
{
    int i, j;

    for (i = 0; i < count; i++) {
	for (j = 0; j < data[i].key_data_ver; j++) {
	    if (data[i].key_data_length[j]) {
	    	free(data[i].key_data_contents[j]);
	    }
	}
    }
    free(data);
}

static krb5_error_code
add_key_rnd(context, master_key, ks_tuple, ks_tuple_count, db_entry, kvno)
    krb5_context	  context;
    krb5_keyblock       * master_key;
    krb5_key_salt_tuple	* ks_tuple;
    int			  ks_tuple_count;
    krb5_db_entry	* db_entry;
    int			  kvno;
{
    krb5_principal	  krbtgt_princ;
    krb5_keyblock	  key;
    krb5_db_entry	  krbtgt_entry;
    krb5_key_data	* krbtgt_kdata;
    krb5_boolean	  more;
    int			  max_kvno, one, i, j;
    krb5_error_code	  retval;

    retval = krb5_build_principal_ext(context, &krbtgt_princ,
				      db_entry->princ->realm.length,
				      db_entry->princ->realm.data,
				      KRB5_TGS_NAME_SIZE,
				      KRB5_TGS_NAME,
				      db_entry->princ->realm.length,
				      db_entry->princ->realm.data,
				      0);
    if (retval)
	return retval;

    /* Get tgt from database */
    retval = krb5_db_get_principal(context, krbtgt_princ, &krbtgt_entry,
				   &one, &more);
    krb5_free_principal(context, krbtgt_princ); /* don't need it anymore */
    if (retval)
	return(retval);
    if ((one > 1) || (more)) {
	krb5_db_free_principal(context, &krbtgt_entry, one);
	return KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE;
    }
    if (!one) 
	return KRB5_KDB_NOENTRY;

    /* Get max kvno */
    for (max_kvno = j = 0; j < krbtgt_entry.n_key_data; j++) {
	 if (max_kvno < krbtgt_entry.key_data[j].key_data_kvno) {
	     max_kvno = krbtgt_entry.key_data[j].key_data_kvno;
	}
    }

    for (i = 0; i < ks_tuple_count; i++) {
	krb5_boolean similar;

	similar = 0;

	/*
	 * We could use krb5_keysalt_iterate to replace this loop, or use
	 * krb5_keysalt_is_present for the loop below, but we want to avoid
	 * circular library dependencies.
	 */
	for (j = 0; j < i; j++) {
	    if ((retval = krb5_c_enctype_compare(context,
						 ks_tuple[i].ks_enctype,
						 ks_tuple[j].ks_enctype,
						 &similar)))
		return(retval);

	    if (similar)
		break;
	}

	if (similar)
	    continue;

        if (retval = krb5_dbe_create_key_data(context, db_entry)) 
	    goto add_key_rnd_err;

	/* there used to be code here to extract the old key, and derive
	   a new key from it.  Now that there's a unified prng, that isn't
	   necessary. */

	/* make new key */
	if ((retval = krb5_c_make_random_key(context, ks_tuple[i].ks_enctype,
					     &key)))
	    goto add_key_rnd_err;

    	retval = krb5_dbekd_encrypt_key_data(context, master_key, 
					     &key, NULL, kvno, 
					     &db_entry->key_data[db_entry->n_key_data-1]);

	krb5_free_keyblock_contents(context, &key);

	if (retval)
	    goto add_key_rnd_err;
    }

add_key_rnd_err:
    krb5_db_free_principal(context, &krbtgt_entry, one);

    return(retval);
}

/*
 * Change random key for a krb5_db_entry 
 * Assumes the max kvno
 *
 * As a side effect all old keys are nuked if keepold is false.
 */
krb5_error_code
krb5_dbe_crk(context, master_key, ks_tuple, ks_tuple_count, keepold, db_entry)
    krb5_context	  context;
    krb5_keyblock       * master_key;
    krb5_key_salt_tuple	* ks_tuple;
    int			  ks_tuple_count;
    krb5_boolean	  keepold;
    krb5_db_entry	* db_entry;
{
    int 		  key_data_count;
    int			  n_new_key_data;
    krb5_key_data 	* key_data;
    krb5_error_code	  retval;
    int			  kvno;
    int			  i;

    /* First save the old keydata */
    kvno = get_key_data_kvno(context, db_entry->n_key_data, db_entry->key_data);
    key_data_count = db_entry->n_key_data;
    key_data = db_entry->key_data;
    db_entry->key_data = NULL;
    db_entry->n_key_data = 0;

    /* increment the kvno */
    kvno++;

    retval = add_key_rnd(context, master_key, ks_tuple,
			 ks_tuple_count, db_entry, kvno);
    if (retval) {
	cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
	db_entry->n_key_data = key_data_count;
	db_entry->key_data = key_data;
    } else if (keepold) {
	n_new_key_data = db_entry->n_key_data;
	for (i = 0; i < key_data_count; i++) {
	    retval = krb5_dbe_create_key_data(context, db_entry);
	    if (retval) {
		cleanup_key_data(context, db_entry->n_key_data,
				 db_entry->key_data);
		break;
	    }
	    db_entry->key_data[i+n_new_key_data] = key_data[i];
	    memset(&key_data[i], 0, sizeof(krb5_key_data));
	}
    } else {
	cleanup_key_data(context, key_data_count, key_data);
    }
    return(retval);
}

/*
 * Add random key for a krb5_db_entry 
 * Assumes the max kvno
 *
 * As a side effect all old keys older than the max kvno are nuked.
 */
krb5_error_code
krb5_dbe_ark(context, master_key, ks_tuple, ks_tuple_count, db_entry)
    krb5_context	  context;
    krb5_keyblock       * master_key;
    krb5_key_salt_tuple	* ks_tuple;
    int			  ks_tuple_count;
    krb5_db_entry	* db_entry;
{
    int 		  key_data_count;
    krb5_key_data 	* key_data;
    krb5_error_code	  retval;
    int			  kvno;
    int			  i;

    /* First save the old keydata */
    kvno = get_key_data_kvno(context, db_entry->n_key_data, db_entry->key_data);
    key_data_count = db_entry->n_key_data;
    key_data = db_entry->key_data;
    db_entry->key_data = NULL;
    db_entry->n_key_data = 0;

    /* increment the kvno */
    kvno++;

    if (retval = add_key_rnd(context, master_key, ks_tuple, 
			     ks_tuple_count, db_entry, kvno)) {
	cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
	db_entry->n_key_data = key_data_count;
	db_entry->key_data = key_data;
    } else {
	/* Copy keys with key_data_kvno == kvno - 1 ( = old kvno ) */
	for (i = 0; i < key_data_count; i++) {
	    if (key_data[i].key_data_kvno == (kvno - 1)) {
		if (retval = krb5_dbe_create_key_data(context, db_entry)) {
		    cleanup_key_data(context, db_entry->n_key_data,
				     db_entry->key_data);
		    break;
		}
		/* We should decrypt/re-encrypt the data to use the same mkvno*/
		db_entry->key_data[db_entry->n_key_data - 1] = key_data[i];
		memset(&key_data[i], 0, sizeof(krb5_key_data));
	    }
	}
	cleanup_key_data(context, key_data_count, key_data);
    }
    return(retval);
}

/*
 * Add key_data for a krb5_db_entry 
 * If passwd is NULL the assumes that the caller wants a random password.
 */
static krb5_error_code
add_key_pwd(context, master_key, ks_tuple, ks_tuple_count, passwd, 
	    db_entry, kvno)
    krb5_context	  context;
    krb5_keyblock       * master_key;
    krb5_key_salt_tuple	* ks_tuple;
    int			  ks_tuple_count;
    char 		* passwd;
    krb5_db_entry	* db_entry;
    int			  kvno;
{
    krb5_error_code	  retval;
    krb5_keysalt	  key_salt;
    krb5_keyblock	  key;
    krb5_data	  	  pwd;
    krb5_boolean	  found;
    int			  i, j;

    retval = 0;

    for (i = 0; i < ks_tuple_count; i++) {
	krb5_boolean similar;

	similar = 0;

	/*
	 * We could use krb5_keysalt_iterate to replace this loop, or use
	 * krb5_keysalt_is_present for the loop below, but we want to avoid
	 * circular library dependencies.
	 */
	for (j = 0; j < i; j++) {
	    if ((retval = krb5_c_enctype_compare(context,
						 ks_tuple[i].ks_enctype,
						 ks_tuple[j].ks_enctype,
						 &similar)))
		return(retval);

	    if (similar &&
		(ks_tuple[j].ks_salttype == ks_tuple[i].ks_salttype))
		break;
	}

	if (j < i)
	    continue;

	if (retval = krb5_dbe_create_key_data(context, db_entry)) 
	    return(retval);

	/* Convert password string to key using appropriate salt */
	switch (key_salt.type = ks_tuple[i].ks_salttype) {
    	case KRB5_KDB_SALTTYPE_ONLYREALM: {
            krb5_data * saltdata;
            if (retval = krb5_copy_data(context, krb5_princ_realm(context,
					db_entry->princ), &saltdata))
	 	return(retval);

	    key_salt.data = *saltdata;
	    krb5_xfree(saltdata);
	}
		break;
    	case KRB5_KDB_SALTTYPE_NOREALM:
            if (retval=krb5_principal2salt_norealm(context, db_entry->princ,
                                                         &key_salt.data)) 
		return(retval);
            break;
	case KRB5_KDB_SALTTYPE_NORMAL:
            if (retval = krb5_principal2salt(context, db_entry->princ,
					         &key_salt.data)) 
		return(retval);
            break;
    	case KRB5_KDB_SALTTYPE_V4:
            key_salt.data.length = 0;
            key_salt.data.data = 0;
            break;
    	case KRB5_KDB_SALTTYPE_AFS3: {
#if 0
            krb5_data * saltdata;
            if (retval = krb5_copy_data(context, krb5_princ_realm(context,
					db_entry->princ), &saltdata))
	 	return(retval);

	    key_salt.data = *saltdata;
	    key_salt.data.length = -1; /*length actually used below...*/
	    krb5_xfree(saltdata);
#else
	    /* Why do we do this? Well, the afs_mit_string_to_key needs to
	       use strlen, and the realm is not NULL terminated.... */
	    int slen = (*krb5_princ_realm(context,db_entry->princ)).length;
	    if(!(key_salt.data.data = (char *) malloc(slen+1)))
	        return ENOMEM;
	    key_salt.data.data[slen] = 0;
	    memcpy((char *)key_salt.data.data,
		   (char *)(*krb5_princ_realm(context,db_entry->princ)).data,
		   slen);
	    key_salt.data.length = -1; /*length actually used below...*/
#endif

	}
		break;
	default:
	    return(KRB5_KDB_BAD_SALTTYPE);
	}

    	pwd.data = passwd;
    	pwd.length = strlen(passwd);

	if ((retval = krb5_c_string_to_key(context, ks_tuple[i].ks_enctype,
					   &pwd, &key_salt.data, &key))) {
	     if (key_salt.data.data)
		  free(key_salt.data.data);
	     return(retval);
	}

	if (key_salt.data.length == -1)
	    key_salt.data.length = 
	      krb5_princ_realm(context, db_entry->princ)->length;

	if (retval = krb5_dbekd_encrypt_key_data(context, master_key, &key,
		     (const krb5_keysalt *)&key_salt,
		     kvno, &db_entry->key_data[db_entry->n_key_data-1])) {
	    if (key_salt.data.data)
		 free(key_salt.data.data);
	    krb5_xfree(key.contents);
	    return(retval);
	}
	if (key_salt.data.data)
	     free(key_salt.data.data);
	krb5_xfree(key.contents);
    }
    return(retval);
}

/*
 * Change password for a krb5_db_entry 
 * Assumes the max kvno
 *
 * As a side effect all old keys are nuked if keepold is false.
 */
krb5_error_code
krb5_dbe_cpw(context, master_key, ks_tuple, ks_tuple_count, passwd,
	     new_kvno, keepold, db_entry)
    krb5_context	  context;
    krb5_keyblock       * master_key;
    krb5_key_salt_tuple	* ks_tuple;
    int			  ks_tuple_count;
    char 		* passwd;
    int			  new_kvno;
    krb5_boolean	  keepold;
    krb5_db_entry	* db_entry;
{
    int 		  key_data_count;
    int			  n_new_key_data;
    krb5_key_data 	* key_data;
    krb5_error_code	  retval;
    int			  old_kvno;
    int			  i;

    /* First save the old keydata */
    old_kvno = get_key_data_kvno(context, db_entry->n_key_data,
				 db_entry->key_data);
    key_data_count = db_entry->n_key_data;
    key_data = db_entry->key_data;
    db_entry->key_data = NULL;
    db_entry->n_key_data = 0;

    /* increment the kvno.  if the requested kvno is too small, 
       increment the old kvno */
    if (new_kvno < old_kvno+1)
       new_kvno = old_kvno+1;

    retval = add_key_pwd(context, master_key, ks_tuple, ks_tuple_count,
			 passwd, db_entry, new_kvno);
    if (retval) {
	cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
	db_entry->n_key_data = key_data_count;
	db_entry->key_data = key_data;
    } else if (keepold) {
	n_new_key_data = db_entry->n_key_data;
	for (i = 0; i < key_data_count; i++) {
	    retval = krb5_dbe_create_key_data(context, db_entry);
	    if (retval) {
		cleanup_key_data(context, db_entry->n_key_data,
				 db_entry->key_data);
		break;
	    }
	    db_entry->key_data[i+n_new_key_data] = key_data[i];
	    memset(&key_data[i], 0, sizeof(krb5_key_data));
	}
    } else {
	cleanup_key_data(context, key_data_count, key_data);
    }
    return(retval);
}

/*
 * Add password for a krb5_db_entry 
 * Assumes the max kvno
 *
 * As a side effect all old keys older than the max kvno are nuked.
 */
krb5_error_code
krb5_dbe_apw(context, master_key, ks_tuple, ks_tuple_count, passwd, db_entry)
    krb5_context	  context;
    krb5_keyblock       * master_key;
    krb5_key_salt_tuple	* ks_tuple;
    int			  ks_tuple_count;
    char 		* passwd;
    krb5_db_entry	* db_entry;
{
    int 		  key_data_count;
    krb5_key_data 	* key_data;
    krb5_error_code	  retval;
    int			  old_kvno, new_kvno;
    int			  i;

    /* First save the old keydata */
    old_kvno = get_key_data_kvno(context, db_entry->n_key_data,
				 db_entry->key_data);
    key_data_count = db_entry->n_key_data;
    key_data = db_entry->key_data;
    db_entry->key_data = NULL;
    db_entry->n_key_data = 0;

    /* increment the kvno */
    new_kvno = old_kvno+1;

    if (retval = add_key_pwd(context, master_key, ks_tuple, ks_tuple_count,
			     passwd, db_entry, new_kvno)) {
	cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
	db_entry->n_key_data = key_data_count;
	db_entry->key_data = key_data;
    } else {
	/* Copy keys with key_data_kvno == old_kvno */
	for (i = 0; i < key_data_count; i++) {
	    if (key_data[i].key_data_kvno == old_kvno) {
		if (retval = krb5_dbe_create_key_data(context, db_entry)) {
		    cleanup_key_data(context, db_entry->n_key_data,
				     db_entry->key_data);
		    break;
		}
		/* We should decrypt/re-encrypt the data to use the same mkvno*/
		db_entry->key_data[db_entry->n_key_data - 1] = key_data[i];
		memset(&key_data[i], 0, sizeof(krb5_key_data));
	    }
	}
	cleanup_key_data(context, key_data_count, key_data);
    }
    return(retval);
}
