/* * lib/kdb/t_kdb.c * * Copyright 1995 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * */ /* * t_kdb.c - Test [and optionally obtain timing information about] the * Kerberos database functions. */ #define KDB5_DISPATCH #include "k5-int.h" #include #include #include #include "com_err.h" #if HAVE_SRAND48 #define RAND() lrand48() #define SRAND(a) srand48(a) #define RAND_TYPE long #elif HAVE_SRAND #define RAND() rand() #define SRAND(a) srand(a) #define RAND_TYPE int #elif HAVE_SRANDOM #define RAND() random() #define SRAND(a) srandom(a) #define RAND_TYPE long #else /* no random */ need a random number generator #endif /* no random */ #define T_KDB_N_PASSES 100 #define T_KDB_DEF_DB "test_db" #define MAX_PNAME_LEN 1024 #define MAX_PRINC_COMPS 8 #define MAX_COMP_SIZE 32 #define RANDOM(a,b) (a + (RAND() % (b-a))) enum dbtype { DB_UFO, DB_DEFAULT, DB_BERKELEY, DB_DBM }; char *programname = (char *) NULL; krb5_data mprinc_data_entries[] = { { 0, sizeof("master")-1, "master"}, { 0, sizeof("key")-1, "key"} }; krb5_principal_data master_princ_data = { 0, /* Magic number */ { 0, sizeof("test.realm")-1, "test.realm"}, /* Realm */ mprinc_data_entries, /* Name/instance */ sizeof(mprinc_data_entries)/ sizeof(mprinc_data_entries[0]), /* Number */ KRB5_NT_SRV_INST /* Type */ }; struct timeval tstart_time, tend_time; struct timezone dontcare; krb5_principal *recorded_principals = (krb5_principal *) NULL; char **recorded_names = (char **) NULL; #ifdef BERK_DB_DBM extern DBM *db_dbm_open PROTOTYPE((char *, int, int)); extern void db_dbm_close PROTOTYPE((DBM *)); extern datum db_dbm_fetch PROTOTYPE((DBM *, datum)); extern datum db_dbm_firstkey PROTOTYPE((DBM *)); extern datum db_dbm_nextkey PROTOTYPE((DBM *)); extern int db_dbm_delete PROTOTYPE((DBM *, datum)); extern int db_dbm_store PROTOTYPE((DBM *, datum, datum, int)); extern int db_dbm_error PROTOTYPE((DBM *)); extern int db_dbm_clearerr PROTOTYPE((DBM *)); extern int db_dbm_dirfno PROTOTYPE((DBM *)); static kdb5_dispatch_table berkeley_dispatch = { "Berkeley Hashed Database", ".db", /* Index file name ext */ (char *) NULL, /* Data file name ext */ ".ok", /* Lock file name ext */ db_dbm_open, /* Open Database */ db_dbm_close, /* Close Database */ db_dbm_fetch, /* Fetch Key */ db_dbm_firstkey, /* Fetch First Key */ db_dbm_nextkey, /* Fetch Next Key */ db_dbm_delete, /* Delete Key */ db_dbm_store, /* Store Key */ db_dbm_error, /* Get Database Error */ db_dbm_clearerr, /* Clear Database Error */ db_dbm_dirfno, /* Get Database FD num */ (int (*)()) NULL /* Get Database FD num */ }; #endif #if defined(NDBM) || defined(ODBM) /* * The following prototypes are necessary in case dbm_error and * dbm_clearerr are in the library but not prototyped * (e.g. NetBSD-1.0) */ #ifdef MISSING_ERROR_PROTO int dbm_error PROTOTYPE((DBM *)); #endif #ifdef MISSING_CLEARERR_PROTO int dbm_clearerr PROTOTYPE((DBM *)); #endif static kdb5_dispatch_table dbm_dispatch = { "Stock [N]DBM Database", ".dir", /* Index file name ext */ ".pag", /* Data file name ext */ ".ok", /* Lock file name ext */ dbm_open, /* Open Database */ dbm_close, /* Close Database */ dbm_fetch, /* Fetch Key */ dbm_firstkey, /* Fetch First Key */ dbm_nextkey, /* Fetch Next Key */ dbm_delete, /* Delete Key */ dbm_store, /* Store Key */ /* * The following are #ifdef'd because they have the potential to be * macros rather than functions. */ #ifdef dbm_error (int (*)()) NULL, /* Get Database Error */ #else /* dbm_error */ #ifdef HAVE_DBM_ERROR dbm_error, /* Get Database Error */ #else (int (*)()) NULL, /* Get Database Error */ #endif #endif /* dbm_error */ #ifdef dbm_clearerr (int (*)()) NULL, /* Clear Database Error */ #else /* dbm_clearerr */ #ifdef HAVE_DBM_CLEARERR dbm_clearerr, /* Clear Database Error */ #else (int (*)()) NULL, /* Clear Database Error */ #endif #endif /* dbm_clearerr */ #ifdef dbm_dirfno (int (*)()) NULL, /* Get Database FD num */ #else /* dbm_dirfno */ dbm_dirfno, /* Get Database FD num */ #endif /* dbm_dirfno */ #ifdef dbm_pagfno (int (*)()) NULL, /* Get Database FD num */ #else /* dbm_pagfno */ dbm_pagfno, /* Get Database FD num */ #endif /* dbm_pagfno */ }; #endif /* NDBM || ODBM */ /* * Timer macros. */ #define swatch_on() ((void) gettimeofday(&tstart_time, &dontcare)) #define swatch_eltime() ((gettimeofday(&tend_time, &dontcare)) ? -1.0 : \ (((float) (tend_time.tv_sec - \ tstart_time.tv_sec)) + \ (((float) (tend_time.tv_usec - \ tstart_time.tv_usec))/1000000.0))) /* * Free all principals and names in the recorded names list. */ static void free_principals(kcontext, nentries) krb5_context kcontext; int nentries; { int i; if (recorded_principals) { for (i=0; ienctype, &lkey); if (kret) goto out; rkey = &lkey; } else rkey = key; if ((kret = krb5_dbe_create_key_data(kcontext, &dbent))) goto out; if ((kret = krb5_dbekd_encrypt_key_data(kcontext, mkey, rkey, NULL, 1, &dbent.key_data[0]))) goto out; if (!key) krb5_free_keyblock_contents(kcontext, rkey); kret = krb5_db_put_principal(kcontext, &dbent, &nentries); if ((!kret) && (nentries != 1)) kret = KRB5_KDB_UK_SERROR; out: krb5_dbe_free_contents(kcontext, &dbent); return(kret); } /* * Generate a principal name. */ static krb5_error_code gen_principal(kcontext, realm, do_rand, n, princp, namep) krb5_context kcontext; char *realm; int do_rand; int n; krb5_principal *princp; char **namep; { static char pnamebuf[MAX_PNAME_LEN]; static char *instnames[] = { "instance1", "xxx2", "whereami3", "ABCDEFG4", "foofoo5" }; static char *princnames[] = { "princ1", "user2", "service3" }; krb5_error_code kret; char *instname; char *princbase; int ncomps; int i, complen, j; char *cp; if (do_rand) { ncomps = RANDOM(1,MAX_PRINC_COMPS); cp = pnamebuf; for (i=0; i= pnamebuf + sizeof(pnamebuf)) break; } if(cp + strlen(realm) >= pnamebuf + sizeof(pnamebuf)) break; *cp = '/'; cp++; } if(cp + strlen(realm) < pnamebuf + sizeof(pnamebuf)) { cp[-1] = '@'; strcpy(cp, realm); } else { strcpy(cp , ""); } } else { instname = instnames[n % (sizeof(instnames)/sizeof(instnames[0]))]; princbase = princnames[n % (sizeof(princnames)/sizeof(princnames[0]))]; sprintf(pnamebuf, "%s%d/%s@%s", princbase, n, instname, realm); } kret = krb5_parse_name(kcontext, pnamebuf, princp); *namep = (!kret) ? pnamebuf : (char *) NULL; return(kret); } /* * Find a principal in the database. */ static krb5_error_code find_principal(kcontext, principal, docompare) krb5_context kcontext; krb5_principal principal; krb5_boolean docompare; { krb5_error_code kret; krb5_db_entry dbent; krb5_principal mod_princ; krb5_timestamp mod_time; int how_many; krb5_boolean more; more = 0; how_many = 1; if ((kret = krb5_db_get_principal(kcontext, principal, &dbent, &how_many, &more))) return(kret); if (how_many == 0) return(KRB5_KDB_NOENTRY); if ((kret = krb5_dbe_lookup_mod_princ_data(kcontext, &dbent, &mod_time, &mod_princ))) return(kret); if (docompare) { if ((dbent.max_life != KRB5_KDB_MAX_LIFE) || (dbent.max_renewable_life != KRB5_KDB_MAX_RLIFE) || (dbent.expiration != KRB5_KDB_EXPIRATION) || (dbent.attributes != KRB5_KDB_DEF_FLAGS) || !krb5_principal_compare(kcontext, principal, dbent.princ) || !krb5_principal_compare(kcontext, principal, mod_princ)) kret = KRB5_PRINC_NOMATCH; } krb5_db_free_principal(kcontext, &dbent, how_many); krb5_free_principal(kcontext, mod_princ); if (!kret) return(((how_many == 1) && (more == 0)) ? 0 : KRB5KRB_ERR_GENERIC); else return(kret); } /* * Delete a principal. */ static krb5_error_code delete_principal(kcontext, principal) krb5_context kcontext; krb5_principal principal; { krb5_error_code kret; int num2delete; num2delete = 1; if ((kret = krb5_db_delete_principal(kcontext, principal, &num2delete))) return(kret); return((num2delete == 1) ? 0 : KRB5KRB_ERR_GENERIC); } static int do_testing(db, passes, verbose, timing, rcases, check, save_db, dontclean, ptest, hash) char *db; int passes; int verbose; int timing; int rcases; int check; int save_db; int dontclean; int ptest; int hash; { krb5_error_code kret; krb5_context kcontext; char *op, *linkage, *oparg; krb5_principal master_princ; char *mkey_name; char *realm; char *mkey_fullname; char *master_passwd; krb5_data salt_data; krb5_encrypt_block master_encblock; krb5_keyblock master_keyblock; krb5_data passwd; krb5_pointer rseed; krb5_boolean db_open, db_created; int passno; krb5_principal principal; char *pname; float elapsed; krb5_keyblock stat_kb; krb5_int32 crflags; mkey_name = "master/key"; realm = master_princ_data.realm.data; mkey_fullname = (char *) NULL; master_princ = (krb5_principal) NULL; master_passwd = "master_password"; db_open = 0; db_created = 0; linkage = ""; oparg = ""; crflags = hash ? KRB5_KDB_CREATE_HASH : KRB5_KDB_CREATE_BTREE; /* Set up some initial context */ op = "initializing krb5"; kret = krb5_init_context(&kcontext); if (kret) goto goodbye; /* * The database had better not exist. */ op = "making sure database doesn't exist"; if (!(kret = krb5_db_set_name(kcontext, db))) { kret = EEXIST; goto goodbye; } /* Set up the master key name */ op = "setting up master key name"; if ((kret = krb5_db_setup_mkey_name(kcontext, mkey_name, realm, &mkey_fullname, &master_princ))) goto goodbye; if (verbose) fprintf(stdout, "%s: Initializing '%s', master key is '%s'\n", programname, db, mkey_fullname); op = "salting master key"; if ((kret = krb5_principal2salt(kcontext, master_princ, &salt_data))) goto goodbye; op = "converting master key"; krb5_use_enctype(kcontext, &master_encblock, DEFAULT_KDC_ENCTYPE); master_keyblock.enctype = DEFAULT_KDC_ENCTYPE; passwd.length = strlen(master_passwd); passwd.data = master_passwd; if ((kret = krb5_string_to_key(kcontext, &master_encblock, &master_keyblock, &passwd, &salt_data))) goto goodbye; /* Clean up */ free(salt_data.data); /* Process master key */ op = "processing master key"; if ((kret = krb5_process_key(kcontext, &master_encblock, &master_keyblock))) goto goodbye; /* Initialize random key generator */ op = "initializing random key generator"; if ((kret = krb5_init_random_key(kcontext, &master_encblock, &master_keyblock, &rseed))) goto goodbye; /* Create database */ op = "creating database"; if ((kret = krb5_db_create(kcontext, db, crflags))) goto goodbye; db_created = 1; /* Set this database as active. */ op = "setting active database"; if ((kret = krb5_db_set_name(kcontext, db))) goto goodbye; /* Initialize database */ op = "initializing database"; if ((kret = krb5_db_init(kcontext))) goto goodbye; db_open = 1; op = "adding master principal"; if ((kret = add_principal(kcontext, master_princ, &master_keyblock, &master_keyblock, rseed))) goto goodbye; stat_kb.enctype = DEFAULT_KDC_ENCTYPE; stat_kb.length = 8; stat_kb.contents = (krb5_octet *) "helpmeee"; /* We are now ready to proceed to test. */ if (verbose) fprintf(stdout, "%s: Beginning %stest\n", programname, (rcases) ? "random " : ""); init_princ_recording(kcontext, passes); if (rcases) { struct tacc { float t_time; int t_number; } accumulated[3]; int i, nvalid, discrim, highwater, coinflip; krb5_keyblock *kbp; /* Generate random cases */ for (i=0; i<3; i++) { accumulated[i].t_time = 0.0; accumulated[i].t_number = 0; } /* * Generate principal names. */ if (verbose > 1) fprintf(stdout, "%s: generating %d names\n", programname, passes); for (passno=0; passno passes) nvalid = passes; if (verbose > 1) fprintf(stdout, "%s: priming database with %d principals\n", programname, nvalid); highwater = 0; for (passno=0; passno 4) fprintf(stderr, "*A(%s)\n", playback_name(passno)); highwater++; } if (verbose > 1) fprintf(stderr, "%s: beginning random loop\n", programname); /* Loop through some number of times and pick random operations */ for (i=0; i<3*passes; i++) { discrim = RANDOM(0,100); /* Add a principal 25% of the time, if possible */ if ((discrim < 25) && (nvalid < passes)) { op = "adding principal"; coinflip = RANDOM(0,2); kbp = (coinflip) ? &stat_kb : (krb5_keyblock *) NULL; if (timing) { swatch_on(); } if ((kret = add_principal(kcontext, playback_principal(nvalid), &master_keyblock, kbp, rseed))) { oparg = playback_name(nvalid); goto cya; } if (timing) { elapsed = swatch_eltime(); accumulated[0].t_time += elapsed; accumulated[0].t_number++; } if (verbose > 4) fprintf(stderr, "*A(%s)\n", playback_name(nvalid)); nvalid++; if (nvalid > highwater) highwater = nvalid; } /* Delete a principal 15% of the time, if possible */ else if ((discrim > 85) && (nvalid > 10)) { op = "deleting principal"; if (timing) { swatch_on(); } if ((kret = delete_principal(kcontext, playback_principal(nvalid-1)))) { oparg = playback_name(nvalid-1); goto cya; } if (timing) { elapsed = swatch_eltime(); accumulated[2].t_time += elapsed; accumulated[2].t_number++; } if (verbose > 4) fprintf(stderr, "XD(%s)\n", playback_name(nvalid-1)); nvalid--; } /* Otherwise, find a principal */ else { op = "looking up principal"; passno = RANDOM(0, nvalid); if (timing) { swatch_on(); } if ((kret = find_principal(kcontext, playback_principal(passno), check))) { oparg = playback_name(passno); goto cya; } if (timing) { elapsed = swatch_eltime(); accumulated[1].t_time += elapsed; accumulated[1].t_number++; } if (verbose > 4) fprintf(stderr, "-S(%s)\n", playback_name(passno)); } } if (!dontclean) { /* Clean up the remaining principals */ if (verbose > 1) fprintf(stdout, "%s: deleting remaining %d principals\n", programname, nvalid); for (passno=0; passno 4) fprintf(stderr, "XD(%s)\n", playback_name(passno)); } } cya: if (verbose) fprintf(stdout, "%s: highwater mark was %d principals\n", programname, highwater); if (accumulated[0].t_number && timing) fprintf(stdout, "%s: performed %8d additions in %9.4f seconds (%9.4f/add)\n", programname, accumulated[0].t_number, accumulated[0].t_time, accumulated[0].t_time / (float) accumulated[0].t_number); if (accumulated[1].t_number && timing) fprintf(stdout, "%s: performed %8d lookups in %9.4f seconds (%9.4f/search)\n", programname, accumulated[1].t_number, accumulated[1].t_time, accumulated[1].t_time / (float) accumulated[1].t_number); if (accumulated[2].t_number && timing) fprintf(stdout, "%s: performed %8d deletions in %9.4f seconds (%9.4f/delete)\n", programname, accumulated[2].t_number, accumulated[2].t_time, accumulated[2].t_time / (float) accumulated[2].t_number); if (kret) goto goodbye; } else { /* * Generate principal names. */ for (passno=0; passno 4) fprintf(stderr, "*A(%s)\n", playback_name(passno)); } if (timing) { elapsed = swatch_eltime(); fprintf(stdout, "%s: added %d principals in %9.4f seconds (%9.4f/add)\n", programname, passes, elapsed, elapsed/((float) passes)); } /* * Lookup principals. */ if (timing) { swatch_on(); } for (passno=0; passno 4) fprintf(stderr, "-S(%s)\n", playback_name(passno)); } if (timing) { elapsed = swatch_eltime(); fprintf(stdout, "%s: found %d principals in %9.4f seconds (%9.4f/search)\n", programname, passes, elapsed, elapsed/((float) passes)); } /* * Delete principals. */ if (!dontclean) { if (timing) { swatch_on(); } for (passno=passes-1; passno>=0; passno--) { op = "deleting principal"; if ((kret = delete_principal(kcontext, playback_principal(passno)))) goto goodbye; if (verbose > 4) fprintf(stderr, "XD(%s)\n", playback_name(passno)); } if (timing) { elapsed = swatch_eltime(); fprintf(stdout, "%s: deleted %d principals in %9.4f seconds (%9.4f/delete)\n", programname, passes, elapsed, elapsed/((float) passes)); } } } goodbye: if (kret) fprintf(stderr, "%s: error while %s %s%s(%s)\n", programname, op, linkage, oparg, error_message(kret)); if (!kret && ptest) { int nper; pid_t children[32], child; int nprocs, existat, i, j, fd; nprocs = ptest + 1; if (nprocs > 32) nprocs = 32; nper = passes / nprocs; unlink("./test.lock"); for (i=0; i 4) fprintf(stderr, "*A[%ld](%s)\n", (long) getpid(), playback_name(base+j)); } for (j=0; (j 4) fprintf(stderr, "-S[%ld](%s)\n", (long) getpid(), playback_name(base+j)); } for (j=0; (j 4) fprintf(stderr, "XD[%ld](%s)\n", (long) getpid(), playback_name(base+j)); } krb5_db_fini(ccontext); krb5_free_context(ccontext); exit((kret) ? 1 : 0); } else children[i] = child; } fd = open("./test.lock", O_CREAT|O_RDWR|O_EXCL, 0666); close(fd); sleep(1); unlink("./test.lock"); for (i=0; i] - Use as the number of passes. * [-c] - Check contents. * [-v] - Verbose output. * [-d ] - Database name. * [-s] - Save database even on successful completion. * [-D] - Leave database dirty. */ int main(argc, argv) int argc; char *argv[]; { int option; extern char *optarg; int do_time, do_random, num_passes, check_cont, verbose, error; int save_db, dont_clean, do_ptest, hash; char *db_name; programname = argv[0]; if (strrchr(programname, (int) '/')) programname = strrchr(programname, (int) '/') + 1; SRAND((RAND_TYPE)time((void *) NULL)); /* Default values. */ do_time = 0; do_random = 0; num_passes = T_KDB_N_PASSES; check_cont = 0; verbose = 0; db_name = T_KDB_DEF_DB; save_db = 0; dont_clean = 0; error = 0; do_ptest = 0; hash = 0; /* Parse argument list */ while ((option = getopt(argc, argv, "cd:n:prstvDh")) != -1) { switch (option) { case 'c': check_cont = 1; break; case 'd': db_name = optarg; break; case 'n': if (sscanf(optarg, "%d", &num_passes) != 1) { fprintf(stderr, "%s: %s is not a valid number for %c option\n", programname, optarg, option); error++; } break; case 'p': do_ptest++; break; case 'r': do_random = 1; break; case 's': save_db = 1; break; case 't': do_time = 1; break; case 'v': verbose++; break; case 'D': dont_clean = 1; break; case 'h': hash = 1; break; default: error++; break; } } if (error) fprintf(stderr, "%s: usage is %s [-cprstv] [-d ] [-n ]\n", programname, programname); else error = do_testing(db_name, num_passes, verbose, do_time, do_random, check_cont, save_db, dont_clean, do_ptest, hash); return(error); }