Sync usage with man page.
[netbsd-mini2440.git] / crypto / dist / heimdal / kcm / acquire.c
blobd70b4970d224a459d90e80522fdc850e5a8dcc03
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("$Heimdal: acquire.c 22118 2007-12-03 21:44:00Z lha $"
36 "$NetBSD$");
38 static krb5_error_code
39 change_pw_and_update_keytab(krb5_context context, kcm_ccache ccache);
42 * Get a new ticket using a keytab/cached key and swap it into
43 * an existing redentials cache
46 krb5_error_code
47 kcm_ccache_acquire(krb5_context context,
48 kcm_ccache ccache,
49 krb5_creds **credp)
51 krb5_error_code ret = 0;
52 krb5_creds cred;
53 krb5_const_realm realm;
54 krb5_get_init_creds_opt opt;
55 krb5_ccache_data ccdata;
56 char *in_tkt_service = NULL;
57 int done = 0;
59 memset(&cred, 0, sizeof(cred));
61 KCM_ASSERT_VALID(ccache);
63 /* We need a cached key or keytab to acquire credentials */
64 if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) {
65 if (ccache->key.keyblock.keyvalue.length == 0)
66 krb5_abortx(context,
67 "kcm_ccache_acquire: KCM_FLAGS_USE_CACHED_KEY without key");
68 } else if (ccache->flags & KCM_FLAGS_USE_KEYTAB) {
69 if (ccache->key.keytab == NULL)
70 krb5_abortx(context,
71 "kcm_ccache_acquire: KCM_FLAGS_USE_KEYTAB without keytab");
72 } else {
73 kcm_log(0, "Cannot acquire initial credentials for cache %s without key",
74 ccache->name);
75 return KRB5_FCC_INTERNAL;
78 HEIMDAL_MUTEX_lock(&ccache->mutex);
80 /* Fake up an internal ccache */
81 kcm_internal_ccache(context, ccache, &ccdata);
83 /* Now, actually acquire the creds */
84 if (ccache->server != NULL) {
85 ret = krb5_unparse_name(context, ccache->server, &in_tkt_service);
86 if (ret) {
87 kcm_log(0, "Failed to unparse service principal name for cache %s: %s",
88 ccache->name, krb5_get_err_text(context, ret));
89 return ret;
93 realm = krb5_principal_get_realm(context, ccache->client);
95 krb5_get_init_creds_opt_init(&opt);
96 krb5_get_init_creds_opt_set_default_flags(context, "kcm", realm, &opt);
97 if (ccache->tkt_life != 0)
98 krb5_get_init_creds_opt_set_tkt_life(&opt, ccache->tkt_life);
99 if (ccache->renew_life != 0)
100 krb5_get_init_creds_opt_set_renew_life(&opt, ccache->renew_life);
102 if (ccache->flags & KCM_FLAGS_USE_CACHED_KEY) {
103 ret = krb5_get_init_creds_keyblock(context,
104 &cred,
105 ccache->client,
106 &ccache->key.keyblock,
108 in_tkt_service,
109 &opt);
110 } else {
111 /* loosely based on lib/krb5/init_creds_pw.c */
112 while (!done) {
113 ret = krb5_get_init_creds_keytab(context,
114 &cred,
115 ccache->client,
116 ccache->key.keytab,
118 in_tkt_service,
119 &opt);
120 switch (ret) {
121 case KRB5KDC_ERR_KEY_EXPIRED:
122 if (in_tkt_service != NULL &&
123 strcmp(in_tkt_service, "kadmin/changepw") == 0) {
124 goto out;
127 ret = change_pw_and_update_keytab(context, ccache);
128 if (ret)
129 goto out;
130 break;
131 case 0:
132 default:
133 done = 1;
134 break;
139 if (ret) {
140 kcm_log(0, "Failed to acquire credentials for cache %s: %s",
141 ccache->name, krb5_get_err_text(context, ret));
142 if (in_tkt_service != NULL)
143 free(in_tkt_service);
144 goto out;
147 if (in_tkt_service != NULL)
148 free(in_tkt_service);
150 /* Swap them in */
151 kcm_ccache_remove_creds_internal(context, ccache);
153 ret = kcm_ccache_store_cred_internal(context, ccache, &cred, 0, credp);
154 if (ret) {
155 kcm_log(0, "Failed to store credentials for cache %s: %s",
156 ccache->name, krb5_get_err_text(context, ret));
157 krb5_free_cred_contents(context, &cred);
158 goto out;
161 out:
162 HEIMDAL_MUTEX_unlock(&ccache->mutex);
164 return ret;
167 static krb5_error_code
168 change_pw(krb5_context context,
169 kcm_ccache ccache,
170 char *cpn,
171 char *newpw)
173 krb5_error_code ret;
174 krb5_creds cpw_cred;
175 int result_code;
176 krb5_data result_code_string;
177 krb5_data result_string;
178 krb5_get_init_creds_opt options;
180 memset(&cpw_cred, 0, sizeof(cpw_cred));
182 krb5_get_init_creds_opt_init(&options);
183 krb5_get_init_creds_opt_set_tkt_life(&options, 60);
184 krb5_get_init_creds_opt_set_forwardable(&options, FALSE);
185 krb5_get_init_creds_opt_set_proxiable(&options, FALSE);
187 krb5_data_zero(&result_code_string);
188 krb5_data_zero(&result_string);
190 ret = krb5_get_init_creds_keytab(context,
191 &cpw_cred,
192 ccache->client,
193 ccache->key.keytab,
195 "kadmin/changepw",
196 &options);
197 if (ret) {
198 kcm_log(0, "Failed to acquire password change credentials "
199 "for principal %s: %s",
200 cpn, krb5_get_err_text(context, ret));
201 goto out;
204 ret = krb5_set_password(context,
205 &cpw_cred,
206 newpw,
207 ccache->client,
208 &result_code,
209 &result_code_string,
210 &result_string);
211 if (ret) {
212 kcm_log(0, "Failed to change password for principal %s: %s",
213 cpn, krb5_get_err_text(context, ret));
214 goto out;
217 if (result_code) {
218 kcm_log(0, "Failed to change password for principal %s: %.*s",
219 cpn,
220 (int)result_string.length,
221 result_string.length > 0 ? (char *)result_string.data : "");
222 goto out;
225 out:
226 krb5_data_free(&result_string);
227 krb5_data_free(&result_code_string);
228 krb5_free_cred_contents(context, &cpw_cred);
230 return ret;
233 struct kcm_keyseed_data {
234 krb5_salt salt;
235 const char *password;
238 static krb5_error_code
239 kcm_password_key_proc(krb5_context context,
240 krb5_enctype etype,
241 krb5_salt salt,
242 krb5_const_pointer keyseed,
243 krb5_keyblock **key)
245 krb5_error_code ret;
246 struct kcm_keyseed_data *s = (struct kcm_keyseed_data *)keyseed;
248 /* we may be called multiple times */
249 krb5_free_salt(context, s->salt);
250 krb5_data_zero(&s->salt.saltvalue);
252 /* stash the salt */
253 s->salt.salttype = salt.salttype;
255 ret = krb5_data_copy(&s->salt.saltvalue,
256 salt.saltvalue.data,
257 salt.saltvalue.length);
258 if (ret)
259 return ret;
261 *key = (krb5_keyblock *)malloc(sizeof(**key));
262 if (*key == NULL) {
263 return ENOMEM;
266 ret = krb5_string_to_key_salt(context, etype, s->password,
267 s->salt, *key);
268 if (ret) {
269 free(*key);
270 *key = NULL;
273 return ret;
276 static krb5_error_code
277 get_salt_and_kvno(krb5_context context,
278 kcm_ccache ccache,
279 krb5_enctype *etypes,
280 char *cpn,
281 char *newpw,
282 krb5_salt *salt,
283 unsigned *kvno)
285 krb5_error_code ret;
286 krb5_creds creds;
287 krb5_ccache_data ccdata;
288 krb5_flags options = 0;
289 krb5_kdc_rep reply;
290 struct kcm_keyseed_data s;
292 memset(&creds, 0, sizeof(creds));
293 memset(&reply, 0, sizeof(reply));
295 s.password = NULL;
296 s.salt.salttype = (int)ETYPE_NULL;
297 krb5_data_zero(&s.salt.saltvalue);
299 *kvno = 0;
300 kcm_internal_ccache(context, ccache, &ccdata);
301 s.password = newpw;
303 /* Do an AS-REQ to determine salt and key version number */
304 ret = krb5_copy_principal(context, ccache->client, &creds.client);
305 if (ret)
306 return ret;
308 /* Yes, get a ticket to ourselves */
309 ret = krb5_copy_principal(context, ccache->client, &creds.server);
310 if (ret) {
311 krb5_free_principal(context, creds.client);
312 return ret;
315 ret = krb5_get_in_tkt(context,
316 options,
317 NULL,
318 etypes,
319 NULL,
320 kcm_password_key_proc,
322 NULL,
323 NULL,
324 &creds,
325 &ccdata,
326 &reply);
327 if (ret) {
328 kcm_log(0, "Failed to get self ticket for principal %s: %s",
329 cpn, krb5_get_err_text(context, ret));
330 krb5_free_salt(context, s.salt);
331 } else {
332 *salt = s.salt; /* retrieve stashed salt */
333 if (reply.kdc_rep.enc_part.kvno != NULL)
334 *kvno = *(reply.kdc_rep.enc_part.kvno);
336 /* ccache may have been modified but it will get trashed anyway */
338 krb5_free_cred_contents(context, &creds);
339 krb5_free_kdc_rep(context, &reply);
341 return ret;
344 static krb5_error_code
345 update_keytab_entry(krb5_context context,
346 kcm_ccache ccache,
347 krb5_enctype etype,
348 char *cpn,
349 char *spn,
350 char *newpw,
351 krb5_salt salt,
352 unsigned kvno)
354 krb5_error_code ret;
355 krb5_keytab_entry entry;
356 krb5_data pw;
358 memset(&entry, 0, sizeof(entry));
360 pw.data = (char *)newpw;
361 pw.length = strlen(newpw);
363 ret = krb5_string_to_key_data_salt(context, etype, pw,
364 salt, &entry.keyblock);
365 if (ret) {
366 kcm_log(0, "String to key conversion failed for principal %s "
367 "and etype %d: %s",
368 cpn, etype, krb5_get_err_text(context, ret));
369 return ret;
372 if (spn == NULL) {
373 ret = krb5_copy_principal(context, ccache->client,
374 &entry.principal);
375 if (ret) {
376 kcm_log(0, "Failed to copy principal name %s: %s",
377 cpn, krb5_get_err_text(context, ret));
378 return ret;
380 } else {
381 ret = krb5_parse_name(context, spn, &entry.principal);
382 if (ret) {
383 kcm_log(0, "Failed to parse SPN alias %s: %s",
384 spn, krb5_get_err_text(context, ret));
385 return ret;
389 entry.vno = kvno;
390 entry.timestamp = time(NULL);
392 ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
393 if (ret) {
394 kcm_log(0, "Failed to update keytab for principal %s "
395 "and etype %d: %s",
396 cpn, etype, krb5_get_err_text(context, ret));
399 krb5_kt_free_entry(context, &entry);
401 return ret;
404 static krb5_error_code
405 update_keytab_entries(krb5_context context,
406 kcm_ccache ccache,
407 krb5_enctype *etypes,
408 char *cpn,
409 char *spn,
410 char *newpw,
411 krb5_salt salt,
412 unsigned kvno)
414 krb5_error_code ret = 0;
415 int i;
417 for (i = 0; etypes[i] != ETYPE_NULL; i++) {
418 ret = update_keytab_entry(context, ccache, etypes[i],
419 cpn, spn, newpw, salt, kvno);
420 if (ret)
421 break;
424 return ret;
427 static void
428 generate_random_pw(krb5_context context,
429 char *buf,
430 size_t bufsiz)
432 unsigned char x[512], *p;
433 size_t i;
435 memset(x, 0, sizeof(x));
436 krb5_generate_random_block(x, sizeof(x));
437 p = x;
439 for (i = 0; i < bufsiz; i++) {
440 while (isprint(*p) == 0)
441 p++;
443 if (p - x >= sizeof(x)) {
444 krb5_generate_random_block(x, sizeof(x));
445 p = x;
447 buf[i] = (char)*p++;
449 buf[bufsiz - 1] = '\0';
450 memset(x, 0, sizeof(x));
453 static krb5_error_code
454 change_pw_and_update_keytab(krb5_context context,
455 kcm_ccache ccache)
457 char newpw[121];
458 krb5_error_code ret;
459 unsigned kvno;
460 krb5_salt salt;
461 krb5_enctype *etypes = NULL;
462 int i;
463 char *cpn = NULL;
464 char **spns = NULL;
466 krb5_data_zero(&salt.saltvalue);
468 ret = krb5_unparse_name(context, ccache->client, &cpn);
469 if (ret) {
470 kcm_log(0, "Failed to unparse name: %s",
471 krb5_get_err_text(context, ret));
472 goto out;
475 ret = krb5_get_default_in_tkt_etypes(context, &etypes);
476 if (ret) {
477 kcm_log(0, "Failed to determine default encryption types: %s",
478 krb5_get_err_text(context, ret));
479 goto out;
482 /* Generate a random password (there is no set keys protocol) */
483 generate_random_pw(context, newpw, sizeof(newpw));
485 /* Change it */
486 ret = change_pw(context, ccache, cpn, newpw);
487 if (ret)
488 goto out;
490 /* Do an AS-REQ to determine salt and key version number */
491 ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
492 &salt, &kvno);
493 if (ret) {
494 kcm_log(0, "Failed to determine salting principal for principal %s: %s",
495 cpn, krb5_get_err_text(context, ret));
496 goto out;
499 /* Add canonical name */
500 ret = update_keytab_entries(context, ccache, etypes, cpn,
501 NULL, newpw, salt, kvno);
502 if (ret)
503 goto out;
505 /* Add SPN aliases, if any */
506 spns = krb5_config_get_strings(context, NULL, "kcm",
507 "system_ccache", "spn_aliases", NULL);
508 if (spns != NULL) {
509 for (i = 0; spns[i] != NULL; i++) {
510 ret = update_keytab_entries(context, ccache, etypes, cpn,
511 spns[i], newpw, salt, kvno);
512 if (ret)
513 goto out;
517 kcm_log(0, "Changed expired password for principal %s in cache %s",
518 cpn, ccache->name);
520 out:
521 if (cpn != NULL)
522 free(cpn);
523 if (spns != NULL)
524 krb5_config_free_strings(spns);
525 if (etypes != NULL)
526 free(etypes);
527 krb5_free_salt(context, salt);
528 memset(newpw, 0, sizeof(newpw));
530 return ret;