559 lines
14 KiB
C
559 lines
14 KiB
C
/*
|
|
* Copyright 1987, 1988 by the Massachusetts Institute of Technology.
|
|
*
|
|
* For copying and distribution information, please see the file
|
|
* <mit-copyright.h>.
|
|
*
|
|
* Kerberos database manipulation utility. This program allows you to
|
|
* dump a kerberos database to an ascii readable file and load this
|
|
* file into the database. Read locking of the database is done during a
|
|
* dump operation. NO LOCKING is done during a load operation. Loads
|
|
* should happen with other processes shutdown.
|
|
*
|
|
* Written July 9, 1987 by Jeffrey I. Schiller
|
|
*/
|
|
|
|
#include "adm_locl.h"
|
|
#include <getarg.h>
|
|
|
|
__RCSID("$KTH-KRB: kdb_util.c,v 1.46 2001/02/20 23:07:49 assar Exp $"
|
|
"$NetBSD: kdb_util.c,v 1.3 2002/09/12 12:33:10 joda Exp $");
|
|
|
|
static des_cblock master_key, new_master_key;
|
|
static des_key_schedule master_key_schedule, new_master_key_schedule;
|
|
|
|
/* cv_key is a procedure which takes a principle and changes its key,
|
|
either for a new method of encrypting the keys, or a new master key.
|
|
if cv_key is null no transformation of key is done (other than net byte
|
|
order). */
|
|
|
|
struct callback_args {
|
|
void (*cv_key)(Principal *);
|
|
FILE *output_file;
|
|
};
|
|
|
|
static void
|
|
print_time(FILE *file, time_t timeval)
|
|
{
|
|
struct tm *tm;
|
|
tm = gmtime(&timeval);
|
|
fprintf(file, " %04d%02d%02d%02d%02d",
|
|
tm->tm_year + 1900,
|
|
tm->tm_mon + 1,
|
|
tm->tm_mday,
|
|
tm->tm_hour,
|
|
tm->tm_min);
|
|
}
|
|
|
|
static long
|
|
time_explode(char *cp)
|
|
{
|
|
char wbuf[5];
|
|
struct tm tp;
|
|
int local;
|
|
|
|
memset(&tp, 0, sizeof(tp)); /* clear out the struct */
|
|
|
|
if (strlen(cp) > 10) { /* new format */
|
|
strlcpy(wbuf, cp, sizeof(wbuf));
|
|
tp.tm_year = atoi(wbuf) - 1900;
|
|
cp += 4; /* step over the year */
|
|
local = 0; /* GMT */
|
|
} else { /* old format: local time,
|
|
year is 2 digits, assuming 19xx */
|
|
wbuf[0] = *cp++;
|
|
wbuf[1] = *cp++;
|
|
wbuf[2] = 0;
|
|
tp.tm_year = atoi(wbuf);
|
|
local = 1; /* local */
|
|
}
|
|
|
|
wbuf[0] = *cp++;
|
|
wbuf[1] = *cp++;
|
|
wbuf[2] = 0;
|
|
tp.tm_mon = atoi(wbuf)-1;
|
|
|
|
wbuf[0] = *cp++;
|
|
wbuf[1] = *cp++;
|
|
tp.tm_mday = atoi(wbuf);
|
|
|
|
wbuf[0] = *cp++;
|
|
wbuf[1] = *cp++;
|
|
tp.tm_hour = atoi(wbuf);
|
|
|
|
wbuf[0] = *cp++;
|
|
wbuf[1] = *cp++;
|
|
tp.tm_min = atoi(wbuf);
|
|
|
|
return(tm2time(tp, local));
|
|
}
|
|
|
|
static int
|
|
dump_db_1(void *arg,
|
|
Principal *principal) /* replace null strings with "*" */
|
|
{
|
|
struct callback_args *a = (struct callback_args *)arg;
|
|
|
|
if (principal->instance[0] == '\0') {
|
|
principal->instance[0] = '*';
|
|
principal->instance[1] = '\0';
|
|
}
|
|
if (principal->mod_name[0] == '\0') {
|
|
principal->mod_name[0] = '*';
|
|
principal->mod_name[1] = '\0';
|
|
}
|
|
if (principal->mod_instance[0] == '\0') {
|
|
principal->mod_instance[0] = '*';
|
|
principal->mod_instance[1] = '\0';
|
|
}
|
|
if (a->cv_key != NULL) {
|
|
(*a->cv_key) (principal);
|
|
}
|
|
fprintf(a->output_file, "%s %s %d %d %d %d %x %x",
|
|
principal->name,
|
|
principal->instance,
|
|
principal->max_life,
|
|
principal->kdc_key_ver,
|
|
principal->key_version,
|
|
principal->attributes,
|
|
(int)htonl (principal->key_low),
|
|
(int)htonl (principal->key_high));
|
|
print_time(a->output_file, principal->exp_date);
|
|
print_time(a->output_file, principal->mod_date);
|
|
fprintf(a->output_file, " %s %s\n",
|
|
principal->mod_name,
|
|
principal->mod_instance);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dump_db (char *db_file, FILE *output_file, void (*cv_key) (Principal *))
|
|
{
|
|
struct callback_args a;
|
|
|
|
a.cv_key = cv_key;
|
|
a.output_file = output_file;
|
|
|
|
kerb_db_iterate (dump_db_1, &a);
|
|
return fflush(output_file);
|
|
}
|
|
|
|
static int
|
|
add_file(void *db, FILE *file)
|
|
{
|
|
int ret;
|
|
int lineno = 0;
|
|
char line[1024];
|
|
unsigned long key[2]; /* yes, long */
|
|
Principal pr;
|
|
|
|
char exp_date[64], mod_date[64];
|
|
|
|
int life, kkvno, kvno;
|
|
|
|
while(1){
|
|
memset(&pr, 0, sizeof(pr));
|
|
errno = 0;
|
|
if(fgets(line, sizeof(line), file) == NULL){
|
|
if(errno != 0)
|
|
err (1, "fgets");
|
|
break;
|
|
}
|
|
lineno++;
|
|
ret = sscanf(line, "%s %s %d %d %d %hd %lx %lx %s %s %s %s",
|
|
pr.name, pr.instance,
|
|
&life, &kkvno, &kvno,
|
|
&pr.attributes,
|
|
&key[0], &key[1],
|
|
exp_date, mod_date,
|
|
pr.mod_name, pr.mod_instance);
|
|
if(ret != 12){
|
|
warnx("Line %d malformed (ignored)", lineno);
|
|
continue;
|
|
}
|
|
pr.key_low = ntohl (key[0]);
|
|
pr.key_high = ntohl (key[1]);
|
|
pr.max_life = life;
|
|
pr.kdc_key_ver = kkvno;
|
|
pr.key_version = kvno;
|
|
pr.exp_date = time_explode(exp_date);
|
|
pr.mod_date = time_explode(mod_date);
|
|
if (pr.instance[0] == '*')
|
|
pr.instance[0] = 0;
|
|
if (pr.mod_name[0] == '*')
|
|
pr.mod_name[0] = 0;
|
|
if (pr.mod_instance[0] == '*')
|
|
pr.mod_instance[0] = 0;
|
|
if (kerb_db_update(db, &pr, 1) != 1) {
|
|
warn ("store %s.%s aborted",
|
|
pr.name, pr.instance);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
load_db (char *db_file, FILE *input_file)
|
|
{
|
|
long *db;
|
|
int code;
|
|
char *temp_db_file;
|
|
|
|
asprintf (&temp_db_file, "%s~", db_file);
|
|
if(temp_db_file == NULL)
|
|
errx (1, "out of memory");
|
|
|
|
/* Create the database */
|
|
if ((code = kerb_db_create(temp_db_file)) != 0)
|
|
err (1, "creating temp database %s", temp_db_file);
|
|
kerb_db_set_name(temp_db_file);
|
|
db = kerb_db_begin_update();
|
|
if (db == NULL)
|
|
err (1, "opening temp database %s", temp_db_file);
|
|
|
|
if(add_file(db, input_file))
|
|
errx (1, "Load aborted");
|
|
|
|
kerb_db_end_update(db);
|
|
if ((code = kerb_db_rename(temp_db_file, db_file)) != 0)
|
|
warn("database rename failed");
|
|
fclose(input_file);
|
|
free(temp_db_file);
|
|
}
|
|
|
|
static void
|
|
merge_db(char *db_file, FILE *input_file)
|
|
{
|
|
void *db;
|
|
|
|
db = kerb_db_begin_update();
|
|
if(db == NULL)
|
|
err (1, "Couldn't open database");
|
|
if(add_file(db, input_file))
|
|
errx (1, "Merge aborted");
|
|
kerb_db_end_update(db);
|
|
}
|
|
|
|
static void
|
|
update_ok_file (char *file_name)
|
|
{
|
|
/* handle slave locking/failure stuff */
|
|
char *file_ok;
|
|
int fd;
|
|
|
|
asprintf (&file_ok, "%s.dump_ok", file_name);
|
|
if (file_ok == NULL)
|
|
errx (1, "out of memory");
|
|
if ((fd = open(file_ok, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0)
|
|
err (1, "Error creating %s", file_ok);
|
|
free(file_ok);
|
|
close(fd);
|
|
/*
|
|
* Some versions of BSD don't update the mtime in the above open so
|
|
* we call utimes just in case.
|
|
*/
|
|
if (utime(file_name, NULL) < 0)
|
|
err (1, "utime %s", file_name);
|
|
}
|
|
|
|
static void
|
|
convert_key_new_master (Principal *p)
|
|
{
|
|
des_cblock key;
|
|
|
|
/* leave null keys alone */
|
|
if ((p->key_low == 0) && (p->key_high == 0)) return;
|
|
|
|
/* move current key to des_cblock for encryption, special case master key
|
|
since that's changing */
|
|
if ((strncmp (p->name, KERB_M_NAME, ANAME_SZ) == 0) &&
|
|
(strncmp (p->instance, KERB_M_INST, INST_SZ) == 0)) {
|
|
memcpy (key, new_master_key, sizeof(des_cblock));
|
|
(p->key_version)++;
|
|
} else {
|
|
copy_to_key(&p->key_low, &p->key_high, key);
|
|
kdb_encrypt_key (&key, &key, &master_key,
|
|
master_key_schedule, DES_DECRYPT);
|
|
}
|
|
|
|
kdb_encrypt_key (&key, &key, &new_master_key,
|
|
new_master_key_schedule, DES_ENCRYPT);
|
|
|
|
copy_from_key(key, &(p->key_low), &(p->key_high));
|
|
memset(key, 0, sizeof (key)); /* a little paranoia ... */
|
|
|
|
(p->kdc_key_ver)++;
|
|
}
|
|
|
|
static void
|
|
clear_secrets (void)
|
|
{
|
|
memset(master_key, 0, sizeof (des_cblock));
|
|
memset(master_key_schedule, 0, sizeof (des_key_schedule));
|
|
memset(new_master_key, 0, sizeof (des_cblock));
|
|
memset(new_master_key_schedule, 0, sizeof (des_key_schedule));
|
|
}
|
|
|
|
static int prompt_flag = 1;
|
|
|
|
static void
|
|
convert_new_master_key (char *db_file, FILE *out)
|
|
{
|
|
#ifdef RANDOM_MKEY
|
|
errx (1, "Sorry, this function is not available with "
|
|
"the new master key scheme.");
|
|
#else
|
|
if(prompt_flag) {
|
|
printf ("\n\nEnter the CURRENT master key.");
|
|
fflush(stdout);
|
|
}
|
|
|
|
if (kdb_get_master_key (prompt_flag ? KDB_GET_PROMPT : 0, &master_key,
|
|
master_key_schedule) != 0) {
|
|
clear_secrets ();
|
|
errx (1, "Couldn't get master key.");
|
|
}
|
|
|
|
if (kdb_verify_master_key (&master_key, master_key_schedule, stderr) < 0) {
|
|
clear_secrets ();
|
|
exit (1);
|
|
}
|
|
|
|
printf ("\n\nNow enter the NEW master key. Do not forget it!!");
|
|
fflush(stdout);
|
|
if (kdb_get_master_key (KDB_GET_TWICE, &new_master_key,
|
|
new_master_key_schedule) != 0) {
|
|
clear_secrets ();
|
|
errx (1, "Couldn't get new master key.");
|
|
}
|
|
|
|
dump_db (db_file, out, convert_key_new_master);
|
|
{
|
|
char *fname;
|
|
|
|
asprintf(&fname, "%s.new", MKEYFILE);
|
|
if(fname == NULL) {
|
|
clear_secrets();
|
|
errx(1, "malloc: failed");
|
|
}
|
|
kdb_kstash(&new_master_key, fname);
|
|
free(fname);
|
|
}
|
|
#endif /* RANDOM_MKEY */
|
|
}
|
|
|
|
static void
|
|
convert_key_old_db (Principal *p)
|
|
{
|
|
des_cblock key;
|
|
|
|
/* leave null keys alone */
|
|
if ((p->key_low == 0) && (p->key_high == 0)) return;
|
|
|
|
copy_to_key(&p->key_low, &p->key_high, key);
|
|
|
|
#ifndef NOENCRYPTION
|
|
des_pcbc_encrypt(key,key,
|
|
(long)sizeof(des_cblock),master_key_schedule,
|
|
(des_cblock *)master_key_schedule, DES_DECRYPT);
|
|
#endif
|
|
|
|
/* make new key, new style */
|
|
kdb_encrypt_key (&key, &key, &master_key, master_key_schedule, DES_ENCRYPT);
|
|
|
|
copy_from_key(key, &(p->key_low), &(p->key_high));
|
|
memset(key, 0, sizeof (key)); /* a little paranoia ... */
|
|
}
|
|
|
|
static void
|
|
convert_old_format_db (char *db_file, FILE *out)
|
|
{
|
|
des_cblock key_from_db;
|
|
Principal principal_data[1];
|
|
int n, more;
|
|
|
|
if (kdb_get_master_key (KDB_GET_PROMPT, &master_key,
|
|
master_key_schedule) != 0L) {
|
|
clear_secrets();
|
|
errx (1, "Couldn't get master key.");
|
|
}
|
|
|
|
/* can't call kdb_verify_master_key because this is an old style db */
|
|
/* lookup the master key version */
|
|
n = kerb_get_principal(KERB_M_NAME, KERB_M_INST, principal_data,
|
|
1 /* only one please */, &more);
|
|
if ((n != 1) || more)
|
|
errx (1, "verify_master_key: Kerberos error on master key lookup, %d found.", n);
|
|
|
|
/* set up the master key */
|
|
fprintf(stderr, "Current Kerberos master key version is %d.\n",
|
|
principal_data[0].kdc_key_ver);
|
|
|
|
/*
|
|
* now use the master key to decrypt (old style) the key in the db, had better
|
|
* be the same!
|
|
*/
|
|
copy_to_key(&principal_data[0].key_low,
|
|
&principal_data[0].key_high,
|
|
key_from_db);
|
|
#ifndef NOENCRYPTION
|
|
des_pcbc_encrypt(&key_from_db,&key_from_db,(long)sizeof(key_from_db),
|
|
master_key_schedule,(des_cblock *)master_key_schedule, DES_DECRYPT);
|
|
#endif
|
|
/* the decrypted database key had better equal the master key */
|
|
|
|
n = memcmp(master_key, key_from_db, sizeof(master_key));
|
|
memset(key_from_db, 0, sizeof(key_from_db));
|
|
|
|
if (n) {
|
|
fprintf(stderr, "\n\07\07verify_master_key: Invalid master key, ");
|
|
fprintf(stderr, "does not match database.\n");
|
|
exit (1);
|
|
}
|
|
|
|
fprintf(stderr, "Master key verified.\n");
|
|
|
|
dump_db (db_file, out, convert_key_old_db);
|
|
}
|
|
|
|
static int help_flag;
|
|
static int version_flag;
|
|
|
|
static struct getargs args[] = {
|
|
{ NULL, 'n', arg_negative_flag, &prompt_flag, "don't prompt for master key" },
|
|
{ "help", 'h', arg_flag, &help_flag },
|
|
{ "version", 0, arg_flag, &version_flag }
|
|
};
|
|
|
|
static void
|
|
usage (int ret)
|
|
{
|
|
arg_printusage (args,
|
|
sizeof(args) / sizeof(args[0]),
|
|
NULL,
|
|
"operation file [database]");
|
|
fprintf(stderr, "Operation is one of: load, merge, dump, slave_dump,\n");
|
|
fprintf(stderr, " new_master_key, convert_old_db\n");
|
|
fprintf(stderr, "use file `-' for stdout\n");
|
|
|
|
exit (ret);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int ret;
|
|
int optind = 0;
|
|
FILE *file;
|
|
enum {
|
|
OP_LOAD,
|
|
OP_MERGE,
|
|
OP_DUMP,
|
|
OP_SLAVE_DUMP,
|
|
OP_NEW_MASTER,
|
|
OP_CONVERT_OLD_DB
|
|
} op;
|
|
char *file_name;
|
|
char *db_name;
|
|
|
|
setprogname (argv[0]);
|
|
|
|
if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv,
|
|
&optind))
|
|
usage (1);
|
|
|
|
if (help_flag)
|
|
usage (0);
|
|
|
|
if (version_flag) {
|
|
print_version(NULL);
|
|
return 0;
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc != 2 && argc != 3)
|
|
usage (1);
|
|
if (argc == 2)
|
|
db_name = DBM_FILE;
|
|
else
|
|
db_name = argv[2];
|
|
|
|
ret = kerb_db_set_name (db_name);
|
|
|
|
/* this makes starting slave servers ~14.3 times easier */
|
|
if(ret && strcmp(argv[0], "load") == 0)
|
|
ret = kerb_db_create (db_name);
|
|
|
|
if(ret)
|
|
err (1, "Can't open database");
|
|
|
|
if (!strcmp(argv[0], "load"))
|
|
op = OP_LOAD;
|
|
else if (!strcmp(argv[0], "merge"))
|
|
op = OP_MERGE;
|
|
else if (!strcmp(argv[0], "dump"))
|
|
op = OP_DUMP;
|
|
else if (!strcmp(argv[0], "slave_dump"))
|
|
op = OP_SLAVE_DUMP;
|
|
else if (!strcmp(argv[0], "new_master_key"))
|
|
op = OP_NEW_MASTER;
|
|
else if (!strcmp(argv[0], "convert_old_db"))
|
|
op = OP_CONVERT_OLD_DB;
|
|
else {
|
|
warnx ("%s is an invalid operation.", argv[0]);
|
|
warnx ("Valid operations are \"load\", \"merge\", "
|
|
"\"dump\", \"slave_dump\", \"new_master_key\", "
|
|
"and \"convert_old_db\"");
|
|
return 1;
|
|
}
|
|
|
|
file_name = argv[1];
|
|
if (strcmp (file_name, "-") == 0
|
|
&& op != OP_LOAD
|
|
&& op != OP_MERGE)
|
|
file = stdout;
|
|
else {
|
|
char *mode;
|
|
|
|
if (op == OP_LOAD || op == OP_MERGE)
|
|
mode = "r";
|
|
else
|
|
mode = "w";
|
|
|
|
file = fopen (file_name, mode);
|
|
}
|
|
if (file == NULL)
|
|
err (1, "open %s", argv[1]);
|
|
|
|
switch (op) {
|
|
case OP_DUMP:
|
|
case OP_SLAVE_DUMP:
|
|
if ((dump_db(db_name, file, NULL) == EOF)
|
|
|| (fflush(file) != 0)
|
|
|| (fsync(fileno(file)) != 0)
|
|
|| (fclose(file) == EOF))
|
|
err(1, "%s", file_name);
|
|
if(op == OP_SLAVE_DUMP)
|
|
update_ok_file(file_name);
|
|
break;
|
|
case OP_LOAD:
|
|
load_db (db_name, file);
|
|
break;
|
|
case OP_MERGE:
|
|
merge_db (db_name, file);
|
|
break;
|
|
case OP_NEW_MASTER:
|
|
convert_new_master_key (db_name, file);
|
|
printf("Don't forget to do a `kdb_util load %s' to reload the database!\n", file_name);
|
|
break;
|
|
case OP_CONVERT_OLD_DB:
|
|
convert_old_format_db (db_name, file);
|
|
printf("Don't forget to do a `kdb_util load %s' to reload the database!\n", file_name);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|