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