1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 * Copyright (C) 1998 by the FundsXpress, INC.
4 *
5 * All rights reserved.
6 *
7 * Export of this software from the United States of America may require
8 * a specific license from the United States Government. It is the
9 * responsibility of any person or organization contemplating export to
10 * obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of FundsXpress. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. FundsXpress makes no representations about the suitability of
20 * this software for any purpose. It is provided "as is" without express
21 * or implied warranty.
22 *
23 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
25 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
26 */
27
28 #include "k5-platform.h"
29 #include "k5-buf.h"
30 #include "k5-base64.h"
31 #include <locale.h>
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 #include <string.h>
36 #include <ctype.h>
37
38 static char *prog;
39 static int quiet = 0;
40
41 static void
xusage()42 xusage()
43 {
44 fprintf(stderr, _("usage: %s [-c ccache] [-e etype] [-k keytab] [-q] "
45 "[-u | -S sname]\n"
46 "\t[[{-F cert_file | {-I | -U} for_user} [-P]] | "
47 "--u2u ccache]\n"
48 "\t[--cached-only] [--no-store] [--out-cache] "
49 "service1 service2 ...\n"),
50 prog);
51 exit(1);
52 }
53
54 static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr,
55 char *keytab_name, char *sname, int cached_only,
56 int canon, int no_store, int unknown, char *for_user,
57 int for_user_enterprise, char *for_user_cert_file,
58 int proxy, const char *out_ccname,
59 const char *u2u_ccname);
60
61 #include <com_err.h>
62 static void extended_com_err_fn(const char *myprog, errcode_t code,
63 const char *fmt, va_list args);
64
65 int
main(int argc,char * argv[])66 main(int argc, char *argv[])
67 {
68 enum { OPTION_U2U = 256, OPTION_OUT_CACHE = 257 };
69 const char *shopts = "uCc:e:hk:qPS:I:U:F:";
70 int option;
71 char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL;
72 char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL;
73 char *for_user_cert_file = NULL, *out_ccname = NULL;
74 int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0;
75 int impersonate = 0, cached_only = 0, no_store = 0;
76 struct option lopts[] = {
77 { "cached-only", 0, &cached_only, 1 },
78 { "no-store", 0, &no_store, 1 },
79 { "out-cache", 1, NULL, OPTION_OUT_CACHE },
80 { "u2u", 1, NULL, OPTION_U2U },
81 { NULL, 0, NULL, 0 }
82 };
83
84 setlocale(LC_ALL, "");
85 set_com_err_hook(extended_com_err_fn);
86
87 prog = strrchr(argv[0], '/');
88 prog = prog ? (prog + 1) : argv[0];
89
90 while ((option = getopt_long(argc, argv, shopts, lopts, NULL)) != -1) {
91 switch (option) {
92 case 'C':
93 canon = 1;
94 break;
95 case 'c':
96 ccachestr = optarg;
97 break;
98 case 'e':
99 etypestr = optarg;
100 break;
101 case 'h':
102 xusage();
103 break;
104 case 'k':
105 keytab_name = optarg;
106 break;
107 case 'q':
108 quiet = 1;
109 break;
110 case 'P':
111 proxy = 1; /* S4U2Proxy - constrained delegation */
112 break;
113 case 'S':
114 sname = optarg;
115 if (unknown == 1) {
116 fprintf(stderr,
117 _("Options -u and -S are mutually exclusive\n"));
118 xusage();
119 }
120 break;
121 case 'u':
122 unknown = 1;
123 if (sname != NULL) {
124 fprintf(stderr,
125 _("Options -u and -S are mutually exclusive\n"));
126 xusage();
127 }
128 break;
129 case 'I':
130 impersonate = 1;
131 for_user = optarg;
132 break;
133 case 'U':
134 impersonate = 1;
135 for_user_enterprise = 1;
136 for_user = optarg;
137 break;
138 case 'F':
139 impersonate = 1;
140 for_user_cert_file = optarg;
141 break;
142 case OPTION_U2U:
143 u2u_ccname = optarg;
144 break;
145 case OPTION_OUT_CACHE:
146 out_ccname = optarg;
147 break;
148 case 0:
149 /* If this option set a flag, do nothing else now. */
150 break;
151 default:
152 xusage();
153 break;
154 }
155 }
156
157 if (u2u_ccname != NULL && impersonate) {
158 fprintf(stderr,
159 _("Options --u2u and -I|-U|-F are mutually exclusive\n"));
160 xusage();
161 }
162
163 if (proxy) {
164 if (!impersonate) {
165 fprintf(stderr, _("Option -P (constrained delegation) requires "
166 "option -I|-U|-F (protocol transition)\n"));
167 xusage();
168 }
169 }
170
171 if (argc - optind < 1)
172 xusage();
173
174 do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name,
175 sname, cached_only, canon, no_store, unknown, for_user,
176 for_user_enterprise, for_user_cert_file, proxy, out_ccname,
177 u2u_ccname);
178 return 0;
179 }
180
181 #include <k5-int.h>
182 static krb5_context context;
extended_com_err_fn(const char * myprog,errcode_t code,const char * fmt,va_list args)183 static void extended_com_err_fn(const char *myprog, errcode_t code,
184 const char *fmt, va_list args)
185 {
186 const char *emsg;
187
188 emsg = krb5_get_error_message(context, code);
189 fprintf(stderr, "%s: %s ", myprog, emsg);
190 krb5_free_error_message(context, emsg);
191 vfprintf(stderr, fmt, args);
192 fprintf(stderr, "\n");
193 }
194
195 /* Read a line from fp into buf. Trim any trailing whitespace, and return a
196 * pointer to the first non-whitespace character. */
197 static const char *
read_line(FILE * fp,char * buf,size_t bufsize)198 read_line(FILE *fp, char *buf, size_t bufsize)
199 {
200 char *end, *begin;
201
202 if (fgets(buf, bufsize, fp) == NULL)
203 return NULL;
204
205 end = buf + strlen(buf);
206 while (end > buf && isspace((uint8_t)end[-1]))
207 *--end = '\0';
208
209 begin = buf;
210 while (isspace((uint8_t)*begin))
211 begin++;
212
213 return begin;
214 }
215
216 /* Read a certificate from file_name in PEM format, placing the DER
217 * representation of the certificate in *der_out. */
218 static krb5_error_code
read_pem_file(char * file_name,krb5_data * der_out)219 read_pem_file(char *file_name, krb5_data *der_out)
220 {
221 krb5_error_code ret = 0;
222 FILE *fp = NULL;
223 const char *begin_line = "-----BEGIN CERTIFICATE-----";
224 const char *end_line = "-----END ", *line;
225 char linebuf[256], *b64;
226 struct k5buf buf = EMPTY_K5BUF;
227 uint8_t *der_cert;
228 size_t dlen;
229
230 *der_out = empty_data();
231
232 fp = fopen(file_name, "r");
233 if (fp == NULL)
234 return errno;
235
236 for (;;) {
237 line = read_line(fp, linebuf, sizeof(linebuf));
238 if (line == NULL) {
239 ret = EINVAL;
240 k5_setmsg(context, ret, _("No begin line not found"));
241 goto cleanup;
242 }
243 if (strncmp(line, begin_line, strlen(begin_line)) == 0)
244 break;
245 }
246
247 k5_buf_init_dynamic(&buf);
248 for (;;) {
249 line = read_line(fp, linebuf, sizeof(linebuf));
250 if (line == NULL) {
251 ret = EINVAL;
252 k5_setmsg(context, ret, _("No end line found"));
253 goto cleanup;
254 }
255
256 if (strncmp(line, end_line, strlen(end_line)) == 0)
257 break;
258
259 /* Header lines would be expected for an actual privacy-enhanced mail
260 * message, but not for a certificate. */
261 if (*line == '\0' || strchr(line, ':') != NULL) {
262 ret = EINVAL;
263 k5_setmsg(context, ret, _("Unexpected header line"));
264 goto cleanup;
265 }
266
267 k5_buf_add(&buf, line);
268 }
269
270 b64 = k5_buf_cstring(&buf);
271 if (b64 == NULL) {
272 ret = ENOMEM;
273 goto cleanup;
274 }
275 der_cert = k5_base64_decode(b64, &dlen);
276 if (der_cert == NULL) {
277 ret = EINVAL;
278 k5_setmsg(context, ret, _("Invalid base64"));
279 goto cleanup;
280 }
281
282 *der_out = make_data(der_cert, dlen);
283
284 cleanup:
285 fclose(fp);
286 k5_buf_free(&buf);
287 return ret;
288 }
289
290 /* Request a single service ticket and display its status (unless quiet is
291 * set). On failure, display an error message and return non-zero. */
292 static krb5_error_code
kvno(const char * name,krb5_ccache ccache,krb5_principal me,krb5_enctype etype,krb5_keytab keytab,const char * sname,krb5_flags options,int unknown,krb5_principal for_user_princ,krb5_data * for_user_cert,int proxy,krb5_data * u2u_ticket,krb5_creds ** creds_out)293 kvno(const char *name, krb5_ccache ccache, krb5_principal me,
294 krb5_enctype etype, krb5_keytab keytab, const char *sname,
295 krb5_flags options, int unknown, krb5_principal for_user_princ,
296 krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket,
297 krb5_creds **creds_out)
298 {
299 krb5_error_code ret;
300 krb5_principal server = NULL;
301 krb5_ticket *ticket = NULL;
302 krb5_creds in_creds, *creds = NULL;
303 char *princ = NULL;
304
305 *creds_out = NULL;
306 memset(&in_creds, 0, sizeof(in_creds));
307
308 if (sname != NULL) {
309 ret = krb5_sname_to_principal(context, name, sname, KRB5_NT_SRV_HST,
310 &server);
311 } else {
312 ret = krb5_parse_name(context, name, &server);
313 }
314 if (ret) {
315 if (!quiet)
316 com_err(prog, ret, _("while parsing principal name %s"), name);
317 goto cleanup;
318 }
319 if (unknown)
320 krb5_princ_type(context, server) = KRB5_NT_UNKNOWN;
321
322 ret = krb5_unparse_name(context, server, &princ);
323 if (ret) {
324 com_err(prog, ret, _("while formatting parsed principal name for "
325 "'%s'"), name);
326 goto cleanup;
327 }
328
329 in_creds.keyblock.enctype = etype;
330
331 if (u2u_ticket != NULL)
332 in_creds.second_ticket = *u2u_ticket;
333
334 if (for_user_princ != NULL || for_user_cert != NULL) {
335 if (!proxy && !krb5_principal_compare(context, me, server)) {
336 ret = EINVAL;
337 com_err(prog, ret,
338 _("client and server principal names must match"));
339 goto cleanup;
340 }
341
342 in_creds.client = for_user_princ;
343 in_creds.server = me;
344 ret = krb5_get_credentials_for_user(context, options, ccache,
345 &in_creds, for_user_cert, &creds);
346 } else {
347 in_creds.client = me;
348 in_creds.server = server;
349 ret = krb5_get_credentials(context, options, ccache, &in_creds,
350 &creds);
351 }
352
353 if (ret) {
354 com_err(prog, ret, _("while getting credentials for %s"), princ);
355 goto cleanup;
356 }
357
358 /* We need a native ticket. */
359 ret = krb5_decode_ticket(&creds->ticket, &ticket);
360 if (ret) {
361 com_err(prog, ret, _("while decoding ticket for %s"), princ);
362 goto cleanup;
363 }
364
365 if (keytab != NULL) {
366 ret = krb5_server_decrypt_ticket_keytab(context, keytab, ticket);
367 if (ret) {
368 if (!quiet) {
369 fprintf(stderr, "%s: kvno = %d, keytab entry invalid\n", princ,
370 ticket->enc_part.kvno);
371 }
372 com_err(prog, ret, _("while decrypting ticket for %s"), princ);
373 goto cleanup;
374 }
375 if (!quiet) {
376 printf(_("%s: kvno = %d, keytab entry valid\n"), princ,
377 ticket->enc_part.kvno);
378 }
379 } else {
380 if (!quiet)
381 printf(_("%s: kvno = %d\n"), princ, ticket->enc_part.kvno);
382 }
383
384 if (proxy) {
385 in_creds.client = creds->client;
386 creds->client = NULL;
387 krb5_free_creds(context, creds);
388 creds = NULL;
389 in_creds.server = server;
390
391 ret = krb5_get_credentials_for_proxy(context, KRB5_GC_CANONICALIZE,
392 ccache, &in_creds, ticket,
393 &creds);
394 krb5_free_principal(context, in_creds.client);
395 if (ret) {
396 com_err(prog, ret, _("%s: constrained delegation failed"),
397 princ);
398 goto cleanup;
399 }
400 }
401
402 *creds_out = creds;
403 creds = NULL;
404
405 cleanup:
406 krb5_free_principal(context, server);
407 krb5_free_ticket(context, ticket);
408 krb5_free_creds(context, creds);
409 krb5_free_unparsed_name(context, princ);
410 return ret;
411 }
412
413 /* Fetch the encoded local TGT for ccname's default client principal. */
414 static krb5_error_code
get_u2u_ticket(const char * ccname,krb5_data ** ticket_out)415 get_u2u_ticket(const char *ccname, krb5_data **ticket_out)
416 {
417 krb5_error_code ret;
418 krb5_ccache cc = NULL;
419 krb5_creds mcred, *creds = NULL;
420
421 *ticket_out = NULL;
422 memset(&mcred, 0, sizeof(mcred));
423
424 ret = krb5_cc_resolve(context, ccname, &cc);
425 if (ret)
426 goto cleanup;
427 ret = krb5_cc_get_principal(context, cc, &mcred.client);
428 if (ret)
429 goto cleanup;
430 ret = krb5_build_principal_ext(context, &mcred.server,
431 mcred.client->realm.length,
432 mcred.client->realm.data,
433 KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
434 mcred.client->realm.length,
435 mcred.client->realm.data, 0);
436 if (ret)
437 goto cleanup;
438 ret = krb5_get_credentials(context, KRB5_GC_CACHED, cc, &mcred, &creds);
439 if (ret)
440 goto cleanup;
441
442 ret = krb5_copy_data(context, &creds->ticket, ticket_out);
443
444 cleanup:
445 if (cc != NULL)
446 krb5_cc_close(context, cc);
447 krb5_free_cred_contents(context, &mcred);
448 krb5_free_creds(context, creds);
449 return ret;
450 }
451
452 static void
do_v5_kvno(int count,char * names[],char * ccachestr,char * etypestr,char * keytab_name,char * sname,int cached_only,int canon,int no_store,int unknown,char * for_user,int for_user_enterprise,char * for_user_cert_file,int proxy,const char * out_ccname,const char * u2u_ccname)453 do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
454 char *keytab_name, char *sname, int cached_only, int canon,
455 int no_store, int unknown, char *for_user, int for_user_enterprise,
456 char *for_user_cert_file, int proxy, const char *out_ccname,
457 const char *u2u_ccname)
458 {
459 krb5_error_code ret;
460 int i, errors, flags, initialized = 0;
461 krb5_enctype etype;
462 krb5_ccache ccache, mcc, out_ccache = NULL;
463 krb5_principal me;
464 krb5_keytab keytab = NULL;
465 krb5_principal for_user_princ = NULL;
466 krb5_flags options = 0;
467 krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL;
468 krb5_creds *creds;
469
470 if (canon)
471 options |= KRB5_GC_CANONICALIZE;
472 if (cached_only)
473 options |= KRB5_GC_CACHED;
474 if (no_store || out_ccname != NULL)
475 options |= KRB5_GC_NO_STORE;
476
477 ret = krb5_init_context(&context);
478 if (ret) {
479 com_err(prog, ret, _("while initializing krb5 library"));
480 exit(1);
481 }
482
483 if (etypestr) {
484 ret = krb5_string_to_enctype(etypestr, &etype);
485 if (ret) {
486 com_err(prog, ret, _("while converting etype"));
487 exit(1);
488 }
489 } else {
490 etype = 0;
491 }
492
493 if (ccachestr)
494 ret = krb5_cc_resolve(context, ccachestr, &ccache);
495 else
496 ret = krb5_cc_default(context, &ccache);
497 if (ret) {
498 com_err(prog, ret, _("while opening ccache"));
499 exit(1);
500 }
501
502 if (out_ccname != NULL) {
503 ret = krb5_cc_resolve(context, out_ccname, &out_ccache);
504 if (ret) {
505 com_err(prog, ret, _("while resolving output ccache"));
506 exit(1);
507 }
508 }
509
510 if (keytab_name != NULL) {
511 ret = krb5_kt_resolve(context, keytab_name, &keytab);
512 if (ret) {
513 com_err(prog, ret, _("resolving keytab %s"), keytab_name);
514 exit(1);
515 }
516 }
517
518 if (for_user) {
519 flags = for_user_enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
520 ret = krb5_parse_name_flags(context, for_user, flags, &for_user_princ);
521 if (ret) {
522 com_err(prog, ret, _("while parsing principal name %s"), for_user);
523 exit(1);
524 }
525 }
526
527 if (for_user_cert_file != NULL) {
528 ret = read_pem_file(for_user_cert_file, &cert_data);
529 if (ret) {
530 com_err(prog, ret, _("while reading certificate file %s"),
531 for_user_cert_file);
532 exit(1);
533 }
534 user_cert = &cert_data;
535 }
536
537 if (u2u_ccname != NULL) {
538 ret = get_u2u_ticket(u2u_ccname, &u2u_ticket);
539 if (ret) {
540 com_err(prog, ret, _("while getting user-to-user ticket from %s"),
541 u2u_ccname);
542 exit(1);
543 }
544 options |= KRB5_GC_USER_USER;
545 }
546
547 ret = krb5_cc_get_principal(context, ccache, &me);
548 if (ret) {
549 com_err(prog, ret, _("while getting client principal name"));
550 exit(1);
551 }
552
553 if (out_ccache != NULL) {
554 ret = krb5_cc_new_unique(context, "MEMORY", NULL, &mcc);
555 if (ret) {
556 com_err(prog, ret, _("while creating temporary output ccache"));
557 exit(1);
558 }
559 }
560
561 errors = 0;
562 for (i = 0; i < count; i++) {
563 if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown,
564 for_user_princ, user_cert, proxy, u2u_ticket, &creds) != 0) {
565 errors++;
566 } else if (out_ccache != NULL) {
567 if (!initialized) {
568 ret = krb5_cc_initialize(context, mcc, creds->client);
569 if (ret) {
570 com_err(prog, ret, _("while initializing output ccache"));
571 exit(1);
572 }
573 initialized = 1;
574 }
575 if (count == 1)
576 ret = k5_cc_store_primary_cred(context, mcc, creds);
577 else
578 ret = krb5_cc_store_cred(context, mcc, creds);
579 if (ret) {
580 com_err(prog, ret, _("while storing creds in output ccache"));
581 exit(1);
582 }
583 }
584
585 krb5_free_creds(context, creds);
586 }
587
588 if (!errors && out_ccache != NULL) {
589 ret = krb5_cc_move(context, mcc, out_ccache);
590 if (ret) {
591 com_err(prog, ret, _("while writing output ccache"));
592 exit(1);
593 }
594 }
595
596 if (keytab != NULL)
597 krb5_kt_close(context, keytab);
598 krb5_free_principal(context, me);
599 krb5_free_principal(context, for_user_princ);
600 krb5_cc_close(context, ccache);
601 krb5_free_data(context, u2u_ticket);
602 krb5_free_data_contents(context, &cert_data);
603 krb5_free_context(context);
604
605 if (errors)
606 exit(1);
607
608 exit(0);
609 }
610