kinit.c revision 1.1.1.2.10.1 1 /* $NetBSD: kinit.c,v 1.1.1.2.10.1 2017/04/21 16:50:44 bouyer Exp $ */
2
3 /*
4 * Copyright (c) 1997-2007 Kungliga Tekniska Hgskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of the Institute nor the names of its contributors
22 * may be used to endorse or promote products derived from this software
23 * without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38 #include "kuser_locl.h"
39
40 #ifdef __APPLE__
41 #include <Security/Security.h>
42 #endif
43
44 #ifndef NO_NTLM
45 #include <krb5/heimntlm.h>
46 #endif
47
48 #ifndef SIGINFO
49 #define SIGINFO SIGUSR1
50 #endif
51
52 int forwardable_flag = -1;
53 int proxiable_flag = -1;
54 int renewable_flag = -1;
55 int renew_flag = 0;
56 int pac_flag = -1;
57 int validate_flag = 0;
58 int version_flag = 0;
59 int help_flag = 0;
60 int addrs_flag = -1;
61 struct getarg_strings extra_addresses;
62 int anonymous_flag = 0;
63 char *lifetime = NULL;
64 char *renew_life = NULL;
65 char *server_str = NULL;
66 char *cred_cache = NULL;
67 char *start_str = NULL;
68 static int switch_cache_flags = 1;
69 struct getarg_strings etype_str;
70 int use_keytab = 0;
71 char *keytab_str = NULL;
72 static krb5_keytab kt = NULL;
73 int do_afslog = -1;
74 int fcache_version;
75 char *password_file = NULL;
76 char *pk_user_id = NULL;
77 int pk_enterprise_flag = 0;
78 struct hx509_certs_data *ent_user_id = NULL;
79 char *pk_x509_anchors = NULL;
80 int pk_use_enckey = 0;
81 static int canonicalize_flag = 0;
82 static int enterprise_flag = 0;
83 static int ok_as_delegate_flag = 0;
84 static char *fast_armor_cache_string = NULL;
85 static int use_referrals_flag = 0;
86 static int windows_flag = 0;
87 #ifndef NO_NTLM
88 static char *ntlm_domain;
89 #endif
90
91
92 static struct getargs args[] = {
93 /*
94 * used by MIT
95 * a: ~A
96 * V: verbose
97 * F: ~f
98 * P: ~p
99 * C: v4 cache name?
100 * 5:
101 *
102 * old flags
103 * 4:
104 * 9:
105 */
106 { "afslog", 0 , arg_flag, &do_afslog,
107 NP_("obtain afs tokens", ""), NULL },
108
109 { "cache", 'c', arg_string, &cred_cache,
110 NP_("credentials cache", ""), "cachename" },
111
112 { "forwardable", 'F', arg_negative_flag, &forwardable_flag,
113 NP_("get tickets not forwardable", ""), NULL },
114
115 { NULL, 'f', arg_flag, &forwardable_flag,
116 NP_("get forwardable tickets", ""), NULL },
117
118 { "keytab", 't', arg_string, &keytab_str,
119 NP_("keytab to use", ""), "keytabname" },
120
121 { "lifetime", 'l', arg_string, &lifetime,
122 NP_("lifetime of tickets", ""), "time" },
123
124 { "proxiable", 'p', arg_flag, &proxiable_flag,
125 NP_("get proxiable tickets", ""), NULL },
126
127 { "renew", 'R', arg_flag, &renew_flag,
128 NP_("renew TGT", ""), NULL },
129
130 { "renewable", 0, arg_flag, &renewable_flag,
131 NP_("get renewable tickets", ""), NULL },
132
133 { "renewable-life", 'r', arg_string, &renew_life,
134 NP_("renewable lifetime of tickets", ""), "time" },
135
136 { "server", 'S', arg_string, &server_str,
137 NP_("server to get ticket for", ""), "principal" },
138
139 { "start-time", 's', arg_string, &start_str,
140 NP_("when ticket gets valid", ""), "time" },
141
142 { "use-keytab", 'k', arg_flag, &use_keytab,
143 NP_("get key from keytab", ""), NULL },
144
145 { "validate", 'v', arg_flag, &validate_flag,
146 NP_("validate TGT", ""), NULL },
147
148 { "enctypes", 'e', arg_strings, &etype_str,
149 NP_("encryption types to use", ""), "enctypes" },
150
151 { "fcache-version", 0, arg_integer, &fcache_version,
152 NP_("file cache version to create", ""), NULL },
153
154 { "addresses", 'A', arg_negative_flag, &addrs_flag,
155 NP_("request a ticket with no addresses", ""), NULL },
156
157 { "extra-addresses",'a', arg_strings, &extra_addresses,
158 NP_("include these extra addresses", ""), "addresses" },
159
160 { "anonymous", 0, arg_flag, &anonymous_flag,
161 NP_("request an anonymous ticket", ""), NULL },
162
163 { "request-pac", 0, arg_flag, &pac_flag,
164 NP_("request a Windows PAC", ""), NULL },
165
166 { "password-file", 0, arg_string, &password_file,
167 NP_("read the password from a file", ""), NULL },
168
169 { "canonicalize",0, arg_flag, &canonicalize_flag,
170 NP_("canonicalize client principal", ""), NULL },
171
172 { "enterprise",0, arg_flag, &enterprise_flag,
173 NP_("parse principal as a KRB5-NT-ENTERPRISE name", ""), NULL },
174 #ifdef PKINIT
175 { "pk-enterprise", 0, arg_flag, &pk_enterprise_flag,
176 NP_("use enterprise name from certificate", ""), NULL },
177
178 { "pk-user", 'C', arg_string, &pk_user_id,
179 NP_("principal's public/private/certificate identifier", ""), "id" },
180
181 { "x509-anchors", 'D', arg_string, &pk_x509_anchors,
182 NP_("directory with CA certificates", ""), "directory" },
183
184 { "pk-use-enckey", 0, arg_flag, &pk_use_enckey,
185 NP_("Use RSA encrypted reply (instead of DH)", ""), NULL },
186 #endif
187 #ifndef NO_NTLM
188 { "ntlm-domain", 0, arg_string, &ntlm_domain,
189 NP_("NTLM domain", ""), "domain" },
190 #endif
191
192 { "change-default", 0, arg_negative_flag, &switch_cache_flags,
193 NP_("switch the default cache to the new credentials cache", ""), NULL },
194
195 { "ok-as-delegate", 0, arg_flag, &ok_as_delegate_flag,
196 NP_("honor ok-as-delegate on tickets", ""), NULL },
197
198 { "fast-armor-cache", 0, arg_string, &fast_armor_cache_string,
199 NP_("use this credential cache as FAST armor cache", ""), "cache" },
200
201 { "use-referrals", 0, arg_flag, &use_referrals_flag,
202 NP_("only use referrals, no dns canalisation", ""), NULL },
203
204 { "windows", 0, arg_flag, &windows_flag,
205 NP_("get windows behavior", ""), NULL },
206
207 { "version", 0, arg_flag, &version_flag, NULL, NULL },
208 { "help", 0, arg_flag, &help_flag, NULL, NULL }
209 };
210
211 static void
212 usage(int ret)
213 {
214 arg_printusage_i18n(args, sizeof(args)/sizeof(*args), N_("Usage: ", ""),
215 NULL, "[principal [command]]", getarg_i18n);
216 exit(ret);
217 }
218
219 static krb5_error_code
220 get_server(krb5_context context,
221 krb5_principal client,
222 const char *server,
223 krb5_principal *princ)
224 {
225 krb5_const_realm realm;
226 if (server)
227 return krb5_parse_name(context, server, princ);
228
229 realm = krb5_principal_get_realm(context, client);
230 return krb5_make_principal(context, princ, realm,
231 KRB5_TGS_NAME, realm, NULL);
232 }
233
234 static krb5_error_code
235 copy_configs(krb5_context context,
236 krb5_ccache dst,
237 krb5_ccache src,
238 krb5_principal start_ticket_server)
239 {
240 krb5_error_code ret;
241 const char *cfg_names[] = {"realm-config", "FriendlyName", NULL};
242 const char *cfg_names_w_pname[] = {"fast_avail", NULL};
243 krb5_data cfg_data;
244 size_t i;
245
246 for (i = 0; cfg_names[i]; i++) {
247 ret = krb5_cc_get_config(context, src, NULL, cfg_names[i], &cfg_data);
248 if (ret == KRB5_CC_NOTFOUND || ret == KRB5_CC_END) {
249 continue;
250 } else if (ret) {
251 krb5_warn(context, ret, "krb5_cc_get_config");
252 return ret;
253 }
254 ret = krb5_cc_set_config(context, dst, NULL, cfg_names[i], &cfg_data);
255 if (ret)
256 krb5_warn(context, ret, "krb5_cc_set_config");
257 }
258 for (i = 0; start_ticket_server && cfg_names_w_pname[i]; i++) {
259 ret = krb5_cc_get_config(context, src, start_ticket_server,
260 cfg_names_w_pname[i], &cfg_data);
261 if (ret == KRB5_CC_NOTFOUND || ret == KRB5_CC_END) {
262 continue;
263 } else if (ret) {
264 krb5_warn(context, ret, "krb5_cc_get_config");
265 return ret;
266 }
267 ret = krb5_cc_set_config(context, dst, start_ticket_server,
268 cfg_names_w_pname[i], &cfg_data);
269 if (ret && ret != KRB5_CC_NOTFOUND)
270 krb5_warn(context, ret, "krb5_cc_set_config");
271 }
272 /*
273 * We don't copy cc configs for any other principals though (mostly
274 * those are per-target time offsets and the like, so it's bad to
275 * lose them, but hardly the end of the world, and as they may not
276 * expire anyways, it's good to let them go).
277 */
278 return 0;
279 }
280
281 static krb5_error_code
282 renew_validate(krb5_context context,
283 int renew,
284 int validate,
285 krb5_ccache cache,
286 const char *server,
287 krb5_deltat life)
288 {
289 krb5_error_code ret;
290 krb5_ccache tempccache = NULL;
291 krb5_creds in, *out = NULL;
292 krb5_kdc_flags flags;
293
294 memset(&in, 0, sizeof(in));
295
296 ret = krb5_cc_get_principal(context, cache, &in.client);
297 if (ret) {
298 krb5_warn(context, ret, "krb5_cc_get_principal");
299 return ret;
300 }
301 ret = get_server(context, in.client, server, &in.server);
302 if (ret) {
303 krb5_warn(context, ret, "get_server");
304 goto out;
305 }
306
307 if (renew) {
308 /*
309 * no need to check the error here, it's only to be
310 * friendly to the user
311 */
312 krb5_get_credentials(context, KRB5_GC_CACHED, cache, &in, &out);
313 }
314
315 flags.i = 0;
316 flags.b.renewable = flags.b.renew = renew;
317 flags.b.validate = validate;
318
319 if (forwardable_flag != -1)
320 flags.b.forwardable = forwardable_flag;
321 else if (out)
322 flags.b.forwardable = out->flags.b.forwardable;
323
324 if (proxiable_flag != -1)
325 flags.b.proxiable = proxiable_flag;
326 else if (out)
327 flags.b.proxiable = out->flags.b.proxiable;
328
329 if (anonymous_flag)
330 flags.b.request_anonymous = anonymous_flag;
331 if (life)
332 in.times.endtime = time(NULL) + life;
333
334 if (out) {
335 krb5_free_creds(context, out);
336 out = NULL;
337 }
338
339
340 ret = krb5_get_kdc_cred(context,
341 cache,
342 flags,
343 NULL,
344 NULL,
345 &in,
346 &out);
347 if (ret) {
348 krb5_warn(context, ret, "krb5_get_kdc_cred");
349 goto out;
350 }
351
352 ret = krb5_cc_new_unique(context, krb5_cc_get_type(context, cache),
353 NULL, &tempccache);
354 if (ret) {
355 krb5_warn(context, ret, "krb5_cc_new_unique");
356 goto out;
357 }
358
359 ret = krb5_cc_initialize(context, tempccache, in.client);
360 if (ret) {
361 krb5_warn(context, ret, "krb5_cc_initialize");
362 goto out;
363 }
364
365 ret = krb5_cc_store_cred(context, tempccache, out);
366 if (ret) {
367 krb5_warn(context, ret, "krb5_cc_store_cred");
368 goto out;
369 }
370
371 /*
372 * We want to preserve cc configs as some are security-relevant, and
373 * anyways it's the friendly thing to do.
374 */
375 ret = copy_configs(context, tempccache, cache, out->server);
376 if (ret)
377 goto out;
378
379 ret = krb5_cc_move(context, tempccache, cache);
380 if (ret) {
381 krb5_warn(context, ret, "krb5_cc_move");
382 goto out;
383 }
384 tempccache = NULL;
385
386 out:
387 if (tempccache)
388 krb5_cc_close(context, tempccache);
389 if (out)
390 krb5_free_creds(context, out);
391 krb5_free_cred_contents(context, &in);
392 return ret;
393 }
394
395 #ifndef NO_NTLM
396
397 static krb5_error_code
398 store_ntlmkey(krb5_context context, krb5_ccache id,
399 const char *domain, struct ntlm_buf *buf)
400 {
401 krb5_error_code ret;
402 krb5_data data;
403 char *name;
404 int aret;
405
406 ret = krb5_cc_get_config(context, id, NULL, "default-ntlm-domain", &data);
407 if (ret == 0) {
408 krb5_data_free(&data);
409 } else {
410 data.length = strlen(domain);
411 data.data = rk_UNCONST(domain);
412 ret = krb5_cc_set_config(context, id, NULL, "default-ntlm-domain", &data);
413 if (ret != 0)
414 return ret;
415 }
416
417 aret = asprintf(&name, "ntlm-key-%s", domain);
418 if (aret == -1 || name == NULL)
419 return krb5_enomem(context);
420
421 data.length = buf->length;
422 data.data = buf->data;
423
424 ret = krb5_cc_set_config(context, id, NULL, name, &data);
425 free(name);
426 return ret;
427 }
428 #endif
429
430 static krb5_error_code
431 get_new_tickets(krb5_context context,
432 krb5_principal principal,
433 krb5_ccache ccache,
434 krb5_deltat ticket_life,
435 int interactive)
436 {
437 krb5_error_code ret;
438 krb5_creds cred;
439 char passwd[256];
440 krb5_deltat start_time = 0;
441 krb5_deltat renew = 0;
442 const char *renewstr = NULL;
443 krb5_enctype *enctype = NULL;
444 krb5_ccache tempccache = NULL;
445 krb5_init_creds_context ctx = NULL;
446 krb5_get_init_creds_opt *opt = NULL;
447 krb5_prompter_fct prompter = krb5_prompter_posix;
448 #ifndef NO_NTLM
449 struct ntlm_buf ntlmkey;
450 memset(&ntlmkey, 0, sizeof(ntlmkey));
451 #endif
452 passwd[0] = '\0';
453
454 if (!interactive)
455 prompter = NULL;
456
457 if (password_file) {
458 FILE *f;
459
460 if (strcasecmp("STDIN", password_file) == 0)
461 f = stdin;
462 else
463 f = fopen(password_file, "r");
464 if (f == NULL) {
465 krb5_warnx(context, "Failed to open the password file %s",
466 password_file);
467 return errno;
468 }
469
470 if (fgets(passwd, sizeof(passwd), f) == NULL) {
471 krb5_warnx(context, N_("Failed to read password from file %s", ""),
472 password_file);
473 fclose(f);
474 return EINVAL; /* XXX Need a better error */
475 }
476 if (f != stdin)
477 fclose(f);
478 passwd[strcspn(passwd, "\n")] = '\0';
479 }
480
481 #ifdef __APPLE__
482 if (passwd[0] == '\0') {
483 const char *realm;
484 OSStatus osret;
485 UInt32 length;
486 void *buffer;
487 char *name;
488
489 realm = krb5_principal_get_realm(context, principal);
490
491 ret = krb5_unparse_name_flags(context, principal,
492 KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name);
493 if (ret)
494 goto nopassword;
495
496 osret = SecKeychainFindGenericPassword(NULL, strlen(realm), realm,
497 strlen(name), name,
498 &length, &buffer, NULL);
499 free(name);
500 if (osret == noErr && length < sizeof(passwd) - 1) {
501 memcpy(passwd, buffer, length);
502 passwd[length] = '\0';
503 }
504 nopassword:
505 do { } while(0);
506 }
507 #endif
508
509 memset(&cred, 0, sizeof(cred));
510
511 ret = krb5_get_init_creds_opt_alloc(context, &opt);
512 if (ret) {
513 krb5_warn(context, ret, "krb5_get_init_creds_opt_alloc");
514 goto out;
515 }
516
517 krb5_get_init_creds_opt_set_default_flags(context, "kinit",
518 krb5_principal_get_realm(context, principal), opt);
519
520 if (forwardable_flag != -1)
521 krb5_get_init_creds_opt_set_forwardable(opt, forwardable_flag);
522 if (proxiable_flag != -1)
523 krb5_get_init_creds_opt_set_proxiable(opt, proxiable_flag);
524 if (anonymous_flag)
525 krb5_get_init_creds_opt_set_anonymous(opt, anonymous_flag);
526 if (pac_flag != -1)
527 krb5_get_init_creds_opt_set_pac_request(context, opt,
528 pac_flag ? TRUE : FALSE);
529 if (canonicalize_flag)
530 krb5_get_init_creds_opt_set_canonicalize(context, opt, TRUE);
531 if (pk_enterprise_flag || enterprise_flag || canonicalize_flag || windows_flag)
532 krb5_get_init_creds_opt_set_win2k(context, opt, TRUE);
533 if (pk_user_id || ent_user_id || anonymous_flag) {
534 ret = krb5_get_init_creds_opt_set_pkinit(context, opt,
535 principal,
536 pk_user_id,
537 pk_x509_anchors,
538 NULL,
539 NULL,
540 pk_use_enckey ? 2 : 0 |
541 anonymous_flag ? 4 : 0,
542 prompter,
543 NULL,
544 passwd);
545 if (ret) {
546 krb5_warn(context, ret, "krb5_get_init_creds_opt_set_pkinit");
547 goto out;
548 }
549 if (ent_user_id)
550 krb5_get_init_creds_opt_set_pkinit_user_certs(context, opt, ent_user_id);
551 }
552
553 if (addrs_flag != -1)
554 krb5_get_init_creds_opt_set_addressless(context, opt,
555 addrs_flag ? FALSE : TRUE);
556
557 if (renew_life == NULL && renewable_flag)
558 renewstr = "6 months";
559 if (renew_life)
560 renewstr = renew_life;
561 if (renewstr) {
562 renew = parse_time(renewstr, "s");
563 if (renew < 0)
564 errx(1, "unparsable time: %s", renewstr);
565
566 krb5_get_init_creds_opt_set_renew_life(opt, renew);
567 }
568
569 if (ticket_life != 0)
570 krb5_get_init_creds_opt_set_tkt_life(opt, ticket_life);
571
572 if (start_str) {
573 int tmp = parse_time(start_str, "s");
574 if (tmp < 0)
575 errx(1, N_("unparsable time: %s", ""), start_str);
576
577 start_time = tmp;
578 }
579
580 if (etype_str.num_strings) {
581 int i;
582
583 enctype = malloc(etype_str.num_strings * sizeof(*enctype));
584 if (enctype == NULL)
585 errx(1, "out of memory");
586 for(i = 0; i < etype_str.num_strings; i++) {
587 ret = krb5_string_to_enctype(context,
588 etype_str.strings[i],
589 &enctype[i]);
590 if (ret)
591 errx(1, "unrecognized enctype: %s", etype_str.strings[i]);
592 }
593 krb5_get_init_creds_opt_set_etype_list(opt, enctype,
594 etype_str.num_strings);
595 }
596
597 ret = krb5_init_creds_init(context, principal, prompter, NULL, start_time, opt, &ctx);
598 if (ret) {
599 krb5_warn(context, ret, "krb5_init_creds_init");
600 goto out;
601 }
602
603 if (server_str) {
604 ret = krb5_init_creds_set_service(context, ctx, server_str);
605 if (ret) {
606 krb5_warn(context, ret, "krb5_init_creds_set_service");
607 goto out;
608 }
609 }
610
611 if (fast_armor_cache_string) {
612 krb5_ccache fastid;
613
614 ret = krb5_cc_resolve(context, fast_armor_cache_string, &fastid);
615 if (ret) {
616 krb5_warn(context, ret, "krb5_cc_resolve(FAST cache)");
617 goto out;
618 }
619
620 ret = krb5_init_creds_set_fast_ccache(context, ctx, fastid);
621 if (ret) {
622 krb5_warn(context, ret, "krb5_init_creds_set_fast_ccache");
623 goto out;
624 }
625 }
626
627 if (use_keytab || keytab_str) {
628 ret = krb5_init_creds_set_keytab(context, ctx, kt);
629 if (ret) {
630 krb5_warn(context, ret, "krb5_init_creds_set_keytab");
631 goto out;
632 }
633 } else if (pk_user_id || ent_user_id || anonymous_flag) {
634
635 } else if (!interactive && passwd[0] == '\0') {
636 static int already_warned = 0;
637
638 if (!already_warned)
639 krb5_warnx(context, "Not interactive, failed to get "
640 "initial ticket");
641 krb5_get_init_creds_opt_free(context, opt);
642 already_warned = 1;
643 return 0;
644 } else {
645
646 if (passwd[0] == '\0') {
647 char *p, *prompt;
648 int aret = 0;
649
650 ret = krb5_unparse_name(context, principal, &p);
651 if (ret)
652 errx(1, "failed to generate passwd prompt: not enough memory");
653
654 aret = asprintf(&prompt, N_("%s's Password: ", ""), p);
655 free(p);
656 if (aret == -1)
657 errx(1, "failed to generate passwd prompt: not enough memory");
658
659 if (UI_UTIL_read_pw_string(passwd, sizeof(passwd)-1, prompt, 0)){
660 memset(passwd, 0, sizeof(passwd));
661 errx(1, "failed to read password");
662 }
663 free(prompt);
664 }
665
666 if (passwd[0]) {
667 ret = krb5_init_creds_set_password(context, ctx, passwd);
668 if (ret) {
669 krb5_warn(context, ret, "krb5_init_creds_set_password");
670 goto out;
671 }
672 }
673 }
674
675 ret = krb5_init_creds_get(context, ctx);
676
677 #ifndef NO_NTLM
678 if (ntlm_domain && passwd[0])
679 heim_ntlm_nt_key(passwd, &ntlmkey);
680 #endif
681 memset(passwd, 0, sizeof(passwd));
682
683 switch(ret){
684 case 0:
685 break;
686 case KRB5_LIBOS_PWDINTR: /* don't print anything if it was just C-c:ed */
687 exit(1);
688 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
689 case KRB5KRB_AP_ERR_MODIFIED:
690 case KRB5KDC_ERR_PREAUTH_FAILED:
691 case KRB5_GET_IN_TKT_LOOP:
692 krb5_warnx(context, N_("Password incorrect", ""));
693 goto out;
694 case KRB5KRB_AP_ERR_V4_REPLY:
695 krb5_warnx(context, N_("Looks like a Kerberos 4 reply", ""));
696 goto out;
697 case KRB5KDC_ERR_KEY_EXPIRED:
698 krb5_warnx(context, N_("Password expired", ""));
699 goto out;
700 default:
701 krb5_warn(context, ret, "krb5_get_init_creds");
702 goto out;
703 }
704
705 krb5_process_last_request(context, opt, ctx);
706
707 ret = krb5_init_creds_get_creds(context, ctx, &cred);
708 if (ret) {
709 krb5_warn(context, ret, "krb5_init_creds_get_creds");
710 goto out;
711 }
712
713 if (ticket_life != 0) {
714 if (labs(cred.times.endtime - cred.times.starttime - ticket_life) > 30) {
715 char life[64];
716 unparse_time_approx(cred.times.endtime - cred.times.starttime,
717 life, sizeof(life));
718 krb5_warnx(context, N_("NOTICE: ticket lifetime is %s", ""), life);
719 }
720 }
721 if (renew_life) {
722 if (labs(cred.times.renew_till - cred.times.starttime - renew) > 30) {
723 char life[64];
724 unparse_time_approx(cred.times.renew_till - cred.times.starttime,
725 life, sizeof(life));
726 krb5_warnx(context,
727 N_("NOTICE: ticket renewable lifetime is %s", ""),
728 life);
729 }
730 }
731 krb5_free_cred_contents(context, &cred);
732
733 ret = krb5_cc_new_unique(context, krb5_cc_get_type(context, ccache),
734 NULL, &tempccache);
735 if (ret) {
736 krb5_warn(context, ret, "krb5_cc_new_unique");
737 goto out;
738 }
739
740 ret = krb5_init_creds_store(context, ctx, tempccache);
741 if (ret) {
742 krb5_warn(context, ret, "krb5_init_creds_store");
743 goto out;
744 }
745
746 krb5_init_creds_free(context, ctx);
747 ctx = NULL;
748
749 ret = krb5_cc_move(context, tempccache, ccache);
750 if (ret) {
751 krb5_warn(context, ret, "krb5_cc_move");
752 goto out;
753 }
754 tempccache = NULL;
755
756 if (switch_cache_flags)
757 krb5_cc_switch(context, ccache);
758
759 #ifndef NO_NTLM
760 if (ntlm_domain && ntlmkey.data)
761 store_ntlmkey(context, ccache, ntlm_domain, &ntlmkey);
762 #endif
763
764 if (ok_as_delegate_flag || windows_flag || use_referrals_flag) {
765 unsigned char d = 0;
766 krb5_data data;
767
768 if (ok_as_delegate_flag || windows_flag)
769 d |= 1;
770 if (use_referrals_flag || windows_flag)
771 d |= 2;
772
773 data.length = 1;
774 data.data = &d;
775
776 krb5_cc_set_config(context, ccache, NULL, "realm-config", &data);
777 }
778
779 out:
780 krb5_get_init_creds_opt_free(context, opt);
781 if (ctx)
782 krb5_init_creds_free(context, ctx);
783 if (tempccache)
784 krb5_cc_close(context, tempccache);
785
786 if (enctype)
787 free(enctype);
788
789 return ret;
790 }
791
792 static time_t
793 ticket_lifetime(krb5_context context, krb5_ccache cache, krb5_principal client,
794 const char *server, time_t *renew)
795 {
796 krb5_creds in_cred, *cred;
797 krb5_error_code ret;
798 time_t timeout;
799 time_t curtime;
800
801 memset(&in_cred, 0, sizeof(in_cred));
802
803 if (renew != NULL)
804 *renew = 0;
805
806 ret = krb5_cc_get_principal(context, cache, &in_cred.client);
807 if (ret) {
808 krb5_warn(context, ret, "krb5_cc_get_principal");
809 return 0;
810 }
811 ret = get_server(context, in_cred.client, server, &in_cred.server);
812 if (ret) {
813 krb5_free_principal(context, in_cred.client);
814 krb5_warn(context, ret, "get_server");
815 return 0;
816 }
817
818 ret = krb5_get_credentials(context, KRB5_GC_CACHED,
819 cache, &in_cred, &cred);
820 krb5_free_principal(context, in_cred.client);
821 krb5_free_principal(context, in_cred.server);
822 if (ret) {
823 krb5_warn(context, ret, "krb5_get_credentials");
824 return 0;
825 }
826 curtime = time(NULL);
827 timeout = cred->times.endtime - curtime;
828 if (timeout < 0)
829 timeout = 0;
830 if (renew) {
831 *renew = cred->times.renew_till - curtime;
832 if (*renew < 0)
833 *renew = 0;
834 }
835 krb5_free_creds(context, cred);
836 return timeout;
837 }
838
839 static time_t expire;
840
841 static char siginfo_msg[1024] = "No credentials\n";
842
843 static void
844 update_siginfo_msg(time_t exp, const char *srv)
845 {
846 /* Note that exp is relative time */
847 memset(siginfo_msg, 0, sizeof(siginfo_msg));
848 memcpy(&siginfo_msg, "Updating...\n", sizeof("Updating...\n"));
849 if (exp) {
850 if (srv == NULL) {
851 snprintf(siginfo_msg, sizeof(siginfo_msg),
852 N_("kinit: TGT expires in %llu seconds\n", ""),
853 (unsigned long long)expire);
854 } else {
855 snprintf(siginfo_msg, sizeof(siginfo_msg),
856 N_("kinit: Ticket for %s expired\n", ""), srv);
857 }
858 return;
859 }
860
861 /* Expired creds */
862 if (srv == NULL) {
863 snprintf(siginfo_msg, sizeof(siginfo_msg),
864 N_("kinit: TGT expired\n", ""));
865 } else {
866 snprintf(siginfo_msg, sizeof(siginfo_msg),
867 N_("kinit: Ticket for %s expired\n", ""), srv);
868 }
869 }
870
871 #ifdef HAVE_SIGACTION
872 static void
873 handle_siginfo(int sig)
874 {
875 struct iovec iov[2];
876
877 iov[0].iov_base = rk_UNCONST(siginfo_msg);
878 iov[0].iov_len = strlen(siginfo_msg);
879 iov[1].iov_base = "\n";
880 iov[1].iov_len = 1;
881
882 writev(STDERR_FILENO, iov, sizeof(iov)/sizeof(iov[0]));
883 }
884 #endif
885
886 struct renew_ctx {
887 krb5_context context;
888 krb5_ccache ccache;
889 krb5_principal principal;
890 krb5_deltat ticket_life;
891 krb5_deltat timeout;
892 };
893
894 static time_t
895 renew_func(void *ptr)
896 {
897 krb5_error_code ret;
898 struct renew_ctx *ctx = ptr;
899 time_t renew_expire;
900 static time_t exp_delay = 1;
901
902 /*
903 * NOTE: We count on the ccache implementation to notice changes to the
904 * actual ccache filesystem/whatever objects. There should be no ccache
905 * types for which this is not the case, but it might not hurt to
906 * re-krb5_cc_resolve() after each successful renew_validate()/
907 * get_new_tickets() call.
908 */
909
910 expire = ticket_lifetime(ctx->context, ctx->ccache, ctx->principal,
911 server_str, &renew_expire);
912
913 /*
914 * When a keytab is available to obtain new tickets, if we are within
915 * half of the original ticket lifetime of the renew limit, get a new
916 * TGT instead of renewing the existing TGT. Note, ctx->ticket_life
917 * is zero by default (without a '-l' option) and cannot be used to
918 * set the time scale on which we decide whether we're "close to the
919 * renew limit".
920 */
921 if (use_keytab || keytab_str)
922 expire += ctx->timeout;
923 if (renew_expire > expire) {
924 ret = renew_validate(ctx->context, 1, validate_flag, ctx->ccache,
925 server_str, ctx->ticket_life);
926 } else {
927 ret = get_new_tickets(ctx->context, ctx->principal, ctx->ccache,
928 ctx->ticket_life, 0);
929 }
930 expire = ticket_lifetime(ctx->context, ctx->ccache, ctx->principal,
931 server_str, &renew_expire);
932
933 #ifndef NO_AFS
934 if (ret == 0 && server_str == NULL && do_afslog && k_hasafs())
935 krb5_afslog(ctx->context, ctx->ccache, NULL, NULL);
936 #endif
937
938 update_siginfo_msg(expire, server_str);
939
940 /*
941 * If our tickets have expired and we been able to either renew them
942 * or obtain new tickets, then we still call this function but we use
943 * an exponential backoff. This should take care of the case where
944 * we are using stored credentials but the KDC has been unavailable
945 * for some reason...
946 */
947
948 if (expire < 1) {
949 /*
950 * We can't ask to keep spamming stderr but not syslog, so we warn
951 * only once.
952 */
953 if (exp_delay == 1) {
954 krb5_warnx(ctx->context, N_("NOTICE: Could not renew/refresh "
955 "tickets", ""));
956 }
957 if (exp_delay < 7200)
958 exp_delay += exp_delay / 2 + 1;
959 return exp_delay;
960 }
961 exp_delay = 1;
962
963 return expire / 2 + 1;
964 }
965
966 static void
967 set_princ_realm(krb5_context context,
968 krb5_principal principal,
969 const char *realm)
970 {
971 krb5_error_code ret;
972
973 if ((ret = krb5_principal_set_realm(context, principal, realm)) != 0)
974 krb5_err(context, 1, ret, "krb5_principal_set_realm");
975 }
976
977 static void
978 parse_name_realm(krb5_context context,
979 const char *name,
980 int flags,
981 const char *realm,
982 krb5_principal *princ)
983 {
984 krb5_error_code ret;
985
986 if (realm)
987 flags |= KRB5_PRINCIPAL_PARSE_NO_DEF_REALM;
988 if ((ret = krb5_parse_name_flags(context, name, flags, princ)) != 0)
989 krb5_err(context, 1, ret, "krb5_parse_name_flags");
990 if (realm && krb5_principal_get_realm(context, *princ) == NULL)
991 set_princ_realm(context, *princ, realm);
992 }
993
994 static char *
995 get_default_realm(krb5_context context)
996 {
997 char *realm;
998 krb5_error_code ret;
999
1000 if ((ret = krb5_get_default_realm(context, &realm)) != 0)
1001 krb5_err(context, 1, ret, "krb5_get_default_realm");
1002 return realm;
1003 }
1004
1005 static void
1006 get_default_principal(krb5_context context, krb5_principal *princ)
1007 {
1008 krb5_error_code ret;
1009
1010 if ((ret = krb5_get_default_principal(context, princ)) != 0)
1011 krb5_err(context, 1, ret, "krb5_get_default_principal");
1012 }
1013
1014 static char *
1015 get_user_realm(krb5_context context)
1016 {
1017 krb5_error_code ret;
1018 char *user_realm = NULL;
1019
1020 /*
1021 * If memory allocation fails, we don't try to use the wrong realm,
1022 * that will trigger misleading error messages complicate support.
1023 */
1024 krb5_appdefault_string(context, "kinit", NULL, "user_realm", "",
1025 &user_realm);
1026 if (user_realm == NULL) {
1027 ret = krb5_enomem(context);
1028 krb5_err(context, 1, ret, "krb5_appdefault_string");
1029 }
1030
1031 if (*user_realm == 0) {
1032 free(user_realm);
1033 user_realm = NULL;
1034 }
1035
1036 return user_realm;
1037 }
1038
1039 static void
1040 get_princ(krb5_context context, krb5_principal *principal, const char *name)
1041 {
1042 krb5_error_code ret;
1043 krb5_principal tmp;
1044 int parseflags = 0;
1045 char *user_realm;
1046
1047 if (name == NULL) {
1048 krb5_ccache ccache;
1049
1050 /* If credential cache provides a client principal, use that. */
1051 if (krb5_cc_default(context, &ccache) == 0) {
1052 ret = krb5_cc_get_principal(context, ccache, principal);
1053 krb5_cc_close(context, ccache);
1054 if (ret == 0)
1055 return;
1056 }
1057 }
1058
1059 user_realm = get_user_realm(context);
1060
1061 if (name) {
1062 if (canonicalize_flag || enterprise_flag)
1063 parseflags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE;
1064
1065 parse_name_realm(context, name, parseflags, user_realm, &tmp);
1066
1067 if (user_realm && krb5_principal_get_num_comp(context, tmp) > 1) {
1068 /* Principal is instance qualified, reparse with default realm. */
1069 krb5_free_principal(context, tmp);
1070 parse_name_realm(context, name, parseflags, NULL, principal);
1071 } else {
1072 *principal = tmp;
1073 }
1074 } else {
1075 get_default_principal(context, principal);
1076 if (user_realm)
1077 set_princ_realm(context, *principal, user_realm);
1078 }
1079
1080 if (user_realm)
1081 free(user_realm);
1082 }
1083
1084 static void
1085 get_princ_kt(krb5_context context,
1086 krb5_principal *principal,
1087 char *name)
1088 {
1089 krb5_error_code ret;
1090 krb5_principal tmp;
1091 krb5_ccache ccache;
1092 krb5_kt_cursor cursor;
1093 krb5_keytab_entry entry;
1094 char *def_realm;
1095
1096 if (name == NULL) {
1097 /*
1098 * If the credential cache exists and specifies a client principal,
1099 * use that.
1100 */
1101 if (krb5_cc_default(context, &ccache) == 0) {
1102 ret = krb5_cc_get_principal(context, ccache, principal);
1103 krb5_cc_close(context, ccache);
1104 if (ret == 0)
1105 return;
1106 }
1107 }
1108
1109 if (name) {
1110 /* If the principal specifies an explicit realm, just use that. */
1111 int parseflags = KRB5_PRINCIPAL_PARSE_NO_DEF_REALM;
1112
1113 parse_name_realm(context, name, parseflags, NULL, &tmp);
1114 if (krb5_principal_get_realm(context, tmp) != NULL) {
1115 *principal = tmp;
1116 return;
1117 }
1118 } else {
1119 /* Otherwise, search keytab for bare name of the default principal. */
1120 get_default_principal(context, &tmp);
1121 set_princ_realm(context, tmp, NULL);
1122 }
1123
1124 def_realm = get_default_realm(context);
1125
1126 ret = krb5_kt_start_seq_get(context, kt, &cursor);
1127 if (ret)
1128 krb5_err(context, 1, ret, "krb5_kt_start_seq_get");
1129
1130 while (ret == 0 &&
1131 krb5_kt_next_entry(context, kt, &entry, &cursor) == 0) {
1132 const char *realm;
1133
1134 if (!krb5_principal_compare_any_realm(context, tmp, entry.principal))
1135 continue;
1136 if (*principal &&
1137 krb5_principal_compare(context, *principal, entry.principal))
1138 continue;
1139 /* The default realm takes precedence */
1140 realm = krb5_principal_get_realm(context, entry.principal);
1141 if (*principal && strcmp(def_realm, realm) == 0) {
1142 krb5_free_principal(context, *principal);
1143 ret = krb5_copy_principal(context, entry.principal, principal);
1144 break;
1145 }
1146 if (!*principal)
1147 ret = krb5_copy_principal(context, entry.principal, principal);
1148 }
1149 if (ret != 0 || (ret = krb5_kt_end_seq_get(context, kt, &cursor)) != 0)
1150 krb5_err(context, 1, ret, "get_princ_kt");
1151 if (!*principal) {
1152 if (name)
1153 parse_name_realm(context, name, 0, NULL, principal);
1154 else
1155 krb5_err(context, 1, KRB5_CC_NOTFOUND, "get_princ_kt");
1156 }
1157
1158 krb5_free_principal(context, tmp);
1159 free(def_realm);
1160 }
1161
1162 static krb5_error_code
1163 get_switched_ccache(krb5_context context,
1164 const char * type,
1165 krb5_principal principal,
1166 krb5_ccache *ccache)
1167 {
1168 krb5_error_code ret;
1169
1170 #ifdef _WIN32
1171 if (strcmp(type, "API") == 0) {
1172 /*
1173 * Windows stores the default ccache name in the
1174 * registry which is shared across multiple logon
1175 * sessions for the same user. The API credential
1176 * cache provides a unique name space per logon
1177 * session. Therefore there is no need to generate
1178 * a unique ccache name. Instead use the principal
1179 * name. This provides a friendlier user experience.
1180 */
1181 char * unparsed_name;
1182 char * cred_cache;
1183
1184 ret = krb5_unparse_name(context, principal,
1185 &unparsed_name);
1186 if (ret)
1187 krb5_err(context, 1, ret,
1188 N_("unparsing principal name", ""));
1189
1190 ret = asprintf(&cred_cache, "API:%s", unparsed_name);
1191 krb5_free_unparsed_name(context, unparsed_name);
1192 if (ret == -1 || cred_cache == NULL)
1193 krb5_err(context, 1, ret,
1194 N_("building credential cache name", ""));
1195
1196 ret = krb5_cc_resolve(context, cred_cache, ccache);
1197 free(cred_cache);
1198 } else if (strcmp(type, "MSLSA") == 0) {
1199 /*
1200 * The Windows MSLSA cache when it is writeable
1201 * stores tickets for multiple client principals
1202 * in a single credential cache.
1203 */
1204 ret = krb5_cc_resolve(context, "MSLSA:", ccache);
1205 } else {
1206 ret = krb5_cc_new_unique(context, type, NULL, ccache);
1207 }
1208 #else /* !_WIN32 */
1209 ret = krb5_cc_new_unique(context, type, NULL, ccache);
1210 #endif /* _WIN32 */
1211
1212 return ret;
1213 }
1214
1215 int
1216 main(int argc, char **argv)
1217 {
1218 krb5_error_code ret;
1219 krb5_context context;
1220 krb5_ccache ccache;
1221 krb5_principal principal = NULL;
1222 int optidx = 0;
1223 krb5_deltat ticket_life = 0;
1224 #ifdef HAVE_SIGACTION
1225 struct sigaction sa;
1226 #endif
1227
1228 setprogname(argv[0]);
1229
1230 setlocale(LC_ALL, "");
1231 bindtextdomain("heimdal_kuser", HEIMDAL_LOCALEDIR);
1232 textdomain("heimdal_kuser");
1233
1234 ret = krb5_init_context(&context);
1235 if (ret == KRB5_CONFIG_BADFORMAT)
1236 errx(1, "krb5_init_context failed to parse configuration file");
1237 else if (ret)
1238 errx(1, "krb5_init_context failed: %d", ret);
1239
1240 if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
1241 usage(1);
1242
1243 if (help_flag)
1244 usage(0);
1245
1246 if (version_flag) {
1247 print_version(NULL);
1248 exit(0);
1249 }
1250
1251 argc -= optidx;
1252 argv += optidx;
1253
1254 /*
1255 * Open the keytab now, we use the keytab to determine the principal's
1256 * realm when the requested principal has no realm.
1257 */
1258 if (use_keytab || keytab_str) {
1259 if (keytab_str)
1260 ret = krb5_kt_resolve(context, keytab_str, &kt);
1261 else
1262 ret = krb5_kt_default(context, &kt);
1263 if (ret)
1264 krb5_err(context, 1, ret, "resolving keytab");
1265 }
1266
1267 if (pk_enterprise_flag) {
1268 ret = krb5_pk_enterprise_cert(context, pk_user_id,
1269 argv[0], &principal,
1270 &ent_user_id);
1271 if (ret)
1272 krb5_err(context, 1, ret, "krb5_pk_enterprise_certs");
1273
1274 pk_user_id = NULL;
1275
1276 } else if (anonymous_flag) {
1277
1278 ret = krb5_make_principal(context, &principal, argv[0],
1279 KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME,
1280 NULL);
1281 if (ret)
1282 krb5_err(context, 1, ret, "krb5_make_principal");
1283 krb5_principal_set_type(context, principal, KRB5_NT_WELLKNOWN);
1284
1285 } else if (use_keytab || keytab_str) {
1286 get_princ_kt(context, &principal, argv[0]);
1287 } else {
1288 get_princ(context, &principal, argv[0]);
1289 }
1290
1291 if (fcache_version)
1292 krb5_set_fcache_version(context, fcache_version);
1293
1294 if (renewable_flag == -1)
1295 /* this seems somewhat pointless, but whatever */
1296 krb5_appdefault_boolean(context, "kinit",
1297 krb5_principal_get_realm(context, principal),
1298 "renewable", FALSE, &renewable_flag);
1299 if (do_afslog == -1)
1300 krb5_appdefault_boolean(context, "kinit",
1301 krb5_principal_get_realm(context, principal),
1302 "afslog", TRUE, &do_afslog);
1303
1304 if (cred_cache)
1305 ret = krb5_cc_resolve(context, cred_cache, &ccache);
1306 else {
1307 if (argc > 1) {
1308 char s[1024];
1309 ret = krb5_cc_new_unique(context, NULL, NULL, &ccache);
1310 if (ret)
1311 krb5_err(context, 1, ret, "creating cred cache");
1312 snprintf(s, sizeof(s), "%s:%s",
1313 krb5_cc_get_type(context, ccache),
1314 krb5_cc_get_name(context, ccache));
1315 setenv("KRB5CCNAME", s, 1);
1316 } else {
1317 ret = krb5_cc_cache_match(context, principal, &ccache);
1318 if (ret) {
1319 const char *type;
1320 ret = krb5_cc_default(context, &ccache);
1321 if (ret)
1322 krb5_err(context, 1, ret,
1323 N_("resolving credentials cache", ""));
1324
1325 /*
1326 * Check if the type support switching, and we do,
1327 * then do that instead over overwriting the current
1328 * default credential
1329 */
1330 type = krb5_cc_get_type(context, ccache);
1331 if (krb5_cc_support_switch(context, type)) {
1332 krb5_cc_close(context, ccache);
1333 ret = get_switched_ccache(context, type, principal,
1334 &ccache);
1335 }
1336 }
1337 }
1338 }
1339 if (ret)
1340 krb5_err(context, 1, ret, N_("resolving credentials cache", ""));
1341
1342 #ifndef NO_AFS
1343 if (argc > 1 && k_hasafs())
1344 k_setpag();
1345 #endif
1346
1347 if (lifetime) {
1348 int tmp = parse_time(lifetime, "s");
1349 if (tmp < 0)
1350 errx(1, N_("unparsable time: %s", ""), lifetime);
1351
1352 ticket_life = tmp;
1353 }
1354
1355 if (addrs_flag == 0 && extra_addresses.num_strings > 0)
1356 krb5_errx(context, 1,
1357 N_("specifying both extra addresses and "
1358 "no addresses makes no sense", ""));
1359 {
1360 int i;
1361 krb5_addresses addresses;
1362 memset(&addresses, 0, sizeof(addresses));
1363 for(i = 0; i < extra_addresses.num_strings; i++) {
1364 ret = krb5_parse_address(context, extra_addresses.strings[i],
1365 &addresses);
1366 if (ret == 0) {
1367 krb5_add_extra_addresses(context, &addresses);
1368 krb5_free_addresses(context, &addresses);
1369 }
1370 }
1371 free_getarg_strings(&extra_addresses);
1372 }
1373
1374 if (renew_flag || validate_flag) {
1375 ret = renew_validate(context, renew_flag, validate_flag,
1376 ccache, server_str, ticket_life);
1377
1378 #ifndef NO_AFS
1379 if (ret == 0 && server_str == NULL && do_afslog && k_hasafs())
1380 krb5_afslog(context, ccache, NULL, NULL);
1381 #endif
1382
1383 exit(ret != 0);
1384 }
1385
1386 ret = get_new_tickets(context, principal, ccache, ticket_life, 1);
1387 if (ret)
1388 exit(1);
1389
1390 #ifndef NO_AFS
1391 if (ret == 0 && server_str == NULL && do_afslog && k_hasafs())
1392 krb5_afslog(context, ccache, NULL, NULL);
1393 #endif
1394
1395 if (argc > 1) {
1396 struct renew_ctx ctx;
1397 time_t timeout;
1398
1399 timeout = ticket_lifetime(context, ccache, principal,
1400 server_str, NULL) / 2;
1401
1402 ctx.context = context;
1403 ctx.ccache = ccache;
1404 ctx.principal = principal;
1405 ctx.ticket_life = ticket_life;
1406 ctx.timeout = timeout;
1407
1408 #ifdef HAVE_SIGACTION
1409 memset(&sa, 0, sizeof(sa));
1410 sigemptyset(&sa.sa_mask);
1411 sa.sa_handler = handle_siginfo;
1412
1413 sigaction(SIGINFO, &sa, NULL);
1414 #endif
1415
1416 ret = simple_execvp_timed(argv[1], argv+1,
1417 renew_func, &ctx, timeout);
1418 #define EX_NOEXEC 126
1419 #define EX_NOTFOUND 127
1420 if (ret == EX_NOEXEC)
1421 krb5_warnx(context, N_("permission denied: %s", ""), argv[1]);
1422 else if (ret == EX_NOTFOUND)
1423 krb5_warnx(context, N_("command not found: %s", ""), argv[1]);
1424
1425 krb5_cc_destroy(context, ccache);
1426 #ifndef NO_AFS
1427 if (k_hasafs())
1428 k_unlog();
1429 #endif
1430 } else {
1431 krb5_cc_close(context, ccache);
1432 ret = 0;
1433 }
1434 krb5_free_principal(context, principal);
1435 if (kt)
1436 krb5_kt_close(context, kt);
1437 krb5_free_context(context);
1438 return ret;
1439 }
1440