fix warnings
[heimdal.git] / kcm / acquire.c
blob75d6f434ec13844e8092e8a99dc1d22cd5837093
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_creds_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_change_password(context,
204 &cpw_cred,
205 newpw,
206 &result_code,
207 &result_code_string,
208 &result_string);
209 if (ret) {
210 kcm_log(0, "Failed to change password for principal %s: %s",
211 cpn, krb5_get_err_text(context, ret));
212 goto out;
215 if (result_code) {
216 kcm_log(0, "Failed to change password for principal %s: %.*s",
217 cpn,
218 (int)result_string.length,
219 result_string.length > 0 ? (char *)result_string.data : "");
220 goto out;
223 out:
224 krb5_data_free(&result_string);
225 krb5_data_free(&result_code_string);
226 krb5_free_cred_contents(context, &cpw_cred);
228 return ret;
231 struct kcm_keyseed_data {
232 krb5_salt salt;
233 const char *password;
236 static krb5_error_code
237 kcm_password_key_proc(krb5_context context,
238 krb5_enctype etype,
239 krb5_salt salt,
240 krb5_const_pointer keyseed,
241 krb5_keyblock **key)
243 krb5_error_code ret;
244 struct kcm_keyseed_data *s = (struct kcm_keyseed_data *)keyseed;
246 /* stash the salt */
247 s->salt.salttype = salt.salttype;
249 ret = krb5_data_copy(&s->salt.saltvalue,
250 salt.saltvalue.data,
251 salt.saltvalue.length);
252 if (ret)
253 return ret;
255 *key = (krb5_keyblock *)malloc(sizeof(**key));
256 if (*key == NULL) {
257 return ENOMEM;
260 ret = krb5_string_to_key_salt(context, etype, s->password,
261 s->salt, *key);
262 if (ret) {
263 free(*key);
264 *key = NULL;
267 return ret;
270 static krb5_error_code
271 get_salt_and_kvno(krb5_context context,
272 kcm_ccache ccache,
273 krb5_enctype *etypes,
274 char *cpn,
275 char *newpw,
276 krb5_salt *salt,
277 unsigned *kvno)
279 krb5_error_code ret;
280 krb5_creds creds;
281 krb5_ccache_data ccdata;
282 krb5_flags options = 0;
283 krb5_kdc_rep reply;
284 struct kcm_keyseed_data s;
286 memset(&creds, 0, sizeof(creds));
287 memset(&reply, 0, sizeof(reply));
288 memset(&s, 0, sizeof(s));
290 *kvno = 0;
291 kcm_internal_ccache(context, ccache, &ccdata);
292 s.password = newpw;
294 /* Do an AS-REQ to determine salt and key version number */
295 ret = krb5_copy_principal(context, ccache->client, &creds.client);
296 if (ret)
297 return ret;
299 /* Yes, get a ticket to ourselves */
300 ret = krb5_copy_principal(context, ccache->client, &creds.server);
301 if (ret) {
302 krb5_free_principal(context, creds.client);
303 return ret;
306 ret = krb5_get_in_tkt(context,
307 options,
308 NULL,
309 etypes,
310 NULL,
311 kcm_password_key_proc,
313 NULL,
314 NULL,
315 &creds,
316 &ccdata,
317 &reply);
318 if (ret) {
319 kcm_log(0, "Failed to get self ticket for principal %s: %s",
320 cpn, krb5_get_err_text(context, ret));
321 krb5_free_salt(context, s.salt);
322 } else {
323 *salt = s.salt; /* retrieve stashed salt */
324 if (reply.kdc_rep.enc_part.kvno != NULL)
325 *kvno = *(reply.kdc_rep.enc_part.kvno);
327 /* ccache may have been modified but it will get trashed anyway */
329 krb5_free_creds_contents(context, &creds);
330 krb5_free_kdc_rep(context, &reply);
332 return ret;
335 static krb5_error_code
336 update_keytab_entry(krb5_context context,
337 kcm_ccache ccache,
338 krb5_enctype etype,
339 char *cpn,
340 char *spn,
341 char *newpw,
342 krb5_salt salt,
343 unsigned kvno)
345 krb5_error_code ret;
346 krb5_keytab_entry entry;
347 krb5_data pw;
349 memset(&entry, 0, sizeof(entry));
351 pw.data = (char *)newpw;
352 pw.length = strlen(newpw);
354 ret = krb5_string_to_key_data_salt(context, etype, pw,
355 salt, &entry.keyblock);
356 if (ret) {
357 kcm_log(0, "String to key conversion failed for principal %s "
358 "and etype %d: %s",
359 cpn, etype, krb5_get_err_text(context, ret));
360 return ret;
363 if (spn == NULL) {
364 ret = krb5_copy_principal(context, ccache->client,
365 &entry.principal);
366 if (ret) {
367 kcm_log(0, "Failed to copy principal name %s: %s",
368 cpn, krb5_get_err_text(context, ret));
369 return ret;
371 } else {
372 ret = krb5_parse_name(context, spn, &entry.principal);
373 if (ret) {
374 kcm_log(0, "Failed to parse SPN alias %s: %s",
375 spn, krb5_get_err_text(context, ret));
376 return ret;
380 entry.vno = kvno;
381 entry.timestamp = time(NULL);
383 ret = krb5_kt_add_entry(context, ccache->key.keytab, &entry);
384 if (ret) {
385 kcm_log(0, "Failed to update keytab for principal %s "
386 "and etype %d: %s",
387 cpn, etype, krb5_get_err_text(context, ret));
390 krb5_kt_free_entry(context, &entry);
392 return ret;
395 static krb5_error_code
396 update_keytab_entries(krb5_context context,
397 kcm_ccache ccache,
398 krb5_enctype *etypes,
399 char *cpn,
400 char *spn,
401 char *newpw,
402 krb5_salt salt,
403 unsigned kvno)
405 krb5_error_code ret = 0;
406 int i;
408 for (i = 0; etypes[i] != ETYPE_NULL; i++) {
409 ret = update_keytab_entry(context, ccache, etypes[i],
410 cpn, spn, newpw, salt, kvno);
411 if (ret)
412 break;
415 return ret;
418 static void
419 generate_random_pw(krb5_context context,
420 unsigned char *buf,
421 size_t bufsiz)
423 unsigned char x[512], *p;
424 size_t i;
426 memset(x, 0, sizeof(x));
427 krb5_generate_random_block(x, sizeof(x));
428 p = x;
430 for (i = 0; i < bufsiz; i++) {
431 while (isprint(*p) == 0)
432 p++;
434 if (p - x >= sizeof(x)) {
435 krb5_generate_random_block(x, sizeof(x));
436 p = x;
438 buf[i] = *p++;
440 buf[bufsiz - 1] = '\0';
441 memset(x, 0, sizeof(x));
444 static krb5_error_code
445 change_pw_and_update_keytab(krb5_context context,
446 kcm_ccache ccache)
448 char newpw[121];
449 krb5_error_code ret;
450 unsigned kvno;
451 krb5_salt salt;
452 krb5_enctype *etypes = NULL;
453 int i;
454 char *cpn = NULL;
455 char **spns = NULL;
457 krb5_data_zero(&salt.saltvalue);
459 ret = krb5_unparse_name(context, ccache->client, &cpn);
460 if (ret) {
461 kcm_log(0, "Failed to unparse name: %s",
462 krb5_get_err_text(context, ret));
463 goto out;
466 ret = krb5_get_default_in_tkt_etypes(context, &etypes);
467 if (ret) {
468 kcm_log(0, "Failed to determine default encryption types: %s",
469 krb5_get_err_text(context, ret));
470 goto out;
473 /* Generate a random password (there is no set keys protocol) */
474 generate_random_pw(context, newpw, sizeof(newpw));
476 /* Change it */
477 ret = change_pw(context, ccache, cpn, newpw);
478 if (ret)
479 goto out;
481 /* Do an AS-REQ to determine salt and key version number */
482 ret = get_salt_and_kvno(context, ccache, etypes, cpn, newpw,
483 &salt, &kvno);
484 if (ret) {
485 kcm_log(0, "Failed to determine salting principal for principal %s: %s",
486 cpn, krb5_get_err_text(context, ret));
487 goto out;
490 /* Add canonical name */
491 ret = update_keytab_entries(context, ccache, etypes, cpn,
492 NULL, newpw, salt, kvno);
493 if (ret)
494 goto out;
496 /* Add SPN aliases, if any */
497 spns = krb5_config_get_strings(context, NULL, "kcm",
498 "spn_aliases", NULL);
499 if (spns != NULL) {
500 for (i = 0; spns[i] != NULL; i++) {
501 ret = update_keytab_entries(context, ccache, etypes, cpn,
502 spns[i], newpw, salt, kvno);
503 if (ret)
504 goto out;
508 kcm_log(0, "Changed expired password for principal %s in cache %s",
509 cpn, ccache->name);
511 out:
512 if (cpn != NULL)
513 free(cpn);
514 if (spns != NULL)
515 krb5_config_free_strings(spns);
516 if (etypes != NULL)
517 free(etypes);
518 krb5_free_salt(context, salt);
519 memset(newpw, 0, sizeof(newpw));
521 return ret;