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", ®exp) != 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