1/*
2 * Copyright (c) 1997-2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include "kuser_locl.h"
37#include "parse_units.h"
38#include "kcc-commands.h"
39
40static char*
41printable_time_internal(time_t t, int x)
42{
43    static char s[128];
44    char *p;
45
46    if ((p = ctime(&t)) == NULL)
47	strlcpy(s, "?", sizeof(s));
48    else
49	strlcpy(s, p + 4, sizeof(s));
50    s[x] = 0;
51    return s;
52}
53
54static char*
55printable_time(time_t t)
56{
57    return printable_time_internal(t, 20);
58}
59
60static char*
61printable_time_long(time_t t)
62{
63    return printable_time_internal(t, 20);
64}
65
66#define COL_ISSUED		NP_("  Issued","")
67#define COL_EXPIRES		NP_("  Expires", "")
68#define COL_FLAGS		NP_("Flags", "")
69#define COL_NAME		NP_("  Name", "")
70#define COL_PRINCIPAL		NP_("  Principal", "in klist output")
71#define COL_PRINCIPAL_KVNO	NP_("  Principal (kvno)", "in klist output")
72#define COL_CACHENAME		NP_("  Cache name", "name in klist output")
73#define COL_DEFCACHE		NP_("", "")
74
75static void
76print_cred(krb5_context context, krb5_creds *cred, rtbl_t ct, int do_flags)
77{
78    char *str;
79    krb5_error_code ret;
80    krb5_timestamp sec;
81
82    krb5_timeofday (context, &sec);
83
84
85    if(cred->times.starttime)
86	rtbl_add_column_entry(ct, COL_ISSUED,
87			      printable_time(cred->times.starttime));
88    else
89	rtbl_add_column_entry(ct, COL_ISSUED,
90			      printable_time(cred->times.authtime));
91
92    if(cred->times.endtime > sec)
93	rtbl_add_column_entry(ct, COL_EXPIRES,
94			      printable_time(cred->times.endtime));
95    else
96	rtbl_add_column_entry(ct, COL_EXPIRES, N_(">>>Expired<<<", ""));
97    ret = krb5_unparse_name (context, cred->server, &str);
98    if (ret)
99	krb5_err(context, 1, ret, "krb5_unparse_name");
100    rtbl_add_column_entry(ct, COL_PRINCIPAL, str);
101    if(do_flags) {
102	char s[16], *sp = s;
103	if(cred->flags.b.forwardable)
104	    *sp++ = 'F';
105	if(cred->flags.b.forwarded)
106	    *sp++ = 'f';
107	if(cred->flags.b.proxiable)
108	    *sp++ = 'P';
109	if(cred->flags.b.proxy)
110	    *sp++ = 'p';
111	if(cred->flags.b.may_postdate)
112	    *sp++ = 'D';
113	if(cred->flags.b.postdated)
114	    *sp++ = 'd';
115	if(cred->flags.b.renewable)
116	    *sp++ = 'R';
117	if(cred->flags.b.initial)
118	    *sp++ = 'I';
119	if(cred->flags.b.invalid)
120	    *sp++ = 'i';
121	if(cred->flags.b.pre_authent)
122	    *sp++ = 'A';
123	if(cred->flags.b.hw_authent)
124	    *sp++ = 'H';
125	*sp = '\0';
126	rtbl_add_column_entry(ct, COL_FLAGS, s);
127    }
128    free(str);
129}
130
131static void
132print_cred_verbose(krb5_context context, krb5_creds *cred, int do_json)
133{
134    size_t j;
135    char *str;
136    krb5_error_code ret;
137    krb5_timestamp sec;
138
139    if (do_json) { /* XXX support more json formating later */
140	printf("{ \"verbose-supported\" : false }");
141	return;
142    }
143
144    krb5_timeofday (context, &sec);
145
146    ret = krb5_unparse_name(context, cred->server, &str);
147    if(ret)
148	exit(1);
149    printf(N_("Server: %s\n", ""), str);
150    free (str);
151
152    ret = krb5_unparse_name(context, cred->client, &str);
153    if(ret)
154	exit(1);
155    printf(N_("Client: %s\n", ""), str);
156    free (str);
157
158    if (!krb5_is_config_principal(context, cred->client)) {
159	Ticket t;
160	size_t len;
161	char *s;
162
163	decode_Ticket(cred->ticket.data, cred->ticket.length, &t, &len);
164	ret = krb5_enctype_to_string(context, t.enc_part.etype, &s);
165	printf(N_("Ticket etype: ", ""));
166	if (ret == 0) {
167	    printf("%s", s);
168	    free(s);
169	} else {
170	    printf(N_("unknown-enctype(%d)", ""), t.enc_part.etype);
171	}
172	if(t.enc_part.kvno)
173	    printf(N_(", kvno %d", ""), *t.enc_part.kvno);
174	printf("\n");
175	if(cred->session.keytype != t.enc_part.etype) {
176	    ret = krb5_enctype_to_string(context, cred->session.keytype, &str);
177	    if(ret)
178		krb5_warn(context, ret, "session keytype");
179	    else {
180		printf(N_("Session key: %s\n", "enctype"), str);
181		free(str);
182	    }
183	}
184	free_Ticket(&t);
185	printf(N_("Ticket length: %lu\n", ""),
186	       (unsigned long)cred->ticket.length);
187    }
188    printf(N_("Auth time:  %s\n", ""),
189	   printable_time_long(cred->times.authtime));
190    if(cred->times.authtime != cred->times.starttime)
191	printf(N_("Start time: %s\n", ""),
192	       printable_time_long(cred->times.starttime));
193    printf(N_("End time:   %s", ""),
194	   printable_time_long(cred->times.endtime));
195    if(sec > cred->times.endtime)
196	printf(N_(" (expired)", ""));
197    printf("\n");
198    if(cred->flags.b.renewable)
199	printf(N_("Renew till: %s\n", ""),
200	       printable_time_long(cred->times.renew_till));
201    {
202	char flags[1024];
203	unparse_flags(TicketFlags2int(cred->flags.b),
204		      asn1_TicketFlags_units(),
205		      flags, sizeof(flags));
206	printf(N_("Ticket flags: %s\n", ""), flags);
207    }
208    printf(N_("Addresses: ", ""));
209    if (cred->addresses.len != 0) {
210	for(j = 0; j < cred->addresses.len; j++){
211	    char buf[128];
212	    size_t len;
213	    if(j) printf(", ");
214	    ret = krb5_print_address(&cred->addresses.val[j],
215				     buf, sizeof(buf), &len);
216
217	    if(ret == 0)
218		printf("%s", buf);
219	}
220    } else {
221	printf(N_("addressless", ""));
222    }
223    printf("\n\n");
224}
225
226/*
227 * Print all tickets in `ccache' on stdout, verbosily iff do_verbose.
228 */
229
230static void
231print_tickets (krb5_context context,
232	       krb5_ccache ccache,
233	       krb5_principal principal,
234	       int do_verbose,
235	       int do_flags,
236	       int do_hidden,
237	       int do_json)
238{
239    char *str, *name, *fullname;
240    krb5_error_code ret;
241    krb5_cc_cursor cursor;
242    krb5_creds creds;
243    krb5_deltat sec;
244
245    rtbl_t ct = NULL;
246
247    ret = krb5_unparse_name (context, principal, &str);
248    if (ret)
249	krb5_err (context, 1, ret, "krb5_unparse_name");
250
251    ret = krb5_cc_get_full_name(context, ccache, &fullname);
252    if (ret)
253	krb5_err (context, 1, ret, "krb5_cc_get_full_name");
254
255    if (!do_json) {
256	printf ("%17s: %s\n", N_("Credentials cache", ""), fullname);
257	printf ("%17s: %s\n", N_("Principal", ""), str);
258
259	ret = krb5_cc_get_friendly_name(context, ccache, &name);
260	if (ret == 0) {
261	    if (strcmp(name, str) != 0)
262		printf ("%17s: %s\n", N_("Friendly name", ""), name);
263	    free(name);
264	}
265	free (str);
266
267	if(do_verbose) {
268	    printf ("%17s: %d\n", N_("Cache version", ""),
269		    krb5_cc_get_version(context, ccache));
270	} else {
271	    krb5_cc_set_flags(context, ccache, KRB5_TC_NOTICKET);
272	}
273
274	ret = krb5_cc_get_kdc_offset(context, ccache, &sec);
275
276	if (ret == 0 && do_verbose && sec != 0) {
277	    char buf[BUFSIZ];
278	    int val;
279	    int sig;
280
281	    val = (int)sec;
282	    sig = 1;
283	    if (val < 0) {
284		sig = -1;
285		val = -val;
286	    }
287
288	    unparse_time (val, buf, sizeof(buf));
289
290	    printf ("%17s: %s%s\n", N_("KDC time offset", ""),
291		    sig == -1 ? "-" : "", buf);
292	}
293	printf("\n");
294    } else {
295	printf ("{ \"cache\" : \"%s\", \"principal\" : \"%s\", ", fullname, str);
296    }
297
298    ret = krb5_cc_start_seq_get (context, ccache, &cursor);
299    if (ret)
300	krb5_err(context, 1, ret, "krb5_cc_start_seq_get");
301
302    if(!do_verbose) {
303	ct = rtbl_create();
304	rtbl_add_column(ct, COL_ISSUED, 0);
305	rtbl_add_column(ct, COL_EXPIRES, 0);
306	if(do_flags)
307	    rtbl_add_column(ct, COL_FLAGS, 0);
308	rtbl_add_column(ct, COL_PRINCIPAL, 0);
309	rtbl_set_separator(ct, "  ");
310	if (do_json) {
311	    rtbl_set_flags(ct, RTBL_JSON);
312	    printf("\"tickets\" : ");
313	}
314    }
315    if (do_verbose && do_json)
316	printf("\"tickets\" : [");
317    while ((ret = krb5_cc_next_cred (context,
318				     ccache,
319				     &cursor,
320				     &creds)) == 0) {
321	if (!do_hidden && krb5_is_config_principal(context, creds.server)) {
322	    ;
323	}else if(do_verbose){
324	    print_cred_verbose(context, &creds, do_json);
325	}else{
326	    print_cred(context, &creds, ct, do_flags);
327	}
328	krb5_free_cred_contents (context, &creds);
329    }
330    if(ret != KRB5_CC_END)
331	krb5_err(context, 1, ret, "krb5_cc_get_next");
332    ret = krb5_cc_end_seq_get (context, ccache, &cursor);
333    if (ret)
334	krb5_err (context, 1, ret, "krb5_cc_end_seq_get");
335    if(!do_verbose) {
336	rtbl_format(ct, stdout);
337	rtbl_destroy(ct);
338    }
339    if (do_json) {
340	if (do_verbose)
341	    printf("]");
342	printf("}");
343    }
344}
345
346/*
347 * Check if there's a tgt for the realm of `principal' and ccache and
348 * if so return 0, else 1
349 */
350
351static int
352check_for_tgt (krb5_context context,
353	       krb5_ccache ccache,
354	       krb5_principal principal,
355	       time_t *expiration)
356{
357    krb5_error_code ret;
358    krb5_creds pattern;
359    krb5_creds creds;
360    krb5_const_realm client_realm;
361    int expired;
362
363    krb5_cc_clear_mcred(&pattern);
364
365    client_realm = krb5_principal_get_realm(context, principal);
366
367    ret = krb5_make_principal (context, &pattern.server,
368			       client_realm, KRB5_TGS_NAME, client_realm, NULL);
369    if (ret)
370	krb5_err (context, 1, ret, "krb5_make_principal");
371    pattern.client = principal;
372
373    ret = krb5_cc_retrieve_cred (context, ccache, 0, &pattern, &creds);
374    krb5_free_principal (context, pattern.server);
375    if (ret) {
376	if (ret == KRB5_CC_NOTFOUND)
377	    return 1;
378	krb5_err (context, 1, ret, "krb5_cc_retrieve_cred");
379    }
380
381    expired = time(NULL) > creds.times.endtime;
382
383    if (expiration)
384	*expiration = creds.times.endtime;
385
386    krb5_free_cred_contents (context, &creds);
387
388    return expired;
389}
390
391/*
392 * Print a list of all AFS tokens
393 */
394
395#ifndef NO_AFS
396
397static void
398display_tokens(int do_verbose)
399{
400    uint32_t i;
401    unsigned char t[4096];
402    struct ViceIoctl parms;
403
404    parms.in = (void *)&i;
405    parms.in_size = sizeof(i);
406    parms.out = (void *)t;
407    parms.out_size = sizeof(t);
408
409    for (i = 0;; i++) {
410        int32_t size_secret_tok, size_public_tok;
411        unsigned char *cell;
412	struct ClearToken ct;
413	unsigned char *r = t;
414	struct timeval tv;
415	char buf1[20], buf2[20];
416
417	if(k_pioctl(NULL, VIOCGETTOK, &parms, 0) < 0) {
418	    if(errno == EDOM)
419		break;
420	    continue;
421	}
422	if(parms.out_size > sizeof(t))
423	    continue;
424	if(parms.out_size < sizeof(size_secret_tok))
425	    continue;
426	t[min(parms.out_size,sizeof(t)-1)] = 0;
427	memcpy(&size_secret_tok, r, sizeof(size_secret_tok));
428	/* dont bother about the secret token */
429	r += size_secret_tok + sizeof(size_secret_tok);
430	if (parms.out_size < (r - t) + sizeof(size_public_tok))
431	    continue;
432	memcpy(&size_public_tok, r, sizeof(size_public_tok));
433	r += sizeof(size_public_tok);
434	if (parms.out_size < (r - t) + size_public_tok + sizeof(int32_t))
435	    continue;
436	memcpy(&ct, r, size_public_tok);
437	r += size_public_tok;
438	/* there is a int32_t with length of cellname, but we dont read it */
439	r += sizeof(int32_t);
440	cell = r;
441
442	gettimeofday (&tv, NULL);
443	strlcpy (buf1, printable_time(ct.BeginTimestamp),
444		 sizeof(buf1));
445	if (do_verbose || tv.tv_sec < ct.EndTimestamp)
446	    strlcpy (buf2, printable_time(ct.EndTimestamp),
447		     sizeof(buf2));
448	else
449	    strlcpy (buf2, N_(">>> Expired <<<", ""), sizeof(buf2));
450
451	printf("%s  %s  ", buf1, buf2);
452
453	if ((ct.EndTimestamp - ct.BeginTimestamp) & 1)
454	    printf(N_("User's (AFS ID %d) tokens for %s", ""), ct.ViceId, cell);
455	else
456	    printf(N_("Tokens for %s", ""), cell);
457	if (do_verbose)
458	    printf(" (%d)", ct.AuthHandle);
459	putchar('\n');
460    }
461}
462#endif
463
464/*
465 * display the ccache in `cred_cache'
466 */
467
468static int
469display_v5_ccache (krb5_context context, krb5_ccache ccache,
470		   int do_test, int do_verbose,
471		   int do_flags, int do_hidden,
472		   int do_json)
473{
474    krb5_error_code ret;
475    krb5_principal principal;
476    int exit_status = 0;
477
478
479    ret = krb5_cc_get_principal (context, ccache, &principal);
480    if (ret) {
481	if (do_json) {
482	    printf("{}");
483	    return 0;
484	}
485	if(ret == ENOENT) {
486	    if (!do_test)
487		krb5_warnx(context, N_("No ticket file: %s", ""),
488			   krb5_cc_get_name(context, ccache));
489	    return 1;
490	} else
491	    krb5_err (context, 1, ret, "krb5_cc_get_principal");
492    }
493    if (do_test)
494	exit_status = check_for_tgt (context, ccache, principal, NULL);
495    else
496	print_tickets (context, ccache, principal, do_verbose,
497		       do_flags, do_hidden, do_json);
498
499    ret = krb5_cc_close (context, ccache);
500    if (ret)
501	krb5_err (context, 1, ret, "krb5_cc_close");
502
503    krb5_free_principal (context, principal);
504
505    return exit_status;
506}
507
508/*
509 *
510 */
511
512static int
513list_caches(krb5_context context, struct klist_options *opt)
514{
515    krb5_cccol_cursor cursor;
516    const char *cdef_name;
517    char *def_name;
518    krb5_error_code ret;
519    krb5_ccache id;
520    rtbl_t ct;
521
522    cdef_name = krb5_cc_default_name(context);
523    if (cdef_name == NULL)
524	krb5_errx(context, 1, "krb5_cc_default_name");
525    def_name = strdup(cdef_name);
526
527    ret = krb5_cccol_cursor_new(context, &cursor);
528    if (ret == KRB5_CC_NOSUPP) {
529	free(def_name);
530	return 0;
531    } else if (ret)
532	krb5_err (context, 1, ret, "krb5_cc_cache_get_first");
533
534    ct = rtbl_create();
535    rtbl_add_column(ct, COL_DEFCACHE, 0);
536    rtbl_add_column(ct, COL_NAME, 0);
537    rtbl_add_column(ct, COL_CACHENAME, 0);
538    rtbl_add_column(ct, COL_EXPIRES, 0);
539    rtbl_add_column(ct, COL_DEFCACHE, 0);
540    rtbl_set_prefix(ct, "   ");
541    rtbl_set_column_prefix(ct, COL_DEFCACHE, "");
542    rtbl_set_column_prefix(ct, COL_NAME, " ");
543    if (opt->json_flag)
544	rtbl_set_flags(ct, RTBL_JSON);
545
546    while (krb5_cccol_cursor_next(context, cursor, &id) == 0 && id != NULL) {
547	krb5_principal principal = NULL;
548	int expired = 0;
549	char *name;
550	time_t t;
551
552	ret = krb5_cc_get_principal(context, id, &principal);
553	if (ret)
554	    continue;
555
556	expired = check_for_tgt (context, id, principal, &t);
557
558	ret = krb5_cc_get_friendly_name(context, id, &name);
559	if (ret == 0) {
560	    const char *str;
561	    char *fname;
562
563	    rtbl_add_column_entry(ct, COL_NAME, name);
564	    free(name);
565
566	    if (expired)
567		str = N_(">>> Expired <<<", "");
568	    else
569		str = printable_time(t);
570	    rtbl_add_column_entry(ct, COL_EXPIRES, str);
571
572	    ret = krb5_cc_get_full_name(context, id, &fname);
573	    if (ret)
574		krb5_err (context, 1, ret, "krb5_cc_get_full_name");
575
576	    rtbl_add_column_entry(ct, COL_CACHENAME, fname);
577	    if (opt->json_flag)
578		;
579	    else if (strcmp(fname, def_name) == 0)
580		rtbl_add_column_entry(ct, COL_DEFCACHE, "*");
581	    else
582		rtbl_add_column_entry(ct, COL_DEFCACHE, "");
583
584	    krb5_xfree(fname);
585	}
586	krb5_cc_close(context, id);
587
588	krb5_free_principal(context, principal);
589    }
590
591    krb5_cccol_cursor_free(context, &cursor);
592
593    free(def_name);
594    rtbl_format(ct, stdout);
595    rtbl_destroy(ct);
596
597    if (opt->json_flag)
598	printf("\n");
599
600    return 0;
601}
602
603/*
604 *
605 */
606
607int
608klist(struct klist_options *opt, int argc, char **argv)
609{
610    krb5_error_code ret;
611    int exit_status = 0;
612
613    int do_verbose =
614	opt->verbose_flag ||
615	opt->a_flag ||
616	opt->n_flag;
617    int do_test =
618	opt->test_flag ||
619	opt->s_flag;
620
621    if(opt->version_flag) {
622	print_version(NULL);
623	exit(0);
624    }
625
626    if (opt->list_all_flag) {
627	exit_status = list_caches(kcc_context, opt);
628	return exit_status;
629    }
630
631    if (opt->v5_flag) {
632	krb5_ccache id;
633
634	if (opt->all_content_flag) {
635	    krb5_cc_cache_cursor cursor;
636	    int first = 1;
637
638	    ret = krb5_cc_cache_get_first(kcc_context, NULL, &cursor);
639	    if (ret)
640		krb5_err(kcc_context, 1, ret, "krb5_cc_cache_get_first");
641
642	    if (opt->json_flag)
643		printf("[");
644	    while (krb5_cc_cache_next(kcc_context, cursor, &id) == 0) {
645		if (opt->json_flag && !first)
646		    printf(",");
647
648		exit_status |= display_v5_ccache(kcc_context, id, do_test,
649						 do_verbose, opt->flags_flag,
650						 opt->hidden_flag, opt->json_flag);
651		if (!opt->json_flag)
652		    printf("\n\n");
653
654		first = 0;
655	    }
656	    krb5_cc_cache_end_seq_get(kcc_context, cursor);
657	    if (opt->json_flag)
658		printf("]");
659	} else {
660	    if(opt->cache_string) {
661		ret = krb5_cc_resolve(kcc_context, opt->cache_string, &id);
662		if (ret)
663		    krb5_err(kcc_context, 1, ret, "%s", opt->cache_string);
664	    } else {
665		ret = krb5_cc_default(kcc_context, &id);
666		if (ret)
667		    krb5_err(kcc_context, 1, ret, "krb5_cc_resolve");
668	    }
669	    exit_status = display_v5_ccache(kcc_context, id, do_test,
670					    do_verbose, opt->flags_flag,
671					    opt->hidden_flag, opt->json_flag);
672	}
673    }
674
675    if (!do_test) {
676#ifndef NO_AFS
677	if (opt->tokens_flag && k_hasafs()) {
678	    if (opt->v5_flag)
679		printf("\n");
680	    display_tokens(opt->verbose_flag);
681	}
682#endif
683    }
684
685    return exit_status;
686}
687