Path: blob/main/crypto/krb5/src/kadmin/dbutil/kdb5_mkey.c
34889 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright 2009 Sun Microsystems, Inc. All rights reserved.3* Use is subject to license terms.4*/56#include <k5-int.h>7#include <kdb.h>8#include <kadm5/server_internal.h>9#include <kadm5/admin.h>10#include <adm_proto.h>11#include "kdb5_util.h"12#include <time.h>13#include "k5-regex.h"1415extern krb5_keyblock master_keyblock; /* current mkey */16extern krb5_kvno master_kvno;17extern krb5_principal master_princ;18extern krb5_data master_salt;19extern char *mkey_fullname;20extern char *mkey_password;21extern char *progname;22extern int exit_status;23extern kadm5_config_params global_params;24extern krb5_context util_context;25extern time_t get_date(char *);2627static const char *28strdate(krb5_timestamp when)29{30struct tm *tm;31static char out[40];32time_t lcltim = ts2tt(when);3334tm = localtime(&lcltim);35if (tm == NULL ||36strftime(out, sizeof(out), "%a %b %d %H:%M:%S %Z %Y", tm) == 0)37strlcpy(out, "(error)", sizeof(out));38return out;39}4041krb5_kvno42get_next_kvno(krb5_context context, krb5_db_entry *entry)43{44krb5_kvno new_kvno;4546new_kvno = krb5_db_get_key_data_kvno(context, entry->n_key_data,47entry->key_data);48new_kvno++;49/* deal with wrapping */50if (new_kvno == 0)51new_kvno = 1; /* knvo must not be 0 as this is special value (IGNORE_VNO) */5253return (new_kvno);54}5556krb5_error_code57add_new_mkey(krb5_context context, krb5_db_entry *master_entry,58krb5_keyblock *new_mkey, krb5_kvno use_mkvno)59{60krb5_error_code retval = 0;61int old_key_data_count, i;62krb5_kvno new_mkey_kvno;63krb5_key_data tmp_key_data;64krb5_mkey_aux_node *mkey_aux_data_head = NULL, **mkey_aux_data;65krb5_keylist_node *keylist_node;66krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(context);6768/* do this before modifying master_entry key_data */69new_mkey_kvno = get_next_kvno(context, master_entry);70/* verify the requested mkvno if not 0 is the one that would be used here. */71if (use_mkvno != 0 && new_mkey_kvno != use_mkvno)72return (KRB5_KDB_KVNONOMATCH);7374old_key_data_count = master_entry->n_key_data;7576/* alloc enough space to hold new and existing key_data */77/*78* The encrypted key is malloc'ed by krb5_dbe_encrypt_key_data and79* krb5_key_data key_data_contents is a pointer to this key. Using some80* logic from master_key_convert().81*/82for (i = 0; i < master_entry->n_key_data; i++)83krb5_free_key_data_contents(context, &master_entry->key_data[i]);84free(master_entry->key_data);85master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) *86(old_key_data_count + 1));87if (master_entry->key_data == NULL)88return (ENOMEM);8990memset(master_entry->key_data, 0,91sizeof(krb5_key_data) * (old_key_data_count + 1));92master_entry->n_key_data = old_key_data_count + 1;9394/* Note, mkey does not have salt */95/* add new mkey encrypted with itself to mkey princ entry */96if ((retval = krb5_dbe_encrypt_key_data(context, new_mkey, new_mkey, NULL,97(int) new_mkey_kvno,98&master_entry->key_data[0]))) {99return (retval);100}101/* the mvkno should be that of the newest mkey */102if ((retval = krb5_dbe_update_mkvno(context, master_entry, new_mkey_kvno))) {103krb5_free_key_data_contents(context, &master_entry->key_data[0]);104return (retval);105}106/*107* Need to decrypt old keys with the current mkey which is in the global108* master_keyblock and encrypt those keys with the latest mkey. And while109* the old keys are being decrypted, use those to create the110* KRB5_TL_MKEY_AUX entries which store the latest mkey encrypted by one of111* the older mkeys.112*113* The new mkey is followed by existing keys.114*115* First, set up for creating a krb5_mkey_aux_node list which will be used116* to update the mkey aux data for the mkey princ entry.117*/118mkey_aux_data_head = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));119if (mkey_aux_data_head == NULL) {120retval = ENOMEM;121goto clean_n_exit;122}123memset(mkey_aux_data_head, 0, sizeof(krb5_mkey_aux_node));124mkey_aux_data = &mkey_aux_data_head;125126for (keylist_node = master_keylist, i = 1; keylist_node != NULL;127keylist_node = keylist_node->next, i++) {128129/*130* Create a list of krb5_mkey_aux_node nodes. One node contains the new131* mkey encrypted by an old mkey and the old mkey's kvno (one node per132* old mkey).133*/134if (*mkey_aux_data == NULL) {135/* *mkey_aux_data points to next field of previous node */136*mkey_aux_data = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));137if (*mkey_aux_data == NULL) {138retval = ENOMEM;139goto clean_n_exit;140}141memset(*mkey_aux_data, 0, sizeof(krb5_mkey_aux_node));142}143144memset(&tmp_key_data, 0, sizeof(tmp_key_data));145/* encrypt the new mkey with the older mkey */146retval = krb5_dbe_encrypt_key_data(context, &keylist_node->keyblock,147new_mkey, NULL, (int) new_mkey_kvno,148&tmp_key_data);149if (retval)150goto clean_n_exit;151152(*mkey_aux_data)->latest_mkey = tmp_key_data;153(*mkey_aux_data)->mkey_kvno = keylist_node->kvno;154mkey_aux_data = &((*mkey_aux_data)->next);155156/*157* Store old key in master_entry keydata past the new mkey158*/159retval = krb5_dbe_encrypt_key_data(context, new_mkey,160&keylist_node->keyblock,161NULL, (int) keylist_node->kvno,162&master_entry->key_data[i]);163if (retval)164goto clean_n_exit;165}166assert(i == old_key_data_count + 1);167168if ((retval = krb5_dbe_update_mkey_aux(context, master_entry,169mkey_aux_data_head))) {170goto clean_n_exit;171}172master_entry->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;173174clean_n_exit:175krb5_dbe_free_mkey_aux_list(context, mkey_aux_data_head);176return (retval);177}178179void180kdb5_add_mkey(int argc, char *argv[])181{182int optchar;183krb5_error_code retval;184char *pw_str = 0;185unsigned int pw_size = 0;186int do_stash = 0;187krb5_data pwd;188krb5_kvno new_mkey_kvno;189krb5_keyblock new_mkeyblock;190krb5_enctype new_master_enctype = ENCTYPE_UNKNOWN;191char *new_mkey_password;192krb5_db_entry *master_entry = NULL;193krb5_timestamp now;194195/*196* The command table entry for this command causes open_db_and_mkey() to be197* called first to open the KDB and get the current mkey.198*/199200memset(&new_mkeyblock, 0, sizeof(new_mkeyblock));201master_salt.data = NULL;202203while ((optchar = getopt(argc, argv, "e:s")) != -1) {204switch(optchar) {205case 'e':206if (krb5_string_to_enctype(optarg, &new_master_enctype)) {207com_err(progname, EINVAL, _("%s is an invalid enctype"),208optarg);209exit_status++;210return;211}212break;213case 's':214do_stash++;215break;216case '?':217default:218usage();219return;220}221}222223if (new_master_enctype == ENCTYPE_UNKNOWN)224new_master_enctype = global_params.enctype;225226retval = krb5_db_get_principal(util_context, master_princ, 0,227&master_entry);228if (retval != 0) {229com_err(progname, retval, _("while getting master key principal %s"),230mkey_fullname);231exit_status++;232goto cleanup_return;233}234235printf(_("Creating new master key for master key principal '%s'\n"),236mkey_fullname);237238printf(_("You will be prompted for a new database Master Password.\n"));239printf(_("It is important that you NOT FORGET this password.\n"));240fflush(stdout);241242pw_size = 1024;243pw_str = malloc(pw_size);244if (pw_str == NULL) {245com_err(progname, ENOMEM, _("while creating new master key"));246exit_status++;247goto cleanup_return;248}249250retval = krb5_read_password(util_context, KRB5_KDC_MKEY_1, KRB5_KDC_MKEY_2,251pw_str, &pw_size);252if (retval) {253com_err(progname, retval,254_("while reading new master key from keyboard"));255exit_status++;256goto cleanup_return;257}258new_mkey_password = pw_str;259260pwd.data = new_mkey_password;261pwd.length = strlen(new_mkey_password);262retval = krb5_principal2salt(util_context, master_princ, &master_salt);263if (retval) {264com_err(progname, retval, _("while calculating master key salt"));265exit_status++;266goto cleanup_return;267}268269retval = krb5_c_string_to_key(util_context, new_master_enctype,270&pwd, &master_salt, &new_mkeyblock);271if (retval) {272com_err(progname, retval,273_("while transforming master key from password"));274exit_status++;275goto cleanup_return;276}277278new_mkey_kvno = get_next_kvno(util_context, master_entry);279retval = add_new_mkey(util_context, master_entry, &new_mkeyblock,280new_mkey_kvno);281if (retval) {282com_err(progname, retval,283_("adding new master key to master principal"));284exit_status++;285goto cleanup_return;286}287288if ((retval = krb5_timeofday(util_context, &now))) {289com_err(progname, retval, _("while getting current time"));290exit_status++;291goto cleanup_return;292}293294if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,295now, master_princ))) {296com_err(progname, retval, _("while updating the master key principal "297"modification time"));298exit_status++;299goto cleanup_return;300}301302if ((retval = krb5_db_put_principal(util_context, master_entry))) {303com_err(progname, retval, _("while adding master key entry to the "304"database"));305exit_status++;306goto cleanup_return;307}308309if (do_stash) {310retval = krb5_db_store_master_key(util_context,311global_params.stash_file,312master_princ,313new_mkey_kvno,314&new_mkeyblock,315mkey_password);316if (retval) {317com_err(progname, retval, _("while storing key"));318printf(_("Warning: couldn't stash master key.\n"));319}320}321322cleanup_return:323/* clean up */324krb5_db_free_principal(util_context, master_entry);325zap((char *)new_mkeyblock.contents, new_mkeyblock.length);326free(new_mkeyblock.contents);327if (pw_str) {328zap(pw_str, pw_size);329free(pw_str);330}331free(master_salt.data);332return;333}334335void336kdb5_use_mkey(int argc, char *argv[])337{338krb5_error_code retval;339krb5_kvno use_kvno;340krb5_timestamp now, start_time;341krb5_actkvno_node *actkvno_list = NULL, *new_actkvno = NULL,342*prev_actkvno, *cur_actkvno;343krb5_db_entry *master_entry = NULL;344krb5_keylist_node *keylist_node;345krb5_boolean inserted = FALSE;346krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);347348if (argc < 2 || argc > 3) {349/* usage calls exit */350usage();351}352353use_kvno = atoi(argv[1]);354if (use_kvno == 0) {355com_err(progname, EINVAL, _("0 is an invalid KVNO value"));356exit_status++;357return;358} else {359/* verify use_kvno is valid */360for (keylist_node = master_keylist; keylist_node != NULL;361keylist_node = keylist_node->next) {362if (use_kvno == keylist_node->kvno)363break;364}365if (!keylist_node) {366com_err(progname, EINVAL, _("%d is an invalid KVNO value"),367use_kvno);368exit_status++;369return;370}371}372373if ((retval = krb5_timeofday(util_context, &now))) {374com_err(progname, retval, _("while getting current time"));375exit_status++;376return;377}378379if (argc == 3) {380time_t t = get_date(argv[2]);381if (t == -1) {382com_err(progname, 0, _("could not parse date-time string '%s'"),383argv[2]);384exit_status++;385return;386} else387start_time = (krb5_timestamp) t;388} else {389start_time = now;390}391392/*393* Need to:394*395* 1. get mkey princ396* 2. get krb5_actkvno_node list397* 3. add use_kvno to actkvno list (sorted in right spot)398* 4. update mkey princ's tl data399* 5. put mkey princ.400*/401402retval = krb5_db_get_principal(util_context, master_princ, 0,403&master_entry);404if (retval != 0) {405com_err(progname, retval, _("while getting master key principal %s"),406mkey_fullname);407exit_status++;408goto cleanup_return;409}410411retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);412if (retval != 0) {413com_err(progname, retval,414_("while looking up active version of master key"));415exit_status++;416goto cleanup_return;417}418419/*420* If an entry already exists with the same kvno either delete it or if it's421* the only entry, just set its active time.422*/423for (prev_actkvno = NULL, cur_actkvno = actkvno_list;424cur_actkvno != NULL;425prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {426427if (cur_actkvno->act_kvno == use_kvno) {428/* delete it */429if (prev_actkvno) {430prev_actkvno->next = cur_actkvno->next;431cur_actkvno->next = NULL;432krb5_dbe_free_actkvno_list(util_context, cur_actkvno);433} else {434if (cur_actkvno->next) {435/* delete it from front of list */436actkvno_list = cur_actkvno->next;437cur_actkvno->next = NULL;438krb5_dbe_free_actkvno_list(util_context, cur_actkvno);439} else {440/* There's only one entry, go ahead and change the time */441cur_actkvno->act_time = start_time;442inserted = TRUE;443}444}445break;446}447}448449if (!inserted) {450/* alloc enough space to hold new and existing key_data */451new_actkvno = (krb5_actkvno_node *) malloc(sizeof(krb5_actkvno_node));452if (new_actkvno == NULL) {453com_err(progname, ENOMEM, _("while adding new master key"));454exit_status++;455goto cleanup_return;456}457memset(new_actkvno, 0, sizeof(krb5_actkvno_node));458new_actkvno->act_kvno = use_kvno;459new_actkvno->act_time = start_time;460461/* insert new act kvno node */462463if (actkvno_list == NULL) {464/* new actkvno is the list */465actkvno_list = new_actkvno;466} else {467for (prev_actkvno = NULL, cur_actkvno = actkvno_list;468cur_actkvno != NULL;469prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {470471if (ts_after(cur_actkvno->act_time, new_actkvno->act_time)) {472if (prev_actkvno) {473prev_actkvno->next = new_actkvno;474new_actkvno->next = cur_actkvno;475} else {476new_actkvno->next = actkvno_list;477actkvno_list = new_actkvno;478}479break;480} else if (cur_actkvno->next == NULL) {481/* end of line, just add new node to end of list */482cur_actkvno->next = new_actkvno;483break;484}485}486}487}488489if (ts_after(actkvno_list->act_time, now)) {490com_err(progname, EINVAL,491_("there must be one master key currently active"));492exit_status++;493goto cleanup_return;494}495496if ((retval = krb5_dbe_update_actkvno(util_context, master_entry,497actkvno_list))) {498com_err(progname, retval,499_("while updating actkvno data for master principal entry"));500exit_status++;501goto cleanup_return;502}503504if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,505now, master_princ))) {506com_err(progname, retval, _("while updating the master key principal "507"modification time"));508exit_status++;509goto cleanup_return;510}511512master_entry->mask |= KADM5_TL_DATA;513514if ((retval = krb5_db_put_principal(util_context, master_entry))) {515com_err(progname, retval,516_("while adding master key entry to the database"));517exit_status++;518goto cleanup_return;519}520521cleanup_return:522/* clean up */523krb5_db_free_principal(util_context, master_entry);524krb5_dbe_free_actkvno_list(util_context, actkvno_list);525return;526}527528void529kdb5_list_mkeys(int argc, char *argv[])530{531krb5_error_code retval;532char *output_str = NULL, enctype[BUFSIZ];533krb5_kvno act_kvno;534krb5_timestamp act_time;535krb5_actkvno_node *actkvno_list = NULL, *cur_actkvno;536krb5_db_entry *master_entry = NULL;537krb5_keylist_node *cur_kb_node;538krb5_keyblock *act_mkey;539krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);540541if (master_keylist == NULL) {542com_err(progname, 0, _("master keylist not initialized"));543exit_status++;544return;545}546547retval = krb5_db_get_principal(util_context, master_princ, 0,548&master_entry);549if (retval != 0) {550com_err(progname, retval, _("while getting master key principal %s"),551mkey_fullname);552exit_status++;553goto cleanup_return;554}555556retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);557if (retval != 0) {558com_err(progname, retval, _("while looking up active kvno list"));559exit_status++;560goto cleanup_return;561}562563retval = krb5_dbe_find_act_mkey(util_context, actkvno_list, &act_kvno,564&act_mkey);565if (retval != 0) {566com_err(progname, retval, _("while looking up active master key"));567exit_status++;568goto cleanup_return;569}570571printf("Master keys for Principal: %s\n", mkey_fullname);572573for (cur_kb_node = master_keylist; cur_kb_node != NULL;574cur_kb_node = cur_kb_node->next) {575576if ((retval = krb5_enctype_to_name(cur_kb_node->keyblock.enctype,577FALSE, enctype, sizeof(enctype)))) {578com_err(progname, retval, _("while getting enctype description"));579exit_status++;580goto cleanup_return;581}582583act_time = -1; /* assume actkvno entry not found */584for (cur_actkvno = actkvno_list; cur_actkvno != NULL;585cur_actkvno = cur_actkvno->next) {586if (cur_actkvno->act_kvno == cur_kb_node->kvno) {587act_time = cur_actkvno->act_time;588break;589}590}591592if (cur_kb_node->kvno == act_kvno) {593/* * indicates kvno is currently active */594retval = asprintf(&output_str,595_("KVNO: %d, Enctype: %s, Active on: %s *\n"),596cur_kb_node->kvno, enctype, strdate(act_time));597} else {598if (act_time != -1) {599retval = asprintf(&output_str,600_("KVNO: %d, Enctype: %s, Active on: %s\n"),601cur_kb_node->kvno, enctype, strdate(act_time));602} else {603retval = asprintf(&output_str,604_("KVNO: %d, Enctype: %s, No activate time "605"set\n"), cur_kb_node->kvno, enctype);606}607}608if (retval == -1) {609com_err(progname, ENOMEM, _("asprintf could not allocate enough "610"memory to hold output"));611exit_status++;612goto cleanup_return;613}614printf("%s", output_str);615free(output_str);616output_str = NULL;617}618619cleanup_return:620/* clean up */621krb5_db_free_principal(util_context, master_entry);622free(output_str);623krb5_dbe_free_actkvno_list(util_context, actkvno_list);624return;625}626627struct update_enc_mkvno {628unsigned int re_match_count;629unsigned int already_current;630unsigned int updated;631unsigned int dry_run : 1;632unsigned int verbose : 1;633regex_t preg;634};635636/* XXX Duplicated in libkadm5srv! */637/*638* Function: glob_to_regexp639*640* Arguments:641*642* glob (r) the shell-style glob (?*[]) to convert643* realm (r) the default realm to append, or NULL644* regexp (w) the ed-style regexp created from glob645*646* Effects:647*648* regexp is filled in with allocated memory containing a regular649* expression that matches what the shell-style glob would match.650* If glob does not contain an "@" character and realm is not651* NULL, "@*" is appended to the regexp.652*653* Conversion algorithm:654*655* quoted characters are copied quoted656* ? is converted to .657* * is converted to .*658* active characters are quoted: ^, $, .659* [ and ] are active but supported and have the same meaning, so660* they are copied661* other characters are copied662* regexp is anchored with ^ and $663*/664static int glob_to_regexp(char *glob, char *realm, char **regexp)665{666int append_realm;667char *p;668669/* validate the glob */670if (glob[strlen(glob)-1] == '\\')671return EINVAL;672673/* A character of glob can turn into two in regexp, plus ^ and $ */674/* and trailing null. If glob has no @, also allocate space for */675/* the realm. */676append_realm = (realm != NULL) && (strchr(glob, '@') == NULL);677p = (char *) malloc(strlen(glob)*2+ 3 + (append_realm ? 3 : 0));678if (p == NULL)679return ENOMEM;680*regexp = p;681682*p++ = '^';683while (*glob) {684switch (*glob) {685case '?':686*p++ = '.';687break;688case '*':689*p++ = '.';690*p++ = '*';691break;692case '.':693case '^':694case '$':695*p++ = '\\';696*p++ = *glob;697break;698case '\\':699*p++ = '\\';700*p++ = *++glob;701break;702default:703*p++ = *glob;704break;705}706glob++;707}708709if (append_realm) {710*p++ = '@';711*p++ = '.';712*p++ = '*';713}714715*p++ = '$';716*p++ = '\0';717return 0;718}719720static int721update_princ_encryption_1(void *cb, krb5_db_entry *ent)722{723struct update_enc_mkvno *p = cb;724char *pname = 0;725krb5_error_code retval;726krb5_timestamp now;727int result;728krb5_kvno old_mkvno;729730retval = krb5_unparse_name(util_context, ent->princ, &pname);731if (retval) {732com_err(progname, retval,733_("getting string representation of principal name"));734goto fail;735}736737if (krb5_principal_compare(util_context, ent->princ, master_princ)) {738goto skip;739}740741if (regexec(&p->preg, pname, 0, NULL, 0) != 0)742goto skip;743p->re_match_count++;744retval = krb5_dbe_get_mkvno(util_context, ent, &old_mkvno);745if (retval) {746com_err(progname, retval,747_("determining master key used for principal '%s'"), pname);748goto fail;749}750/* Line up "skip" and "update" messages for viewing. */751if (old_mkvno == new_mkvno) {752if (p->dry_run && p->verbose)753printf(_("would skip: %s\n"), pname);754else if (p->verbose)755printf(_("skipping: %s\n"), pname);756p->already_current++;757goto skip;758}759if (p->dry_run) {760if (p->verbose)761printf(_("would update: %s\n"), pname);762p->updated++;763goto skip;764} else if (p->verbose)765printf(_("updating: %s\n"), pname);766retval = master_key_convert (util_context, ent);767if (retval) {768com_err(progname, retval,769_("error re-encrypting key for principal '%s'"), pname);770goto fail;771}772if ((retval = krb5_timeofday(util_context, &now))) {773com_err(progname, retval, _("while getting current time"));774goto fail;775}776777if ((retval = krb5_dbe_update_mod_princ_data(util_context, ent,778now, master_princ))) {779com_err(progname, retval,780_("while updating principal '%s' modification time"), pname);781goto fail;782}783784ent->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;785786if ((retval = krb5_db_put_principal(util_context, ent))) {787com_err(progname, retval, _("while updating principal '%s' key data "788"in the database"), pname);789goto fail;790}791p->updated++;792skip:793result = 0;794goto egress;795fail:796exit_status++;797result = 1;798egress:799if (pname)800krb5_free_unparsed_name(util_context, pname);801return result;802}803804extern int are_you_sure (const char *, ...)805#if !defined(__cplusplus) && (__GNUC__ > 2)806__attribute__((__format__(__printf__, 1, 2)))807#endif808;809810int811are_you_sure (const char *format, ...)812{813va_list va;814char ansbuf[100];815816va_start(va, format);817vprintf(format, va);818va_end(va);819printf(_("\n(type 'yes' to confirm)? "));820fflush(stdout);821if (fgets(ansbuf, sizeof(ansbuf), stdin) == NULL)822return 0;823if (strcmp(ansbuf, "yes\n"))824return 0;825return 1;826}827828void829kdb5_update_princ_encryption(int argc, char *argv[])830{831struct update_enc_mkvno data = { 0 };832char *name_pattern = NULL;833int force = 0;834int optchar;835krb5_error_code retval;836krb5_actkvno_node *actkvno_list = 0;837krb5_db_entry *master_entry = NULL;838char *regexp = NULL;839krb5_keyblock *act_mkey;840krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);841krb5_flags iterflags = 0;842843while ((optchar = getopt(argc, argv, "fnv")) != -1) {844switch (optchar) {845case 'f':846force = 1;847break;848case 'n':849data.dry_run = 1;850break;851case 'v':852data.verbose = 1;853break;854case '?':855case ':':856default:857usage();858}859}860if (argv[optind] != NULL) {861name_pattern = argv[optind];862if (argv[optind+1] != NULL)863usage();864}865866if (master_keylist == NULL) {867com_err(progname, 0, _("master keylist not initialized"));868exit_status++;869goto cleanup;870}871872/* The glob_to_regexp code only cares if the "realm" parameter is873NULL or not; the string data is irrelevant. */874if (name_pattern == NULL)875name_pattern = "*";876if (glob_to_regexp(name_pattern, "hi", ®exp) != 0) {877com_err(progname, ENOMEM,878_("converting glob pattern '%s' to regular expression"),879name_pattern);880exit_status++;881goto cleanup;882}883884if (regcomp(&data.preg, regexp, REG_NOSUB) != 0) {885/* XXX syslog msg or regerr(regerrno) */886com_err(progname, 0, _("error compiling converted regexp '%s'"),887regexp);888exit_status++;889goto cleanup;890}891892retval = krb5_db_get_principal(util_context, master_princ, 0,893&master_entry);894if (retval != 0) {895com_err(progname, retval, _("while getting master key principal %s"),896mkey_fullname);897exit_status++;898goto cleanup;899}900901retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);902if (retval != 0) {903com_err(progname, retval, _("while looking up active kvno list"));904exit_status++;905goto cleanup;906}907908retval = krb5_dbe_find_act_mkey(util_context, actkvno_list, &new_mkvno,909&act_mkey);910if (retval) {911com_err(progname, retval, _("while looking up active master key"));912exit_status++;913goto cleanup;914}915new_master_keyblock = *act_mkey;916917if (!force &&918!data.dry_run &&919!are_you_sure(_("Re-encrypt all keys not using master key vno %u?"),920new_mkvno)) {921printf(_("OK, doing nothing.\n"));922exit_status++;923goto cleanup;924}925if (data.verbose) {926if (data.dry_run) {927printf(_("Principals whose keys WOULD BE re-encrypted to master "928"key vno %u:\n"), new_mkvno);929} else {930printf(_("Principals whose keys are being re-encrypted to master "931"key vno %u if necessary:\n"), new_mkvno);932}933}934935if (!data.dry_run) {936/* Grab a write lock so we don't have to upgrade to a write lock and937* reopen the DB while iterating. */938iterflags = KRB5_DB_ITER_WRITE;939}940941retval = krb5_db_iterate(util_context, name_pattern,942update_princ_encryption_1, &data, iterflags);943/* If exit_status is set, then update_princ_encryption_1 already944printed a message. */945if (retval != 0 && exit_status == 0) {946com_err(progname, retval, _("trying to process principal database"));947exit_status++;948}949if (data.dry_run) {950printf(_("%u principals processed: %u would be updated, %u already "951"current\n"),952data.re_match_count, data.updated, data.already_current);953} else {954printf(_("%u principals processed: %u updated, %u already current\n"),955data.re_match_count, data.updated, data.already_current);956}957958cleanup:959krb5_db_free_principal(util_context, master_entry);960free(regexp);961regfree(&data.preg);962memset(&new_master_keyblock, 0, sizeof(new_master_keyblock));963krb5_dbe_free_actkvno_list(util_context, actkvno_list);964}965966struct kvnos_in_use {967krb5_kvno kvno;968unsigned int use_count;969};970971struct purge_args {972krb5_context kcontext;973struct kvnos_in_use *kvnos;974unsigned int num_kvnos;975};976977static krb5_error_code978find_mkvnos_in_use(krb5_pointer ptr,979krb5_db_entry *entry)980{981krb5_error_code retval;982struct purge_args * args;983unsigned int i;984krb5_kvno mkvno;985986args = (struct purge_args *) ptr;987988retval = krb5_dbe_get_mkvno(args->kcontext, entry, &mkvno);989if (retval)990return (retval);991992for (i = 0; i < args->num_kvnos; i++) {993if (args->kvnos[i].kvno == mkvno) {994/* XXX do I need to worry about use_count wrapping? */995args->kvnos[i].use_count++;996break;997}998}999return 0;1000}10011002void1003kdb5_purge_mkeys(int argc, char *argv[])1004{1005int optchar;1006krb5_error_code retval;1007krb5_timestamp now;1008krb5_db_entry *master_entry = NULL;1009krb5_boolean force = FALSE, dry_run = FALSE, verbose = FALSE;1010struct purge_args args;1011char buf[5];1012unsigned int i, j, k, num_kvnos_inuse, num_kvnos_purged;1013unsigned int old_key_data_count;1014krb5_actkvno_node *actkvno_list = NULL, *actkvno_entry, *prev_actkvno_entry;1015krb5_mkey_aux_node *mkey_aux_list = NULL, *mkey_aux_entry, *prev_mkey_aux_entry;1016krb5_key_data *old_key_data;10171018/*1019* Verify that the master key list has been initialized before doing1020* anything else.1021*/1022if (krb5_db_mkey_list_alias(util_context) == NULL) {1023com_err(progname, KRB5_KDB_DBNOTINITED,1024_("master keylist not initialized"));1025exit_status++;1026return;1027}10281029memset(&args, 0, sizeof(args));10301031optind = 1;1032while ((optchar = getopt(argc, argv, "fnv")) != -1) {1033switch(optchar) {1034case 'f':1035force = TRUE;1036break;1037case 'n':1038dry_run = TRUE; /* mkey princ will not be modified */1039force = TRUE; /* implied */1040break;1041case 'v':1042verbose = TRUE;1043break;1044case '?':1045default:1046usage();1047return;1048}1049}10501051retval = krb5_db_get_principal(util_context, master_princ, 0,1052&master_entry);1053if (retval != 0) {1054com_err(progname, retval, _("while getting master key principal %s"),1055mkey_fullname);1056exit_status++;1057goto cleanup_return;1058}10591060if (!force) {1061printf(_("Will purge all unused master keys stored in the '%s' "1062"principal, are you sure?\n"), mkey_fullname);1063printf(_("(type 'yes' to confirm)? "));1064if (fgets(buf, sizeof(buf), stdin) == NULL) {1065exit_status++;1066goto cleanup_return;1067}1068if (strcmp(buf, "yes\n")) {1069exit_status++;1070goto cleanup_return;1071}1072printf(_("OK, purging unused master keys from '%s'...\n"),1073mkey_fullname);1074}10751076/* save the old keydata */1077old_key_data_count = master_entry->n_key_data;1078if (old_key_data_count == 1) {1079if (verbose)1080printf(_("There is only one master key which can not be "1081"purged.\n"));1082goto cleanup_return;1083}1084old_key_data = master_entry->key_data;10851086args.kvnos = (struct kvnos_in_use *) malloc(sizeof(struct kvnos_in_use) * old_key_data_count);1087if (args.kvnos == NULL) {1088retval = ENOMEM;1089com_err(progname, ENOMEM, _("while allocating args.kvnos"));1090exit_status++;1091goto cleanup_return;1092}1093memset(args.kvnos, 0, sizeof(struct kvnos_in_use) * old_key_data_count);1094args.num_kvnos = old_key_data_count;1095args.kcontext = util_context;10961097/* populate the kvnos array with all the current mkvnos */1098for (i = 0; i < old_key_data_count; i++)1099args.kvnos[i].kvno = master_entry->key_data[i].key_data_kvno;11001101if ((retval = krb5_db_iterate(util_context,1102NULL,1103find_mkvnos_in_use,1104(krb5_pointer) &args, 0))) {1105com_err(progname, retval, _("while finding master keys in use"));1106exit_status++;1107goto cleanup_return;1108}1109/*1110* args.kvnos has been marked with the mkvno's that are currently protecting1111* princ entries1112*/1113if (dry_run) {1114printf(_("Would purge the following master key(s) from %s:\n"),1115mkey_fullname);1116} else {1117printf(_("Purging the following master key(s) from %s:\n"),1118mkey_fullname);1119}11201121/* find # of keys still in use or print out verbose info */1122for (i = num_kvnos_inuse = num_kvnos_purged = 0; i < args.num_kvnos; i++) {1123if (args.kvnos[i].use_count > 0) {1124num_kvnos_inuse++;1125} else {1126/* this key would be deleted */1127if (args.kvnos[i].kvno == master_kvno) {1128com_err(progname, KRB5_KDB_STORED_MKEY_NOTCURRENT,1129_("master key stash file needs updating, command "1130"aborting"));1131exit_status++;1132goto cleanup_return;1133}1134num_kvnos_purged++;1135printf(_("KVNO: %d\n"), args.kvnos[i].kvno);1136}1137}1138/* didn't find any keys to purge */1139if (num_kvnos_inuse == args.num_kvnos) {1140printf(_("All keys in use, nothing purged.\n"));1141goto cleanup_return;1142}1143if (dry_run) {1144/* bail before doing anything else */1145printf(_("%d key(s) would be purged.\n"), num_kvnos_purged);1146goto cleanup_return;1147}11481149retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);1150if (retval != 0) {1151com_err(progname, retval, _("while looking up active kvno list"));1152exit_status++;1153goto cleanup_return;1154}11551156retval = krb5_dbe_lookup_mkey_aux(util_context, master_entry, &mkey_aux_list);1157if (retval != 0) {1158com_err(progname, retval, _("while looking up mkey aux data list"));1159exit_status++;1160goto cleanup_return;1161}11621163master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) * num_kvnos_inuse);1164if (master_entry->key_data == NULL) {1165retval = ENOMEM;1166com_err(progname, ENOMEM, _("while allocating key_data"));1167exit_status++;1168goto cleanup_return;1169}1170memset(master_entry->key_data, 0, sizeof(krb5_key_data) * num_kvnos_inuse);1171master_entry->n_key_data = num_kvnos_inuse; /* there's only 1 mkey per kvno */11721173/*1174* Assuming that the latest mkey will not be purged because it will always1175* be "in use" so this code will not bother with encrypting keys again.1176*/1177for (i = k = 0; i < old_key_data_count; i++) {1178for (j = 0; j < args.num_kvnos; j++) {1179if (args.kvnos[j].kvno == (krb5_kvno) old_key_data[i].key_data_kvno) {1180if (args.kvnos[j].use_count != 0) {1181master_entry->key_data[k++] = old_key_data[i];1182memset(&old_key_data[i], 0, sizeof(old_key_data[i]));1183break;1184} else {1185/* remove unused mkey */1186/* adjust the actkno data */1187for (prev_actkvno_entry = actkvno_entry = actkvno_list;1188actkvno_entry != NULL;1189actkvno_entry = actkvno_entry->next) {11901191if (actkvno_entry->act_kvno == args.kvnos[j].kvno) {1192if (actkvno_entry == actkvno_list) {1193/* remove from head */1194actkvno_list = actkvno_entry->next;1195} else if (actkvno_entry->next == NULL) {1196/* remove from tail */1197prev_actkvno_entry->next = NULL;1198} else {1199/* remove in between */1200prev_actkvno_entry->next = actkvno_entry->next;1201}1202actkvno_entry->next = NULL;1203krb5_dbe_free_actkvno_list(util_context, actkvno_entry);1204break; /* deleted entry, no need to loop further */1205} else {1206prev_actkvno_entry = actkvno_entry;1207}1208}1209/* adjust the mkey aux data */1210for (prev_mkey_aux_entry = mkey_aux_entry = mkey_aux_list;1211mkey_aux_entry != NULL;1212mkey_aux_entry = mkey_aux_entry->next) {12131214if (mkey_aux_entry->mkey_kvno == args.kvnos[j].kvno) {1215if (mkey_aux_entry == mkey_aux_list) {1216mkey_aux_list = mkey_aux_entry->next;1217} else if (mkey_aux_entry->next == NULL) {1218prev_mkey_aux_entry->next = NULL;1219} else {1220prev_mkey_aux_entry->next = mkey_aux_entry->next;1221}1222mkey_aux_entry->next = NULL;1223krb5_dbe_free_mkey_aux_list(util_context, mkey_aux_entry);1224break; /* deleted entry, no need to loop further */1225} else {1226prev_mkey_aux_entry = mkey_aux_entry;1227}1228}1229}1230}1231}1232}1233assert(k == num_kvnos_inuse);12341235/* Free any key data entries we did not consume in the loop above. */1236for (i = 0; i < old_key_data_count; i++)1237krb5_dbe_free_key_data_contents(util_context, &old_key_data[i]);1238free(old_key_data);12391240if ((retval = krb5_dbe_update_actkvno(util_context, master_entry,1241actkvno_list))) {1242com_err(progname, retval,1243_("while updating actkvno data for master principal entry"));1244exit_status++;1245goto cleanup_return;1246}12471248if ((retval = krb5_dbe_update_mkey_aux(util_context, master_entry,1249mkey_aux_list))) {1250com_err(progname, retval,1251_("while updating mkey_aux data for master principal entry"));1252exit_status++;1253goto cleanup_return;1254}12551256if ((retval = krb5_timeofday(util_context, &now))) {1257com_err(progname, retval, _("while getting current time"));1258exit_status++;1259goto cleanup_return;1260}12611262if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,1263now, master_princ))) {1264com_err(progname, retval, _("while updating the master key principal "1265"modification time"));1266exit_status++;1267goto cleanup_return;1268}12691270master_entry->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;12711272if ((retval = krb5_db_put_principal(util_context, master_entry))) {1273com_err(progname, retval,1274_("while adding master key entry to the database"));1275exit_status++;1276goto cleanup_return;1277}1278printf(_("%d key(s) purged.\n"), num_kvnos_purged);12791280cleanup_return:1281krb5_db_free_principal(util_context, master_entry);1282free(args.kvnos);1283krb5_dbe_free_actkvno_list(util_context, actkvno_list);1284krb5_dbe_free_mkey_aux_list(util_context, mkey_aux_list);1285return;1286}128712881289