Automatically change machine passwords on expiry, and write to keytab
with correct salt/kvno git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@14562 ec53bebd-3082-4978-b11e-865c3cabbd6b
This commit is contained in:
397
kcm/acquire.c
397
kcm/acquire.c
@@ -34,6 +34,9 @@
|
|||||||
|
|
||||||
RCSID("$Id$");
|
RCSID("$Id$");
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
change_pw_and_update_keytab(krb5_context context, kcm_ccache ccache);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get a new ticket using a keytab/cached key and swap it into
|
* Get a new ticket using a keytab/cached key and swap it into
|
||||||
* an existing redentials cache
|
* an existing redentials cache
|
||||||
@@ -50,6 +53,7 @@ kcm_ccache_acquire(krb5_context context,
|
|||||||
krb5_get_init_creds_opt opt;
|
krb5_get_init_creds_opt opt;
|
||||||
krb5_ccache_data ccdata;
|
krb5_ccache_data ccdata;
|
||||||
char *in_tkt_service = NULL;
|
char *in_tkt_service = NULL;
|
||||||
|
int done = 0;
|
||||||
|
|
||||||
memset(&cred, 0, sizeof(cred));
|
memset(&cred, 0, sizeof(cred));
|
||||||
|
|
||||||
@@ -81,7 +85,7 @@ kcm_ccache_acquire(krb5_context context,
|
|||||||
if (ret) {
|
if (ret) {
|
||||||
kcm_log(0, "Failed to unparse service principal name for cache %s: %s",
|
kcm_log(0, "Failed to unparse service principal name for cache %s: %s",
|
||||||
ccache->name, krb5_get_err_text(context, ret));
|
ccache->name, krb5_get_err_text(context, ret));
|
||||||
goto out;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,13 +107,32 @@ kcm_ccache_acquire(krb5_context context,
|
|||||||
in_tkt_service,
|
in_tkt_service,
|
||||||
&opt);
|
&opt);
|
||||||
} else {
|
} else {
|
||||||
ret = krb5_get_init_creds_keytab(context,
|
/* loosely based on lib/krb5/init_creds_pw.c */
|
||||||
&cred,
|
while (!done) {
|
||||||
ccache->client,
|
ret = krb5_get_init_creds_keytab(context,
|
||||||
ccache->key.keytab,
|
&cred,
|
||||||
0,
|
ccache->client,
|
||||||
in_tkt_service,
|
ccache->key.keytab,
|
||||||
&opt);
|
0,
|
||||||
|
in_tkt_service,
|
||||||
|
&opt);
|
||||||
|
switch (ret) {
|
||||||
|
case KRB5KDC_ERR_KEY_EXPIRED:
|
||||||
|
if (in_tkt_service != NULL &&
|
||||||
|
strcmp(in_tkt_service, "kadmin/changepw") == 0) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = change_pw_and_update_keytab(context, ccache);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
done = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
@@ -140,3 +163,361 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
change_pw(krb5_context context,
|
||||||
|
kcm_ccache ccache,
|
||||||
|
char *cpn,
|
||||||
|
char *newpw)
|
||||||
|
{
|
||||||
|
krb5_error_code ret;
|
||||||
|
krb5_creds cpw_cred;
|
||||||
|
int result_code;
|
||||||
|
krb5_data result_code_string;
|
||||||
|
krb5_data result_string;
|
||||||
|
krb5_get_init_creds_opt options;
|
||||||
|
|
||||||
|
memset(&cpw_cred, 0, sizeof(cpw_cred));
|
||||||
|
|
||||||
|
krb5_get_init_creds_opt_init(&options);
|
||||||
|
krb5_get_init_creds_opt_set_tkt_life(&options, 60);
|
||||||
|
krb5_get_init_creds_opt_set_forwardable(&options, FALSE);
|
||||||
|
krb5_get_init_creds_opt_set_proxiable(&options, FALSE);
|
||||||
|
|
||||||
|
krb5_data_zero(&result_code_string);
|
||||||
|
krb5_data_zero(&result_string);
|
||||||
|
|
||||||
|
ret = krb5_get_init_creds_keytab(context,
|
||||||
|
&cpw_cred,
|
||||||
|
ccache->client,
|
||||||
|
ccache->key.keytab,
|
||||||
|
0,
|
||||||
|
"kadmin/changepw",
|
||||||
|
&options);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to acquire password change credentials "
|
||||||
|
"for principal %s: %s",
|
||||||
|
cpn, krb5_get_err_text(context, ret));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = krb5_change_password(context,
|
||||||
|
&cpw_cred,
|
||||||
|
newpw,
|
||||||
|
&result_code,
|
||||||
|
&result_code_string,
|
||||||
|
&result_string);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to change password for principal %s: %s",
|
||||||
|
cpn, krb5_get_err_text(context, ret));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_code) {
|
||||||
|
kcm_log(0, "Failed to change password for principal %s: %.*s",
|
||||||
|
cpn,
|
||||||
|
(int)result_string.length,
|
||||||
|
result_string.length > 0 ? (char *)result_string.data : "");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
krb5_data_free(&result_string);
|
||||||
|
krb5_data_free(&result_code_string);
|
||||||
|
krb5_free_cred_contents(context, &cpw_cred);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct kcm_keyseed_data {
|
||||||
|
krb5_salt salt;
|
||||||
|
const char *password;
|
||||||
|
};
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
kcm_password_key_proc(krb5_context context,
|
||||||
|
krb5_enctype etype,
|
||||||
|
krb5_salt salt,
|
||||||
|
krb5_const_pointer keyseed,
|
||||||
|
krb5_keyblock **key)
|
||||||
|
{
|
||||||
|
krb5_error_code ret;
|
||||||
|
struct kcm_keyseed_data *s = (struct kcm_keyseed_data *)keyseed;
|
||||||
|
|
||||||
|
/* stash the salt */
|
||||||
|
s->salt.salttype = salt.salttype;
|
||||||
|
|
||||||
|
ret = krb5_data_copy(&s->salt.saltvalue,
|
||||||
|
salt.saltvalue.data,
|
||||||
|
salt.saltvalue.length);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*key = (krb5_keyblock *)malloc(sizeof(**key));
|
||||||
|
if (*key == NULL) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = krb5_string_to_key_salt(context, etype, s->password,
|
||||||
|
s->salt, *key);
|
||||||
|
if (ret) {
|
||||||
|
free(*key);
|
||||||
|
*key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
get_salt_and_kvno(krb5_context context,
|
||||||
|
kcm_ccache ccache,
|
||||||
|
krb5_enctype *etypes,
|
||||||
|
char *cpn,
|
||||||
|
char *newpw,
|
||||||
|
krb5_salt *salt,
|
||||||
|
unsigned *kvno)
|
||||||
|
{
|
||||||
|
krb5_error_code ret;
|
||||||
|
krb5_creds creds;
|
||||||
|
krb5_ccache_data ccdata;
|
||||||
|
krb5_flags options = 0;
|
||||||
|
krb5_kdc_rep reply;
|
||||||
|
struct kcm_keyseed_data s;
|
||||||
|
|
||||||
|
memset(&creds, 0, sizeof(creds));
|
||||||
|
memset(&reply, 0, sizeof(reply));
|
||||||
|
memset(&s, 0, sizeof(s));
|
||||||
|
|
||||||
|
*kvno = 0;
|
||||||
|
kcm_internal_ccache(context, ccache, &ccdata);
|
||||||
|
s.password = newpw;
|
||||||
|
|
||||||
|
/* Do an AS-REQ to determine salt and key version number */
|
||||||
|
ret = krb5_copy_principal(context, ccache->client, &creds.client);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Yes, get a ticket to ourselves */
|
||||||
|
ret = krb5_copy_principal(context, ccache->client, &creds.server);
|
||||||
|
if (ret) {
|
||||||
|
krb5_free_principal(context, creds.client);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = krb5_get_in_tkt(context,
|
||||||
|
options,
|
||||||
|
NULL,
|
||||||
|
etypes,
|
||||||
|
NULL,
|
||||||
|
kcm_password_key_proc,
|
||||||
|
&s,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&creds,
|
||||||
|
&ccdata,
|
||||||
|
&reply);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to get self ticket for principal %s: %s",
|
||||||
|
cpn, krb5_get_err_text(context, ret));
|
||||||
|
krb5_free_salt(context, s.salt);
|
||||||
|
} else {
|
||||||
|
*salt = s.salt; /* retrieve stashed salt */
|
||||||
|
if (reply.kdc_rep.enc_part.kvno != NULL)
|
||||||
|
*kvno = *(reply.kdc_rep.enc_part.kvno);
|
||||||
|
}
|
||||||
|
/* ccache may have been modified but it will get trashed anyway */
|
||||||
|
|
||||||
|
krb5_free_creds_contents(context, &creds);
|
||||||
|
krb5_free_kdc_rep(context, &reply);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
update_keytab_entry(krb5_context context,
|
||||||
|
kcm_ccache ccache,
|
||||||
|
krb5_enctype etype,
|
||||||
|
char *cpn,
|
||||||
|
char *spn,
|
||||||
|
char *newpw,
|
||||||
|
krb5_salt salt,
|
||||||
|
unsigned kvno)
|
||||||
|
{
|
||||||
|
krb5_error_code ret;
|
||||||
|
krb5_keytab_entry entry;
|
||||||
|
krb5_data pw;
|
||||||
|
|
||||||
|
memset(&entry, 0, sizeof(entry));
|
||||||
|
|
||||||
|
pw.data = (char *)newpw;
|
||||||
|
pw.length = strlen(newpw);
|
||||||
|
|
||||||
|
ret = krb5_string_to_key_data_salt(context, etype, pw,
|
||||||
|
salt, &entry.keyblock);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "String to key conversion failed for principal %s "
|
||||||
|
"and etype %d: %s",
|
||||||
|
cpn, etype, krb5_get_err_text(context, ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spn == NULL) {
|
||||||
|
ret = krb5_copy_principal(context, ccache->client,
|
||||||
|
&entry.principal);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to copy principal name %s: %s",
|
||||||
|
cpn, krb5_get_err_text(context, ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = krb5_parse_name(context, spn, &entry.principal);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to parse SPN alias %s: %s",
|
||||||
|
spn, krb5_get_err_text(context, ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.vno = kvno;
|
||||||
|
entry.timestamp = time(NULL);
|
||||||
|
|
||||||
|
ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to update keytab for principal %s "
|
||||||
|
"and etype %d: %s",
|
||||||
|
cpn, etype, krb5_get_err_text(context, ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
krb5_kt_free_entry(context, &entry);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
update_keytab_entries(krb5_context context,
|
||||||
|
kcm_ccache ccache,
|
||||||
|
krb5_enctype *etypes,
|
||||||
|
char *cpn,
|
||||||
|
char *spn,
|
||||||
|
char *newpw,
|
||||||
|
krb5_salt salt,
|
||||||
|
unsigned kvno)
|
||||||
|
{
|
||||||
|
krb5_error_code ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; etypes[i] != ETYPE_NULL; i++) {
|
||||||
|
ret = update_keytab_entry(context, ccache, etypes[i],
|
||||||
|
cpn, spn, newpw, salt, kvno);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
generate_random_pw(krb5_context context,
|
||||||
|
unsigned char *buf,
|
||||||
|
size_t bufsiz)
|
||||||
|
{
|
||||||
|
unsigned char x[512], *p;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
memset(x, 0, sizeof(x));
|
||||||
|
krb5_generate_random_block(x, sizeof(x));
|
||||||
|
p = x;
|
||||||
|
|
||||||
|
for (i = 0; i < bufsiz; i++) {
|
||||||
|
while (isprint(*p) == 0)
|
||||||
|
p++;
|
||||||
|
|
||||||
|
if (p - x >= sizeof(x)) {
|
||||||
|
krb5_generate_random_block(x, sizeof(x));
|
||||||
|
p = x;
|
||||||
|
}
|
||||||
|
buf[i] = *p++;
|
||||||
|
}
|
||||||
|
buf[bufsiz - 1] = '\0';
|
||||||
|
memset(x, 0, sizeof(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
static krb5_error_code
|
||||||
|
change_pw_and_update_keytab(krb5_context context,
|
||||||
|
kcm_ccache ccache)
|
||||||
|
{
|
||||||
|
char newpw[121];
|
||||||
|
krb5_error_code ret;
|
||||||
|
unsigned kvno;
|
||||||
|
krb5_salt salt;
|
||||||
|
krb5_enctype *etypes = NULL;
|
||||||
|
int i;
|
||||||
|
char *cpn = NULL;
|
||||||
|
char **spns = NULL;
|
||||||
|
|
||||||
|
krb5_data_zero(&salt.saltvalue);
|
||||||
|
|
||||||
|
ret = krb5_unparse_name(context, ccache->client, &cpn);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to unparse name: %s",
|
||||||
|
krb5_get_err_text(context, ret));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = krb5_get_default_in_tkt_etypes(context, &etypes);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to determine default encryption types: %s",
|
||||||
|
krb5_get_err_text(context, ret));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate a random password (there is no set keys protocol) */
|
||||||
|
generate_random_pw(context, newpw, sizeof(newpw));
|
||||||
|
|
||||||
|
/* Change it */
|
||||||
|
ret = change_pw(context, ccache, cpn, newpw);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* Do an AS-REQ to determine salt and key version number */
|
||||||
|
ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
|
||||||
|
&salt, &kvno);
|
||||||
|
if (ret) {
|
||||||
|
kcm_log(0, "Failed to determine salting principal for principal %s: %s",
|
||||||
|
cpn, krb5_get_err_text(context, ret));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add canonical name */
|
||||||
|
ret = update_keytab_entries(context, ccache, etypes, cpn,
|
||||||
|
NULL, newpw, salt, kvno);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* Add SPN aliases, if any */
|
||||||
|
spns = krb5_config_get_strings(context, NULL, "kcm",
|
||||||
|
"spn_aliases", NULL);
|
||||||
|
if (spns != NULL) {
|
||||||
|
for (i = 0; spns[i] != NULL; i++) {
|
||||||
|
ret = update_keytab_entries(context, ccache, etypes, cpn,
|
||||||
|
spns[i], newpw, salt, kvno);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kcm_log(0, "Changed expired password for principal %s in cache %s",
|
||||||
|
cpn, ccache->name);
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (cpn != NULL)
|
||||||
|
free(cpn);
|
||||||
|
if (spns != NULL)
|
||||||
|
krb5_config_free_strings(spns);
|
||||||
|
if (etypes != NULL)
|
||||||
|
free(etypes);
|
||||||
|
krb5_free_salt(context, salt);
|
||||||
|
memset(newpw, 0, sizeof(newpw));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user