/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* kadmin/dbutil/kdb5_util.c - Administer a KDC database */
/*
 * (C) Copyright 1990,1991, 1996, 2008, 2009 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.
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <k5-int.h>
#include <kadm5/admin.h>
#include <locale.h>
#include <adm_proto.h>
#include <time.h>
#include "kdb5_util.h"

/*
 * XXX Ick, ick, ick.  These global variables shouldn't be global....
 */
char *mkey_password = 0;

/*
 * I can't figure out any way for this not to be global, given how ss
 * works.
 */

int exit_status = 0;
krb5_context util_context;
kadm5_config_params global_params;

void usage()
{
    fprintf(stderr,
            _("Usage: kdb5_util [-x db_args]* [-r realm] [-d dbname] "
              "[-k mkeytype] [-M mkeyname]\n"
              "\t        [-kv mkeyVNO] [-sf stashfilename] [-m] cmd "
              "[cmd_options]\n"
              "\tcreate  [-s]\n"
              "\tdestroy [-f]\n"
              "\tstash   [-f keyfile]\n"
              "\tdump    [-old|-ov|-b6|-b7|-r13|-r18] [-verbose]\n"
              "\t        [-mkey_convert] [-new_mkey_file mkey_file]\n"
              "\t        [-rev] [-recurse] [filename [princs...]]\n"
              "\tload    [-old|-ov|-b6|-b7|-r13|-r18] [-verbose] [-update] "
              "filename\n"
              "\tark     [-e etype_list] principal\n"
              "\tadd_mkey [-e etype] [-s]\n"
              "\tuse_mkey kvno [time]\n"
              "\tlist_mkeys\n"));
    /* avoid a string length compiler warning */
    fprintf(stderr,
            _("\tupdate_princ_encryption [-f] [-n] [-v] [princ-pattern]\n"
              "\tpurge_mkeys [-f] [-n] [-v]\n"
              "\nwhere,\n\t[-x db_args]* - any number of database specific "
              "arguments.\n"
              "\t\t\tLook at each database documentation for supported "
              "arguments\n"));
    exit(1);
}

krb5_keyblock master_keyblock;
krb5_kvno   master_kvno; /* fetched */
extern krb5_principal master_princ;
krb5_db_entry *master_entry = NULL;
int     valid_master_key = 0;

char *progname;
krb5_boolean manual_mkey = FALSE;
krb5_boolean dbactive = FALSE;

static int open_db_and_mkey(void);

static void add_random_key(int, char **);

typedef void (*cmd_func)(int, char **);

struct _cmd_table {
    char *name;
    cmd_func func;
    int opendb;
} cmd_table[] = {
    {"create", kdb5_create, 0},
    {"destroy", kdb5_destroy, 1}, /* 1 opens the kdb */
    {"stash", kdb5_stash, 1},
    {"dump", dump_db, 1},
    {"load", load_db, 0},
    {"ark", add_random_key, 1},
    {"add_mkey", kdb5_add_mkey, 1},
    {"use_mkey", kdb5_use_mkey, 1},
    {"list_mkeys", kdb5_list_mkeys, 1},
    {"update_princ_encryption", kdb5_update_princ_encryption, 1},
    {"purge_mkeys", kdb5_purge_mkeys, 1},
    {NULL, NULL, 0},
};

static struct _cmd_table *cmd_lookup(name)
    char *name;
{
    struct _cmd_table *cmd = cmd_table;
    while (cmd->name) {
        if (strcmp(cmd->name, name) == 0)
            return cmd;
        else
            cmd++;
    }

    return NULL;
}

#define ARG_VAL (--argc > 0 ? (koptarg = *(++argv)) : (char *)(usage(), NULL))

char **db5util_db_args = NULL;
int    db5util_db_args_size = 0;

static void extended_com_err_fn (const char *myprog, errcode_t code,
                                 const char *fmt, va_list args)
{
    const char *emsg;
    if (code) {
        emsg = krb5_get_error_message (util_context, code);
        fprintf (stderr, "%s: %s ", myprog, emsg);
        krb5_free_error_message (util_context, emsg);
    } else {
        fprintf (stderr, "%s: ", myprog);
    }
    vfprintf (stderr, fmt, args);
    fprintf (stderr, "\n");
}

int add_db_arg(char *arg)
{
    char **temp;
    db5util_db_args_size++;
    temp = realloc(db5util_db_args,
                   sizeof(char *) * (db5util_db_args_size + 1));
    if (temp == NULL)
        return 0;
    db5util_db_args = temp;
    db5util_db_args[db5util_db_args_size-1] = arg;
    db5util_db_args[db5util_db_args_size]   = NULL;
    return 1;
}

int main(argc, argv)
    int argc;
    char *argv[];
{
    struct _cmd_table *cmd = NULL;
    char *koptarg, **cmd_argv;
    char *db_name_tmp = NULL;
    int cmd_argc;
    krb5_error_code retval;

    setlocale(LC_ALL, "");
    set_com_err_hook(extended_com_err_fn);

    /*
     * Ensure that "progname" is set before calling com_err.
     */
    progname = (strrchr(argv[0], '/') ?
                strrchr(argv[0], '/') + 1 : argv[0]);

    retval = kadm5_init_krb5_context(&util_context);
    if (retval) {
        com_err (progname, retval, _("while initializing Kerberos code"));
        exit(1);
    }

    cmd_argv = (char **) malloc(sizeof(char *)*argc);
    if (cmd_argv == NULL) {
        com_err(progname, ENOMEM, _("while creating sub-command arguments"));
        exit(1);
    }
    memset(cmd_argv, 0, sizeof(char *)*argc);
    cmd_argc = 1;

    argv++; argc--;
    while (*argv) {
        if (strcmp(*argv, "-P") == 0 && ARG_VAL) {
            mkey_password = koptarg;
            manual_mkey = TRUE;
        } else if (strcmp(*argv, "-d") == 0 && ARG_VAL) {
            global_params.dbname = koptarg;
            global_params.mask |= KADM5_CONFIG_DBNAME;

            if (asprintf(&db_name_tmp, "dbname=%s", global_params.dbname) < 0)
            {
                com_err(progname, ENOMEM,
                        _("while parsing command arguments"));
                exit(1);
            }

            if (!add_db_arg(db_name_tmp)) {
                com_err(progname, ENOMEM,
                        _("while parsing command arguments\n"));
                exit(1);
            }

        } else if (strcmp(*argv, "-x") == 0 && ARG_VAL) {
            if (!add_db_arg(koptarg)) {
                com_err(progname, ENOMEM,
                        _("while parsing command arguments\n"));
                exit(1);
            }

        } else if (strcmp(*argv, "-r") == 0 && ARG_VAL) {
            global_params.realm = koptarg;
            global_params.mask |= KADM5_CONFIG_REALM;
            /* not sure this is really necessary */
            if ((retval = krb5_set_default_realm(util_context,
                                                 global_params.realm))) {
                com_err(progname, retval,
                        _("while setting default realm name"));
                exit(1);
            }
        } else if (strcmp(*argv, "-k") == 0 && ARG_VAL) {
            if (krb5_string_to_enctype(koptarg, &global_params.enctype)) {
                com_err(progname, EINVAL, _(": %s is an invalid enctype"),
                        koptarg);
                exit(1);
            } else
                global_params.mask |= KADM5_CONFIG_ENCTYPE;
        } else if (strcmp(*argv, "-kv") == 0 && ARG_VAL) {
            global_params.kvno = (krb5_kvno) atoi(koptarg);
            if (global_params.kvno == IGNORE_VNO) {
                com_err(progname, EINVAL, _(": %s is an invalid mkeyVNO"),
                        koptarg);
                exit(1);
            } else
                global_params.mask |= KADM5_CONFIG_KVNO;
        } else if (strcmp(*argv, "-M") == 0 && ARG_VAL) {
            global_params.mkey_name = koptarg;
            global_params.mask |= KADM5_CONFIG_MKEY_NAME;
        } else if (strcmp(*argv, "-sf") == 0 && ARG_VAL) {
            global_params.stash_file = koptarg;
            global_params.mask |= KADM5_CONFIG_STASH_FILE;
        } else if (strcmp(*argv, "-m") == 0) {
            manual_mkey = TRUE;
            global_params.mkey_from_kbd = 1;
            global_params.mask |= KADM5_CONFIG_MKEY_FROM_KBD;
        } else if (cmd_lookup(*argv) != NULL) {
            if (cmd_argv[0] == NULL)
                cmd_argv[0] = *argv;
            else
                usage();
        } else {
            cmd_argv[cmd_argc++] = *argv;
        }
        argv++; argc--;
    }

    if (cmd_argv[0] == NULL)
        usage();

    if( !util_context->default_realm )
    {
        char *temp = NULL;
        retval = krb5_get_default_realm(util_context, &temp);
        if( retval )
        {
            com_err(progname, retval, _("while getting default realm"));
            exit(1);
        }
        util_context->default_realm = temp;
    }

    retval = kadm5_get_config_params(util_context, 1,
                                     &global_params, &global_params);
    if (retval) {
        com_err(progname, retval,
                _("while retreiving configuration parameters"));
        exit(1);
    }

    /*
     * Dump creates files which should not be world-readable.  It is
     * easiest to do a single umask call here.
     */
    (void) umask(077);

    master_keyblock.enctype = global_params.enctype;
    if ((master_keyblock.enctype != ENCTYPE_UNKNOWN) &&
        (!krb5_c_valid_enctype(master_keyblock.enctype))) {
        com_err(progname, KRB5_PROG_KEYTYPE_NOSUPP,
                "while setting up enctype %d", master_keyblock.enctype);
    }

    cmd = cmd_lookup(cmd_argv[0]);
    if (cmd->opendb && open_db_and_mkey())
        return exit_status;

    if (global_params.iprop_enabled == TRUE)
        ulog_set_role(util_context, IPROP_MASTER);
    else
        ulog_set_role(util_context, IPROP_NULL);

    (*cmd->func)(cmd_argc, cmd_argv);

    if( db_name_tmp )
        free( db_name_tmp );

    if( db5util_db_args )
        free(db5util_db_args);

    kadm5_free_config_params(util_context, &global_params);
    krb5_free_context(util_context);
    return exit_status;
}

#if 0
/*
 * This function is no longer used in kdb5_util (and it would no
 * longer work, anyway).
 */
void set_dbname(argc, argv)
    int argc;
    char *argv[];
{
    krb5_error_code retval;

    if (argc < 3) {
        com_err(argv[0], 0, _("Too few arguments"));
        com_err(progname, 0, _("Usage: %s dbpathname realmname"), argv[0]);
        exit_status++;
        return;
    }
    if (dbactive) {
        if ((retval = krb5_db_fini(util_context)) && retval!= KRB5_KDB_DBNOTINITED) {
            com_err(progname, retval, _("while closing previous database"));
            exit_status++;
            return;
        }
        if (valid_master_key) {
            krb5_free_keyblock_contents(util_context, &master_keyblock);
            master_keyblock.contents = NULL;
            valid_master_key = 0;
        }
        krb5_free_principal(util_context, master_princ);
        dbactive = FALSE;
    }

    (void) set_dbname_help(progname, argv[1]);
    return;
}
#endif

/*
 * open_db_and_mkey: Opens the KDC and policy database, and sets the
 * global master_* variables.  Sets dbactive to TRUE if the databases
 * are opened, and valid_master_key to 1 if the global master
 * variables are set properly.  Returns 0 on success, and 1 on
 * failure, but it is not considered a failure if the master key
 * cannot be fetched (the master key stash file may not exist when the
 * program is run).
 */
static int open_db_and_mkey()
{
    krb5_error_code retval;
    krb5_data scratch, pwd, seed;

    dbactive = FALSE;
    valid_master_key = 0;

    if ((retval = krb5_db_open(util_context, db5util_db_args,
                               KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN))) {
        com_err(progname, retval, _("while initializing database"));
        exit_status++;
        return(1);
    }

    /* assemble & parse the master key name */

    if ((retval = krb5_db_setup_mkey_name(util_context,
                                          global_params.mkey_name,
                                          global_params.realm,
                                          0, &master_princ))) {
        com_err(progname, retval, _("while setting up master key name"));
        exit_status++;
        return(1);
    }
    if ((retval = krb5_db_get_principal(util_context, master_princ, 0,
                                        &master_entry))) {
        com_err(progname, retval, _("while retrieving master entry"));
        exit_status++;
        (void) krb5_db_fini(util_context);
        return(1);
    }

    if (global_params.mask & KADM5_CONFIG_KVNO)
        master_kvno = global_params.kvno; /* user specified */
    else
        master_kvno = IGNORE_VNO;

    /* the databases are now open, and the master principal exists */
    dbactive = TRUE;

    if (mkey_password) {
        pwd.data = mkey_password;
        pwd.length = strlen(mkey_password);
        retval = krb5_principal2salt(util_context, master_princ, &scratch);
        if (retval) {
            com_err(progname, retval, _("while calculated master key salt"));
            exit_status++;
            return(1);
        }

        /* If no encryption type is set, use the default */
        if (master_keyblock.enctype == ENCTYPE_UNKNOWN)
            master_keyblock.enctype = DEFAULT_KDC_ENCTYPE;
        if (!krb5_c_valid_enctype(master_keyblock.enctype))
            com_err(progname, KRB5_PROG_KEYTYPE_NOSUPP,
                    "while setting up enctype %d",
                    master_keyblock.enctype);

        retval = krb5_c_string_to_key(util_context, master_keyblock.enctype,
                                      &pwd, &scratch, &master_keyblock);
        if (retval) {
            com_err(progname, retval,
                    _("while transforming master key from password"));
            exit_status++;
            return(1);
        }
        free(scratch.data);
        mkey_password = 0;

    } else {
        if ((retval = krb5_db_fetch_mkey(util_context, master_princ,
                                         master_keyblock.enctype,
                                         manual_mkey, FALSE,
                                         global_params.stash_file,
                                         &master_kvno,
                                         0, &master_keyblock))) {
            com_err(progname, retval, _("while reading master key"));
            com_err(progname, 0, _("Warning: proceeding without master key"));
            exit_status++;
            return(0);
        }
    }

    if ((retval = krb5_db_fetch_mkey_list(util_context, master_princ,
                                          &master_keyblock))) {
        com_err(progname, retval, "while getting master key list");
        com_err(progname, 0, "Warning: proceeding without master key list");
        exit_status++;
        return(0);
    }

    seed.length = master_keyblock.length;
    seed.data = (char *) master_keyblock.contents;

    if ((retval = krb5_c_random_seed(util_context, &seed))) {
        com_err(progname, retval, _("while seeding random number generator"));
        exit_status++;
        memset(master_keyblock.contents, 0, master_keyblock.length);
        krb5_free_keyblock_contents(util_context, &master_keyblock);
        return(1);
    }

    if (global_params.iprop_enabled) {
        if (ulog_map(util_context, global_params.iprop_logfile,
                     global_params.iprop_ulogsize)) {
            fprintf(stderr, _("%s: Could not map log\n"), progname);
            exit_status++;
            return(1);
        }
    }

    valid_master_key = 1;
    dbactive = TRUE;
    return 0;
}

#ifdef HAVE_GETCWD
#undef getwd
#endif

int
quit()
{
    krb5_error_code retval;
    static krb5_boolean finished = 0;

    if (finished)
        return 0;
    retval = krb5_db_fini(util_context);
    memset(master_keyblock.contents, 0, master_keyblock.length);
    finished = TRUE;
    if (retval && retval != KRB5_KDB_DBNOTINITED) {
        com_err(progname, retval, _("while closing database"));
        exit_status++;
        return 1;
    }
    return 0;
}

static void
add_random_key(argc, argv)
    int argc;
    char **argv;
{
    krb5_error_code ret;
    krb5_principal princ;
    krb5_db_entry *dbent;
    krb5_timestamp now;

    krb5_key_salt_tuple *keysalts = NULL;
    krb5_int32 num_keysalts = 0;

    int free_keysalts;
    char *me = progname;
    char *ks_str = NULL;
    char *pr_str;
    krb5_keyblock *tmp_mkey;

    if (argc < 2)
        usage();
    for (argv++, argc--; *argv; argv++, argc--) {
        if (!strcmp(*argv, "-e")) {
            argv++; argc--;
            ks_str = *argv;
            continue;
        } else
            break;
    }
    if (argc < 1)
        usage();
    pr_str = *argv;
    ret = krb5_parse_name(util_context, pr_str, &princ);
    if (ret) {
        com_err(me, ret, _("while parsing principal name %s"), pr_str);
        exit_status++;
        return;
    }
    ret = krb5_db_get_principal(util_context, princ, 0, &dbent);
    if (ret) {
        com_err(me, ret, _("while fetching principal %s"), pr_str);
        exit_status++;
        return;
    }
    ret = krb5_string_to_keysalts(ks_str,
                                  NULL, NULL, 0,
                                  &keysalts,
                                  &num_keysalts);
    if (ret) {
        com_err(me, ret, _("while parsing keysalts %s"), ks_str);
        exit_status++;
        return;
    }
    if (!num_keysalts || keysalts == NULL) {
        num_keysalts = global_params.num_keysalts;
        keysalts = global_params.keysalts;
        free_keysalts = 0;
    } else
        free_keysalts = 1;

    /* Find the mkey used to protect the existing keys */
    ret = krb5_dbe_find_mkey(util_context, dbent, &tmp_mkey);
    if (ret) {
        com_err(me, ret, _("while finding mkey"));
        krb5_db_free_principal(util_context, dbent);
        exit_status++;
        return;
    }

    ret = krb5_dbe_ark(util_context, tmp_mkey, keysalts, num_keysalts, dbent);
    if (free_keysalts)
        free(keysalts);
    if (ret) {
        com_err(me, ret, "while randomizing principal %s", pr_str);
        krb5_db_free_principal(util_context, dbent);
        exit_status++;
        return;
    }
    dbent->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
    ret = krb5_timeofday(util_context, &now);
    if (ret) {
        com_err(me, ret, _("while getting time"));
        krb5_db_free_principal(util_context, dbent);
        exit_status++;
        return;
    }
    ret = krb5_dbe_update_last_pwd_change(util_context, dbent, now);
    if (ret) {
        com_err(me, ret, _("while setting changetime"));
        krb5_db_free_principal(util_context, dbent);
        exit_status++;
        return;
    }
    ret = krb5_db_put_principal(util_context, dbent);
    krb5_db_free_principal(util_context, dbent);
    if (ret) {
        com_err(me, ret, _("while saving principal %s"), pr_str);
        exit_status++;
        return;
    }
    printf(_("%s changed\n"), pr_str);
}
