xref: /freebsd/crypto/krb5/src/kadmin/dbutil/kdb5_mkey.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
4  * Use is subject to license terms.
5  */
6 
7 #include <k5-int.h>
8 #include <kdb.h>
9 #include <kadm5/server_internal.h>
10 #include <kadm5/admin.h>
11 #include <adm_proto.h>
12 #include "kdb5_util.h"
13 #include <time.h>
14 #include "k5-regex.h"
15 
16 extern krb5_keyblock master_keyblock; /* current mkey */
17 extern krb5_kvno   master_kvno;
18 extern krb5_principal master_princ;
19 extern krb5_data master_salt;
20 extern char *mkey_fullname;
21 extern char *mkey_password;
22 extern char *progname;
23 extern int exit_status;
24 extern kadm5_config_params global_params;
25 extern krb5_context util_context;
26 extern time_t get_date(char *);
27 
28 static const char *
strdate(krb5_timestamp when)29 strdate(krb5_timestamp when)
30 {
31     struct tm *tm;
32     static char out[40];
33     time_t lcltim = ts2tt(when);
34 
35     tm = localtime(&lcltim);
36     if (tm == NULL ||
37         strftime(out, sizeof(out), "%a %b %d %H:%M:%S %Z %Y", tm) == 0)
38         strlcpy(out, "(error)", sizeof(out));
39     return out;
40 }
41 
42 krb5_kvno
get_next_kvno(krb5_context context,krb5_db_entry * entry)43 get_next_kvno(krb5_context context, krb5_db_entry *entry)
44 {
45     krb5_kvno new_kvno;
46 
47     new_kvno = krb5_db_get_key_data_kvno(context, entry->n_key_data,
48                                          entry->key_data);
49     new_kvno++;
50     /* deal with wrapping */
51     if (new_kvno == 0)
52         new_kvno = 1; /* knvo must not be 0 as this is special value (IGNORE_VNO) */
53 
54     return (new_kvno);
55 }
56 
57 krb5_error_code
add_new_mkey(krb5_context context,krb5_db_entry * master_entry,krb5_keyblock * new_mkey,krb5_kvno use_mkvno)58 add_new_mkey(krb5_context context, krb5_db_entry *master_entry,
59              krb5_keyblock *new_mkey, krb5_kvno use_mkvno)
60 {
61     krb5_error_code retval = 0;
62     int old_key_data_count, i;
63     krb5_kvno new_mkey_kvno;
64     krb5_key_data tmp_key_data;
65     krb5_mkey_aux_node  *mkey_aux_data_head = NULL, **mkey_aux_data;
66     krb5_keylist_node  *keylist_node;
67     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(context);
68 
69     /* do this before modifying master_entry key_data */
70     new_mkey_kvno = get_next_kvno(context, master_entry);
71     /* verify the requested mkvno if not 0 is the one that would be used here. */
72     if (use_mkvno != 0 && new_mkey_kvno != use_mkvno)
73         return (KRB5_KDB_KVNONOMATCH);
74 
75     old_key_data_count = master_entry->n_key_data;
76 
77     /* alloc enough space to hold new and existing key_data */
78     /*
79      * The encrypted key is malloc'ed by krb5_dbe_encrypt_key_data and
80      * krb5_key_data key_data_contents is a pointer to this key.  Using some
81      * logic from master_key_convert().
82      */
83     for (i = 0; i < master_entry->n_key_data; i++)
84         krb5_free_key_data_contents(context, &master_entry->key_data[i]);
85     free(master_entry->key_data);
86     master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) *
87                                                       (old_key_data_count + 1));
88     if (master_entry->key_data == NULL)
89         return (ENOMEM);
90 
91     memset(master_entry->key_data, 0,
92            sizeof(krb5_key_data) * (old_key_data_count + 1));
93     master_entry->n_key_data = old_key_data_count + 1;
94 
95     /* Note, mkey does not have salt */
96     /* add new mkey encrypted with itself to mkey princ entry */
97     if ((retval = krb5_dbe_encrypt_key_data(context, new_mkey, new_mkey, NULL,
98                                             (int) new_mkey_kvno,
99                                             &master_entry->key_data[0]))) {
100         return (retval);
101     }
102     /* the mvkno should be that of the newest mkey */
103     if ((retval = krb5_dbe_update_mkvno(context, master_entry, new_mkey_kvno))) {
104         krb5_free_key_data_contents(context, &master_entry->key_data[0]);
105         return (retval);
106     }
107     /*
108      * Need to decrypt old keys with the current mkey which is in the global
109      * master_keyblock and encrypt those keys with the latest mkey.  And while
110      * the old keys are being decrypted, use those to create the
111      * KRB5_TL_MKEY_AUX entries which store the latest mkey encrypted by one of
112      * the older mkeys.
113      *
114      * The new mkey is followed by existing keys.
115      *
116      * First, set up for creating a krb5_mkey_aux_node list which will be used
117      * to update the mkey aux data for the mkey princ entry.
118      */
119     mkey_aux_data_head = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));
120     if (mkey_aux_data_head == NULL) {
121         retval = ENOMEM;
122         goto clean_n_exit;
123     }
124     memset(mkey_aux_data_head, 0, sizeof(krb5_mkey_aux_node));
125     mkey_aux_data = &mkey_aux_data_head;
126 
127     for (keylist_node = master_keylist, i = 1; keylist_node != NULL;
128          keylist_node = keylist_node->next, i++) {
129 
130         /*
131          * Create a list of krb5_mkey_aux_node nodes.  One node contains the new
132          * mkey encrypted by an old mkey and the old mkey's kvno (one node per
133          * old mkey).
134          */
135         if (*mkey_aux_data == NULL) {
136             /* *mkey_aux_data points to next field of previous node */
137             *mkey_aux_data = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));
138             if (*mkey_aux_data == NULL) {
139                 retval = ENOMEM;
140                 goto clean_n_exit;
141             }
142             memset(*mkey_aux_data, 0, sizeof(krb5_mkey_aux_node));
143         }
144 
145         memset(&tmp_key_data, 0, sizeof(tmp_key_data));
146         /* encrypt the new mkey with the older mkey */
147         retval = krb5_dbe_encrypt_key_data(context, &keylist_node->keyblock,
148                                            new_mkey, NULL, (int) new_mkey_kvno,
149                                            &tmp_key_data);
150         if (retval)
151             goto clean_n_exit;
152 
153         (*mkey_aux_data)->latest_mkey = tmp_key_data;
154         (*mkey_aux_data)->mkey_kvno = keylist_node->kvno;
155         mkey_aux_data = &((*mkey_aux_data)->next);
156 
157         /*
158          * Store old key in master_entry keydata past the new mkey
159          */
160         retval = krb5_dbe_encrypt_key_data(context, new_mkey,
161                                            &keylist_node->keyblock,
162                                            NULL, (int) keylist_node->kvno,
163                                            &master_entry->key_data[i]);
164         if (retval)
165             goto clean_n_exit;
166     }
167     assert(i == old_key_data_count + 1);
168 
169     if ((retval = krb5_dbe_update_mkey_aux(context, master_entry,
170                                            mkey_aux_data_head))) {
171         goto clean_n_exit;
172     }
173     master_entry->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;
174 
175 clean_n_exit:
176     krb5_dbe_free_mkey_aux_list(context, mkey_aux_data_head);
177     return (retval);
178 }
179 
180 void
kdb5_add_mkey(int argc,char * argv[])181 kdb5_add_mkey(int argc, char *argv[])
182 {
183     int optchar;
184     krb5_error_code retval;
185     char *pw_str = 0;
186     unsigned int pw_size = 0;
187     int do_stash = 0;
188     krb5_data pwd;
189     krb5_kvno new_mkey_kvno;
190     krb5_keyblock new_mkeyblock;
191     krb5_enctype new_master_enctype = ENCTYPE_UNKNOWN;
192     char *new_mkey_password;
193     krb5_db_entry *master_entry = NULL;
194     krb5_timestamp now;
195 
196     /*
197      * The command table entry for this command causes open_db_and_mkey() to be
198      * called first to open the KDB and get the current mkey.
199      */
200 
201     memset(&new_mkeyblock, 0, sizeof(new_mkeyblock));
202     master_salt.data = NULL;
203 
204     while ((optchar = getopt(argc, argv, "e:s")) != -1) {
205         switch(optchar) {
206         case 'e':
207             if (krb5_string_to_enctype(optarg, &new_master_enctype)) {
208                 com_err(progname, EINVAL, _("%s is an invalid enctype"),
209                         optarg);
210                 exit_status++;
211                 return;
212             }
213             break;
214         case 's':
215             do_stash++;
216             break;
217         case '?':
218         default:
219             usage();
220             return;
221         }
222     }
223 
224     if (new_master_enctype == ENCTYPE_UNKNOWN)
225         new_master_enctype = global_params.enctype;
226 
227     retval = krb5_db_get_principal(util_context, master_princ, 0,
228                                    &master_entry);
229     if (retval != 0) {
230         com_err(progname, retval, _("while getting master key principal %s"),
231                 mkey_fullname);
232         exit_status++;
233         goto cleanup_return;
234     }
235 
236     printf(_("Creating new master key for master key principal '%s'\n"),
237            mkey_fullname);
238 
239     printf(_("You will be prompted for a new database Master Password.\n"));
240     printf(_("It is important that you NOT FORGET this password.\n"));
241     fflush(stdout);
242 
243     pw_size = 1024;
244     pw_str = malloc(pw_size);
245     if (pw_str == NULL) {
246         com_err(progname, ENOMEM, _("while creating new master key"));
247         exit_status++;
248         goto cleanup_return;
249     }
250 
251     retval = krb5_read_password(util_context, KRB5_KDC_MKEY_1, KRB5_KDC_MKEY_2,
252                                 pw_str, &pw_size);
253     if (retval) {
254         com_err(progname, retval,
255                 _("while reading new master key from keyboard"));
256         exit_status++;
257         goto cleanup_return;
258     }
259     new_mkey_password = pw_str;
260 
261     pwd.data = new_mkey_password;
262     pwd.length = strlen(new_mkey_password);
263     retval = krb5_principal2salt(util_context, master_princ, &master_salt);
264     if (retval) {
265         com_err(progname, retval, _("while calculating master key salt"));
266         exit_status++;
267         goto cleanup_return;
268     }
269 
270     retval = krb5_c_string_to_key(util_context, new_master_enctype,
271                                   &pwd, &master_salt, &new_mkeyblock);
272     if (retval) {
273         com_err(progname, retval,
274                 _("while transforming master key from password"));
275         exit_status++;
276         goto cleanup_return;
277     }
278 
279     new_mkey_kvno = get_next_kvno(util_context, master_entry);
280     retval = add_new_mkey(util_context, master_entry, &new_mkeyblock,
281                           new_mkey_kvno);
282     if (retval) {
283         com_err(progname, retval,
284                 _("adding new master key to master principal"));
285         exit_status++;
286         goto cleanup_return;
287     }
288 
289     if ((retval = krb5_timeofday(util_context, &now))) {
290         com_err(progname, retval, _("while getting current time"));
291         exit_status++;
292         goto cleanup_return;
293     }
294 
295     if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,
296                                                  now, master_princ))) {
297         com_err(progname, retval, _("while updating the master key principal "
298                                     "modification time"));
299         exit_status++;
300         goto cleanup_return;
301     }
302 
303     if ((retval = krb5_db_put_principal(util_context, master_entry))) {
304         com_err(progname, retval, _("while adding master key entry to the "
305                                     "database"));
306         exit_status++;
307         goto cleanup_return;
308     }
309 
310     if (do_stash) {
311         retval = krb5_db_store_master_key(util_context,
312                                           global_params.stash_file,
313                                           master_princ,
314                                           new_mkey_kvno,
315                                           &new_mkeyblock,
316                                           mkey_password);
317         if (retval) {
318             com_err(progname, retval, _("while storing key"));
319             printf(_("Warning: couldn't stash master key.\n"));
320         }
321     }
322 
323 cleanup_return:
324     /* clean up */
325     krb5_db_free_principal(util_context, master_entry);
326     zap((char *)new_mkeyblock.contents, new_mkeyblock.length);
327     free(new_mkeyblock.contents);
328     if (pw_str) {
329         zap(pw_str, pw_size);
330         free(pw_str);
331     }
332     free(master_salt.data);
333     return;
334 }
335 
336 void
kdb5_use_mkey(int argc,char * argv[])337 kdb5_use_mkey(int argc, char *argv[])
338 {
339     krb5_error_code retval;
340     krb5_kvno  use_kvno;
341     krb5_timestamp now, start_time;
342     krb5_actkvno_node *actkvno_list = NULL, *new_actkvno = NULL,
343         *prev_actkvno, *cur_actkvno;
344     krb5_db_entry *master_entry = NULL;
345     krb5_keylist_node *keylist_node;
346     krb5_boolean inserted = FALSE;
347     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);
348 
349     if (argc < 2 || argc > 3) {
350         /* usage calls exit */
351         usage();
352     }
353 
354     use_kvno = atoi(argv[1]);
355     if (use_kvno == 0) {
356         com_err(progname, EINVAL, _("0 is an invalid KVNO value"));
357         exit_status++;
358         return;
359     } else {
360         /* verify use_kvno is valid */
361         for (keylist_node = master_keylist; keylist_node != NULL;
362              keylist_node = keylist_node->next) {
363             if (use_kvno == keylist_node->kvno)
364                 break;
365         }
366         if (!keylist_node) {
367             com_err(progname, EINVAL, _("%d is an invalid KVNO value"),
368                     use_kvno);
369             exit_status++;
370             return;
371         }
372     }
373 
374     if ((retval = krb5_timeofday(util_context, &now))) {
375         com_err(progname, retval, _("while getting current time"));
376         exit_status++;
377         return;
378     }
379 
380     if (argc == 3) {
381         time_t t = get_date(argv[2]);
382         if (t == -1) {
383             com_err(progname, 0, _("could not parse date-time string '%s'"),
384                     argv[2]);
385             exit_status++;
386             return;
387         } else
388             start_time = (krb5_timestamp) t;
389     } else {
390         start_time = now;
391     }
392 
393     /*
394      * Need to:
395      *
396      * 1. get mkey princ
397      * 2. get krb5_actkvno_node list
398      * 3. add use_kvno to actkvno list (sorted in right spot)
399      * 4. update mkey princ's tl data
400      * 5. put mkey princ.
401      */
402 
403     retval = krb5_db_get_principal(util_context, master_princ, 0,
404                                    &master_entry);
405     if (retval != 0) {
406         com_err(progname, retval, _("while getting master key principal %s"),
407                 mkey_fullname);
408         exit_status++;
409         goto cleanup_return;
410     }
411 
412     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
413     if (retval != 0) {
414         com_err(progname, retval,
415                 _("while looking up active version of master key"));
416         exit_status++;
417         goto cleanup_return;
418     }
419 
420     /*
421      * If an entry already exists with the same kvno either delete it or if it's
422      * the only entry, just set its active time.
423      */
424     for (prev_actkvno = NULL, cur_actkvno = actkvno_list;
425          cur_actkvno != NULL;
426          prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {
427 
428         if (cur_actkvno->act_kvno == use_kvno) {
429             /* delete it */
430             if (prev_actkvno) {
431                 prev_actkvno->next = cur_actkvno->next;
432                 cur_actkvno->next = NULL;
433                 krb5_dbe_free_actkvno_list(util_context, cur_actkvno);
434             } else {
435                 if (cur_actkvno->next) {
436                     /* delete it from front of list */
437                     actkvno_list = cur_actkvno->next;
438                     cur_actkvno->next = NULL;
439                     krb5_dbe_free_actkvno_list(util_context, cur_actkvno);
440                 } else {
441                     /* There's only one entry, go ahead and change the time */
442                     cur_actkvno->act_time = start_time;
443                     inserted = TRUE;
444                 }
445             }
446             break;
447         }
448     }
449 
450     if (!inserted) {
451         /* alloc enough space to hold new and existing key_data */
452         new_actkvno = (krb5_actkvno_node *) malloc(sizeof(krb5_actkvno_node));
453         if (new_actkvno == NULL) {
454             com_err(progname, ENOMEM, _("while adding new master key"));
455             exit_status++;
456             goto cleanup_return;
457         }
458         memset(new_actkvno, 0, sizeof(krb5_actkvno_node));
459         new_actkvno->act_kvno = use_kvno;
460         new_actkvno->act_time = start_time;
461 
462         /* insert new act kvno node */
463 
464         if (actkvno_list == NULL) {
465             /* new actkvno is the list */
466             actkvno_list = new_actkvno;
467         } else {
468             for (prev_actkvno = NULL, cur_actkvno = actkvno_list;
469                  cur_actkvno != NULL;
470                  prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {
471 
472                 if (ts_after(cur_actkvno->act_time, new_actkvno->act_time)) {
473                     if (prev_actkvno) {
474                         prev_actkvno->next = new_actkvno;
475                         new_actkvno->next = cur_actkvno;
476                     } else {
477                         new_actkvno->next = actkvno_list;
478                         actkvno_list = new_actkvno;
479                     }
480                     break;
481                 } else if (cur_actkvno->next == NULL) {
482                     /* end of line, just add new node to end of list */
483                     cur_actkvno->next = new_actkvno;
484                     break;
485                 }
486             }
487         }
488     }
489 
490     if (ts_after(actkvno_list->act_time, now)) {
491         com_err(progname, EINVAL,
492                 _("there must be one master key currently active"));
493         exit_status++;
494         goto cleanup_return;
495     }
496 
497     if ((retval = krb5_dbe_update_actkvno(util_context, master_entry,
498                                           actkvno_list))) {
499         com_err(progname, retval,
500                 _("while updating actkvno data for master principal entry"));
501         exit_status++;
502         goto cleanup_return;
503     }
504 
505     if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,
506                                                  now, master_princ))) {
507         com_err(progname, retval, _("while updating the master key principal "
508                                     "modification time"));
509         exit_status++;
510         goto cleanup_return;
511     }
512 
513     master_entry->mask |= KADM5_TL_DATA;
514 
515     if ((retval = krb5_db_put_principal(util_context, master_entry))) {
516         com_err(progname, retval,
517                 _("while adding master key entry to the database"));
518         exit_status++;
519         goto cleanup_return;
520     }
521 
522 cleanup_return:
523     /* clean up */
524     krb5_db_free_principal(util_context, master_entry);
525     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
526     return;
527 }
528 
529 void
kdb5_list_mkeys(int argc,char * argv[])530 kdb5_list_mkeys(int argc, char *argv[])
531 {
532     krb5_error_code retval;
533     char *output_str = NULL, enctype[BUFSIZ];
534     krb5_kvno  act_kvno;
535     krb5_timestamp act_time;
536     krb5_actkvno_node *actkvno_list = NULL, *cur_actkvno;
537     krb5_db_entry *master_entry = NULL;
538     krb5_keylist_node  *cur_kb_node;
539     krb5_keyblock *act_mkey;
540     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);
541 
542     if (master_keylist == NULL) {
543         com_err(progname, 0, _("master keylist not initialized"));
544         exit_status++;
545         return;
546     }
547 
548     retval = krb5_db_get_principal(util_context, master_princ, 0,
549                                    &master_entry);
550     if (retval != 0) {
551         com_err(progname, retval, _("while getting master key principal %s"),
552                 mkey_fullname);
553         exit_status++;
554         goto cleanup_return;
555     }
556 
557     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
558     if (retval != 0) {
559         com_err(progname, retval, _("while looking up active kvno list"));
560         exit_status++;
561         goto cleanup_return;
562     }
563 
564     retval = krb5_dbe_find_act_mkey(util_context, actkvno_list, &act_kvno,
565                                     &act_mkey);
566     if (retval != 0) {
567         com_err(progname, retval, _("while looking up active master key"));
568         exit_status++;
569         goto cleanup_return;
570     }
571 
572     printf("Master keys for Principal: %s\n", mkey_fullname);
573 
574     for (cur_kb_node = master_keylist; cur_kb_node != NULL;
575          cur_kb_node = cur_kb_node->next) {
576 
577         if ((retval = krb5_enctype_to_name(cur_kb_node->keyblock.enctype,
578                                            FALSE, enctype, sizeof(enctype)))) {
579             com_err(progname, retval, _("while getting enctype description"));
580             exit_status++;
581             goto cleanup_return;
582         }
583 
584         act_time = -1; /* assume actkvno entry not found */
585         for (cur_actkvno = actkvno_list; cur_actkvno != NULL;
586              cur_actkvno = cur_actkvno->next) {
587             if (cur_actkvno->act_kvno == cur_kb_node->kvno) {
588                 act_time = cur_actkvno->act_time;
589                 break;
590             }
591         }
592 
593         if (cur_kb_node->kvno == act_kvno) {
594             /* * indicates kvno is currently active */
595             retval = asprintf(&output_str,
596                               _("KVNO: %d, Enctype: %s, Active on: %s *\n"),
597                               cur_kb_node->kvno, enctype, strdate(act_time));
598         } else {
599             if (act_time != -1) {
600                 retval = asprintf(&output_str,
601                                   _("KVNO: %d, Enctype: %s, Active on: %s\n"),
602                                   cur_kb_node->kvno, enctype, strdate(act_time));
603             } else {
604                 retval = asprintf(&output_str,
605                                   _("KVNO: %d, Enctype: %s, No activate time "
606                                     "set\n"), cur_kb_node->kvno, enctype);
607             }
608         }
609         if (retval == -1) {
610             com_err(progname, ENOMEM, _("asprintf could not allocate enough "
611                                         "memory to hold output"));
612             exit_status++;
613             goto cleanup_return;
614         }
615         printf("%s", output_str);
616         free(output_str);
617         output_str = NULL;
618     }
619 
620 cleanup_return:
621     /* clean up */
622     krb5_db_free_principal(util_context, master_entry);
623     free(output_str);
624     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
625     return;
626 }
627 
628 struct update_enc_mkvno {
629     unsigned int re_match_count;
630     unsigned int already_current;
631     unsigned int updated;
632     unsigned int dry_run : 1;
633     unsigned int verbose : 1;
634     regex_t preg;
635 };
636 
637 /* XXX Duplicated in libkadm5srv! */
638 /*
639  * Function: glob_to_regexp
640  *
641  * Arguments:
642  *
643  *      glob    (r) the shell-style glob (?*[]) to convert
644  *      realm   (r) the default realm to append, or NULL
645  *      regexp  (w) the ed-style regexp created from glob
646  *
647  * Effects:
648  *
649  * regexp is filled in with allocated memory containing a regular
650  * expression that matches what the shell-style glob would match.
651  * If glob does not contain an "@" character and realm is not
652  * NULL, "@*" is appended to the regexp.
653  *
654  * Conversion algorithm:
655  *
656  *      quoted characters are copied quoted
657  *      ? is converted to .
658  *      * is converted to .*
659  *      active characters are quoted: ^, $, .
660  *      [ and ] are active but supported and have the same meaning, so
661  *              they are copied
662  *      other characters are copied
663  *      regexp is anchored with ^ and $
664  */
glob_to_regexp(char * glob,char * realm,char ** regexp)665 static int glob_to_regexp(char *glob, char *realm, char **regexp)
666 {
667     int append_realm;
668     char *p;
669 
670     /* validate the glob */
671     if (glob[strlen(glob)-1] == '\\')
672         return EINVAL;
673 
674     /* A character of glob can turn into two in regexp, plus ^ and $ */
675     /* and trailing null.  If glob has no @, also allocate space for */
676     /* the realm. */
677     append_realm = (realm != NULL) && (strchr(glob, '@') == NULL);
678     p = (char *) malloc(strlen(glob)*2+ 3 + (append_realm ? 3 : 0));
679     if (p == NULL)
680         return ENOMEM;
681     *regexp = p;
682 
683     *p++ = '^';
684     while (*glob) {
685         switch (*glob) {
686         case '?':
687             *p++ = '.';
688             break;
689         case '*':
690             *p++ = '.';
691             *p++ = '*';
692             break;
693         case '.':
694         case '^':
695         case '$':
696             *p++ = '\\';
697             *p++ = *glob;
698             break;
699         case '\\':
700             *p++ = '\\';
701             *p++ = *++glob;
702             break;
703         default:
704             *p++ = *glob;
705             break;
706         }
707         glob++;
708     }
709 
710     if (append_realm) {
711         *p++ = '@';
712         *p++ = '.';
713         *p++ = '*';
714     }
715 
716     *p++ = '$';
717     *p++ = '\0';
718     return 0;
719 }
720 
721 static int
update_princ_encryption_1(void * cb,krb5_db_entry * ent)722 update_princ_encryption_1(void *cb, krb5_db_entry *ent)
723 {
724     struct update_enc_mkvno *p = cb;
725     char *pname = 0;
726     krb5_error_code retval;
727     krb5_timestamp now;
728     int result;
729     krb5_kvno old_mkvno;
730 
731     retval = krb5_unparse_name(util_context, ent->princ, &pname);
732     if (retval) {
733         com_err(progname, retval,
734                 _("getting string representation of principal name"));
735         goto fail;
736     }
737 
738     if (krb5_principal_compare(util_context, ent->princ, master_princ)) {
739         goto skip;
740     }
741 
742     if (regexec(&p->preg, pname, 0, NULL, 0) != 0)
743         goto skip;
744     p->re_match_count++;
745     retval = krb5_dbe_get_mkvno(util_context, ent, &old_mkvno);
746     if (retval) {
747         com_err(progname, retval,
748                 _("determining master key used for principal '%s'"), pname);
749         goto fail;
750     }
751     /* Line up "skip" and "update" messages for viewing.  */
752     if (old_mkvno == new_mkvno) {
753         if (p->dry_run && p->verbose)
754             printf(_("would skip:   %s\n"), pname);
755         else if (p->verbose)
756             printf(_("skipping: %s\n"), pname);
757         p->already_current++;
758         goto skip;
759     }
760     if (p->dry_run) {
761         if (p->verbose)
762             printf(_("would update: %s\n"), pname);
763         p->updated++;
764         goto skip;
765     } else if (p->verbose)
766         printf(_("updating: %s\n"), pname);
767     retval = master_key_convert (util_context, ent);
768     if (retval) {
769         com_err(progname, retval,
770                 _("error re-encrypting key for principal '%s'"), pname);
771         goto fail;
772     }
773     if ((retval = krb5_timeofday(util_context, &now))) {
774         com_err(progname, retval, _("while getting current time"));
775         goto fail;
776     }
777 
778     if ((retval = krb5_dbe_update_mod_princ_data(util_context, ent,
779                                                  now, master_princ))) {
780         com_err(progname, retval,
781                 _("while updating principal '%s' modification time"), pname);
782         goto fail;
783     }
784 
785     ent->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;
786 
787     if ((retval = krb5_db_put_principal(util_context, ent))) {
788         com_err(progname, retval, _("while updating principal '%s' key data "
789                                     "in the database"), pname);
790         goto fail;
791     }
792     p->updated++;
793 skip:
794     result = 0;
795     goto egress;
796 fail:
797     exit_status++;
798     result = 1;
799 egress:
800     if (pname)
801         krb5_free_unparsed_name(util_context, pname);
802     return result;
803 }
804 
805 extern int are_you_sure (const char *, ...)
806 #if !defined(__cplusplus) && (__GNUC__ > 2)
807     __attribute__((__format__(__printf__, 1, 2)))
808 #endif
809     ;
810 
811 int
are_you_sure(const char * format,...)812 are_you_sure (const char *format, ...)
813 {
814     va_list va;
815     char ansbuf[100];
816 
817     va_start(va, format);
818     vprintf(format, va);
819     va_end(va);
820     printf(_("\n(type 'yes' to confirm)? "));
821     fflush(stdout);
822     if (fgets(ansbuf, sizeof(ansbuf), stdin) == NULL)
823         return 0;
824     if (strcmp(ansbuf, "yes\n"))
825         return 0;
826     return 1;
827 }
828 
829 void
kdb5_update_princ_encryption(int argc,char * argv[])830 kdb5_update_princ_encryption(int argc, char *argv[])
831 {
832     struct update_enc_mkvno data = { 0 };
833     char *name_pattern = NULL;
834     int force = 0;
835     int optchar;
836     krb5_error_code retval;
837     krb5_actkvno_node *actkvno_list = 0;
838     krb5_db_entry *master_entry = NULL;
839     char *regexp = NULL;
840     krb5_keyblock *act_mkey;
841     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);
842     krb5_flags iterflags = 0;
843 
844     while ((optchar = getopt(argc, argv, "fnv")) != -1) {
845         switch (optchar) {
846         case 'f':
847             force = 1;
848             break;
849         case 'n':
850             data.dry_run = 1;
851             break;
852         case 'v':
853             data.verbose = 1;
854             break;
855         case '?':
856         case ':':
857         default:
858             usage();
859         }
860     }
861     if (argv[optind] != NULL) {
862         name_pattern = argv[optind];
863         if (argv[optind+1] != NULL)
864             usage();
865     }
866 
867     if (master_keylist == NULL) {
868         com_err(progname, 0, _("master keylist not initialized"));
869         exit_status++;
870         goto cleanup;
871     }
872 
873     /* The glob_to_regexp code only cares if the "realm" parameter is
874        NULL or not; the string data is irrelevant.  */
875     if (name_pattern == NULL)
876         name_pattern = "*";
877     if (glob_to_regexp(name_pattern, "hi", &regexp) != 0) {
878         com_err(progname, ENOMEM,
879                 _("converting glob pattern '%s' to regular expression"),
880                 name_pattern);
881         exit_status++;
882         goto cleanup;
883     }
884 
885     if (regcomp(&data.preg, regexp, REG_NOSUB) != 0) {
886         /* XXX syslog msg or regerr(regerrno) */
887         com_err(progname, 0, _("error compiling converted regexp '%s'"),
888                 regexp);
889         exit_status++;
890         goto cleanup;
891     }
892 
893     retval = krb5_db_get_principal(util_context, master_princ, 0,
894                                    &master_entry);
895     if (retval != 0) {
896         com_err(progname, retval, _("while getting master key principal %s"),
897                 mkey_fullname);
898         exit_status++;
899         goto cleanup;
900     }
901 
902     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
903     if (retval != 0) {
904         com_err(progname, retval, _("while looking up active kvno list"));
905         exit_status++;
906         goto cleanup;
907     }
908 
909     retval = krb5_dbe_find_act_mkey(util_context, actkvno_list, &new_mkvno,
910                                     &act_mkey);
911     if (retval) {
912         com_err(progname, retval, _("while looking up active master key"));
913         exit_status++;
914         goto cleanup;
915     }
916     new_master_keyblock = *act_mkey;
917 
918     if (!force &&
919         !data.dry_run &&
920         !are_you_sure(_("Re-encrypt all keys not using master key vno %u?"),
921                       new_mkvno)) {
922         printf(_("OK, doing nothing.\n"));
923         exit_status++;
924         goto cleanup;
925     }
926     if (data.verbose) {
927         if (data.dry_run) {
928             printf(_("Principals whose keys WOULD BE re-encrypted to master "
929                      "key vno %u:\n"), new_mkvno);
930         } else {
931             printf(_("Principals whose keys are being re-encrypted to master "
932                      "key vno %u if necessary:\n"), new_mkvno);
933         }
934     }
935 
936     if (!data.dry_run) {
937         /* Grab a write lock so we don't have to upgrade to a write lock and
938          * reopen the DB while iterating. */
939         iterflags = KRB5_DB_ITER_WRITE;
940     }
941 
942     retval = krb5_db_iterate(util_context, name_pattern,
943                              update_princ_encryption_1, &data, iterflags);
944     /* If exit_status is set, then update_princ_encryption_1 already
945        printed a message.  */
946     if (retval != 0 && exit_status == 0) {
947         com_err(progname, retval, _("trying to process principal database"));
948         exit_status++;
949     }
950     if (data.dry_run) {
951         printf(_("%u principals processed: %u would be updated, %u already "
952                  "current\n"),
953                data.re_match_count, data.updated, data.already_current);
954     } else {
955         printf(_("%u principals processed: %u updated, %u already current\n"),
956                data.re_match_count, data.updated, data.already_current);
957     }
958 
959 cleanup:
960     krb5_db_free_principal(util_context, master_entry);
961     free(regexp);
962     regfree(&data.preg);
963     memset(&new_master_keyblock, 0, sizeof(new_master_keyblock));
964     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
965 }
966 
967 struct kvnos_in_use {
968     krb5_kvno               kvno;
969     unsigned int            use_count;
970 };
971 
972 struct purge_args {
973     krb5_context         kcontext;
974     struct kvnos_in_use  *kvnos;
975     unsigned int         num_kvnos;
976 };
977 
978 static krb5_error_code
find_mkvnos_in_use(krb5_pointer ptr,krb5_db_entry * entry)979 find_mkvnos_in_use(krb5_pointer   ptr,
980                    krb5_db_entry *entry)
981 {
982     krb5_error_code retval;
983     struct purge_args * args;
984     unsigned int i;
985     krb5_kvno mkvno;
986 
987     args = (struct purge_args *) ptr;
988 
989     retval = krb5_dbe_get_mkvno(args->kcontext, entry, &mkvno);
990     if (retval)
991         return (retval);
992 
993     for (i = 0; i < args->num_kvnos; i++) {
994         if (args->kvnos[i].kvno == mkvno) {
995             /* XXX do I need to worry about use_count wrapping? */
996             args->kvnos[i].use_count++;
997             break;
998         }
999     }
1000     return 0;
1001 }
1002 
1003 void
kdb5_purge_mkeys(int argc,char * argv[])1004 kdb5_purge_mkeys(int argc, char *argv[])
1005 {
1006     int optchar;
1007     krb5_error_code retval;
1008     krb5_timestamp now;
1009     krb5_db_entry *master_entry = NULL;
1010     krb5_boolean force = FALSE, dry_run = FALSE, verbose = FALSE;
1011     struct purge_args args;
1012     char buf[5];
1013     unsigned int i, j, k, num_kvnos_inuse, num_kvnos_purged;
1014     unsigned int old_key_data_count;
1015     krb5_actkvno_node *actkvno_list = NULL, *actkvno_entry, *prev_actkvno_entry;
1016     krb5_mkey_aux_node *mkey_aux_list = NULL, *mkey_aux_entry, *prev_mkey_aux_entry;
1017     krb5_key_data *old_key_data;
1018 
1019     /*
1020      * Verify that the master key list has been initialized before doing
1021      * anything else.
1022      */
1023     if (krb5_db_mkey_list_alias(util_context) == NULL) {
1024         com_err(progname, KRB5_KDB_DBNOTINITED,
1025                 _("master keylist not initialized"));
1026         exit_status++;
1027         return;
1028     }
1029 
1030     memset(&args, 0, sizeof(args));
1031 
1032     optind = 1;
1033     while ((optchar = getopt(argc, argv, "fnv")) != -1) {
1034         switch(optchar) {
1035         case 'f':
1036             force = TRUE;
1037             break;
1038         case 'n':
1039             dry_run = TRUE; /* mkey princ will not be modified */
1040             force = TRUE; /* implied */
1041             break;
1042         case 'v':
1043             verbose = TRUE;
1044             break;
1045         case '?':
1046         default:
1047             usage();
1048             return;
1049         }
1050     }
1051 
1052     retval = krb5_db_get_principal(util_context, master_princ, 0,
1053                                    &master_entry);
1054     if (retval != 0) {
1055         com_err(progname, retval, _("while getting master key principal %s"),
1056                 mkey_fullname);
1057         exit_status++;
1058         goto cleanup_return;
1059     }
1060 
1061     if (!force) {
1062         printf(_("Will purge all unused master keys stored in the '%s' "
1063                  "principal, are you sure?\n"), mkey_fullname);
1064         printf(_("(type 'yes' to confirm)? "));
1065         if (fgets(buf, sizeof(buf), stdin) == NULL) {
1066             exit_status++;
1067             goto cleanup_return;
1068         }
1069         if (strcmp(buf, "yes\n")) {
1070             exit_status++;
1071             goto cleanup_return;
1072         }
1073         printf(_("OK, purging unused master keys from '%s'...\n"),
1074                mkey_fullname);
1075     }
1076 
1077     /* save the old keydata */
1078     old_key_data_count = master_entry->n_key_data;
1079     if (old_key_data_count == 1) {
1080         if (verbose)
1081             printf(_("There is only one master key which can not be "
1082                      "purged.\n"));
1083         goto cleanup_return;
1084     }
1085     old_key_data = master_entry->key_data;
1086 
1087     args.kvnos = (struct kvnos_in_use *) malloc(sizeof(struct kvnos_in_use) * old_key_data_count);
1088     if (args.kvnos == NULL) {
1089         retval = ENOMEM;
1090         com_err(progname, ENOMEM, _("while allocating args.kvnos"));
1091         exit_status++;
1092         goto cleanup_return;
1093     }
1094     memset(args.kvnos, 0, sizeof(struct kvnos_in_use) * old_key_data_count);
1095     args.num_kvnos = old_key_data_count;
1096     args.kcontext = util_context;
1097 
1098     /* populate the kvnos array with all the current mkvnos */
1099     for (i = 0; i < old_key_data_count; i++)
1100         args.kvnos[i].kvno =  master_entry->key_data[i].key_data_kvno;
1101 
1102     if ((retval = krb5_db_iterate(util_context,
1103                                   NULL,
1104                                   find_mkvnos_in_use,
1105                                   (krb5_pointer) &args, 0))) {
1106         com_err(progname, retval, _("while finding master keys in use"));
1107         exit_status++;
1108         goto cleanup_return;
1109     }
1110     /*
1111      * args.kvnos has been marked with the mkvno's that are currently protecting
1112      * princ entries
1113      */
1114     if (dry_run) {
1115         printf(_("Would purge the following master key(s) from %s:\n"),
1116                mkey_fullname);
1117     } else {
1118         printf(_("Purging the following master key(s) from %s:\n"),
1119                mkey_fullname);
1120     }
1121 
1122     /* find # of keys still in use or print out verbose info */
1123     for (i = num_kvnos_inuse = num_kvnos_purged = 0; i < args.num_kvnos; i++) {
1124         if (args.kvnos[i].use_count > 0) {
1125             num_kvnos_inuse++;
1126         } else {
1127             /* this key would be deleted */
1128             if (args.kvnos[i].kvno == master_kvno) {
1129                 com_err(progname, KRB5_KDB_STORED_MKEY_NOTCURRENT,
1130                         _("master key stash file needs updating, command "
1131                           "aborting"));
1132                 exit_status++;
1133                 goto cleanup_return;
1134             }
1135             num_kvnos_purged++;
1136             printf(_("KVNO: %d\n"), args.kvnos[i].kvno);
1137         }
1138     }
1139     /* didn't find any keys to purge */
1140     if (num_kvnos_inuse == args.num_kvnos) {
1141         printf(_("All keys in use, nothing purged.\n"));
1142         goto cleanup_return;
1143     }
1144     if (dry_run) {
1145         /* bail before doing anything else */
1146         printf(_("%d key(s) would be purged.\n"), num_kvnos_purged);
1147         goto cleanup_return;
1148     }
1149 
1150     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
1151     if (retval != 0) {
1152         com_err(progname, retval, _("while looking up active kvno list"));
1153         exit_status++;
1154         goto cleanup_return;
1155     }
1156 
1157     retval = krb5_dbe_lookup_mkey_aux(util_context, master_entry, &mkey_aux_list);
1158     if (retval != 0) {
1159         com_err(progname, retval, _("while looking up mkey aux data list"));
1160         exit_status++;
1161         goto cleanup_return;
1162     }
1163 
1164     master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) * num_kvnos_inuse);
1165     if (master_entry->key_data == NULL) {
1166         retval = ENOMEM;
1167         com_err(progname, ENOMEM, _("while allocating key_data"));
1168         exit_status++;
1169         goto cleanup_return;
1170     }
1171     memset(master_entry->key_data, 0, sizeof(krb5_key_data) * num_kvnos_inuse);
1172     master_entry->n_key_data = num_kvnos_inuse; /* there's only 1 mkey per kvno */
1173 
1174     /*
1175      * Assuming that the latest mkey will not be purged because it will always
1176      * be "in use" so this code will not bother with encrypting keys again.
1177      */
1178     for (i = k = 0; i < old_key_data_count; i++) {
1179         for (j = 0; j < args.num_kvnos; j++) {
1180             if (args.kvnos[j].kvno == (krb5_kvno) old_key_data[i].key_data_kvno) {
1181                 if (args.kvnos[j].use_count != 0) {
1182                     master_entry->key_data[k++] = old_key_data[i];
1183                     memset(&old_key_data[i], 0, sizeof(old_key_data[i]));
1184                     break;
1185                 } else {
1186                     /* remove unused mkey */
1187                     /* adjust the actkno data */
1188                     for (prev_actkvno_entry = actkvno_entry = actkvno_list;
1189                          actkvno_entry != NULL;
1190                          actkvno_entry = actkvno_entry->next) {
1191 
1192                         if (actkvno_entry->act_kvno == args.kvnos[j].kvno) {
1193                             if (actkvno_entry == actkvno_list) {
1194                                 /* remove from head */
1195                                 actkvno_list = actkvno_entry->next;
1196                             } else if (actkvno_entry->next == NULL) {
1197                                 /* remove from tail */
1198                                 prev_actkvno_entry->next = NULL;
1199                             } else {
1200                                 /* remove in between */
1201                                 prev_actkvno_entry->next = actkvno_entry->next;
1202                             }
1203                             actkvno_entry->next = NULL;
1204                             krb5_dbe_free_actkvno_list(util_context, actkvno_entry);
1205                             break; /* deleted entry, no need to loop further */
1206                         } else {
1207                             prev_actkvno_entry = actkvno_entry;
1208                         }
1209                     }
1210                     /* adjust the mkey aux data */
1211                     for (prev_mkey_aux_entry = mkey_aux_entry = mkey_aux_list;
1212                          mkey_aux_entry != NULL;
1213                          mkey_aux_entry = mkey_aux_entry->next) {
1214 
1215                         if (mkey_aux_entry->mkey_kvno == args.kvnos[j].kvno) {
1216                             if (mkey_aux_entry == mkey_aux_list) {
1217                                 mkey_aux_list = mkey_aux_entry->next;
1218                             } else if (mkey_aux_entry->next == NULL) {
1219                                 prev_mkey_aux_entry->next = NULL;
1220                             } else {
1221                                 prev_mkey_aux_entry->next = mkey_aux_entry->next;
1222                             }
1223                             mkey_aux_entry->next = NULL;
1224                             krb5_dbe_free_mkey_aux_list(util_context, mkey_aux_entry);
1225                             break; /* deleted entry, no need to loop further */
1226                         } else {
1227                             prev_mkey_aux_entry = mkey_aux_entry;
1228                         }
1229                     }
1230                 }
1231             }
1232         }
1233     }
1234     assert(k == num_kvnos_inuse);
1235 
1236     /* Free any key data entries we did not consume in the loop above. */
1237     for (i = 0; i < old_key_data_count; i++)
1238         krb5_dbe_free_key_data_contents(util_context, &old_key_data[i]);
1239     free(old_key_data);
1240 
1241     if ((retval = krb5_dbe_update_actkvno(util_context, master_entry,
1242                                           actkvno_list))) {
1243         com_err(progname, retval,
1244                 _("while updating actkvno data for master principal entry"));
1245         exit_status++;
1246         goto cleanup_return;
1247     }
1248 
1249     if ((retval = krb5_dbe_update_mkey_aux(util_context, master_entry,
1250                                            mkey_aux_list))) {
1251         com_err(progname, retval,
1252                 _("while updating mkey_aux data for master principal entry"));
1253         exit_status++;
1254         goto cleanup_return;
1255     }
1256 
1257     if ((retval = krb5_timeofday(util_context, &now))) {
1258         com_err(progname, retval, _("while getting current time"));
1259         exit_status++;
1260         goto cleanup_return;
1261     }
1262 
1263     if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,
1264                                                  now, master_princ))) {
1265         com_err(progname, retval, _("while updating the master key principal "
1266                                     "modification time"));
1267         exit_status++;
1268         goto cleanup_return;
1269     }
1270 
1271     master_entry->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;
1272 
1273     if ((retval = krb5_db_put_principal(util_context, master_entry))) {
1274         com_err(progname, retval,
1275                 _("while adding master key entry to the database"));
1276         exit_status++;
1277         goto cleanup_return;
1278     }
1279     printf(_("%d key(s) purged.\n"), num_kvnos_purged);
1280 
1281 cleanup_return:
1282     krb5_db_free_principal(util_context, master_entry);
1283     free(args.kvnos);
1284     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
1285     krb5_dbe_free_mkey_aux_list(util_context, mkey_aux_list);
1286     return;
1287 }
1288