xref: /freebsd/crypto/krb5/src/clients/kvno/kvno.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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