krb5 commit: Policy extensions + new policy: allowed ks types
Greg Hudson
ghudson at MIT.EDU
Mon Jul 30 19:11:47 EDT 2012
https://github.com/krb5/krb5/commit/5829ca2b348974e52a67b553afc7f7491007c33a
commit 5829ca2b348974e52a67b553afc7f7491007c33a
Author: Nicolas Williams <nico at cryptonector.com>
Date: Wed Jul 18 16:27:35 2012 -0500
Policy extensions + new policy: allowed ks types
This simply adds KADM5_API_VERSION_4 and various fields to the
policy structures:
- attributes (policy-ish principal attributes)
- max_life (max ticket life)
- max_renewable_life (max ticket renewable life)
- allowed_keysalts (allowed key/salt types)
- TL data (future policy extensions)
Of these only allowed_keysalts is currently implemented.
Some refactoring of TL data handling is also done.
ticket: 7223 (new)
.../krb_admins/admin_commands/kadmin_local.rst | 8 +
.../krb_admins/admin_commands/kdb5_util.rst | 15 +
src/clients/kinit/kinit_kdb.c | 2 +-
src/include/kdb.h | 15 +
src/kadmin/cli/kadmin.c | 73 +++--
src/kadmin/dbutil/dump.c | 316 ++++++++++++++++----
src/kadmin/dbutil/kadm5_create.c | 2 +-
src/kadmin/dbutil/kdb5_util.c | 4 +-
src/kadmin/server/ovsec_kadmd.c | 2 +-
src/kadmin/testing/util/tcl_kadm5.c | 2 +
src/lib/kadm5/admin.h | 32 ++-
src/lib/kadm5/admin_internal.h | 2 +-
src/lib/kadm5/clnt/client_init.c | 12 +-
src/lib/kadm5/clnt/clnt_policy.c | 2 +
src/lib/kadm5/kadm_err.et | 1 +
src/lib/kadm5/kadm_rpc_xdr.c | 40 ++-
src/lib/kadm5/misc_free.c | 15 +-
src/lib/kadm5/srv/server_init.c | 2 +-
src/lib/kadm5/srv/svr_policy.c | 190 +++++++++++-
src/lib/kadm5/srv/svr_principal.c | 258 ++++++++++++++---
src/lib/kadm5/unit-test/destroy-test.c | 2 +-
src/lib/kadm5/unit-test/handle-test.c | 2 +-
src/lib/kadm5/unit-test/init-test.c | 2 +-
src/lib/kadm5/unit-test/iter-test.c | 2 +-
src/lib/kadm5/unit-test/randkey-test.c | 2 +-
src/lib/kadm5/unit-test/setkey-test.c | 2 +-
src/lib/kdb/kdb5.c | 20 +-
src/lib/kdb/libkdb5.exports | 1 +
src/plugins/kdb/db2/pol_xdr.c | 75 ++---
src/plugins/kdb/db2/policy_db.h | 1 +
src/slave/kpropd.c | 2 +-
src/tests/Makefile.in | 1 +
src/tests/hist.c | 2 +-
src/tests/t_allowed_keysalts.py | 93 ++++++
src/tests/t_general.py | 16 +
35 files changed, 996 insertions(+), 220 deletions(-)
diff --git a/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst b/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst
index 8e55b3f..fbb6038 100644
--- a/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst
+++ b/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst
@@ -626,6 +626,14 @@ The following options are available:
occur without the specified failure count interval elapsing.
A duration of 0 means forever.
+**-allowedkeysalts**
+ Specifies the key/salt tuples supported for long-term keys when
+ setting or changing a principal's password/keys. See
+ :ref:`Encryption_and_salt_types` in :ref:`kdc.conf(5)` for a list
+ of the accepted values, but note that key/salt tuples must be
+ separated with commas (',') only. To clear the allowed key/salt
+ policy use a value of '-'.
+
Example:
::
diff --git a/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst b/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst
index 3601b4d..ea533f5 100644
--- a/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst
+++ b/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst
@@ -159,6 +159,11 @@ load_dump version 6". If filename is not specified, or is the string
load_dump version 5"). This was the dump format produced on
releases prior to 1.8.
+**-r18**
+ causes the dump to be in the Kerberos 5 1.8 format ("kdb5_util
+ load_dump version 6"). This was the dump format produced on
+ releases prior to 1.11.
+
**-verbose**
causes the name of each principal and policy to be printed as it
is dumped.
@@ -220,6 +225,16 @@ Options:
requires the database to be in "ovsec_adm_import" format. Must be
used with the **-update** option.
+**-r13**
+ requires the database to be in Kerberos 5 1.3 format ("kdb5_util
+ load_dump version 5"). This was the dump format produced on
+ releases prior to 1.8.
+
+**-r18**
+ requires the database to be in Kerberos 5 1.8 format ("kdb5_util
+ load_dump version 6"). This was the dump format produced on
+ releases prior to 1.11.
+
**-hash**
requires the database to be stored as a hash. If this option is
not specified, the database will be stored as a btree. This
diff --git a/src/clients/kinit/kinit_kdb.c b/src/clients/kinit/kinit_kdb.c
index cc3df04..8e949f9 100644
--- a/src/clients/kinit/kinit_kdb.c
+++ b/src/clients/kinit/kinit_kdb.c
@@ -62,7 +62,7 @@ kinit_kdb_init(krb5_context *pcontext, char *realm)
config.realm = realm;
retval = kadm5_init(*pcontext, "kinit", NULL /*pass*/,
"kinit", &config,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&server_handle);
if (retval)
return retval;
diff --git a/src/include/kdb.h b/src/include/kdb.h
index 291a05b..2a5d2d5 100644
--- a/src/include/kdb.h
+++ b/src/include/kdb.h
@@ -220,6 +220,13 @@ typedef struct _osa_policy_ent_t {
krb5_ui_4 pw_max_fail; /* pwdMaxFailure */
krb5_ui_4 pw_failcnt_interval; /* pwdFailureCountInterval */
krb5_ui_4 pw_lockout_duration; /* pwdLockoutDuration */
+ /* Only valid if version > 2 */
+ krb5_ui_4 attributes;
+ krb5_ui_4 max_life;
+ krb5_ui_4 max_renewable_life;
+ char * allowed_keysalts;
+ krb5_int16 n_tl_data;
+ krb5_tl_data * tl_data;
} osa_policy_ent_rec, *osa_policy_ent_t;
typedef void (*osa_adb_iter_policy_func) (void *, osa_policy_ent_t);
@@ -232,6 +239,8 @@ typedef struct __krb5_key_salt_tuple {
#define KRB5_KDB_MAGIC_NUMBER 0xdbdbdbdb
#define KRB5_KDB_V1_BASE_LENGTH 38
+#define KRB5_KDB_MAX_ALLOWED_KS_LEN 512
+
#define KRB5_TL_LAST_PWD_CHANGE 0x0001
#define KRB5_TL_MOD_PRINC 0x0002
#define KRB5_TL_KADM_DATA 0x0003
@@ -566,6 +575,12 @@ krb5_dbe_delete_tl_data( krb5_context context,
krb5_int16 tl_data_type);
krb5_error_code
+krb5_db_update_tl_data(krb5_context context,
+ krb5_int16 * n_tl_datap,
+ krb5_tl_data **tl_datap,
+ krb5_tl_data * new_tl_data);
+
+krb5_error_code
krb5_dbe_update_tl_data( krb5_context context,
krb5_db_entry * entry,
krb5_tl_data * new_tl_data);
diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c
index bcc01b5..649bbc1 100644
--- a/src/kadmin/cli/kadmin.c
+++ b/src/kadmin/cli/kadmin.c
@@ -489,13 +489,13 @@ kadmin_startup(int argc, char *argv[])
"credentials.\n"), princstr);
retval = kadm5_init_with_creds(context, princstr, cc, svcname, ¶ms,
KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3, db_args, &handle);
+ KADM5_API_VERSION_4, db_args, &handle);
} else if (use_anonymous) {
printf(_("Authenticating as principal %s with password; "
"anonymous requested.\n"), princstr);
retval = kadm5_init_anonymous(context, princstr, svcname, ¶ms,
KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3, db_args, &handle);
+ KADM5_API_VERSION_4, db_args, &handle);
} else if (use_keytab) {
if (keytab_name)
printf(_("Authenticating as principal %s with keytab %s.\n"),
@@ -505,13 +505,13 @@ kadmin_startup(int argc, char *argv[])
princstr);
retval = kadm5_init_with_skey(context, princstr, keytab_name, svcname,
¶ms, KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3, db_args, &handle);
+ KADM5_API_VERSION_4, db_args, &handle);
} else {
printf(_("Authenticating as principal %s with password.\n"),
princstr);
retval = kadm5_init_with_password(context, princstr, password, svcname,
¶ms, KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3, db_args,
+ KADM5_API_VERSION_4, db_args,
&handle);
}
if (retval) {
@@ -855,14 +855,14 @@ cleanup:
}
static void
-kadmin_free_tl_data(kadm5_principal_ent_t princ)
+kadmin_free_tl_data(krb5_int16 *n_tl_datap, krb5_tl_data **tl_datap)
{
- krb5_tl_data *tl_data = princ->tl_data, *next;
- int n_tl_data = princ->n_tl_data;
+ krb5_tl_data *tl_data = *tl_datap, *next;
+ int n_tl_data = *n_tl_datap;
int i;
- princ->n_tl_data = 0;
- princ->tl_data = NULL;
+ *n_tl_datap = 0;
+ *tl_datap = NULL;
for (i = 0; tl_data && (i < n_tl_data); i++) {
next = tl_data->tl_data_next;
@@ -872,12 +872,12 @@ kadmin_free_tl_data(kadm5_principal_ent_t princ)
}
}
-/* Construct a tl_data element and add it to the tail of princ->tl_data. */
+/* Construct a tl_data element and add it to the tail of *tl_datap. */
static void
-add_tl_data(kadm5_principal_ent_t princ, krb5_int16 tl_type, krb5_ui_2 len,
- krb5_octet *contents)
+add_tl_data(krb5_int16 *n_tl_datap, krb5_tl_data **tl_datap,
+ krb5_int16 tl_type, krb5_ui_2 len, krb5_octet *contents)
{
- krb5_tl_data *tl_data, **tlp;
+ krb5_tl_data *tl_data;
krb5_octet *copy;
copy = malloc(len);
@@ -893,9 +893,9 @@ add_tl_data(kadm5_principal_ent_t princ, krb5_int16 tl_type, krb5_ui_2 len,
tl_data->tl_data_contents = copy;
tl_data->tl_data_next = NULL;
- for (tlp = &princ->tl_data; *tlp != NULL; tlp = &(*tlp)->tl_data_next);
- *tlp = tl_data;
- princ->n_tl_data++;
+ for (; *tl_datap != NULL; tl_datap = &(*tl_datap)->tl_data_next);
+ *tl_datap = tl_data;
+ (*n_tl_datap)++;
}
static void
@@ -917,7 +917,8 @@ unlock_princ(kadm5_principal_ent_t princ, long *mask, const char *caller)
exit(1);
}
store_32_le((krb5_int32)now, timebuf);
- add_tl_data(princ, KRB5_TL_LAST_ADMIN_UNLOCK, 4, timebuf);
+ add_tl_data(&princ->n_tl_data, &princ->tl_data,
+ KRB5_TL_LAST_ADMIN_UNLOCK, 4, timebuf);
*mask |= KADM5_TL_DATA;
}
@@ -949,7 +950,8 @@ kadmin_parse_princ_args(int argc, char *argv[], kadm5_principal_ent_t oprinc,
if (++i > argc - 2)
return -1;
- add_tl_data(oprinc, KRB5_TL_DB_ARGS, strlen(argv[i]) + 1,
+ add_tl_data(&oprinc->n_tl_data, &oprinc->tl_data,
+ KRB5_TL_DB_ARGS, strlen(argv[i]) + 1,
(krb5_octet *)argv[i]);
*mask |= KADM5_TL_DATA;
continue;
@@ -1259,7 +1261,7 @@ cleanup:
krb5_free_principal(context, princ.principal);
free(ks_tuple);
free(canon);
- kadmin_free_tl_data(&princ);
+ kadmin_free_tl_data(&princ.n_tl_data, &princ.tl_data);
}
void
@@ -1323,7 +1325,7 @@ kadmin_modprinc(int argc, char *argv[])
cleanup:
krb5_free_principal(context, kprinc);
krb5_free_principal(context, princ.principal);
- kadmin_free_tl_data(&princ);
+ kadmin_free_tl_data(&princ.n_tl_data, &princ.tl_data);
free(canon);
free(ks_tuple);
}
@@ -1473,6 +1475,7 @@ static int
kadmin_parse_policy_args(int argc, char *argv[], kadm5_policy_ent_t policy,
long *mask, char *caller)
{
+ krb5_error_code retval;
int i;
time_t now, date;
@@ -1562,6 +1565,25 @@ kadmin_parse_policy_args(int argc, char *argv[], kadm5_policy_ent_t policy,
}
*mask |= KADM5_PW_LOCKOUT_DURATION;
continue;
+ } else if (!strcmp(argv[i], "-allowedkeysalts")) {
+ krb5_key_salt_tuple *ks_tuple = NULL;
+ int n_ks_tuple = 0;
+
+ if (++i > argc - 2)
+ return -1;
+ if (strcmp(argv[i], "-")) {
+ retval = krb5_string_to_keysalts(argv[i], ",", ":.-", 0,
+ &ks_tuple, &n_ks_tuple);
+ if (retval) {
+ com_err(caller, retval, _("while parsing keysalts %s"),
+ argv[i]);
+ return -1;
+ }
+ free(ks_tuple);
+ policy->allowed_keysalts = argv[i];
+ }
+ *mask |= KADM5_POLICY_ALLOWED_KEYSALTS;
+ continue;
} else
return -1;
}
@@ -1580,7 +1602,8 @@ kadmin_addmodpol_usage(char *func)
fprintf(stderr,
_("\t\t[-maxlife time] [-minlife time] [-minlength length]\n"
"\t\t[-minclasses number] [-history number]\n"
- "\t\t[-maxfailure number] [-failurecountinterval time]\n"));
+ "\t\t[-maxfailure number] [-failurecountinterval time]\n"
+ "\t\t[-allowedkeysalts keysalts]\n"));
fprintf(stderr, _("\t\t[-lockoutduration time]\n"));
}
@@ -1683,14 +1706,18 @@ kadmin_getpol(int argc, char *argv[])
strdur(policy.pw_failcnt_interval));
printf(_("Password lockout duration: %s\n"),
strdur(policy.pw_lockout_duration));
+ if (policy.allowed_keysalts != NULL)
+ printf(_("Allowed key/salt types: %s\n"), policy.allowed_keysalts);
} else {
- printf("\"%s\"\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%lu\t%ld\t%ld\n",
+ printf("\"%s\"\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%lu\t%ld\t%ld\t%s\n",
policy.policy, policy.pw_max_life, policy.pw_min_life,
policy.pw_min_length, policy.pw_min_classes,
policy.pw_history_num, policy.policy_refcnt,
(unsigned long)policy.pw_max_fail,
(long)policy.pw_failcnt_interval,
- (long)policy.pw_lockout_duration);
+ (long)policy.pw_lockout_duration,
+ (policy.allowed_keysalts == NULL) ? "-" :
+ policy.allowed_keysalts);
}
kadm5_free_policy_ent(handle, &policy);
}
diff --git a/src/kadmin/dbutil/dump.c b/src/kadmin/dbutil/dump.c
index 2d817df..e421e76 100644
--- a/src/kadmin/dbutil/dump.c
+++ b/src/kadmin/dbutil/dump.c
@@ -49,6 +49,10 @@ krb5_kvno new_mkvno;
static int backwards;
static int recursive;
+#define K5Q1(x) #x
+#define K5Q(x) K5Q1(x)
+#define K5CONST_WIDTH_SCANF_STR(x) "%" K5Q(x) "s"
+
/*
* Use compile(3) if no regcomp present.
*/
@@ -94,6 +98,7 @@ static krb5_error_code dump_ov_princ (krb5_pointer,
krb5_db_entry *);
static void dump_k5beta7_policy (void *, osa_policy_ent_t);
static void dump_r1_8_policy (void *, osa_policy_ent_t);
+static void dump_r1_11_policy (void *, osa_policy_ent_t);
typedef krb5_error_code (*dump_func)(krb5_pointer,
krb5_db_entry *);
@@ -106,6 +111,8 @@ static int process_k5beta7_record (char *, krb5_context,
FILE *, int, int *);
static int process_r1_8_record (char *, krb5_context,
FILE *, int, int *);
+static int process_r1_11_record (char *, krb5_context,
+ FILE *, int, int *);
static int process_ov_record (char *, krb5_context,
FILE *, int, int *);
typedef krb5_error_code (*load_func)(char *, krb5_context,
@@ -185,14 +192,23 @@ dump_version r1_8_version = {
dump_r1_8_policy,
process_r1_8_record,
};
+dump_version r1_11_version = {
+ "Kerberos version 5 release 1.11",
+ "kdb5_util load_dump version 7\n",
+ 0,
+ 0,
+ dump_k5beta7_princ_withpolicy,
+ dump_r1_11_policy,
+ process_r1_11_record,
+};
dump_version ipropx_1_version = {
"Kerberos iprop extensible version",
"ipropx",
0,
0,
dump_k5beta7_princ_withpolicy,
- dump_r1_8_policy,
- process_r1_8_record,
+ dump_r1_11_policy,
+ process_r1_11_record,
};
/* External data */
@@ -222,6 +238,7 @@ static const char null_mprinc_name[] = "kdb5_dump at MISSING";
#define trash_end_fmt _("%s(%d): ignoring trash at end of line: ")
#define read_nomem _("entry (out of memory)")
#define read_header _("dump entry header")
+#define read_negint _("dump entry (unexpected negative numeric field)")
#define read_name_string _("name string")
#define read_key_type _("key type")
#define read_key_data _("key data")
@@ -269,6 +286,7 @@ static const char updateoption[] = "-update";
static const char hashoption[] = "-hash";
static const char ovoption[] = "-ov";
static const char r13option[] = "-r13";
+static const char r18option[] = "-r18";
static const char dump_tmptrail[] = "~";
/*
@@ -700,6 +718,33 @@ dump_k5beta6_iterator(ptr, entry)
return dump_k5beta6_iterator_ext(ptr, entry, 0);
}
+/*
+ * Dumps TL data; common to principals and policies.
+ *
+ * If filter_kadm then the KRB5_TL_KADM_DATA (where a principal's policy
+ * name is stored) is filtered out. This is for dump formats that don't
+ * support policies.
+ */
+static void
+dump_tl_data(FILE *ofile, krb5_tl_data *tlp, krb5_boolean filter_kadm)
+{
+ int i;
+
+ for (; tlp; tlp = tlp->tl_data_next) {
+ if (tlp->tl_data_type == KRB5_TL_KADM_DATA && filter_kadm)
+ continue;
+ fprintf(ofile, "\t%d\t%d\t",
+ (int) tlp->tl_data_type,
+ (int) tlp->tl_data_length);
+ if (tlp->tl_data_length) {
+ for (i = 0; i < tlp->tl_data_length; i++)
+ fprintf(ofile, "%02x", tlp->tl_data_contents[i]);
+ } else {
+ fprintf(ofile, "%d", -1);
+ }
+ }
+}
+
static krb5_error_code
dump_k5beta6_iterator_ext(ptr, entry, kadm)
krb5_pointer ptr;
@@ -795,7 +840,7 @@ dump_k5beta6_iterator_ext(ptr, entry, kadm)
(int) entry->n_key_data,
(int) entry->e_length,
name);
- fprintf(arg->ofile, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t",
+ fprintf(arg->ofile, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",
entry->attributes,
entry->max_life,
entry->max_renewable_life,
@@ -804,21 +849,10 @@ dump_k5beta6_iterator_ext(ptr, entry, kadm)
(arg->flags & FLAG_OMIT_NRA) ? 0 : entry->last_success,
(arg->flags & FLAG_OMIT_NRA) ? 0 : entry->last_failed,
(arg->flags & FLAG_OMIT_NRA) ? 0 : entry->fail_auth_count);
- /* Pound out tagged data. */
- for (tlp = entry->tl_data; tlp; tlp = tlp->tl_data_next) {
- if (tlp->tl_data_type == KRB5_TL_KADM_DATA && !kadm)
- continue; /* see above, [krb5-admin/89] */
- fprintf(arg->ofile, "%d\t%d\t",
- (int) tlp->tl_data_type,
- (int) tlp->tl_data_length);
- if (tlp->tl_data_length)
- for (i=0; i<tlp->tl_data_length; i++)
- fprintf(arg->ofile, "%02x", tlp->tl_data_contents[i]);
- else
- fprintf(arg->ofile, "%d", -1);
- fprintf(arg->ofile, "\t");
- }
+ /* Pound out tagged data. */
+ dump_tl_data(arg->ofile, entry->tl_data, !kadm);
+ fprintf(arg->ofile, "\t");
/* Pound out key data */
for (counter=0; counter<entry->n_key_data; counter++) {
@@ -950,6 +984,28 @@ void dump_r1_8_policy(void *data, osa_policy_ent_t entry)
entry->pw_failcnt_interval, entry->pw_lockout_duration);
}
+void
+dump_r1_11_policy(void *data, osa_policy_ent_t entry)
+{
+ struct dump_args *arg;
+
+ arg = (struct dump_args *) data;
+ fprintf(arg->ofile,
+ "policy\t%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t"
+ "%d\t%d\t%d\t%s\t%d",
+ entry->name,
+ entry->pw_min_life, entry->pw_max_life, entry->pw_min_length,
+ entry->pw_min_classes, entry->pw_history_num,
+ entry->policy_refcnt, entry->pw_max_fail,
+ entry->pw_failcnt_interval, entry->pw_lockout_duration,
+ entry->attributes, entry->max_life, entry->max_renewable_life,
+ entry->allowed_keysalts ? entry->allowed_keysalts : "-",
+ entry->n_tl_data);
+
+ dump_tl_data(arg->ofile, entry->tl_data, FALSE);
+ fprintf(arg->ofile, "\n");
+}
+
static void print_key_data(FILE *f, krb5_key_data *key_data)
{
int c;
@@ -1060,9 +1116,9 @@ static krb5_error_code dump_ov_princ(krb5_pointer ptr, krb5_db_entry *kdb)
/*
* usage is:
- * dump_db [-old] [-b6] [-b7] [-ov] [-r13] [-verbose] [-mkey_convert]
- * [-new_mkey_file mkey_file] [-rev] [-recurse]
- * [filename [principals...]]
+ * dump_db [-old] [-b6] [-b7] [-ov] [-r13] [-r18] [-verbose]
+ * [-mkey_convert] [-new_mkey_file mkey_file] [-rev]
+ * [-recurse] [filename [principals...]]
*/
void
dump_db(argc, argv)
@@ -1085,7 +1141,7 @@ dump_db(argc, argv)
* Parse the arguments.
*/
ofile = (char *) NULL;
- dump = &r1_8_version;
+ dump = &r1_11_version;
arglist.flags = 0;
new_mkey_file = 0;
mkey_convert = 0;
@@ -1107,6 +1163,8 @@ dump_db(argc, argv)
dump = &ov_version;
else if (!strcmp(argv[aindex], r13option))
dump = &r1_3_version;
+ else if (!strcmp(argv[aindex], r18option))
+ dump = &r1_8_version;
else if (!strncmp(argv[aindex], ipropoption, sizeof(ipropoption) - 1)) {
if (log_ctx && log_ctx->iproprole) {
/* Note: ipropx_version is the maximum version acceptable */
@@ -1792,6 +1850,65 @@ process_k5beta_record(fname, kcontext, filep, flags, linenop)
return(retval);
}
+/* Allocate and form a TL data list of a desired size. */
+static int
+alloc_tl_data(krb5_int16 n_tl_data, krb5_tl_data **tldp)
+{
+ krb5_tl_data **tlp = tldp;
+ int i;
+
+ for (i = 0; i < n_tl_data; i++) {
+ *tlp = calloc(1, sizeof(krb5_tl_data));
+ if (*tlp == NULL)
+ return ENOMEM; /* caller cleans up */
+ tlp = &((*tlp)->tl_data_next);
+ }
+
+ return 0;
+}
+
+/* Read TL data; common to principals and policies */
+static int
+process_tl_data(const char *fname, FILE *filep, krb5_tl_data *tl_data,
+ const char **errstr)
+{
+ krb5_tl_data *tl;
+ int nread;
+ krb5_int32 t1, t2;
+
+ for (tl = tl_data; tl; tl = tl->tl_data_next) {
+ nread = fscanf(filep, "%d\t%d\t", &t1, &t2);
+ if (nread != 2) {
+ *errstr = read_ttypelen;
+ return EINVAL;
+ }
+ if (t2 < 0) {
+ *errstr = read_negint;
+ return EINVAL;
+ }
+ tl->tl_data_type = (krb5_int16) t1;
+ tl->tl_data_length = (krb5_int16) t2;
+ if (tl->tl_data_length) {
+ tl->tl_data_contents = malloc(t2 + 1);
+ if (tl->tl_data_contents == NULL)
+ return ENOMEM;
+ if (read_octet_string(filep, tl->tl_data_contents,
+ tl->tl_data_length)) {
+ *errstr = read_tcontents;
+ return EINVAL;
+ }
+ } else {
+ nread = fscanf(filep, "%d", &t1);
+ if (nread != 1 || t1 != -1) {
+ *errstr = read_tcontents;
+ return EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
/*
* process_k5beta6_record() - Handle a dump record in krb5b6 format.
*
@@ -1805,11 +1922,10 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
krb5_db_entry *dbentry;
krb5_int32 t1, t2, t3, t4, t5, t6, t7, t8, t9;
int nread;
- int error = 1;
int i, j;
char *name;
krb5_key_data *kp, *kdatap;
- krb5_tl_data **tlp, *tl;
+ krb5_tl_data *tl;
krb5_octet *op;
krb5_error_code kret;
const char *try2read = read_header;
@@ -1824,7 +1940,6 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
op = NULL;
nread = fscanf(filep, "%d\t%d\t%d\t%d\t%d\t", &t1, &t2, &t3, &t4, &t5);
if (nread == EOF) {
- error = 0;
retval = -1;
goto cleanup;
}
@@ -1836,14 +1951,9 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
goto cleanup;
/* Get memory for and form tagged data linked list */
- tlp = &dbentry->tl_data;
- for (i = 0; i < t3; i++) {
- if (!(*tlp = malloc(sizeof(krb5_tl_data))))
- goto cleanup;
- memset(*tlp, 0, sizeof(krb5_tl_data));
- tlp = &((*tlp)->tl_data_next);
- dbentry->n_tl_data++;
- }
+ if (alloc_tl_data(t3, &dbentry->tl_data))
+ goto cleanup;
+ dbentry->n_tl_data = t3;
/* Get memory for key list */
if (t4 && (kp = malloc(t4*sizeof(krb5_key_data))) == NULL)
@@ -1911,31 +2021,11 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
* that's what I did. [krb5-admin/89]
*/
if (dbentry->n_tl_data) {
+ if (process_tl_data(fname, filep, dbentry->tl_data, &try2read))
+ goto cleanup;
for (tl = dbentry->tl_data; tl; tl = tl->tl_data_next) {
- nread = fscanf(filep, "%d\t%d\t", &t1, &t2);
- if (nread != 2) {
- try2read = read_ttypelen;
- goto cleanup;
- }
- tl->tl_data_type = (krb5_int16) t1;
- tl->tl_data_length = (krb5_int16) t2;
- if (!tl->tl_data_length) {
- /* Should be a null field */
- nread = fscanf(filep, "%d", &t9);
- if ((nread != 1) || (t9 != -1)) {
- try2read = read_tcontents;
- goto cleanup;
- }
- continue;
- }
- if (!(tl->tl_data_contents = malloc(t2 + 1)) ||
- read_octet_string(filep, tl->tl_data_contents, t2)) {
- try2read = read_nomem;
- goto cleanup;
- }
-
/* test to set mask fields */
- if (t1 == KRB5_TL_KADM_DATA) {
+ if (tl->tl_data_type == KRB5_TL_KADM_DATA) {
XDR xdrs;
osa_princ_ent_rec osa_princ_ent;
@@ -2032,10 +2122,9 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
if (flags & FLAG_VERBOSE)
fprintf(stderr, add_princ_fmt, name);
retval = 0;
- error = 0;
cleanup:
- if (error)
+ if (retval > 0)
fprintf(stderr, read_err_fmt, fname, *linenop, try2read);
free(op);
@@ -2110,7 +2199,7 @@ process_r1_8_policy(fname, kcontext, filep, flags, linenop)
* To make this compatible with future policy extensions, we
* ignore any additional values.
*/
- nread = fscanf(filep, "%1024s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",
+ nread = fscanf(filep, "%1024s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d%*[^\n]",
rec.name,
&rec.pw_min_life, &rec.pw_max_life,
&rec.pw_min_length, &rec.pw_min_classes,
@@ -2139,6 +2228,83 @@ process_r1_8_policy(fname, kcontext, filep, flags, linenop)
return 0;
}
+static int
+process_r1_11_policy(char *fname, krb5_context kcontext, FILE *filep,
+ int flags, int *linenop)
+{
+ osa_policy_ent_rec rec;
+ krb5_tl_data *tl, *tl_next;
+ char namebuf[1024];
+ char keysaltbuf[KRB5_KDB_MAX_ALLOWED_KS_LEN + 1];
+ int nread;
+ int ret = 0;
+ const char *try2read = NULL;
+
+ memset(&rec, 0, sizeof(rec));
+
+ (*linenop)++;
+ rec.name = namebuf;
+ rec.allowed_keysalts = keysaltbuf;
+
+ nread = fscanf(filep,
+ "%1023s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t"
+ "%d\t%d\t%d\t"
+ K5CONST_WIDTH_SCANF_STR(KRB5_KDB_MAX_ALLOWED_KS_LEN)
+ "\t%hd",
+ rec.name,
+ &rec.pw_min_life, &rec.pw_max_life,
+ &rec.pw_min_length, &rec.pw_min_classes,
+ &rec.pw_history_num, &rec.policy_refcnt,
+ &rec.pw_max_fail, &rec.pw_failcnt_interval,
+ &rec.pw_lockout_duration,
+ &rec.attributes, &rec.max_life, &rec.max_renewable_life,
+ rec.allowed_keysalts, &rec.n_tl_data);
+ if (nread == EOF)
+ return -1;
+ else if (nread != 15) {
+ fprintf(stderr, "cannot parse policy on line %d (%d read)\n",
+ *linenop, nread);
+ return 1;
+ }
+
+ if (rec.allowed_keysalts && !strcmp(rec.allowed_keysalts, "-"))
+ rec.allowed_keysalts = NULL;
+
+ /* Get TL data */
+ ret = alloc_tl_data(rec.n_tl_data, &rec.tl_data);
+ if (ret)
+ goto cleanup;
+
+ ret = process_tl_data(fname, filep, rec.tl_data, &try2read);
+ if (ret)
+ goto cleanup;
+
+ if ((ret = krb5_db_create_policy(kcontext, &rec)) &&
+ (ret = krb5_db_put_policy(kcontext, &rec))) {
+ fprintf(stderr, "cannot create policy on line %d: %s\n",
+ *linenop, error_message(ret));
+ try2read = NULL;
+ goto cleanup;
+ }
+ if (flags & FLAG_VERBOSE)
+ fprintf(stderr, "created policy %s\n", rec.name);
+
+cleanup:
+ for (tl = rec.tl_data; tl; tl = tl_next) {
+ tl_next = tl->tl_data_next;
+ free(tl->tl_data_contents);
+ free(tl);
+ }
+ if (ret == ENOMEM)
+ try2read = no_mem_fmt;
+ if (ret) {
+ if (try2read)
+ fprintf(stderr, read_err_fmt, fname, *linenop, try2read);
+ return 1;
+ }
+ return 0;
+}
+
/*
* process_k5beta7_record() - Handle a dump record in krb5b7 format.
*
@@ -2250,6 +2416,36 @@ process_r1_8_record(fname, kcontext, filep, flags, linenop)
}
/*
+ * process_r1_11_record() - Handle a dump record in krb5 1.11 format.
+ *
+ * Returns -1 for end of file, 0 for success and 1 for failure.
+ */
+static int
+process_r1_11_record(char *fname, krb5_context kcontext, FILE *filep,
+ int flags, int *linenop)
+{
+ int nread;
+ char rectype[100];
+
+ nread = fscanf(filep, "%100s\t", rectype);
+ if (nread == EOF)
+ return -1;
+ else if (nread != 1)
+ return 1;
+ if (!strcmp(rectype, "princ"))
+ process_k5beta6_record(fname, kcontext, filep, flags, linenop);
+ else if (!strcmp(rectype, "policy"))
+ process_r1_11_policy(fname, kcontext, filep, flags, linenop);
+ else {
+ fprintf(stderr, _("unknown record type \"%s\" on line %d\n"),
+ rectype, *linenop);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
* restore_dump() - Restore the database from any version dump file.
*/
static int
@@ -2333,6 +2529,8 @@ load_db(argc, argv)
load = &ov_version;
else if (!strcmp(argv[aindex], r13option))
load = &r1_3_version;
+ else if (!strcmp(argv[aindex], r18option))
+ load = &r1_8_version;
else if (!strcmp(argv[aindex], ipropoption)) {
if (log_ctx && log_ctx->iproprole) {
load = &iprop_version;
@@ -2426,6 +2624,8 @@ load_db(argc, argv)
load = &r1_3_version;
else if (strcmp(buf, r1_8_version.header) == 0)
load = &r1_8_version;
+ else if (strcmp(buf, r1_11_version.header) == 0)
+ load = &r1_11_version;
else if (strncmp(buf, ov_version.header,
strlen(ov_version.header)) == 0)
load = &ov_version;
diff --git a/src/kadmin/dbutil/kadm5_create.c b/src/kadmin/dbutil/kadm5_create.c
index 9d5ee1d..567f5f5 100644
--- a/src/kadmin/dbutil/kadm5_create.c
+++ b/src/kadmin/dbutil/kadm5_create.c
@@ -108,7 +108,7 @@ int kadm5_create_magic_princs(kadm5_config_params *params,
return retval;
if ((retval = kadm5_init(context, progname, NULL, NULL, params,
KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3,
+ KADM5_API_VERSION_4,
db5util_db_args,
&handle))) {
com_err(progname, retval, _("while initializing the Kerberos admin "
diff --git a/src/kadmin/dbutil/kdb5_util.c b/src/kadmin/dbutil/kdb5_util.c
index ca1cdd2..f12c685 100644
--- a/src/kadmin/dbutil/kdb5_util.c
+++ b/src/kadmin/dbutil/kdb5_util.c
@@ -85,10 +85,10 @@ void usage()
"\tcreate [-s]\n"
"\tdestroy [-f]\n"
"\tstash [-f keyfile]\n"
- "\tdump [-old|-ov|-b6|-b7|-r13] [-verbose]\n"
+ "\tdump [-old|-ov|-b6|-b7|-r13|-r18] [-verbose]\n"
"\t [-mkey_convert] [-new_mkey_file mkey_file]\n"
"\t [-rev] [-recurse] [filename [princs...]]\n"
- "\tload [-old|-ov|-b6|-b7|-r13] [-verbose] [-update] "
+ "\tload [-old|-ov|-b6|-b7|-r13|-r18] [-verbose] [-update] "
"filename\n"
"\tark [-e etype_list] principal\n"
"\tadd_mkey [-e etype] [-s]\n"
diff --git a/src/kadmin/server/ovsec_kadmd.c b/src/kadmin/server/ovsec_kadmd.c
index dbb90cb..b77e765 100644
--- a/src/kadmin/server/ovsec_kadmd.c
+++ b/src/kadmin/server/ovsec_kadmd.c
@@ -318,7 +318,7 @@ int main(int argc, char *argv[])
if((ret = kadm5_init(context, "kadmind", NULL,
NULL, ¶ms,
KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3,
+ KADM5_API_VERSION_4,
db_args,
&global_server_handle)) != KADM5_OK) {
const char *e_txt = krb5_get_error_message (context, ret);
diff --git a/src/kadmin/testing/util/tcl_kadm5.c b/src/kadmin/testing/util/tcl_kadm5.c
index 0d9c7f1..8338cc9 100644
--- a/src/kadmin/testing/util/tcl_kadm5.c
+++ b/src/kadmin/testing/util/tcl_kadm5.c
@@ -2528,6 +2528,8 @@ void Tcl_kadm5_init(Tcl_Interp *interp)
Tcl_SetVar(interp, "KADM5_API_VERSION_2", buf, TCL_GLOBAL_ONLY);
(void) sprintf(buf, "%d", KADM5_API_VERSION_3);
Tcl_SetVar(interp, "KADM5_API_VERSION_3", buf, TCL_GLOBAL_ONLY);
+ (void) sprintf(buf, "%d", KADM5_API_VERSION_4);
+ Tcl_SetVar(interp, "KADM5_API_VERSION_4", buf, TCL_GLOBAL_ONLY);
(void) sprintf(buf, "%d", KADM5_API_VERSION_MASK);
Tcl_SetVar(interp, "KADM5_API_VERSION_MASK", buf, TCL_GLOBAL_ONLY);
(void) sprintf(buf, "%d", KADM5_STRUCT_VERSION_MASK);
diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h
index 020962b..037e2f9 100644
--- a/src/lib/kadm5/admin.h
+++ b/src/lib/kadm5/admin.h
@@ -116,15 +116,20 @@ typedef long kadm5_ret_t;
/* kadm5_policy_ent_t */
-#define KADM5_PW_MAX_LIFE 0x004000
-#define KADM5_PW_MIN_LIFE 0x008000
-#define KADM5_PW_MIN_LENGTH 0x010000
-#define KADM5_PW_MIN_CLASSES 0x020000
-#define KADM5_PW_HISTORY_NUM 0x040000
-#define KADM5_REF_COUNT 0x080000
-#define KADM5_PW_MAX_FAILURE 0x100000
-#define KADM5_PW_FAILURE_COUNT_INTERVAL 0x200000
-#define KADM5_PW_LOCKOUT_DURATION 0x400000
+#define KADM5_PW_MAX_LIFE 0x00004000
+#define KADM5_PW_MIN_LIFE 0x00008000
+#define KADM5_PW_MIN_LENGTH 0x00010000
+#define KADM5_PW_MIN_CLASSES 0x00020000
+#define KADM5_PW_HISTORY_NUM 0x00040000
+#define KADM5_REF_COUNT 0x00080000
+#define KADM5_PW_MAX_FAILURE 0x00100000
+#define KADM5_PW_FAILURE_COUNT_INTERVAL 0x00200000
+#define KADM5_PW_LOCKOUT_DURATION 0x00400000
+#define KADM5_POLICY_ATTRIBUTES 0x00800000
+#define KADM5_POLICY_MAX_LIFE 0x01000000
+#define KADM5_POLICY_MAX_RLIFE 0x02000000
+#define KADM5_POLICY_ALLOWED_KEYSALTS 0x04000000
+#define KADM5_POLICY_TL_DATA 0x08000000
/* kadm5_config_params */
#define KADM5_CONFIG_REALM 0x00000001
@@ -179,6 +184,7 @@ typedef long kadm5_ret_t;
#define KADM5_API_VERSION_MASK 0x12345700
#define KADM5_API_VERSION_2 (KADM5_API_VERSION_MASK|0x02)
#define KADM5_API_VERSION_3 (KADM5_API_VERSION_MASK|0x03)
+#define KADM5_API_VERSION_4 (KADM5_API_VERSION_MASK|0x04)
typedef struct _kadm5_principal_ent_t {
krb5_principal principal;
@@ -218,6 +224,14 @@ typedef struct _kadm5_policy_ent_t {
krb5_kvno pw_max_fail;
krb5_deltat pw_failcnt_interval;
krb5_deltat pw_lockout_duration;
+
+ /* version 4 fields */
+ krb5_flags attributes;
+ krb5_deltat max_life;
+ krb5_deltat max_renewable_life;
+ char *allowed_keysalts;
+ krb5_int16 n_tl_data;
+ krb5_tl_data *tl_data;
} kadm5_policy_ent_rec, *kadm5_policy_ent_t;
/*
diff --git a/src/lib/kadm5/admin_internal.h b/src/lib/kadm5/admin_internal.h
index dc21a65..6d79243 100644
--- a/src/lib/kadm5/admin_internal.h
+++ b/src/lib/kadm5/admin_internal.h
@@ -32,7 +32,7 @@
return KADM5_BAD_API_VERSION; \
if (srvr->api_version < KADM5_API_VERSION_2) \
return old_api_version; \
- if (srvr->api_version > KADM5_API_VERSION_3) \
+ if (srvr->api_version > KADM5_API_VERSION_4) \
return new_api_version; \
}
diff --git a/src/lib/kadm5/clnt/client_init.c b/src/lib/kadm5/clnt/client_init.c
index a8abebf..adc050c 100644
--- a/src/lib/kadm5/clnt/client_init.c
+++ b/src/lib/kadm5/clnt/client_init.c
@@ -197,7 +197,7 @@ init_any(krb5_context context, char *client_name, enum init_type init_type,
handle->destroy_cache = 0;
handle->context = 0;
*handle->lhandle = *handle;
- handle->lhandle->api_version = KADM5_API_VERSION_3;
+ handle->lhandle->api_version = KADM5_API_VERSION_4;
handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
handle->lhandle->lhandle = handle->lhandle;
@@ -337,6 +337,16 @@ init_any(krb5_context context, char *client_name, enum init_type init_type,
#endif
goto error;
}
+ /* Drop down to v3 wire protocol if server does not support v4 */
+ if (r->code == KADM5_NEW_SERVER_API_VERSION &&
+ handle->api_version == KADM5_API_VERSION_4) {
+ handle->api_version = KADM5_API_VERSION_3;
+ r = init_2(&handle->api_version, handle->clnt);
+ if (r == NULL) {
+ code = KADM5_RPC_ERROR;
+ goto error;
+ }
+ }
/* Drop down to v2 wire protocol if server does not support v3 */
if (r->code == KADM5_NEW_SERVER_API_VERSION &&
handle->api_version == KADM5_API_VERSION_3) {
diff --git a/src/lib/kadm5/clnt/clnt_policy.c b/src/lib/kadm5/clnt/clnt_policy.c
index eda354a..3b3823f 100644
--- a/src/lib/kadm5/clnt/clnt_policy.c
+++ b/src/lib/kadm5/clnt/clnt_policy.c
@@ -89,6 +89,8 @@ kadm5_get_policy(void *server_handle, char *name, kadm5_policy_ent_t ent)
gpol_ret *r;
kadm5_server_handle_t handle = server_handle;
+ memset(ent, 0, sizeof(*ent));
+
CHECK_HANDLE(server_handle);
arg.name = name;
diff --git a/src/lib/kadm5/kadm_err.et b/src/lib/kadm5/kadm_err.et
index 5530cca..c717670 100644
--- a/src/lib/kadm5/kadm_err.et
+++ b/src/lib/kadm5/kadm_err.et
@@ -62,4 +62,5 @@ error_code KADM5_MISSING_KRB5_CONF_PARAMS, "Missing parameters in krb5.conf requ
error_code KADM5_XDR_FAILURE, "XDR encoding error"
error_code KADM5_CANT_RESOLVE, "Cannot resolve network address for admin server in requested realm"
error_code KADM5_PASS_Q_GENERIC, "Unspecified password quality failure"
+error_code KADM5_BAD_KEYSALTS, "Invalid key/salt tuples"
end
diff --git a/src/lib/kadm5/kadm_rpc_xdr.c b/src/lib/kadm5/kadm_rpc_xdr.c
index 0b0253a..153b962 100644
--- a/src/lib/kadm5/kadm_rpc_xdr.c
+++ b/src/lib/kadm5/kadm_rpc_xdr.c
@@ -492,17 +492,45 @@ _xdr_kadm5_policy_ent_rec(XDR *xdrs, kadm5_policy_ent_rec *objp, int vers)
if (!xdr_long(xdrs, &objp->policy_refcnt)) {
return (FALSE);
}
- if (vers == KADM5_API_VERSION_3) {
+ if (xdrs->x_op == XDR_DECODE) {
+ objp->pw_max_fail = 0;
+ objp->pw_failcnt_interval = 0;
+ objp->pw_lockout_duration = 0;
+ objp->attributes = 0;
+ objp->max_life = 0;
+ objp->max_renewable_life = 0;
+ objp->allowed_keysalts = NULL;
+ objp->n_tl_data = 0;
+ objp->tl_data = NULL;
+ }
+ if (vers >= KADM5_API_VERSION_3) {
if (!xdr_krb5_kvno(xdrs, &objp->pw_max_fail))
return (FALSE);
if (!xdr_krb5_deltat(xdrs, &objp->pw_failcnt_interval))
return (FALSE);
if (!xdr_krb5_deltat(xdrs, &objp->pw_lockout_duration))
return (FALSE);
- } else if (xdrs->x_op == XDR_DECODE) {
- objp->pw_max_fail = 0;
- objp->pw_failcnt_interval = 0;
- objp->pw_lockout_duration = 0;
+ }
+ if (vers >= KADM5_API_VERSION_4) {
+ if (!xdr_krb5_flags(xdrs, &objp->attributes)) {
+ return (FALSE);
+ }
+ if (!xdr_krb5_deltat(xdrs, &objp->max_life)) {
+ return (FALSE);
+ }
+ if (!xdr_krb5_deltat(xdrs, &objp->max_renewable_life)) {
+ return (FALSE);
+ }
+ if (!xdr_nullstring(xdrs, &objp->allowed_keysalts)) {
+ return (FALSE);
+ }
+ if (!xdr_krb5_int16(xdrs, &objp->n_tl_data)) {
+ return (FALSE);
+ }
+ if (!xdr_nulltype(xdrs, (void **) &objp->tl_data,
+ xdr_krb5_tl_data)) {
+ return FALSE;
+ }
}
return (TRUE);
}
@@ -510,7 +538,7 @@ _xdr_kadm5_policy_ent_rec(XDR *xdrs, kadm5_policy_ent_rec *objp, int vers)
bool_t
xdr_kadm5_policy_ent_rec(XDR *xdrs, kadm5_policy_ent_rec *objp)
{
- return _xdr_kadm5_policy_ent_rec(xdrs, objp, KADM5_API_VERSION_3);
+ return _xdr_kadm5_policy_ent_rec(xdrs, objp, KADM5_API_VERSION_4);
}
bool_t
diff --git a/src/lib/kadm5/misc_free.c b/src/lib/kadm5/misc_free.c
index 1d70791..497b8c2 100644
--- a/src/lib/kadm5/misc_free.c
+++ b/src/lib/kadm5/misc_free.c
@@ -11,10 +11,21 @@
kadm5_ret_t
kadm5_free_policy_ent(void *server_handle, kadm5_policy_ent_t val)
{
+ krb5_tl_data *tl_next;
+
_KADM5_CHECK_HANDLE(server_handle);
- if (val)
- free(val->policy);
+ if (val == NULL)
+ return KADM5_OK;
+
+ free(val->policy);
+ free(val->allowed_keysalts);
+ for (; val->tl_data; val->tl_data = tl_next) {
+ tl_next = val->tl_data->tl_data_next;
+ free(val->tl_data->tl_data_contents);
+ free(val->tl_data);
+ }
+ memset(val, 0, sizeof(*val));
return KADM5_OK;
}
diff --git a/src/lib/kadm5/srv/server_init.c b/src/lib/kadm5/srv/server_init.c
index b170e9e..3c3a879 100644
--- a/src/lib/kadm5/srv/server_init.c
+++ b/src/lib/kadm5/srv/server_init.c
@@ -283,7 +283,7 @@ kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass,
return ENOMEM;
}
*handle->lhandle = *handle;
- handle->lhandle->api_version = KADM5_API_VERSION_3;
+ handle->lhandle->api_version = KADM5_API_VERSION_4;
handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
handle->lhandle->lhandle = handle->lhandle;
diff --git a/src/lib/kadm5/srv/svr_policy.c b/src/lib/kadm5/srv/svr_policy.c
index 4e0065c..3a8f82e 100644
--- a/src/lib/kadm5/srv/svr_policy.c
+++ b/src/lib/kadm5/srv/svr_policy.c
@@ -52,6 +52,24 @@ kadm5_create_policy(void *server_handle,
return kadm5_create_policy_internal(server_handle, entry, mask);
}
+/* Validate allowed_keysalts. */
+static kadm5_ret_t
+validate_allowed_keysalts(char *allowed_keysalts)
+{
+ kadm5_ret_t ret;
+ krb5_key_salt_tuple *ks_tuple = NULL;
+ krb5_int32 n_ks_tuple = 0;
+
+ if (strchr(allowed_keysalts, '\t') != NULL)
+ return KADM5_BAD_KEYSALTS;
+ ret = krb5_string_to_keysalts(allowed_keysalts, ",", ":.-", 0,
+ &ks_tuple, &n_ks_tuple);
+ free(ks_tuple);
+ if (ret == EINVAL)
+ return KADM5_BAD_KEYSALTS;
+ return ret;
+}
+
/*
* Function: kadm5_create_policy_internal
*
@@ -89,7 +107,14 @@ kadm5_create_policy_internal(void *server_handle,
return KADM5_BAD_POLICY;
if (!(mask & KADM5_POLICY))
return KADM5_BAD_MASK;
+ if ((mask & KADM5_POLICY_ALLOWED_KEYSALTS) &&
+ entry->allowed_keysalts != NULL) {
+ ret = validate_allowed_keysalts(entry->allowed_keysalts);
+ if (ret)
+ return ret;
+ }
+ memset(&pent, 0, sizeof(pent));
pent.name = entry->policy;
p = entry->policy;
while(*p != '\0') {
@@ -138,7 +163,32 @@ kadm5_create_policy_internal(void *server_handle,
else
pent.policy_refcnt = entry->policy_refcnt;
- if (handle->api_version == KADM5_API_VERSION_3) {
+ if (handle->api_version >= KADM5_API_VERSION_4) {
+ if (!(mask & KADM5_POLICY_ATTRIBUTES))
+ pent.attributes = 0;
+ else
+ pent.attributes = entry->attributes;
+ if (!(mask & KADM5_POLICY_MAX_LIFE))
+ pent.max_life = 0;
+ else
+ pent.max_life = entry->max_life;
+ if (!(mask & KADM5_POLICY_MAX_RLIFE))
+ pent.max_renewable_life = 0;
+ else
+ pent.max_renewable_life = entry->max_renewable_life;
+ if (!(mask & KADM5_POLICY_ALLOWED_KEYSALTS))
+ pent.allowed_keysalts = 0;
+ else
+ pent.allowed_keysalts = entry->allowed_keysalts;
+ if (!(mask & KADM5_POLICY_TL_DATA)) {
+ pent.n_tl_data = 0;
+ pent.tl_data = NULL;
+ } else {
+ pent.n_tl_data = entry->n_tl_data;
+ pent.tl_data = entry->tl_data;
+ }
+ }
+ if (handle->api_version >= KADM5_API_VERSION_3) {
if (!(mask & KADM5_PW_MAX_FAILURE))
pent.pw_max_fail = 0;
else
@@ -151,10 +201,6 @@ kadm5_create_policy_internal(void *server_handle,
pent.pw_lockout_duration = 0;
else
pent.pw_lockout_duration = entry->pw_lockout_duration;
- } else {
- pent.pw_max_fail = 0;
- pent.pw_failcnt_interval = 0;
- pent.pw_lockout_duration = 0;
}
if ((ret = krb5_db_create_policy(handle->context, &pent)))
@@ -209,13 +255,58 @@ kadm5_modify_policy(void *server_handle,
return kadm5_modify_policy_internal(server_handle, entry, mask);
}
+/* Allocate and form a TL data list of a desired size. */
+static int
+alloc_tl_data(krb5_int16 n_tl_data, krb5_tl_data **tldp)
+{
+ krb5_tl_data **tlp = tldp;
+ int i;
+
+ for (i = 0; i < n_tl_data; i++) {
+ *tlp = calloc(1, sizeof(krb5_tl_data));
+ if (*tlp == NULL)
+ return ENOMEM; /* caller cleans up */
+ memset(*tlp, 0, sizeof(krb5_tl_data));
+ tlp = &((*tlp)->tl_data_next);
+ }
+
+ return 0;
+}
+
+static kadm5_ret_t
+copy_tl_data(krb5_int16 n_tl_data, krb5_tl_data *tl_data,
+ krb5_tl_data **out)
+{
+ kadm5_ret_t ret;
+ krb5_tl_data *tl, *tl_new;
+
+ if ((ret = alloc_tl_data(n_tl_data, out)))
+ return ret; /* caller cleans up */
+
+ tl = tl_data;
+ tl_new = *out;
+ for (; tl; tl = tl->tl_data_next, tl_new = tl_new->tl_data_next) {
+ tl_new->tl_data_contents = malloc(tl->tl_data_length);
+ if (tl_new->tl_data_contents == NULL)
+ return ENOMEM;
+ memcpy(tl_new->tl_data_contents, tl->tl_data_contents,
+ tl->tl_data_length);
+ tl_new->tl_data_type = tl->tl_data_type;
+ tl_new->tl_data_length = tl->tl_data_length;
+ }
+
+ return 0;
+}
+
kadm5_ret_t
kadm5_modify_policy_internal(void *server_handle,
kadm5_policy_ent_t entry, long mask)
{
- kadm5_server_handle_t handle = server_handle;
- osa_policy_ent_t p;
- int ret;
+ kadm5_server_handle_t handle = server_handle;
+ krb5_tl_data *tl;
+ osa_policy_ent_t p;
+ int ret;
+ size_t len;
CHECK_HANDLE(server_handle);
@@ -225,6 +316,20 @@ kadm5_modify_policy_internal(void *server_handle,
return KADM5_BAD_POLICY;
if((mask & KADM5_POLICY))
return KADM5_BAD_MASK;
+ if ((mask & KADM5_POLICY_ALLOWED_KEYSALTS) &&
+ entry->allowed_keysalts != NULL) {
+ ret = validate_allowed_keysalts(entry->allowed_keysalts);
+ if (ret)
+ return ret;
+ }
+ if ((mask & KADM5_POLICY_TL_DATA)) {
+ tl = entry->tl_data;
+ while (tl != NULL) {
+ if (tl->tl_data_type < 256)
+ return KADM5_BAD_TL_TYPE;
+ tl = tl->tl_data_next;
+ }
+ }
ret = krb5_db_get_policy(handle->context, entry->policy, &p);
if (ret == KRB5_KDB_NOENTRY)
@@ -265,7 +370,7 @@ kadm5_modify_policy_internal(void *server_handle,
}
if ((mask & KADM5_REF_COUNT))
p->policy_refcnt = entry->policy_refcnt;
- if (handle->api_version == KADM5_API_VERSION_3) {
+ if (handle->api_version >= KADM5_API_VERSION_3) {
if ((mask & KADM5_PW_MAX_FAILURE))
p->pw_max_fail = entry->pw_max_fail;
if ((mask & KADM5_PW_FAILURE_COUNT_INTERVAL))
@@ -273,7 +378,39 @@ kadm5_modify_policy_internal(void *server_handle,
if ((mask & KADM5_PW_LOCKOUT_DURATION))
p->pw_lockout_duration = entry->pw_lockout_duration;
}
+ if (handle->api_version >= KADM5_API_VERSION_4) {
+ if ((mask & KADM5_POLICY_ATTRIBUTES))
+ p->attributes = entry->attributes;
+ if ((mask & KADM5_POLICY_MAX_LIFE))
+ p->max_life = entry->max_life;
+ if ((mask & KADM5_POLICY_MAX_RLIFE))
+ p->max_renewable_life = entry->max_renewable_life;
+ if ((mask & KADM5_POLICY_ALLOWED_KEYSALTS)) {
+ krb5_db_free(handle->context, p->allowed_keysalts);
+ p->allowed_keysalts = NULL;
+ if (entry->allowed_keysalts != NULL) {
+ len = strlen(entry->allowed_keysalts) + 1;
+ p->allowed_keysalts = krb5_db_alloc(handle->context, NULL,
+ len);
+ if (p->allowed_keysalts == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ memcpy(p->allowed_keysalts, entry->allowed_keysalts, len);
+ }
+ }
+ if ((mask & KADM5_POLICY_TL_DATA)) {
+ for (tl = entry->tl_data; tl != NULL; tl = tl->tl_data_next) {
+ ret = krb5_db_update_tl_data(handle->context, &p->n_tl_data,
+ &p->tl_data, tl);
+ if (ret)
+ goto cleanup;
+ }
+ }
+ }
ret = krb5_db_put_policy(handle->context, p);
+
+cleanup:
krb5_db_free_policy(handle->context, p);
return ret;
}
@@ -283,9 +420,11 @@ kadm5_get_policy(void *server_handle, kadm5_policy_t name,
kadm5_policy_ent_t entry)
{
osa_policy_ent_t t;
- int ret;
+ kadm5_ret_t ret;
kadm5_server_handle_t handle = server_handle;
+ memset(entry, 0, sizeof(*entry));
+
CHECK_HANDLE(server_handle);
krb5_clear_error_message(handle->context);
@@ -301,8 +440,8 @@ kadm5_get_policy(void *server_handle, kadm5_policy_t name,
return ret;
if ((entry->policy = strdup(t->name)) == NULL) {
- krb5_db_free_policy(handle->context, t);
- return ENOMEM;
+ ret = ENOMEM;
+ goto cleanup;
}
entry->pw_min_life = t->pw_min_life;
entry->pw_max_life = t->pw_max_life;
@@ -310,12 +449,33 @@ kadm5_get_policy(void *server_handle, kadm5_policy_t name,
entry->pw_min_classes = t->pw_min_classes;
entry->pw_history_num = t->pw_history_num;
entry->policy_refcnt = t->policy_refcnt;
- if (handle->api_version == KADM5_API_VERSION_3) {
+ if (handle->api_version >= KADM5_API_VERSION_3) {
entry->pw_max_fail = t->pw_max_fail;
entry->pw_failcnt_interval = t->pw_failcnt_interval;
entry->pw_lockout_duration = t->pw_lockout_duration;
}
- krb5_db_free_policy(handle->context, t);
+ if (handle->api_version >= KADM5_API_VERSION_4) {
+ entry->attributes = t->attributes;
+ entry->max_life = t->max_life;
+ entry->max_renewable_life = t->max_renewable_life;
+ if (t->allowed_keysalts) {
+ entry->allowed_keysalts = strdup(t->allowed_keysalts);
+ if (!entry->allowed_keysalts) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ }
+ ret = copy_tl_data(t->n_tl_data, t->tl_data, &entry->tl_data);
+ if (ret)
+ goto cleanup;
+ entry->n_tl_data = t->n_tl_data;
+ }
+
+ ret = 0;
- return KADM5_OK;
+cleanup:
+ if (ret)
+ kadm5_free_policy_ent(handle, entry);
+ krb5_db_free_policy(handle->context, t);
+ return ret;
}
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
index f5ea005..f405f55 100644
--- a/src/lib/kadm5/srv/svr_principal.c
+++ b/src/lib/kadm5/srv/svr_principal.c
@@ -173,6 +173,138 @@ static void cleanup_key_data(context, count, data)
krb5_db_free(context, data);
}
+/* Check whether a ks_tuple is present in an array of ks_tuples. */
+static krb5_boolean
+ks_tuple_present(int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+ krb5_key_salt_tuple *looking_for)
+{
+ int i;
+
+ for (i = 0; i < n_ks_tuple; i++) {
+ if (ks_tuple[i].ks_enctype == looking_for->ks_enctype &&
+ ks_tuple[i].ks_salttype == looking_for->ks_salttype)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
+ * Apply the -allowedkeysalts policy (see kadmin(1)'s addpol/modpol
+ * commands). We use the allowed key/salt tuple list as a default if
+ * no ks tuples as provided by the caller. We reject lists that include
+ * key/salts outside the policy. We re-order the requested ks tuples
+ * (which may be a subset of the policy) to reflect the policy order.
+ */
+static kadm5_ret_t
+apply_keysalt_policy(kadm5_server_handle_t handle, const char *policy,
+ int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+ int *new_n_kstp, krb5_key_salt_tuple **new_kstp)
+{
+ kadm5_ret_t ret;
+ kadm5_policy_ent_rec polent;
+ int ak_n_ks_tuple = 0;
+ int new_n_ks_tuple = 0;
+ krb5_key_salt_tuple *ak_ks_tuple = NULL;
+ krb5_key_salt_tuple *new_ks_tuple = NULL;
+ krb5_key_salt_tuple *subset;
+ int i, m;
+
+ if (new_n_kstp != NULL) {
+ *new_n_kstp = 0;
+ *new_kstp = NULL;
+ }
+
+ memset(&polent, 0, sizeof(polent));
+ if (policy != NULL &&
+ (ret = kadm5_get_policy(handle->lhandle, (char *)policy,
+ &polent)) != KADM5_OK) {
+ if (ret == EINVAL)
+ ret = KADM5_BAD_POLICY;
+ if (ret)
+ goto cleanup;
+ }
+
+ if (polent.allowed_keysalts == NULL && new_n_kstp != NULL) {
+ /* Requested keysalts allowed or default to supported_enctypes. */
+ if (n_ks_tuple == 0) {
+ /* Default to supported_enctypes. */
+ n_ks_tuple = handle->params.num_keysalts;
+ ks_tuple = handle->params.keysalts;
+ }
+ /* Dup the requested or defaulted keysalt tuples. */
+ new_ks_tuple = malloc(n_ks_tuple * sizeof(*new_ks_tuple));
+ if (new_ks_tuple == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ memcpy(new_ks_tuple, ks_tuple, n_ks_tuple * sizeof(*new_ks_tuple));
+ new_n_ks_tuple = n_ks_tuple;
+ ret = 0;
+ goto cleanup;
+ }
+
+ ret = krb5_string_to_keysalts(polent.allowed_keysalts,
+ ", ", /* Tuple separators */
+ ":.-", /* Key/salt separators */
+ 0, /* No duplicates */
+ &ak_ks_tuple,
+ &ak_n_ks_tuple);
+ /*
+ * Malformed policy? Shouldn't happen, but it's remotely possible
+ * someday, so we don't assert, just bail.
+ */
+ if (ret)
+ goto cleanup;
+
+ /* Check that the requested ks_tuples are within policy, if we have one. */
+ for (i = 0; i < n_ks_tuple; i++) {
+ if (!ks_tuple_present(ak_n_ks_tuple, ak_ks_tuple, &ks_tuple[i])) {
+ ret = KADM5_BAD_KEYSALTS;
+ goto cleanup;
+ }
+ }
+
+ /* Have policy but no ks_tuple input? Output the policy. */
+ if (n_ks_tuple == 0) {
+ new_n_ks_tuple = ak_n_ks_tuple;
+ new_ks_tuple = ak_ks_tuple;
+ ak_ks_tuple = NULL;
+ goto cleanup;
+ }
+
+ /*
+ * Now filter the policy ks tuples by the requested ones so as to
+ * preserve in the requested sub-set the relative ordering from the
+ * policy. We could optimize this (if (n_ks_tuple == ak_n_ks_tuple)
+ * then skip this), but we don't bother.
+ */
+ subset = calloc(n_ks_tuple, sizeof(*subset));
+ if (subset == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ for (m = 0, i = 0; i < ak_n_ks_tuple && m < n_ks_tuple; i++) {
+ if (ks_tuple_present(n_ks_tuple, ks_tuple, &ak_ks_tuple[i]))
+ subset[m++] = ak_ks_tuple[i];
+ }
+ new_ks_tuple = subset;
+ new_n_ks_tuple = m;
+ ret = 0;
+
+cleanup:
+ kadm5_free_policy_ent(handle->lhandle, &polent);
+ free(ak_ks_tuple);
+
+ if (new_n_kstp != NULL) {
+ *new_n_kstp = new_n_ks_tuple;
+ *new_kstp = new_ks_tuple;
+ } else {
+ free(new_ks_tuple);
+ }
+ return ret;
+}
+
+
/*
* Set *passptr to NULL if the request looks like the first part of a krb5 1.6
* addprinc -randkey operation. The krb5 1.6 dummy password for these requests
@@ -224,6 +356,8 @@ kadm5_create_principal_3(void *server_handle,
kadm5_server_handle_t handle = server_handle;
krb5_keyblock *act_mkey;
krb5_kvno act_kvno;
+ int new_n_ks_tuple = 0;
+ krb5_key_salt_tuple *new_ks_tuple = NULL;
CHECK_HANDLE(server_handle);
@@ -247,12 +381,6 @@ kadm5_create_principal_3(void *server_handle,
if (entry == NULL)
return EINVAL;
- /* Use default keysalts if caller did not provide any. */
- if (n_ks_tuple == 0) {
- ks_tuple = handle->params.keysalts;
- n_ks_tuple = handle->params.num_keysalts;
- }
-
/*
* Check to see if the principal exists
*/
@@ -362,6 +490,16 @@ kadm5_create_principal_3(void *server_handle,
}
}
+ /*
+ * We need to have setup the TL data, so we have strings, so we can
+ * check enctype policy, which is why we check/initialize ks_tuple
+ * this late.
+ */
+ ret = apply_keysalt_policy(handle, entry->policy, n_ks_tuple, ks_tuple,
+ &new_n_ks_tuple, &new_ks_tuple);
+ if (ret)
+ goto cleanup;
+
/* initialize the keys */
ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, &act_kvno,
@@ -370,13 +508,14 @@ kadm5_create_principal_3(void *server_handle,
goto cleanup;
if (password) {
- ret = krb5_dbe_cpw(handle->context, act_mkey, ks_tuple, n_ks_tuple,
- password, (mask & KADM5_KVNO)?entry->kvno:1,
+ ret = krb5_dbe_cpw(handle->context, act_mkey, new_ks_tuple,
+ new_n_ks_tuple, password,
+ (mask & KADM5_KVNO)?entry->kvno:1,
FALSE, kdb);
} else {
/* Null password means create with random key (new in 1.8). */
ret = krb5_dbe_crk(handle->context, &master_keyblock,
- ks_tuple, n_ks_tuple, FALSE, kdb);
+ new_ks_tuple, new_n_ks_tuple, FALSE, kdb);
}
if (ret)
goto cleanup;
@@ -388,7 +527,7 @@ kadm5_create_principal_3(void *server_handle,
ret = k5_kadm5_hook_create(handle->context, handle->hook_handles,
KADM5_HOOK_STAGE_PRECOMMIT, entry, mask,
- n_ks_tuple, ks_tuple, password);
+ new_n_ks_tuple, new_ks_tuple, password);
if (ret)
goto cleanup;
@@ -441,9 +580,10 @@ kadm5_create_principal_3(void *server_handle,
(void) k5_kadm5_hook_create(handle->context, handle->hook_handles,
KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask,
- n_ks_tuple, ks_tuple, password);
+ new_n_ks_tuple, new_ks_tuple, password);
cleanup:
+ free(new_ks_tuple);
krb5_db_free_principal(handle->context, kdb);
if (have_polent)
(void) kadm5_free_policy_ent(handle->lhandle, &polent);
@@ -1345,6 +1485,8 @@ kadm5_chpass_principal_3(void *server_handle,
osa_pw_hist_ent hist;
krb5_keyblock *act_mkey, *hist_keyblocks = NULL;
krb5_kvno act_kvno, hist_kvno;
+ int new_n_ks_tuple = 0;
+ krb5_key_salt_tuple *new_ks_tuple = NULL;
CHECK_HANDLE(server_handle);
@@ -1359,15 +1501,14 @@ kadm5_chpass_principal_3(void *server_handle,
principal, hist_princ)) == TRUE)
return KADM5_PROTECT_PRINCIPAL;
- /* Use default keysalts if caller did not provide any. */
- if (n_ks_tuple == 0) {
- ks_tuple = handle->params.keysalts;
- n_ks_tuple = handle->params.num_keysalts;
- }
-
if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
return(ret);
+ ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
+ &new_n_ks_tuple, &new_ks_tuple);
+ if (ret)
+ goto done;
+
if ((adb.aux_attributes & KADM5_POLICY)) {
if ((ret = kadm5_get_policy(handle->lhandle, adb.policy, &pol)))
goto done;
@@ -1392,7 +1533,7 @@ kadm5_chpass_principal_3(void *server_handle,
if (ret)
goto done;
- ret = krb5_dbe_cpw(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+ ret = krb5_dbe_cpw(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,
password, 0 /* increment kvno */,
keepold, kdb);
if (ret)
@@ -1504,7 +1645,7 @@ kadm5_chpass_principal_3(void *server_handle,
ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
- n_ks_tuple, ks_tuple, password);
+ new_n_ks_tuple, new_ks_tuple, password);
if (ret)
goto done;
@@ -1513,9 +1654,10 @@ kadm5_chpass_principal_3(void *server_handle,
(void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
KADM5_HOOK_STAGE_POSTCOMMIT, principal,
- keepold, n_ks_tuple, ks_tuple, password);
+ keepold, new_n_ks_tuple, new_ks_tuple, password);
ret = KADM5_OK;
done:
+ free(new_ks_tuple);
if (!hist_added && hist.key_data)
free_history_entry(handle->context, &hist);
kdb_free_entry(handle, kdb, &adb);
@@ -1554,39 +1696,41 @@ kadm5_randkey_principal_3(void *server_handle,
int ret, last_pwd, have_pol = 0;
kadm5_server_handle_t handle = server_handle;
krb5_keyblock *act_mkey;
+ int new_n_ks_tuple = 0;
+ krb5_key_salt_tuple *new_ks_tuple = NULL;
if (keyblocks)
*keyblocks = NULL;
CHECK_HANDLE(server_handle);
- /* Use default keysalts if caller did not provide any. */
- if (n_ks_tuple == 0) {
- ks_tuple = handle->params.keysalts;
- n_ks_tuple = handle->params.num_keysalts;
- }
-
krb5_clear_error_message(handle->context);
if (principal == NULL)
return EINVAL;
+
+ if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+ return(ret);
+
+ ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
+ &new_n_ks_tuple, &new_ks_tuple);
+ if (ret)
+ goto done;
+
if (krb5_principal_compare(handle->context, principal, hist_princ)) {
/* If changing the history entry, the new entry must have exactly one
* key. */
if (keepold)
return KADM5_PROTECT_PRINCIPAL;
- n_ks_tuple = 1;
+ new_n_ks_tuple = 1;
}
- if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
- return(ret);
-
ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, NULL,
&act_mkey);
if (ret)
goto done;
- ret = krb5_dbe_crk(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+ ret = krb5_dbe_crk(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,
keepold, kdb);
if (ret)
goto done;
@@ -1650,7 +1794,7 @@ kadm5_randkey_principal_3(void *server_handle,
ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
- n_ks_tuple, ks_tuple, NULL);
+ new_n_ks_tuple, new_ks_tuple, NULL);
if (ret)
goto done;
if ((ret = kdb_put_entry(handle, kdb, &adb)))
@@ -1658,9 +1802,10 @@ kadm5_randkey_principal_3(void *server_handle,
(void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
KADM5_HOOK_STAGE_POSTCOMMIT, principal,
- keepold, n_ks_tuple, ks_tuple, NULL);
+ keepold, new_n_ks_tuple, new_ks_tuple, NULL);
ret = KADM5_OK;
done:
+ free(new_ks_tuple);
kdb_free_entry(handle, kdb, &adb);
if (have_pol)
kadm5_free_policy_ent(handle->lhandle, &pol);
@@ -1838,6 +1983,24 @@ kadm5_setkey_principal(void *server_handle,
keyblocks, n_keys);
}
+/* Make key/salt list from keys for kadm5_setkey_principal_3() */
+static kadm5_ret_t
+make_ks_from_keys(krb5_context context, int n_keys, krb5_keyblock *keyblocks,
+ krb5_key_salt_tuple **ks_tuple)
+{
+ int i;
+
+ *ks_tuple = calloc(n_keys, sizeof(**ks_tuple));
+ if (ks_tuple == NULL)
+ return ENOMEM;
+
+ for (i = 0; i < n_keys; i++) {
+ (*ks_tuple)[i].ks_enctype = keyblocks[i].enctype;
+ (*ks_tuple)[i].ks_salttype = KRB5_KDB_SALTTYPE_NORMAL;
+ }
+ return 0;
+}
+
kadm5_ret_t
kadm5_setkey_principal_3(void *server_handle,
krb5_principal principal,
@@ -1862,6 +2025,7 @@ kadm5_setkey_principal_3(void *server_handle,
krb5_key_data tmp_key_data;
krb5_key_data *tptr;
krb5_keyblock *act_mkey;
+ krb5_key_salt_tuple *ks_from_keys = NULL;
CHECK_HANDLE(server_handle);
@@ -1874,6 +2038,31 @@ kadm5_setkey_principal_3(void *server_handle,
principal, hist_princ)) == TRUE))
return KADM5_PROTECT_PRINCIPAL;
+ if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+ return(ret);
+
+ if (!n_ks_tuple) {
+ /* Apply policy to the key/salt types implied by the given keys */
+ ret = make_ks_from_keys(handle->context, n_keys, keyblocks,
+ &ks_from_keys);
+ if (ret)
+ goto done;
+ ret = apply_keysalt_policy(handle, adb.policy, n_keys, ks_from_keys,
+ NULL, NULL);
+ free(ks_from_keys);
+ } else {
+ /*
+ * Apply policy to the given ks_tuples. Note that further below
+ * we enforce keyblocks[i].enctype == ks_tuple[i].ks_enctype for
+ * all i from 0 to n_keys, and that n_ks_tuple == n_keys if ks
+ * tuples are given.
+ */
+ ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
+ NULL, NULL);
+ }
+ if (ret)
+ goto done;
+
for (i = 0; i < n_keys; i++) {
for (j = i+1; j < n_keys; j++) {
if ((ret = krb5_c_enctype_compare(handle->context,
@@ -1894,9 +2083,6 @@ kadm5_setkey_principal_3(void *server_handle,
if (n_ks_tuple && n_ks_tuple != n_keys)
return KADM5_SETKEY3_ETYPE_MISMATCH;
- if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
- return(ret);
-
for (kvno = 0, i=0; i<kdb->n_key_data; i++)
if (kdb->key_data[i].key_data_kvno > kvno)
kvno = kdb->key_data[i].key_data_kvno;
diff --git a/src/lib/kadm5/unit-test/destroy-test.c b/src/lib/kadm5/unit-test/destroy-test.c
index ccca249..738cfeb 100644
--- a/src/lib/kadm5/unit-test/destroy-test.c
+++ b/src/lib/kadm5/unit-test/destroy-test.c
@@ -28,7 +28,7 @@ int main()
}
for(x = 0; x < TEST_NUM; x++) {
ret = kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, 0,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&server_handle);
if(ret != KADM5_OK) {
com_err("test", ret, "init");
diff --git a/src/lib/kadm5/unit-test/handle-test.c b/src/lib/kadm5/unit-test/handle-test.c
index a77bc11..29bd2c9 100644
--- a/src/lib/kadm5/unit-test/handle-test.c
+++ b/src/lib/kadm5/unit-test/handle-test.c
@@ -31,7 +31,7 @@ int main(int argc, char *argv[])
kadm5_init_krb5_context(&context);
ret = kadm5_init(context, "admin/none", "admin", KADM5_ADMIN_SERVICE, NULL,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&server_handle);
if(ret != KADM5_OK) {
com_err("test", ret, "init");
diff --git a/src/lib/kadm5/unit-test/init-test.c b/src/lib/kadm5/unit-test/init-test.c
index 354b812..880400c 100644
--- a/src/lib/kadm5/unit-test/init-test.c
+++ b/src/lib/kadm5/unit-test/init-test.c
@@ -21,7 +21,7 @@ int main()
exit(1);
}
ret = kadm5_init(context, "admin", "admin", NULL, ¶ms,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&server_handle);
if (ret == KADM5_RPC_ERROR) {
krb5_free_context(context);
diff --git a/src/lib/kadm5/unit-test/iter-test.c b/src/lib/kadm5/unit-test/iter-test.c
index bc7cfdc..cd85ebe 100644
--- a/src/lib/kadm5/unit-test/iter-test.c
+++ b/src/lib/kadm5/unit-test/iter-test.c
@@ -23,7 +23,7 @@ int main(int argc, char **argv)
exit(1);
}
ret = kadm5_init("admin", "admin", KADM5_ADMIN_SERVICE, 0,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&server_handle);
if (ret != KADM5_OK) {
com_err("iter-test", ret, "while initializing");
diff --git a/src/lib/kadm5/unit-test/randkey-test.c b/src/lib/kadm5/unit-test/randkey-test.c
index 7cf4ee8..dbef88a 100644
--- a/src/lib/kadm5/unit-test/randkey-test.c
+++ b/src/lib/kadm5/unit-test/randkey-test.c
@@ -24,7 +24,7 @@ int main()
krb5_parse_name(context, "testuser", &tprinc);
ret = kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&server_handle);
if(ret != KADM5_OK) {
com_err("test", ret, "init");
diff --git a/src/lib/kadm5/unit-test/setkey-test.c b/src/lib/kadm5/unit-test/setkey-test.c
index 53056e4..c1b9c5d 100644
--- a/src/lib/kadm5/unit-test/setkey-test.c
+++ b/src/lib/kadm5/unit-test/setkey-test.c
@@ -120,7 +120,7 @@ main(int argc, char **argv)
}
ret = kadm5_init(context, authprinc, NULL, KADM5_ADMIN_SERVICE, NULL,
- KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+ KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
&handle);
if (ret) {
com_err(whoami, ret, "while initializing connection");
diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c
index a3c2a5f..3cf116b 100644
--- a/src/lib/kdb/kdb5.c
+++ b/src/lib/kdb/kdb5.c
@@ -2185,8 +2185,8 @@ krb5_dbe_delete_tl_data(krb5_context context, krb5_db_entry *entry,
}
krb5_error_code
-krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
- krb5_tl_data *new_tl_data)
+krb5_db_update_tl_data(krb5_context context, krb5_int16 *n_tl_datap,
+ krb5_tl_data **tl_datap, krb5_tl_data *new_tl_data)
{
krb5_tl_data *tl_data = NULL;
krb5_octet *tmp;
@@ -2206,7 +2206,7 @@ krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
*/
if (new_tl_data->tl_data_type != KRB5_TL_DB_ARGS) { /* db_args can be multiple */
- for (tl_data = entry->tl_data; tl_data;
+ for (tl_data = *tl_datap; tl_data;
tl_data = tl_data->tl_data_next)
if (tl_data->tl_data_type == new_tl_data->tl_data_type)
break;
@@ -2221,9 +2221,9 @@ krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
return (ENOMEM);
}
memset(tl_data, 0, sizeof(krb5_tl_data));
- tl_data->tl_data_next = entry->tl_data;
- entry->tl_data = tl_data;
- entry->n_tl_data++;
+ tl_data->tl_data_next = *tl_datap;
+ *tl_datap = tl_data;
+ (*n_tl_datap)++;
}
/* fill in the record */
@@ -2240,6 +2240,14 @@ krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
}
krb5_error_code
+krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
+ krb5_tl_data *new_tl_data)
+{
+ return krb5_db_update_tl_data(context, &entry->n_tl_data, &entry->tl_data,
+ new_tl_data);
+}
+
+krb5_error_code
krb5_dbe_compute_salt(krb5_context context, const krb5_key_data *key,
krb5_const_principal princ, krb5_int16 *salttype_out,
krb5_data **salt_out)
diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports
index 0e58262..9aa8d1a 100644
--- a/src/lib/kdb/libkdb5.exports
+++ b/src/lib/kdb/libkdb5.exports
@@ -65,6 +65,7 @@ krb5_dbe_update_mkey_aux
krb5_dbe_update_mkvno
krb5_dbe_update_mod_princ_data
krb5_dbe_update_tl_data
+krb5_db_update_tl_data
krb5_dbe_def_encrypt_key_data
krb5_dbe_def_decrypt_key_data
krb5_dbe_decrypt_key_data
diff --git a/src/plugins/kdb/db2/pol_xdr.c b/src/plugins/kdb/db2/pol_xdr.c
index 315d0d1..e857633 100644
--- a/src/plugins/kdb/db2/pol_xdr.c
+++ b/src/plugins/kdb/db2/pol_xdr.c
@@ -2,68 +2,29 @@
#include <krb5.h>
#include <gssrpc/rpc.h>
#include <kdb.h>
+#include <kadm5/admin_xdr.h>
#include "policy_db.h"
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
-static
-bool_t xdr_nullstring(XDR *xdrs, char **objp)
-{
- u_int size;
-
- if (xdrs->x_op == XDR_ENCODE) {
- if (*objp == NULL)
- size = 0;
- else
- size = strlen(*objp) + 1;
- }
- if (! xdr_u_int(xdrs, &size)) {
- return FALSE;
- }
- switch (xdrs->x_op) {
- case XDR_DECODE:
- if (size == 0) {
- *objp = NULL;
- return TRUE;
- } else if (*objp == NULL) {
- *objp = (char *) mem_alloc(size);
- if (*objp == NULL) {
- errno = ENOMEM;
- return FALSE;
- }
- }
- return (xdr_opaque(xdrs, *objp, size));
-
- case XDR_ENCODE:
- if (size != 0)
- return (xdr_opaque(xdrs, *objp, size));
- return TRUE;
-
- case XDR_FREE:
- if (*objp != NULL)
- mem_free(*objp, size);
- *objp = NULL;
- return TRUE;
- }
-
- return FALSE;
-}
-
static int
osa_policy_min_vers(osa_policy_ent_t objp)
{
- int vers;
+ if (objp->attributes ||
+ objp->max_life ||
+ objp->max_renewable_life ||
+ objp->allowed_keysalts ||
+ objp->n_tl_data)
+ return OSA_ADB_POLICY_VERSION_3;
if (objp->pw_max_fail ||
objp->pw_failcnt_interval ||
objp->pw_lockout_duration)
- vers = OSA_ADB_POLICY_VERSION_2;
- else
- vers = OSA_ADB_POLICY_VERSION_1;
+ return OSA_ADB_POLICY_VERSION_2;
- return vers;
+ return OSA_ADB_POLICY_VERSION_1;
}
bool_t
@@ -81,7 +42,8 @@ xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp)
if (!xdr_int(xdrs, &objp->version))
return FALSE;
if (objp->version != OSA_ADB_POLICY_VERSION_1 &&
- objp->version != OSA_ADB_POLICY_VERSION_2)
+ objp->version != OSA_ADB_POLICY_VERSION_2 &&
+ objp->version != OSA_ADB_POLICY_VERSION_3)
return FALSE;
break;
}
@@ -108,5 +70,20 @@ xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp)
if (!xdr_u_int32(xdrs, &objp->pw_lockout_duration))
return (FALSE);
}
+ if (objp->version > OSA_ADB_POLICY_VERSION_2) {
+ if (!xdr_u_int32(xdrs, &objp->attributes))
+ return (FALSE);
+ if (!xdr_u_int32(xdrs, &objp->max_life))
+ return (FALSE);
+ if (!xdr_u_int32(xdrs, &objp->max_renewable_life))
+ return (FALSE);
+ if (!xdr_nullstring(xdrs, &objp->allowed_keysalts))
+ return (FALSE);
+ if (!xdr_short(xdrs, &objp->n_tl_data))
+ return (FALSE);
+ if (!xdr_nulltype(xdrs, (void **) &objp->tl_data,
+ xdr_krb5_tl_data))
+ return FALSE;
+ }
return (TRUE);
}
diff --git a/src/plugins/kdb/db2/policy_db.h b/src/plugins/kdb/db2/policy_db.h
index 6c920bc..07026e3 100644
--- a/src/plugins/kdb/db2/policy_db.h
+++ b/src/plugins/kdb/db2/policy_db.h
@@ -41,6 +41,7 @@ typedef long osa_adb_ret_t;
#define OSA_ADB_POLICY_VERSION_MASK 0x12345D00
#define OSA_ADB_POLICY_VERSION_1 0x12345D01
#define OSA_ADB_POLICY_VERSION_2 0x12345D02
+#define OSA_ADB_POLICY_VERSION_3 0x12345D03
diff --git a/src/slave/kpropd.c b/src/slave/kpropd.c
index aa6e979..309717d 100644
--- a/src/slave/kpropd.c
+++ b/src/slave/kpropd.c
@@ -713,7 +713,7 @@ reinit:
master_svc_princstr,
¶ms,
KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3,
+ KADM5_API_VERSION_4,
db_args,
&server_handle);
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 210bd8d..39a047ec 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -69,6 +69,7 @@ check-pytests:: hist
$(RUNPYTEST) $(srcdir)/t_lockout.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_kadm5_hook.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_keyrollover.py $(PYTESTFLAGS)
+ $(RUNPYTEST) $(srcdir)/t_allowed_keysalts.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_renew.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_renprinc.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_ccache.py $(PYTESTFLAGS)
diff --git a/src/tests/hist.c b/src/tests/hist.c
index c0b2b97..3d9e29e 100644
--- a/src/tests/hist.c
+++ b/src/tests/hist.c
@@ -72,7 +72,7 @@ main(int argc, char **argv)
params.mask |= KADM5_CONFIG_REALM;
params.realm = realm;
check(kadm5_init(ctx, "user", "", "", ¶ms, KADM5_STRUCT_VERSION,
- KADM5_API_VERSION_3, NULL, &handle));
+ KADM5_API_VERSION_4, NULL, &handle));
if (strcmp(argv[1], "make") == 0) {
memset(&kent, 0, sizeof(kent));
kent.principal = hprinc;
diff --git a/src/tests/t_allowed_keysalts.py b/src/tests/t_allowed_keysalts.py
new file mode 100644
index 0000000..8c76335
--- /dev/null
+++ b/src/tests/t_allowed_keysalts.py
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+from k5test import *
+import re
+
+krb5_conf1 = {'all': {'libdefaults': {
+ 'supported_enctypes': 'aes256-cts'}}}
+
+realm = K5Realm(krb5_conf=krb5_conf1, create_host=False, get_creds=False)
+
+# Add policy.
+realm.run_kadminl('addpol -allowedkeysalts aes256-cts:normal ak')
+realm.run_kadminl('addprinc -randkey -e aes256-cts:normal server')
+
+# Test with one-enctype allowed_keysalts.
+realm.run_kadminl('modprinc -policy ak server')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e aes128-cts:normal server')
+if not 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e aes256-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+
+# Now test a multi-enctype allowed_keysalts. Test that subsets are allowed,
+# the the complete set is allowed, that order doesn't matter, and that
+# enctypes outside the set are not allowed.
+
+# Test modpol.
+realm.run_kadminl('modpol -allowedkeysalts '
+ 'aes256-cts:normal,rc4-hmac:normal ak')
+output = realm.run_kadminl('getpol ak')
+if not 'Allowed key/salt types: aes256-cts:normal,rc4-hmac:normal' in output:
+ fail('getpol does not implement allowedkeysalts?')
+
+# Test one subset.
+output = realm.run_kadminl('cpw -randkey -e rc4-hmac:normal server')
+if 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+
+# Test another subset.
+output = realm.run_kadminl('cpw -randkey -e aes256-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e '
+ 'rc4-hmac:normal,aes256-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+
+# Test full set.
+output = realm.run_kadminl('cpw -randkey -e aes256-cts:normal,rc4-hmac:normal '
+ 'server')
+if 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e rc4-hmac:normal,aes128-cts:normal '
+ 'server')
+if not 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('getprinc -terse server')
+if not '2\t1\t6\t18\t0\t1\t6\t23\t0' in output:
+ fail('allowed_keysalts policy did not preserve order')
+
+# Test full set in opposite order.
+output = realm.run_kadminl('cpw -randkey -e rc4-hmac:normal,aes256-cts:normal,'
+ 'aes128-cts:normal server')
+if not 'Invalid key/salt tuples' in output:
+ fail('allowed_keysalts policy not applied properly')
+
+# Check that the order we got is the one from the policy.
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('getprinc -terse server')
+if not '2\t1\t6\t18\t0\t1\t6\t23\t0' in output:
+ fail('allowed_keysalts policy did not preserve order')
+
+# Test reset of allowedkeysalts.
+realm.run_kadminl('modpol -allowedkeysalts - ak')
+output = realm.run_kadminl('getpol ak')
+if 'Allowed key/salt types' in output:
+ fail('failed to clear allowedkeysalts')
+output = realm.run_kadminl('cpw -randkey -e aes128-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+ fail('key change rejected that should have been permitted')
+realm.run_kadminl('getprinc server')
+
+realm.stop()
+
+success('allowed_keysalts')
diff --git a/src/tests/t_general.py b/src/tests/t_general.py
index 2b04b8e..77246d5 100755
--- a/src/tests/t_general.py
+++ b/src/tests/t_general.py
@@ -28,10 +28,26 @@ realm = K5Realm(create_host=False)
realm.run_kadminl('addpol fred')
dumpfile = os.path.join(realm.testdir, 'dump')
realm.run_as_master([kdb5_util, 'dump', dumpfile])
+f = open('testdir/dump', 'a')
+f.write('policy barney 0 0 1 1 1 0 '
+ '0 0 0 0 0 0 - 1 '
+ '2 28 '
+ 'fd100f5064625f6372656174696f6e404b5242544553542e434f4d00')
+f.close()
+realm.run_as_master([kdb5_util, 'load', dumpfile])
+output = realm.run_kadminl('getpols')
+if 'fred\n' not in output:
+ fail('Policy not preserved across dump/load.')
+if 'barney\n' not in output:
+ fail('Policy not loaded.')
+
+realm.run_as_master([kdb5_util, 'dump', dumpfile])
realm.run_as_master([kdb5_util, 'load', dumpfile])
output = realm.run_kadminl('getpols')
if 'fred\n' not in output:
fail('Policy not preserved across dump/load.')
+if 'barney\n' not in output:
+ fail('Policy not preserved across dump/load.')
# Spot-check KRB5_TRACE output
tracefile = os.path.join(realm.testdir, 'trace')
More information about the cvs-krb5
mailing list