test debug
[heimdal.git] / kcm / acquire.c
blobbb467c079d53b969a60eded334bf58ba0748f167
1 /*
2 * Copyright (c) 2005, PADL Software Pty Ltd.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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
30 * SUCH DAMAGE.
33 #include "kcm_locl.h"
35 RCSID("$Id$");
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
45 krb5_error_code
46 kcm_ccache_acquire(krb5_context context,
47 kcm_ccache ccache,
48 krb5_creds **credp)
50 krb5_error_code ret = 0;
51 krb5_creds cred;
52 krb5_const_realm realm;
53 krb5_get_init_creds_opt opt;
54 krb5_ccache_data ccdata;
55 char *in_tkt_service = NULL;
56 int done = 0;
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)
65 krb5_abortx(context,
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)
69 krb5_abortx(context,
70 "kcm_ccache_acquire: KCM_FLAGS_USE_KEYTAB without keytab");
71 } else {
72 kcm_log(0, "Cannot acquire initial credentials for cache %s without key",
73 ccache->name);
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);
85 if (ret) {
86 kcm_log(0, "Failed to unparse service principal name for cache %s: %s",
87 ccache->name, krb5_get_err_text(context, ret));
88 return 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,
103 &cred,
104 ccache->client,
105 &ccache->key.keyblock,
107 in_tkt_service,
108 &opt);
109 } else {
110 /* loosely based on lib/krb5/init_creds_pw.c */
111 while (!done) {
112 ret = krb5_get_init_creds_keytab(context,
113 &cred,
114 ccache->client,
115 ccache->key.keytab,
117 in_tkt_service,
118 &opt);
119 switch (ret) {
120 case KRB5KDC_ERR_KEY_EXPIRED:
121 if (in_tkt_service != NULL &&
122 strcmp(in_tkt_service, "kadmin/changepw") == 0) {
123 goto out;
126 ret = change_pw_and_update_keytab(context, ccache);
127 if (ret)
128 goto out;
129 break;
130 case 0:
131 default:
132 done = 1;
133 break;
138 if (ret) {
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);
143 goto out;
146 if (in_tkt_service != NULL)
147 free(in_tkt_service);
149 /* Swap them in */
150 kcm_ccache_remove_creds_internal(context, ccache);
152 ret = kcm_ccache_store_cred_internal(context, ccache, &cred, 0, credp);
153 if (ret) {
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);
157 goto out;
160 out:
161 HEIMDAL_MUTEX_unlock(&ccache->mutex);
163 return ret;
166 static krb5_error_code
167 change_pw(krb5_context context,
168 kcm_ccache ccache,
169 char *cpn,
170 char *newpw)
172 krb5_error_code ret;
173 krb5_creds cpw_cred;
174 int result_code;
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,
190 &cpw_cred,
191 ccache->client,
192 ccache->key.keytab,
194 "kadmin/changepw",
195 &options);
196 if (ret) {
197 kcm_log(0, "Failed to acquire password change credentials "
198 "for principal %s: %s",
199 cpn, krb5_get_err_text(context, ret));
200 goto out;
203 ret = krb5_set_password(context,
204 &cpw_cred,
205 newpw,
206 ccache->client,
207 &result_code,
208 &result_code_string,
209 &result_string);
210 if (ret) {
211 kcm_log(0, "Failed to change password for principal %s: %s",
212 cpn, krb5_get_err_text(context, ret));
213 goto out;
216 if (result_code) {
217 kcm_log(0, "Failed to change password for principal %s: %.*s",
218 cpn,
219 (int)result_string.length,
220 result_string.length > 0 ? (char *)result_string.data : "");
221 goto out;
224 out:
225 krb5_data_free(&result_string);
226 krb5_data_free(&result_code_string);
227 krb5_free_cred_contents(context, &cpw_cred);
229 return ret;
232 struct kcm_keyseed_data {
233 krb5_salt salt;
234 const char *password;
237 static krb5_error_code
238 kcm_password_key_proc(krb5_context context,
239 krb5_enctype etype,
240 krb5_salt salt,
241 krb5_const_pointer keyseed,
242 krb5_keyblock **key)
244 krb5_error_code ret;
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);
251 /* stash the salt */
252 s->salt.salttype = salt.salttype;
254 ret = krb5_data_copy(&s->salt.saltvalue,
255 salt.saltvalue.data,
256 salt.saltvalue.length);
257 if (ret)
258 return ret;
260 *key = (krb5_keyblock *)malloc(sizeof(**key));
261 if (*key == NULL) {
262 return ENOMEM;
265 ret = krb5_string_to_key_salt(context, etype, s->password,
266 s->salt, *key);
267 if (ret) {
268 free(*key);
269 *key = NULL;
272 return ret;
275 static krb5_error_code
276 get_salt_and_kvno(krb5_context context,
277 kcm_ccache ccache,
278 krb5_enctype *etypes,
279 char *cpn,
280 char *newpw,
281 krb5_salt *salt,
282 unsigned *kvno)
284 krb5_error_code ret;
285 krb5_creds creds;
286 krb5_ccache_data ccdata;
287 krb5_flags options = 0;
288 krb5_kdc_rep reply;
289 struct kcm_keyseed_data s;
291 memset(&creds, 0, sizeof(creds));
292 memset(&reply, 0, sizeof(reply));
294 s.password = NULL;
295 s.salt.salttype = (int)ETYPE_NULL;
296 krb5_data_zero(&s.salt.saltvalue);
298 *kvno = 0;
299 kcm_internal_ccache(context, ccache, &ccdata);
300 s.password = newpw;
302 /* Do an AS-REQ to determine salt and key version number */
303 ret = krb5_copy_principal(context, ccache->client, &creds.client);
304 if (ret)
305 return ret;
307 /* Yes, get a ticket to ourselves */
308 ret = krb5_copy_principal(context, ccache->client, &creds.server);
309 if (ret) {
310 krb5_free_principal(context, creds.client);
311 return ret;
314 ret = krb5_get_in_tkt(context,
315 options,
316 NULL,
317 etypes,
318 NULL,
319 kcm_password_key_proc,
321 NULL,
322 NULL,
323 &creds,
324 &ccdata,
325 &reply);
326 if (ret) {
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);
330 } else {
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);
340 return ret;
343 static krb5_error_code
344 update_keytab_entry(krb5_context context,
345 kcm_ccache ccache,
346 krb5_enctype etype,
347 char *cpn,
348 char *spn,
349 char *newpw,
350 krb5_salt salt,
351 unsigned kvno)
353 krb5_error_code ret;
354 krb5_keytab_entry entry;
355 krb5_data pw;
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);
364 if (ret) {
365 kcm_log(0, "String to key conversion failed for principal %s "
366 "and etype %d: %s",
367 cpn, etype, krb5_get_err_text(context, ret));
368 return ret;
371 if (spn == NULL) {
372 ret = krb5_copy_principal(context, ccache->client,
373 &entry.principal);
374 if (ret) {
375 kcm_log(0, "Failed to copy principal name %s: %s",
376 cpn, krb5_get_err_text(context, ret));
377 return ret;
379 } else {
380 ret = krb5_parse_name(context, spn, &entry.principal);
381 if (ret) {
382 kcm_log(0, "Failed to parse SPN alias %s: %s",
383 spn, krb5_get_err_text(context, ret));
384 return ret;
388 entry.vno = kvno;
389 entry.timestamp = time(NULL);
391 ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
392 if (ret) {
393 kcm_log(0, "Failed to update keytab for principal %s "
394 "and etype %d: %s",
395 cpn, etype, krb5_get_err_text(context, ret));
398 krb5_kt_free_entry(context, &entry);
400 return ret;
403 static krb5_error_code
404 update_keytab_entries(krb5_context context,
405 kcm_ccache ccache,
406 krb5_enctype *etypes,
407 char *cpn,
408 char *spn,
409 char *newpw,
410 krb5_salt salt,
411 unsigned kvno)
413 krb5_error_code ret = 0;
414 int i;
416 for (i = 0; etypes[i] != ETYPE_NULL; i++) {
417 ret = update_keytab_entry(context, ccache, etypes[i],
418 cpn, spn, newpw, salt, kvno);
419 if (ret)
420 break;
423 return ret;
426 static void
427 generate_random_pw(krb5_context context,
428 char *buf,
429 size_t bufsiz)
431 unsigned char x[512], *p;
432 size_t i;
434 memset(x, 0, sizeof(x));
435 krb5_generate_random_block(x, sizeof(x));
436 p = x;
438 for (i = 0; i < bufsiz; i++) {
439 while (isprint(*p) == 0)
440 p++;
442 if (p - x >= sizeof(x)) {
443 krb5_generate_random_block(x, sizeof(x));
444 p = x;
446 buf[i] = (char)*p++;
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,
454 kcm_ccache ccache)
456 char newpw[121];
457 krb5_error_code ret;
458 unsigned kvno;
459 krb5_salt salt;
460 krb5_enctype *etypes = NULL;
461 int i;
462 char *cpn = NULL;
463 char **spns = NULL;
465 krb5_data_zero(&salt.saltvalue);
467 ret = krb5_unparse_name(context, ccache->client, &cpn);
468 if (ret) {
469 kcm_log(0, "Failed to unparse name: %s",
470 krb5_get_err_text(context, ret));
471 goto out;
474 ret = krb5_get_default_in_tkt_etypes(context, &etypes);
475 if (ret) {
476 kcm_log(0, "Failed to determine default encryption types: %s",
477 krb5_get_err_text(context, ret));
478 goto out;
481 /* Generate a random password (there is no set keys protocol) */
482 generate_random_pw(context, newpw, sizeof(newpw));
484 /* Change it */
485 ret = change_pw(context, ccache, cpn, newpw);
486 if (ret)
487 goto out;
489 /* Do an AS-REQ to determine salt and key version number */
490 ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
491 &salt, &kvno);
492 if (ret) {
493 kcm_log(0, "Failed to determine salting principal for principal %s: %s",
494 cpn, krb5_get_err_text(context, ret));
495 goto out;
498 /* Add canonical name */
499 ret = update_keytab_entries(context, ccache, etypes, cpn,
500 NULL, newpw, salt, kvno);
501 if (ret)
502 goto out;
504 /* Add SPN aliases, if any */
505 spns = krb5_config_get_strings(context, NULL, "kcm",
506 "system_ccache", "spn_aliases", NULL);
507 if (spns != NULL) {
508 for (i = 0; spns[i] != NULL; i++) {
509 ret = update_keytab_entries(context, ccache, etypes, cpn,
510 spns[i], newpw, salt, kvno);
511 if (ret)
512 goto out;
516 kcm_log(0, "Changed expired password for principal %s in cache %s",
517 cpn, ccache->name);
519 out:
520 if (cpn != NULL)
521 free(cpn);
522 if (spns != NULL)
523 krb5_config_free_strings(spns);
524 if (etypes != NULL)
525 free(etypes);
526 krb5_free_salt(context, salt);
527 memset(newpw, 0, sizeof(newpw));
529 return ret;