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