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