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