1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
8  *
9  *	Openvision retains the copyright to derivative works of
10  *	this source code.  Do *NOT* create a derivative of this
11  *	source code before consulting with your legal department.
12  *	Do *NOT* integrate *ANY* of this source code into another
13  *	product before consulting with your legal department.
14  *
15  *	For further information, read the top-level Openvision
16  *	copyright which is contained in the top-level MIT Kerberos
17  *	copyright.
18  *
19  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
20  *
21  */
22 
23 
24 /*
25  * admin/edit/kdb5_edit.c
26  *
27  * (C) Copyright 1990,1991, 1996 by the Massachusetts Institute of Technology.
28  * All Rights Reserved.
29  *
30  * Export of this software from the United States of America may
31  *   require a specific license from the United States Government.
32  *   It is the responsibility of any person or organization contemplating
33  *   export to obtain such a license before exporting.
34  *
35  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
36  * distribute this software and its documentation for any purpose and
37  * without fee is hereby granted, provided that the above copyright
38  * notice appear in all copies and that both that copyright notice and
39  * this permission notice appear in supporting documentation, and that
40  * the name of M.I.T. not be used in advertising or publicity pertaining
41  * to distribution of the software without specific, written prior
42  * permission.  Furthermore if you modify this software you must label
43  * your software as modified software and not distribute it in such a
44  * fashion that it might be confused with the original M.I.T. software.
45  * M.I.T. makes no representations about the suitability of
46  * this software for any purpose.  It is provided "as is" without express
47  * or implied warranty.
48  *
49  *
50  * Edit a KDC database.
51  */
52 
53 /*
54  * Copyright (C) 1998 by the FundsXpress, INC.
55  *
56  * All rights reserved.
57  *
58  * Export of this software from the United States of America may require
59  * a specific license from the United States Government.  It is the
60  * responsibility of any person or organization contemplating export to
61  * obtain such a license before exporting.
62  *
63  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
64  * distribute this software and its documentation for any purpose and
65  * without fee is hereby granted, provided that the above copyright
66  * notice appear in all copies and that both that copyright notice and
67  * this permission notice appear in supporting documentation, and that
68  * the name of FundsXpress. not be used in advertising or publicity pertaining
69  * to distribution of the software without specific, written prior
70  * permission.  FundsXpress makes no representations about the suitability of
71  * this software for any purpose.  It is provided "as is" without express
72  * or implied warranty.
73  *
74  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
75  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
76  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
77  */
78 
79 /*
80  *  Yes, I know this is a hack, but we need admin.h without including the
81  *  rpc.h header. Additionally, our rpc.h header brings in
82  *  a des.h header which causes other problems.
83  */
84 #define	_RPC_RPC_H
85 
86 #include <stdio.h>
87 #include <k5-int.h>
88 #include <kadm5/admin.h>
89 #include <rpc/types.h>
90 #include <krb5/adm_proto.h>
91 #include <rpc/xdr.h>
92 #include <time.h>
93 #include <libintl.h>
94 #include <locale.h>
95 #include "kdb5_util.h"
96 
97 char	*Err_no_master_msg = "Master key not entered!\n";
98 char	*Err_no_database = "Database not currently opened!\n";
99 
100 /*
101  * XXX Ick, ick, ick.  These global variables shouldn't be global....
102  */
103 char *mkey_password = 0;
104 
105 /*
106  * I can't figure out any way for this not to be global, given how ss
107  * works.
108  */
109 
110 int exit_status = 0;
111 krb5_context util_context;
112 kadm5_config_params global_params;
113 
usage()114 void usage()
115 {
116      fprintf(stderr, "%s: "
117 	   "kdb5_util [-x db_args]* [-r realm] [-d dbname] [-k mkeytype] [-M mkeyname]\n"
118 	     "\t        [-sf stashfilename] [-P password] [-m] cmd [cmd_options]\n"
119 	     "\tcreate	[-s]\n"
120 	     "\tdestroy	[-f]\n"
121 	     "\tstash	[-f keyfile]\n"
122 	     "\tdump	[-old] [-ov] [-b6] [-verbose] [filename	[princs...]]\n"
123 	     "\t	[-mkey_convert] [-new_mkey_file mkey_file]\n"
124 	     "\t	[-rev] [-recurse] [filename [princs...]]\n"
125 	     "\tload	[-old] [-ov] [-b6] [-verbose] [-update] filename\n"
126 	     "\tark	[-e etype_list] principal\n"
127 	     "\nwhere,\n\t[-x db_args]* - any number of database specific arguments.\n"
128 	     "\t\t\tLook at each database documentation for supported arguments\n",
129 		gettext("Usage"));
130      exit(1);
131 }
132 
133 krb5_keyblock master_key;
134 extern krb5_principal master_princ;
135 krb5_db_entry master_entry;
136 int	valid_master_key = 0;
137 
138 char *progname;
139 krb5_boolean manual_mkey = FALSE;
140 krb5_boolean dbactive = FALSE;
141 
142 static int open_db_and_mkey(void);
143 
144 static void add_random_key(int, char **);
145 
146 typedef void (*cmd_func)(int, char **);
147 
148 struct _cmd_table {
149      char *name;
150      cmd_func func;
151      int opendb;
152 } cmd_table[] = {
153      {"create", kdb5_create, 0},
154      {"destroy", kdb5_destroy, 1},
155      {"stash", kdb5_stash, 1},
156      {"dump", dump_db, 1},
157      {"load", load_db, 0},
158      {"ark", add_random_key, 1},
159      {NULL, NULL, 0},
160 };
161 
cmd_lookup(name)162 static struct _cmd_table *cmd_lookup(name)
163    char *name;
164 {
165      struct _cmd_table *cmd = cmd_table;
166      while (cmd->name) {
167 	  if (strcmp(cmd->name, name) == 0)
168 	       return cmd;
169 	  else
170 	       cmd++;
171      }
172 
173      return NULL;
174 }
175 
176 #define ARG_VAL (--argc > 0 ? (koptarg = *(++argv)) : (char *)(usage(), NULL))
177 
178 char **db5util_db_args = NULL;
179 int    db5util_db_args_size = 0;
180 
extended_com_err_fn(const char * myprog,errcode_t code,const char * fmt,va_list args)181 static void extended_com_err_fn (const char *myprog, errcode_t code,
182 				 const char *fmt, va_list args)
183 {
184     const char *emsg;
185     if (code) {
186 	emsg = krb5_get_error_message (util_context, code);
187 	fprintf (stderr, "%s: %s ", myprog, emsg);
188 	krb5_free_error_message (util_context, emsg);
189     } else {
190 	fprintf (stderr, "%s: ", myprog);
191     }
192     vfprintf (stderr, fmt, args);
193     fprintf (stderr, "\n");
194 }
195 
add_db_arg(char * arg)196 int add_db_arg(char *arg)
197 {
198     char **temp;
199     db5util_db_args_size++;
200     temp = realloc(db5util_db_args,
201 		   sizeof(char *) * (db5util_db_args_size + 1));
202     if (temp == NULL)
203 	return 0;
204     db5util_db_args = temp;
205     db5util_db_args[db5util_db_args_size-1] = arg;
206     db5util_db_args[db5util_db_args_size]   = NULL;
207     return 1;
208 }
209 
main(argc,argv)210 int main(argc, argv)
211     int argc;
212     char *argv[];
213 {
214     struct _cmd_table *cmd = NULL;
215     char *koptarg, **cmd_argv;
216     char *db_name_tmp = NULL;
217     int cmd_argc;
218     krb5_error_code retval;
219 
220 	(void) setlocale(LC_ALL, "");
221     set_com_err_hook(extended_com_err_fn);
222 
223 #if !defined(TEXT_DOMAIN)  /* Should be defined by cc -D */
224 #define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
225 #endif
226 
227 	(void) textdomain(TEXT_DOMAIN);
228 
229 	Err_no_master_msg = gettext("Master key not entered!\n");
230 	Err_no_database = gettext("Database not currently opened!\n");
231 
232 	/*
233 	 * Solaris Kerberos:
234 	 * Ensure that "progname" is set before calling com_err.
235 	 */
236 	progname = (strrchr(argv[0], '/') ?
237 		    strrchr(argv[0], '/') + 1 : argv[0]);
238 
239     retval = kadm5_init_krb5_context(&util_context);
240     if (retval) {
241 	    com_err (progname, retval,
242 		gettext("while initializing Kerberos code"));
243 	    exit(1);
244     }
245 
246     cmd_argv = (char **) malloc(sizeof(char *)*argc);
247     if (cmd_argv == NULL) {
248 		com_err(progname, ENOMEM,
249 		    gettext("while creating sub-command arguments"));
250 	 exit(1);
251     }
252     memset(cmd_argv, 0, sizeof(char *)*argc);
253     cmd_argc = 1;
254 
255     argv++; argc--;
256     while (*argv) {
257        if (strcmp(*argv, "-P") == 0 && ARG_VAL) {
258 	    mkey_password = koptarg;
259 	    manual_mkey = TRUE;
260        } else if (strcmp(*argv, "-d") == 0 && ARG_VAL) {
261 	    global_params.dbname = koptarg;
262 	    global_params.mask |= KADM5_CONFIG_DBNAME;
263 
264 	    db_name_tmp = malloc( strlen(global_params.dbname) + sizeof("dbname="));
265 	    if( db_name_tmp == NULL )
266 	    {
267 		com_err(progname, ENOMEM, "while parsing command arguments");
268 		exit(1);
269 	    }
270 
271 	    strcpy( db_name_tmp, "dbname=");
272 	    strcat( db_name_tmp, global_params.dbname );
273 
274 	    if (!add_db_arg(db_name_tmp)) {
275 		com_err(progname, ENOMEM, "while parsing command arguments\n");
276 		exit(1);
277 	    }
278 
279        } else if (strcmp(*argv, "-x") == 0 && ARG_VAL) {
280 	   if (!add_db_arg(koptarg)) {
281 		com_err(progname, ENOMEM, "while parsing command arguments\n");
282 		exit(1);
283 	   }
284 
285        } else if (strcmp(*argv, "-r") == 0 && ARG_VAL) {
286 	    global_params.realm = koptarg;
287 	    global_params.mask |= KADM5_CONFIG_REALM;
288 	    /* not sure this is really necessary */
289 	    if ((retval = krb5_set_default_realm(util_context,
290 						 global_params.realm))) {
291 				com_err(progname, retval,
292 					gettext("while setting default "
293 						"realm name"));
294 		 exit(1);
295 	    }
296        } else if (strcmp(*argv, "-k") == 0 && ARG_VAL) {
297 	    if (krb5_string_to_enctype(koptarg, &global_params.enctype)) {
298 		/* Solaris Kerberos */
299 		 com_err(progname, 0, gettext("%s is an invalid enctype"), koptarg);
300 	    }
301 	    else
302 		 global_params.mask |= KADM5_CONFIG_ENCTYPE;
303        } else if (strcmp(*argv, "-M") == 0 && ARG_VAL) {
304 	    global_params.mkey_name = koptarg;
305 	    global_params.mask |= KADM5_CONFIG_MKEY_NAME;
306        } else if (((strcmp(*argv, "-sf") == 0)
307 		/* SUNWresync121 - carry the old -f forward too */
308 		|| (strcmp(*argv, "-f") == 0)) && ARG_VAL) {
309 	    global_params.stash_file = koptarg;
310 	    global_params.mask |= KADM5_CONFIG_STASH_FILE;
311        } else if (strcmp(*argv, "-m") == 0) {
312 	    manual_mkey = TRUE;
313 	    global_params.mkey_from_kbd = 1;
314 	    global_params.mask |= KADM5_CONFIG_MKEY_FROM_KBD;
315        } else if (cmd_lookup(*argv) != NULL) {
316 	    if (cmd_argv[0] == NULL)
317 		 cmd_argv[0] = *argv;
318 	    else
319 		 usage();
320        } else {
321 	    cmd_argv[cmd_argc++] = *argv;
322        }
323        argv++; argc--;
324     }
325 
326     if (cmd_argv[0] == NULL)
327 	 usage();
328 
329     if( !util_context->default_realm )
330     {
331 	char *temp = NULL;
332 	retval = krb5_get_default_realm(util_context, &temp);
333 	if( retval )
334 	{
335 	    com_err (progname, retval, "while getting default realm");
336 	    exit(1);
337 	}
338 	util_context->default_realm = temp;
339     }
340 
341     retval = kadm5_get_config_params(util_context, 1,
342 				     &global_params, &global_params);
343     if (retval) {
344 		/* Solaris Kerberos */
345 		com_err(progname, retval,
346 		    gettext("while retreiving configuration parameters"));
347 	 exit(1);
348     }
349 
350     /*
351      * Dump creates files which should not be world-readable.  It is
352      * easiest to do a single umask call here.
353      */
354     (void) umask(077);
355 
356     (void) memset(&master_key, 0, sizeof (krb5_keyblock));
357 
358     if ((global_params.enctype != ENCTYPE_UNKNOWN) &&
359 	(!krb5_c_valid_enctype(global_params.enctype))) {
360 	/* Solaris Kerberos */
361 	com_err(progname, KRB5_PROG_KEYTYPE_NOSUPP,
362 	    gettext("while setting up enctype %d"), global_params.enctype);
363 	exit(1);
364     }
365 
366     cmd = cmd_lookup(cmd_argv[0]);
367     if (cmd->opendb && open_db_and_mkey())
368 	 return exit_status;
369 
370 	if (global_params.iprop_enabled == TRUE)
371 		ulog_set_role(util_context, IPROP_MASTER);
372 	else
373 		ulog_set_role(util_context, IPROP_NULL);
374 
375     (*cmd->func)(cmd_argc, cmd_argv);
376 
377     if( db_name_tmp )
378 	free( db_name_tmp );
379 
380     if( db5util_db_args )
381 	free(db5util_db_args);
382 
383     kadm5_free_config_params(util_context, &global_params);
384     krb5_free_context(util_context);
385     return exit_status;
386 }
387 
388 #if 0
389 /*
390  * This function is no longer used in kdb5_util (and it would no
391  * longer work, anyway).
392  */
393 void set_dbname(argc, argv)
394     int argc;
395     char *argv[];
396 {
397     krb5_error_code retval;
398 
399     if (argc < 3) {
400 		/* Solaris Kerberos */
401 		com_err(progname, 0, gettext("Too few arguments"));
402 		com_err(progname, 0, gettext("Usage: %s dbpathname realmname"),
403 			progname);
404 	exit_status++;
405 	return;
406     }
407     if (dbactive) {
408 	if ((retval = krb5_db_fini(util_context)) && retval!= KRB5_KDB_DBNOTINITED) {
409 	    /* Solaris Kerberos */
410 	    com_err(progname, retval, gettext("while closing previous database"));
411 	    exit_status++;
412 	    return;
413 	}
414 	if (valid_master_key) {
415 	    krb5_free_keyblock_contents(util_context, &master_key);
416 	    master_key.contents = NULL;
417 	    valid_master_key = 0;
418 	}
419 	krb5_free_principal(util_context, master_princ);
420 	dbactive = FALSE;
421     }
422 
423     /* Solaris Kerberos */
424     (void) set_dbname_help(progname, argv[1]);
425     return;
426 }
427 #endif
428 
429 /*
430  * open_db_and_mkey: Opens the KDC and policy database, and sets the
431  * global master_* variables.  Sets dbactive to TRUE if the databases
432  * are opened, and valid_master_key to 1 if the global master
433  * variables are set properly.  Returns 0 on success, and 1 on
434  * failure, but it is not considered a failure if the master key
435  * cannot be fetched (the master key stash file may not exist when the
436  * program is run).
437  */
open_db_and_mkey()438 static int open_db_and_mkey()
439 {
440     krb5_error_code retval;
441     int nentries;
442     krb5_boolean more;
443     krb5_data scratch, pwd, seed;
444 
445     dbactive = FALSE;
446     valid_master_key = 0;
447 
448     if ((retval = krb5_db_open(util_context, db5util_db_args,
449 			       KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN))) {
450 	com_err(progname, retval, "while initializing database");
451 	exit_status++;
452 	return(1);
453     }
454 
455    /* assemble & parse the master key name */
456 
457     if ((retval = krb5_db_setup_mkey_name(util_context,
458 					  global_params.mkey_name,
459 					  global_params.realm,
460 					  0, &master_princ))) {
461 		com_err(progname, retval,
462 		    gettext("while setting up master key name"));
463 	exit_status++;
464 	return(1);
465     }
466     nentries = 1;
467     if ((retval = krb5_db_get_principal(util_context, master_princ,
468 					&master_entry, &nentries, &more))) {
469 		com_err(progname, retval,
470 		    gettext("while retrieving master entry"));
471 	exit_status++;
472 	(void) krb5_db_fini(util_context);
473 	return(1);
474     } else if (more) {
475 	com_err(progname, KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE,
476 		    gettext("while retrieving master entry"));
477 	exit_status++;
478 	(void) krb5_db_fini(util_context);
479 	return(1);
480     } else if (!nentries) {
481 		com_err(progname, KRB5_KDB_NOENTRY,
482 		    gettext("while retrieving master entry"));
483 	exit_status++;
484 	(void) krb5_db_fini(util_context);
485 	return(1);
486     }
487 
488     krb5_db_free_principal(util_context, &master_entry, nentries);
489 
490     /* the databases are now open, and the master principal exists */
491     dbactive = TRUE;
492 
493     if (mkey_password) {
494 	pwd.data = mkey_password;
495 	pwd.length = strlen(mkey_password);
496 	retval = krb5_principal2salt(util_context, master_princ, &scratch);
497 	if (retval) {
498 		com_err(progname, retval,
499 		    gettext("while calculated master key salt"));
500 	    /* Solaris Kerberos */
501 	    exit_status++;
502 	    return(1);
503 	}
504 
505 	/* If no encryption type is set, use the default */
506 	if (global_params.enctype == ENCTYPE_UNKNOWN) {
507 	    global_params.enctype = DEFAULT_KDC_ENCTYPE;
508 	    if (!krb5_c_valid_enctype(global_params.enctype))
509 		com_err(progname, KRB5_PROG_KEYTYPE_NOSUPP,
510 			gettext("while setting up enctype %d"),
511 			global_params.enctype);
512 	}
513 
514 	retval = krb5_c_string_to_key(util_context, global_params.enctype,
515 				      &pwd, &scratch, &master_key);
516 	if (retval) {
517 	    com_err(progname, retval,
518 		gettext("while transforming master key from password"));
519 	    /* Solaris Kerberos */
520 	    exit_status++;
521 	    return(1);
522 	}
523 	free(scratch.data);
524 	mkey_password = 0;
525     } else if ((retval = krb5_db_fetch_mkey(util_context, master_princ,
526 					    global_params.enctype,
527 					    manual_mkey, FALSE,
528 					    global_params.stash_file,
529 					    0, &master_key))) {
530 	com_err(progname, retval,
531 	    gettext("while reading master key"));
532 	com_err(progname, 0,
533 	    gettext("Warning: proceeding without master key"));
534 	/*
535 	 * Solaris Kerberos: We don't want to count as an error if for instance
536 	 * the stash file is not present and we are trying to automate
537 	 * propagation, which really doesn't need a master key to do so.
538 	 */
539 	if (retval != KRB5_KDB_CANTREAD_STORED)
540 		exit_status++;
541 	return(0);
542     }
543     if ((retval = krb5_db_verify_master_key(util_context, master_princ,
544 		&master_key))) {
545 	com_err(progname, retval,
546 		gettext("while verifying master key"));
547 	exit_status++;
548 	krb5_free_keyblock_contents(util_context, &master_key);
549 	return(1);
550     }
551 
552     seed.length = master_key.length;
553     seed.data = (char *)master_key.contents;
554 
555     if ((retval = krb5_c_random_seed(util_context, &seed))) {
556 	com_err(progname, retval,
557 		gettext("while initializing random key generator"));
558 	exit_status++;
559 	krb5_free_keyblock_contents(util_context, &master_key);
560 	return(1);
561     }
562 
563     valid_master_key = 1;
564     dbactive = TRUE;
565     return 0;
566 }
567 
568 #ifdef HAVE_GETCWD
569 #undef getwd
570 #endif
571 
572 int
quit()573 quit()
574 {
575     krb5_error_code retval;
576     static krb5_boolean finished = 0;
577 
578     if (finished)
579 	return 0;
580     retval = krb5_db_fini(util_context);
581     krb5_free_keyblock_contents(util_context, &master_key);
582     finished = TRUE;
583     krb5_free_context(util_context);
584     if (retval && retval != KRB5_KDB_DBNOTINITED) {
585 		com_err(progname, retval, gettext("while closing database"));
586 	exit_status++;
587 	return 1;
588     }
589     return 0;
590 }
591 
592 static void
add_random_key(argc,argv)593 add_random_key(argc, argv)
594     int argc;
595     char **argv;
596 {
597     krb5_error_code ret;
598     krb5_principal princ;
599     krb5_db_entry dbent;
600     int n;
601     krb5_boolean more;
602     krb5_timestamp now;
603 
604     krb5_key_salt_tuple *keysalts = NULL;
605     krb5_int32 num_keysalts = 0;
606 
607     int free_keysalts;
608     /* Solaris Kerberos */
609     char *me = progname;
610     char *ks_str = NULL;
611     char *pr_str;
612 
613     if (argc < 2)
614 	usage();
615     for (argv++, argc--; *argv; argv++, argc--) {
616 	if (!strcmp(*argv, "-e")) {
617 	    argv++; argc--;
618 	    ks_str = *argv;
619 	    continue;
620 	} else
621 	    break;
622     }
623     if (argc < 1)
624 	usage();
625     pr_str = *argv;
626     ret = krb5_parse_name(util_context, pr_str, &princ);
627     if (ret) {
628 	com_err(me, ret, gettext("while parsing principal name %s"), pr_str);
629 	exit_status++;
630 	return;
631     }
632     n = 1;
633     ret = krb5_db_get_principal(util_context, princ, &dbent,
634 				&n, &more);
635     if (ret) {
636 	com_err(me, ret, gettext("while fetching principal %s"), pr_str);
637 	exit_status++;
638 	return;
639     }
640     if (n != 1) {
641 	fprintf(stderr, gettext("principal %s not found\n"), pr_str);
642 	exit_status++;
643 	return;
644     }
645     if (more) {
646 	fprintf(stderr, gettext("principal %s not unique\n"), pr_str);
647 	krb5_db_free_principal(util_context, &dbent, 1);
648 	exit_status++;
649 	return;
650     }
651     ret = krb5_string_to_keysalts(ks_str,
652 				  ", \t", ":.-", 0,
653 				  &keysalts,
654 				  &num_keysalts);
655     if (ret) {
656 	com_err(me, ret, gettext("while parsing keysalts %s"), ks_str);
657 	exit_status++;
658 	return;
659     }
660     if (!num_keysalts || keysalts == NULL) {
661 	num_keysalts = global_params.num_keysalts;
662 	keysalts = global_params.keysalts;
663 	free_keysalts = 0;
664     } else
665 	free_keysalts = 1;
666     ret = krb5_dbe_ark(util_context, &master_key,
667 		       keysalts, num_keysalts,
668 		       &dbent);
669     if (free_keysalts)
670 	free(keysalts);
671     if (ret) {
672 	com_err(me, ret, gettext("while randomizing principal %s"), pr_str);
673 	krb5_db_free_principal(util_context, &dbent, 1);
674 	exit_status++;
675 	return;
676     }
677     dbent.attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
678     ret = krb5_timeofday(util_context, &now);
679     if (ret) {
680 	com_err(me, ret, gettext("while getting time"));
681 	krb5_db_free_principal(util_context, &dbent, 1);
682 	exit_status++;
683 	return;
684     }
685     ret = krb5_dbe_update_last_pwd_change(util_context, &dbent, now);
686     if (ret) {
687 	com_err(me, ret, gettext("while setting changetime"));
688 	krb5_db_free_principal(util_context, &dbent, 1);
689 	exit_status++;
690 	return;
691     }
692     ret = krb5_db_put_principal(util_context, &dbent, &n);
693     krb5_db_free_principal(util_context, &dbent, 1);
694     if (ret) {
695 	com_err(me, ret, gettext("while saving principal %s"), pr_str);
696 	exit_status++;
697 	return;
698     }
699     printf("%s changed\n", pr_str);
700 }
701