Use OID variable instead of function.
[heimdal.git] / kcm / acquire.c
blob37f61a0160b4f951926ea4f48518886f68384745
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 = NULL;
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_alloc(context, &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 if (opt)
162 krb5_get_init_creds_opt_free(context, opt);
164 HEIMDAL_MUTEX_unlock(&ccache->mutex);
166 return ret;
169 static krb5_error_code
170 change_pw(krb5_context context,
171 kcm_ccache ccache,
172 char *cpn,
173 char *newpw)
175 krb5_error_code ret;
176 krb5_creds cpw_cred;
177 int result_code;
178 krb5_data result_code_string;
179 krb5_data result_string;
180 krb5_get_init_creds_opt *options;
182 memset(&cpw_cred, 0, sizeof(cpw_cred));
184 ret = krb5_get_init_creds_opt_alloc(context, &options);
185 if (ret)
186 return ret;
187 krb5_get_init_creds_opt_set_tkt_life(options, 60);
188 krb5_get_init_creds_opt_set_forwardable(options, FALSE);
189 krb5_get_init_creds_opt_set_proxiable(options, FALSE);
191 krb5_data_zero(&result_code_string);
192 krb5_data_zero(&result_string);
194 ret = krb5_get_init_creds_keytab(context,
195 &cpw_cred,
196 ccache->client,
197 ccache->key.keytab,
199 "kadmin/changepw",
200 options);
201 krb5_get_init_creds_opt_free(context, options);
202 if (ret) {
203 kcm_log(0, "Failed to acquire password change credentials "
204 "for principal %s: %s",
205 cpn, krb5_get_err_text(context, ret));
206 goto out;
209 ret = krb5_set_password(context,
210 &cpw_cred,
211 newpw,
212 ccache->client,
213 &result_code,
214 &result_code_string,
215 &result_string);
216 if (ret) {
217 kcm_log(0, "Failed to change password for principal %s: %s",
218 cpn, krb5_get_err_text(context, ret));
219 goto out;
222 if (result_code) {
223 kcm_log(0, "Failed to change password for principal %s: %.*s",
224 cpn,
225 (int)result_string.length,
226 result_string.length > 0 ? (char *)result_string.data : "");
227 goto out;
230 out:
231 krb5_data_free(&result_string);
232 krb5_data_free(&result_code_string);
233 krb5_free_cred_contents(context, &cpw_cred);
235 return ret;
238 struct kcm_keyseed_data {
239 krb5_salt salt;
240 const char *password;
243 static krb5_error_code
244 kcm_password_key_proc(krb5_context context,
245 krb5_enctype etype,
246 krb5_salt salt,
247 krb5_const_pointer keyseed,
248 krb5_keyblock **key)
250 krb5_error_code ret;
251 struct kcm_keyseed_data *s = (struct kcm_keyseed_data *)keyseed;
253 /* we may be called multiple times */
254 krb5_free_salt(context, s->salt);
255 krb5_data_zero(&s->salt.saltvalue);
257 /* stash the salt */
258 s->salt.salttype = salt.salttype;
260 ret = krb5_data_copy(&s->salt.saltvalue,
261 salt.saltvalue.data,
262 salt.saltvalue.length);
263 if (ret)
264 return ret;
266 *key = (krb5_keyblock *)malloc(sizeof(**key));
267 if (*key == NULL) {
268 return ENOMEM;
271 ret = krb5_string_to_key_salt(context, etype, s->password,
272 s->salt, *key);
273 if (ret) {
274 free(*key);
275 *key = NULL;
278 return ret;
281 static krb5_error_code
282 get_salt_and_kvno(krb5_context context,
283 kcm_ccache ccache,
284 krb5_enctype *etypes,
285 char *cpn,
286 char *newpw,
287 krb5_salt *salt,
288 unsigned *kvno)
290 krb5_error_code ret;
291 krb5_creds creds;
292 krb5_ccache_data ccdata;
293 krb5_flags options = 0;
294 krb5_kdc_rep reply;
295 struct kcm_keyseed_data s;
297 memset(&creds, 0, sizeof(creds));
298 memset(&reply, 0, sizeof(reply));
300 s.password = NULL;
301 s.salt.salttype = (int)ETYPE_NULL;
302 krb5_data_zero(&s.salt.saltvalue);
304 *kvno = 0;
305 kcm_internal_ccache(context, ccache, &ccdata);
306 s.password = newpw;
308 /* Do an AS-REQ to determine salt and key version number */
309 ret = krb5_copy_principal(context, ccache->client, &creds.client);
310 if (ret)
311 return ret;
313 /* Yes, get a ticket to ourselves */
314 ret = krb5_copy_principal(context, ccache->client, &creds.server);
315 if (ret) {
316 krb5_free_principal(context, creds.client);
317 return ret;
320 ret = krb5_get_in_tkt(context,
321 options,
322 NULL,
323 etypes,
324 NULL,
325 kcm_password_key_proc,
327 NULL,
328 NULL,
329 &creds,
330 &ccdata,
331 &reply);
332 if (ret) {
333 kcm_log(0, "Failed to get self ticket for principal %s: %s",
334 cpn, krb5_get_err_text(context, ret));
335 krb5_free_salt(context, s.salt);
336 } else {
337 *salt = s.salt; /* retrieve stashed salt */
338 if (reply.kdc_rep.enc_part.kvno != NULL)
339 *kvno = *(reply.kdc_rep.enc_part.kvno);
341 /* ccache may have been modified but it will get trashed anyway */
343 krb5_free_cred_contents(context, &creds);
344 krb5_free_kdc_rep(context, &reply);
346 return ret;
349 static krb5_error_code
350 update_keytab_entry(krb5_context context,
351 kcm_ccache ccache,
352 krb5_enctype etype,
353 char *cpn,
354 char *spn,
355 char *newpw,
356 krb5_salt salt,
357 unsigned kvno)
359 krb5_error_code ret;
360 krb5_keytab_entry entry;
361 krb5_data pw;
363 memset(&entry, 0, sizeof(entry));
365 pw.data = (char *)newpw;
366 pw.length = strlen(newpw);
368 ret = krb5_string_to_key_data_salt(context, etype, pw,
369 salt, &entry.keyblock);
370 if (ret) {
371 kcm_log(0, "String to key conversion failed for principal %s "
372 "and etype %d: %s",
373 cpn, etype, krb5_get_err_text(context, ret));
374 return ret;
377 if (spn == NULL) {
378 ret = krb5_copy_principal(context, ccache->client,
379 &entry.principal);
380 if (ret) {
381 kcm_log(0, "Failed to copy principal name %s: %s",
382 cpn, krb5_get_err_text(context, ret));
383 return ret;
385 } else {
386 ret = krb5_parse_name(context, spn, &entry.principal);
387 if (ret) {
388 kcm_log(0, "Failed to parse SPN alias %s: %s",
389 spn, krb5_get_err_text(context, ret));
390 return ret;
394 entry.vno = kvno;
395 entry.timestamp = time(NULL);
397 ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
398 if (ret) {
399 kcm_log(0, "Failed to update keytab for principal %s "
400 "and etype %d: %s",
401 cpn, etype, krb5_get_err_text(context, ret));
404 krb5_kt_free_entry(context, &entry);
406 return ret;
409 static krb5_error_code
410 update_keytab_entries(krb5_context context,
411 kcm_ccache ccache,
412 krb5_enctype *etypes,
413 char *cpn,
414 char *spn,
415 char *newpw,
416 krb5_salt salt,
417 unsigned kvno)
419 krb5_error_code ret = 0;
420 int i;
422 for (i = 0; etypes[i] != ETYPE_NULL; i++) {
423 ret = update_keytab_entry(context, ccache, etypes[i],
424 cpn, spn, newpw, salt, kvno);
425 if (ret)
426 break;
429 return ret;
432 static void
433 generate_random_pw(krb5_context context,
434 char *buf,
435 size_t bufsiz)
437 unsigned char x[512], *p;
438 size_t i;
440 memset(x, 0, sizeof(x));
441 krb5_generate_random_block(x, sizeof(x));
442 p = x;
444 for (i = 0; i < bufsiz; i++) {
445 while (isprint(*p) == 0)
446 p++;
448 if (p - x >= sizeof(x)) {
449 krb5_generate_random_block(x, sizeof(x));
450 p = x;
452 buf[i] = (char)*p++;
454 buf[bufsiz - 1] = '\0';
455 memset(x, 0, sizeof(x));
458 static krb5_error_code
459 change_pw_and_update_keytab(krb5_context context,
460 kcm_ccache ccache)
462 char newpw[121];
463 krb5_error_code ret;
464 unsigned kvno;
465 krb5_salt salt;
466 krb5_enctype *etypes = NULL;
467 int i;
468 char *cpn = NULL;
469 char **spns = NULL;
471 krb5_data_zero(&salt.saltvalue);
473 ret = krb5_unparse_name(context, ccache->client, &cpn);
474 if (ret) {
475 kcm_log(0, "Failed to unparse name: %s",
476 krb5_get_err_text(context, ret));
477 goto out;
480 ret = krb5_get_default_in_tkt_etypes(context, &etypes);
481 if (ret) {
482 kcm_log(0, "Failed to determine default encryption types: %s",
483 krb5_get_err_text(context, ret));
484 goto out;
487 /* Generate a random password (there is no set keys protocol) */
488 generate_random_pw(context, newpw, sizeof(newpw));
490 /* Change it */
491 ret = change_pw(context, ccache, cpn, newpw);
492 if (ret)
493 goto out;
495 /* Do an AS-REQ to determine salt and key version number */
496 ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
497 &salt, &kvno);
498 if (ret) {
499 kcm_log(0, "Failed to determine salting principal for principal %s: %s",
500 cpn, krb5_get_err_text(context, ret));
501 goto out;
504 /* Add canonical name */
505 ret = update_keytab_entries(context, ccache, etypes, cpn,
506 NULL, newpw, salt, kvno);
507 if (ret)
508 goto out;
510 /* Add SPN aliases, if any */
511 spns = krb5_config_get_strings(context, NULL, "kcm",
512 "system_ccache", "spn_aliases", NULL);
513 if (spns != NULL) {
514 for (i = 0; spns[i] != NULL; i++) {
515 ret = update_keytab_entries(context, ccache, etypes, cpn,
516 spns[i], newpw, salt, kvno);
517 if (ret)
518 goto out;
522 kcm_log(0, "Changed expired password for principal %s in cache %s",
523 cpn, ccache->name);
525 out:
526 if (cpn != NULL)
527 free(cpn);
528 if (spns != NULL)
529 krb5_config_free_strings(spns);
530 if (etypes != NULL)
531 free(etypes);
532 krb5_free_salt(context, salt);
533 memset(newpw, 0, sizeof(newpw));
535 return ret;