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