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