/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* tests/create/kdb5_mkdums.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 "kdb.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,
    1
};

int set_dbname_help (char *, char *);

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

    exit(status);
}

int master_princ_set = 0;
krb5_keyblock master_keyblock;
krb5_principal master_princ;
krb5_pointer master_random;
krb5_context test_context;

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

void add_princ (krb5_context, char *);

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

    krb5_error_code retval;
    char *dbname = 0;
    int enctypedone = 0;
    int num_to_create;
    char principal_string[BUFSIZ];
    char *suffix = 0;
    size_t suffix_size = 0;
    int depth;

    krb5_init_context(&test_context);

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

    progname = argv[0];

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

    while ((optchar = getopt(argc, argv, "D:P:p:n:d: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 create */
            strncpy(principal_string, optarg, sizeof(principal_string) - 1);
            principal_string[sizeof(principal_string) - 1] = '\0';
            suffix = principal_string + strlen(principal_string);
            suffix_size = sizeof(principal_string) -
                (suffix - principal_string);
            break;
        case 'n':                        /* how many to create */
            num_to_create = 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_create && 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);
    }

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

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

    for (n = 1; n <= num_to_create; 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) snprintf(suffix, suffix_size, "%d", n);
        (void) snprintf(tmp, sizeof(tmp), "%s-DEPTH-1", principal_string);
        tmp[sizeof(tmp) - 1] = '\0';
        str_newprinc = tmp;
        add_princ(test_context, str_newprinc);

        for (i = 2; i <= depth; i++) {
            (void) snprintf(tmp2, sizeof(tmp2), "/%s-DEPTH-%d",
                            principal_string, i);
            tmp2[sizeof(tmp2) - 1] = '\0';
            strncat(tmp, tmp2, sizeof(tmp) - 1 - strlen(tmp));
            str_newprinc = tmp;
            add_princ(test_context, str_newprinc);
        }
    }

    retval = krb5_db_fini(test_context);
    memset(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 (master_princ_set)
        krb5_free_principal(test_context, master_princ);
    krb5_free_context(test_context);
    exit(0);
}

void
add_princ(krb5_context context, char *str_newprinc)
{
    krb5_error_code       retval;
    krb5_principal        newprinc;
    krb5_db_entry         *newentry;
    char                  princ_name[4096];

    newentry = calloc(1, sizeof(*newentry));
    if (newentry == NULL) {
        com_err(progname, ENOMEM, "while allocating DB entry");
        return;
    }
    snprintf(princ_name, sizeof(princ_name), "%s@%s", str_newprinc, cur_realm);
    if ((retval = krb5_parse_name(context, princ_name, &newprinc))) {
        com_err(progname, retval, "while parsing '%s'", princ_name);
        return;
    }

    /* Add basic data */
    newentry->len = KRB5_KDB_V1_BASE_LENGTH;
    newentry->attributes = mblock.flags;
    newentry->max_life = mblock.max_life;
    newentry->max_renewable_life = mblock.max_rlife;
    newentry->expiration = mblock.expiration;
    newentry->pw_expiration = mblock.expiration;

    /* Add princ to db entry */
    if ((retval = krb5_copy_principal(context, newprinc, &newentry->princ))) {
        com_err(progname, retval, "while encoding princ to db entry for '%s'",
                princ_name);
        krb5_free_principal(context, newprinc);
        goto error;
    }

    {
        /* Add mod princ to db entry */
        krb5_timestamp now;

        retval = krb5_timeofday(context, &now);
        if (retval) {
            com_err(progname, retval, "while fetching date");
            krb5_free_principal(context, newprinc);
            goto error;
        }
        retval = krb5_dbe_update_mod_princ_data(context, newentry, now,
                                                master_princ);
        if (retval) {
            com_err(progname, retval, "while encoding mod_princ data");
            krb5_free_principal(context, newprinc);
            goto error;
        }
    }

    {   /* Add key and salt data to db entry */
        krb5_data pwd, salt;
        krb5_keyblock key;

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

        krb5_free_principal(context, newprinc);

        pwd.length = strlen(princ_name);
        pwd.data = princ_name;  /* must be able to regenerate */
        if ((retval = krb5_c_string_to_key(context, master_keyblock.enctype,
                                           &pwd, &salt, &key))) {
            com_err(progname,retval,"while converting password to key for '%s'",
                    princ_name);
            krb5_free_data_contents(context, &salt);
            goto error;
        }
        krb5_free_data_contents(context, &salt);

        if ((retval = krb5_dbe_create_key_data(context, newentry))) {
            com_err(progname, retval, "while creating key_data for '%s'",
                    princ_name);
            free(key.contents);
            goto error;
        }

        if ((retval = krb5_dbe_encrypt_key_data(context, &master_keyblock,
                                                &key, NULL, 1,
                                                newentry->key_data))) {
            com_err(progname, retval, "while encrypting key for '%s'",
                    princ_name);
            free(key.contents);
            goto error;
        }
        free(key.contents);
    }

    if ((retval = krb5_db_put_principal(context, newentry))) {
        com_err(progname, retval, "while storing principal date");
        goto error;
    }

error: /* Do cleanup of newentry regardless of error */
    krb5_db_free_principal(context, newentry);
    return;
}

int
set_dbname_help(char *pname, char *dbname)
{
    krb5_error_code retval;
    krb5_data pwd, scratch;
    char *args[2];
    krb5_db_entry *master_entry;

    /* assemble & parse the master key name */

    if ((retval = krb5_db_setup_mkey_name(test_context, mkey_name, cur_realm,
                                          0, &master_princ))) {
        com_err(pname, retval, "while setting up master key name");
        return(1);
    }
    master_princ_set = 1;
    if (mkey_password) {
        pwd.data = mkey_password;
        pwd.length = strlen(mkey_password);
        retval = krb5_principal2salt(test_context, master_princ, &scratch);
        if (retval) {
            com_err(pname, retval, "while calculated master key salt");
            return(1);
        }
        if ((retval = krb5_c_string_to_key(test_context,
                                           master_keyblock.enctype,
                                           &pwd, &scratch,
                                           &master_keyblock))) {
            com_err(pname, retval,
                    "while transforming master key from password");
            return(1);
        }
        free(scratch.data);
    } else {
        if ((retval = krb5_db_fetch_mkey(test_context, master_princ,
                                         master_keyblock.enctype, manual_mkey,
                                         FALSE, 0, NULL, NULL,
                                         &master_keyblock))) {
            com_err(pname, retval, "while reading master key");
            return(1);
        }
    }

    /* Ick!  Current DAL interface requires that the default_realm
       field be set in the krb5_context.  */
    if ((retval = krb5_set_default_realm(test_context, cur_realm))) {
        com_err(pname, retval, "setting default realm");
        return 1;
    }
    /* Pathname is passed to db2 via 'args' parameter.  */
    args[1] = NULL;
    if (asprintf(&args[0], "dbname=%s", dbname) < 0) {
        com_err(pname, errno, "while setting up db parameters");
        return 1;
    }

    if ((retval = krb5_db_open(test_context, args, KRB5_KDB_OPEN_RO))) {
        com_err(pname, retval, "while initializing database");
        return(1);
    }
    /* Done with args */
    free(args[0]);

    if ((retval = krb5_db_fetch_mkey_list(test_context, master_princ,
                                          &master_keyblock))){
        com_err(pname, retval, "while verifying master key");
        (void) krb5_db_fini(test_context);
        return(1);
    }
    if ((retval = krb5_db_get_principal(test_context, master_princ, 0,
                                        &master_entry))) {
        com_err(pname, retval, "while retrieving master entry");
        (void) krb5_db_fini(test_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(test_context, master_entry);
    return 0;
}
