2 * Copyright (c) 2005, PADL Software Pty Ltd.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of PADL Software nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 RCSID("$Id: acquire.c 22118 2007-12-03 21:44:00Z lha $");
37 static krb5_error_code
38 change_pw_and_update_keytab(krb5_context context, kcm_ccache ccache);
41 * Get a new ticket using a keytab/cached key and swap it into
42 * an existing redentials cache
46 kcm_ccache_acquire(krb5_context context,
50 krb5_error_code ret = 0;
52 krb5_const_realm realm;
53 krb5_get_init_creds_opt opt;
54 krb5_ccache_data ccdata;
55 char *in_tkt_service = NULL;
58 memset(&cred, 0, sizeof(cred));
60 KCM_ASSERT_VALID(ccache);
62 /* We need a cached key or keytab to acquire credentials */
63 if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) {
64 if (ccache->key.keyblock.keyvalue.length == 0)
66 "kcm_ccache_acquire: KCM_FLAGS_USE_CACHED_KEY without key");
67 } else if (ccache->flags & KCM_FLAGS_USE_KEYTAB) {
68 if (ccache->key.keytab == NULL)
70 "kcm_ccache_acquire: KCM_FLAGS_USE_KEYTAB without keytab");
72 kcm_log(0, "Cannot acquire initial credentials for cache %s without key",
74 return KRB5_FCC_INTERNAL;
77 HEIMDAL_MUTEX_lock(&ccache->mutex);
79 /* Fake up an internal ccache */
80 kcm_internal_ccache(context, ccache, &ccdata);
82 /* Now, actually acquire the creds */
83 if (ccache->server != NULL) {
84 ret = krb5_unparse_name(context, ccache->server, &in_tkt_service);
86 kcm_log(0, "Failed to unparse service principal name for cache %s: %s",
87 ccache->name, krb5_get_err_text(context, ret));
92 realm = krb5_principal_get_realm(context, ccache->client);
94 krb5_get_init_creds_opt_init(&opt);
95 krb5_get_init_creds_opt_set_default_flags(context, "kcm", realm, &opt);
96 if (ccache->tkt_life != 0)
97 krb5_get_init_creds_opt_set_tkt_life(&opt, ccache->tkt_life);
98 if (ccache->renew_life != 0)
99 krb5_get_init_creds_opt_set_renew_life(&opt, ccache->renew_life);
101 if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) {
102 ret = krb5_get_init_creds_keyblock(context,
105 &ccache->key.keyblock,
110 /* loosely based on lib/krb5/init_creds_pw.c */
112 ret = krb5_get_init_creds_keytab(context,
120 case KRB5KDC_ERR_KEY_EXPIRED:
121 if (in_tkt_service != NULL &&
122 strcmp(in_tkt_service, "kadmin/changepw") == 0) {
126 ret = change_pw_and_update_keytab(context, ccache);
139 kcm_log(0, "Failed to acquire credentials for cache %s: %s",
140 ccache->name, krb5_get_err_text(context, ret));
141 if (in_tkt_service != NULL)
142 free(in_tkt_service);
146 if (in_tkt_service != NULL)
147 free(in_tkt_service);
150 kcm_ccache_remove_creds_internal(context, ccache);
152 ret = kcm_ccache_store_cred_internal(context, ccache, &cred, 0, credp);
154 kcm_log(0, "Failed to store credentials for cache %s: %s",
155 ccache->name, krb5_get_err_text(context, ret));
156 krb5_free_cred_contents(context, &cred);
161 HEIMDAL_MUTEX_unlock(&ccache->mutex);
166 static krb5_error_code
167 change_pw(krb5_context context,
175 krb5_data result_code_string;
176 krb5_data result_string;
177 krb5_get_init_creds_opt options;
179 memset(&cpw_cred, 0, sizeof(cpw_cred));
181 krb5_get_init_creds_opt_init(&options);
182 krb5_get_init_creds_opt_set_tkt_life(&options, 60);
183 krb5_get_init_creds_opt_set_forwardable(&options, FALSE);
184 krb5_get_init_creds_opt_set_proxiable(&options, FALSE);
186 krb5_data_zero(&result_code_string);
187 krb5_data_zero(&result_string);
189 ret = krb5_get_init_creds_keytab(context,
197 kcm_log(0, "Failed to acquire password change credentials "
198 "for principal %s: %s",
199 cpn, krb5_get_err_text(context, ret));
203 ret = krb5_set_password(context,
211 kcm_log(0, "Failed to change password for principal %s: %s",
212 cpn, krb5_get_err_text(context, ret));
217 kcm_log(0, "Failed to change password for principal %s: %.*s",
219 (int)result_string.length,
220 result_string.length > 0 ? (char *)result_string.data : "");
225 krb5_data_free(&result_string);
226 krb5_data_free(&result_code_string);
227 krb5_free_cred_contents(context, &cpw_cred);
232 struct kcm_keyseed_data {
234 const char *password;
237 static krb5_error_code
238 kcm_password_key_proc(krb5_context context,
241 krb5_const_pointer keyseed,
245 struct kcm_keyseed_data *s = (struct kcm_keyseed_data *)keyseed;
247 /* we may be called multiple times */
248 krb5_free_salt(context, s->salt);
249 krb5_data_zero(&s->salt.saltvalue);
252 s->salt.salttype = salt.salttype;
254 ret = krb5_data_copy(&s->salt.saltvalue,
256 salt.saltvalue.length);
260 *key = (krb5_keyblock *)malloc(sizeof(**key));
265 ret = krb5_string_to_key_salt(context, etype, s->password,
275 static krb5_error_code
276 get_salt_and_kvno(krb5_context context,
278 krb5_enctype *etypes,
286 krb5_ccache_data ccdata;
287 krb5_flags options = 0;
289 struct kcm_keyseed_data s;
291 memset(&creds, 0, sizeof(creds));
292 memset(&reply, 0, sizeof(reply));
295 s.salt.salttype = (int)ETYPE_NULL;
296 krb5_data_zero(&s.salt.saltvalue);
299 kcm_internal_ccache(context, ccache, &ccdata);
302 /* Do an AS-REQ to determine salt and key version number */
303 ret = krb5_copy_principal(context, ccache->client, &creds.client);
307 /* Yes, get a ticket to ourselves */
308 ret = krb5_copy_principal(context, ccache->client, &creds.server);
310 krb5_free_principal(context, creds.client);
314 ret = krb5_get_in_tkt(context,
319 kcm_password_key_proc,
327 kcm_log(0, "Failed to get self ticket for principal %s: %s",
328 cpn, krb5_get_err_text(context, ret));
329 krb5_free_salt(context, s.salt);
331 *salt = s.salt; /* retrieve stashed salt */
332 if (reply.kdc_rep.enc_part.kvno != NULL)
333 *kvno = *(reply.kdc_rep.enc_part.kvno);
335 /* ccache may have been modified but it will get trashed anyway */
337 krb5_free_cred_contents(context, &creds);
338 krb5_free_kdc_rep(context, &reply);
343 static krb5_error_code
344 update_keytab_entry(krb5_context context,
354 krb5_keytab_entry entry;
357 memset(&entry, 0, sizeof(entry));
359 pw.data = (char *)newpw;
360 pw.length = strlen(newpw);
362 ret = krb5_string_to_key_data_salt(context, etype, pw,
363 salt, &entry.keyblock);
365 kcm_log(0, "String to key conversion failed for principal %s "
367 cpn, etype, krb5_get_err_text(context, ret));
372 ret = krb5_copy_principal(context, ccache->client,
375 kcm_log(0, "Failed to copy principal name %s: %s",
376 cpn, krb5_get_err_text(context, ret));
380 ret = krb5_parse_name(context, spn, &entry.principal);
382 kcm_log(0, "Failed to parse SPN alias %s: %s",
383 spn, krb5_get_err_text(context, ret));
389 entry.timestamp = time(NULL);
391 ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
393 kcm_log(0, "Failed to update keytab for principal %s "
395 cpn, etype, krb5_get_err_text(context, ret));
398 krb5_kt_free_entry(context, &entry);
403 static krb5_error_code
404 update_keytab_entries(krb5_context context,
406 krb5_enctype *etypes,
413 krb5_error_code ret = 0;
416 for (i = 0; etypes[i] != ETYPE_NULL; i++) {
417 ret = update_keytab_entry(context, ccache, etypes[i],
418 cpn, spn, newpw, salt, kvno);
427 generate_random_pw(krb5_context context,
431 unsigned char x[512], *p;
434 memset(x, 0, sizeof(x));
435 krb5_generate_random_block(x, sizeof(x));
438 for (i = 0; i < bufsiz; i++) {
439 while (isprint(*p) == 0)
442 if (p - x >= sizeof(x)) {
443 krb5_generate_random_block(x, sizeof(x));
448 buf[bufsiz - 1] = '\0';
449 memset(x, 0, sizeof(x));
452 static krb5_error_code
453 change_pw_and_update_keytab(krb5_context context,
460 krb5_enctype *etypes = NULL;
465 krb5_data_zero(&salt.saltvalue);
467 ret = krb5_unparse_name(context, ccache->client, &cpn);
469 kcm_log(0, "Failed to unparse name: %s",
470 krb5_get_err_text(context, ret));
474 ret = krb5_get_default_in_tkt_etypes(context, &etypes);
476 kcm_log(0, "Failed to determine default encryption types: %s",
477 krb5_get_err_text(context, ret));
481 /* Generate a random password (there is no set keys protocol) */
482 generate_random_pw(context, newpw, sizeof(newpw));
485 ret = change_pw(context, ccache, cpn, newpw);
489 /* Do an AS-REQ to determine salt and key version number */
490 ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
493 kcm_log(0, "Failed to determine salting principal for principal %s: %s",
494 cpn, krb5_get_err_text(context, ret));
498 /* Add canonical name */
499 ret = update_keytab_entries(context, ccache, etypes, cpn,
500 NULL, newpw, salt, kvno);
504 /* Add SPN aliases, if any */
505 spns = krb5_config_get_strings(context, NULL, "kcm",
506 "system_ccache", "spn_aliases", NULL);
508 for (i = 0; spns[i] != NULL; i++) {
509 ret = update_keytab_entries(context, ccache, etypes, cpn,
510 spns[i], newpw, salt, kvno);
516 kcm_log(0, "Changed expired password for principal %s in cache %s",
523 krb5_config_free_strings(spns);
526 krb5_free_salt(context, salt);
527 memset(newpw, 0, sizeof(newpw));