xref: /original-bsd/lib/libtelnet/encrypt.c (revision 3588a932)
1 /*-
2  * Copyright (c) 1991 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)encrypt.c	5.1 (Berkeley) 02/28/91";
10 #endif /* not lint */
11 
12 /*
13  * Copyright (C) 1990 by the Massachusetts Institute of Technology
14  *
15  * Export of this software from the United States of America is assumed
16  * to require a specific license from the United States Government.
17  * It is the responsibility of any person or organization contemplating
18  * export to obtain such a license before exporting.
19  *
20  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
21  * distribute this software and its documentation for any purpose and
22  * without fee is hereby granted, provided that the above copyright
23  * notice appear in all copies and that both that copyright notice and
24  * this permission notice appear in supporting documentation, and that
25  * the name of M.I.T. not be used in advertising or publicity pertaining
26  * to distribution of the software without specific, written prior
27  * permission.  M.I.T. makes no representations about the suitability of
28  * this software for any purpose.  It is provided "as is" without express
29  * or implied warranty.
30  */
31 
32 #if	defined(ENCRYPT)
33 
34 #define	ENCRYPT_NAMES
35 #include <arpa/telnet.h>
36 
37 #include "encrypt.h"
38 #include "misc.h"
39 
40 #ifdef	__STDC__
41 #include <stdlib.h>
42 #endif
43 #ifdef	NO_STRING_H
44 #include <strings.h>
45 #else
46 #include <string.h>
47 #endif
48 
49 /*
50  * These functions pointers point to the current routines
51  * for encrypting and decrypting data.
52  */
53 void	(*encrypt_output) P((unsigned char *, int));
54 int	(*decrypt_input) P((int));
55 
56 int encrypt_debug_mode = 0;
57 static int decrypt_mode = 0;
58 static int encrypt_mode = 0;
59 static int encrypt_verbose = 0;
60 static int autoencrypt = 0;
61 static int autodecrypt = 0;
62 static int havesessionkey = 0;
63 static int encrypt_mark = 0;
64 static int decrypt_mark = 0;
65 static int Server = 0;
66 static char *Name = "Noname";
67 
68 #define	typemask(x)	((x) > 0 ? 1 << ((x)-1) : 0)
69 
70 static long i_support_encrypt = typemask(ENCTYPE_KRBDES);
71 static long i_support_decrypt = typemask(ENCTYPE_KRBDES);
72 static long remote_supports_encrypt = 0;
73 static long remote_supports_decrypt = 0;
74 
75 static Encryptions encryptions[] = {
76 #if	defined(KRBDES_ENCRYPT)
77     { "KRBDES",		ENCTYPE_KRBDES,
78 			krbdes_encrypt,
79 			krbdes_decrypt,
80 			krbdes_init,
81 			krbdes_start,
82 			krbdes_is,
83 			krbdes_reply,
84 			krbdes_session,
85 			krbdes_printsub },
86 #endif
87     { 0, },
88 };
89 
90 static unsigned char str_send[64] = { IAC, SB, TELOPT_ENCRYPT,
91 					 ENCRYPT_SUPPORT, };
92 static unsigned char str_suplen = 0;
93 static unsigned char str_start[] = { IAC, SB, TELOPT_ENCRYPT, 0, IAC, SE };
94 static unsigned char str_end[] = { IAC, SB, TELOPT_ENCRYPT, 0, IAC, SE };
95 
96 	Encryptions *
97 findencryption(type)
98 	int type;
99 {
100 	Encryptions *ep = encryptions;
101 
102 	if (!(i_support_encrypt & remote_supports_decrypt & typemask(type)))
103 		return(0);
104 	while (ep->type && ep->type != type)
105 		++ep;
106 	return(ep->type ? ep : 0);
107 }
108 
109 	Encryptions *
110 finddecryption(type)
111 	int type;
112 {
113 	Encryptions *ep = encryptions;
114 
115 	if (!(i_support_decrypt & remote_supports_encrypt & typemask(type)))
116 		return(0);
117 	while (ep->type && ep->type != type)
118 		++ep;
119 	return(ep->type ? ep : 0);
120 }
121 
122 	void
123 encrypt_init(name, server)
124 	char *name;
125 	int server;
126 {
127 	Encryptions *ep = encryptions;
128 
129 	Name = name;
130 	Server = server;
131 	i_support_encrypt = i_support_decrypt = 0;
132 	remote_supports_encrypt = remote_supports_decrypt = 0;
133 	encrypt_mode = 0;
134 	decrypt_mode = 0;
135 	encrypt_output = 0;
136 	decrypt_input = 0;
137 #ifdef notdef
138 	encrypt_verbose = !server;
139 #endif
140 
141 	str_suplen = 4;
142 
143 	while (ep->type) {
144 		if (encrypt_debug_mode)
145 			printf(">>>%s: I will support %s\r\n",
146 				Name, ENCTYPE_NAME(ep->type));
147 		i_support_encrypt |= typemask(ep->type);
148 		i_support_decrypt |= typemask(ep->type);
149 		if ((str_send[str_suplen++] = ep->type) == IAC)
150 			str_send[str_suplen++] = IAC;
151 		if (ep->init)
152 			(*ep->init)(Server);
153 		++ep;
154 	}
155 	str_send[str_suplen++] = IAC;
156 	str_send[str_suplen++] = SE;
157 }
158 
159 	void
160 encrypt_list_types()
161 {
162 	Encryptions *ep = encryptions;
163 
164 	printf("Valid encryption types:\n");
165 	while (ep->type) {
166 		printf("\t%s\n\n", ENCTYPE_NAME(ep->type));
167 		++ep;
168 	}
169 }
170 
171 	int
172 EncryptEnable(type, mode)
173 	char *type, *mode;
174 {
175 	if (isprefix(type, "help") || isprefix(type, "?")) {
176 		printf("Usage: encrypt enable <type> [input|output]\n");
177 		encrypt_list_types();
178 		return(0);
179 	}
180 	if (EncryptType(type, mode))
181 		return(EncryptStart(mode));
182 	return(0);
183 }
184 
185 	int
186 EncryptType(type, mode)
187 	char *type;
188 	char *mode;
189 {
190 	register Encryptions *ep;
191 
192 	if (isprefix(type, "help") || isprefix(type, "?")) {
193 		printf("Usage: encrypt type <type> [input|output]\n");
194 		encrypt_list_types();
195 		return(0);
196 	}
197 
198 	ep = (Encryptions *)genget(type, encryptions, sizeof(Encryptions));
199 
200 	if (ep == 0) {
201 		printf("%s: invalid encryption type\n", type);
202 		return(0);
203 	}
204 	if (Ambiguous(ep)) {
205 		printf("Ambiguous type '%s'\n", type);
206 		return(0);
207 	}
208 
209 	if (mode) {
210 		if (isprefix(mode, "input"))
211 			decrypt_mode = ep->type;
212 		else if (isprefix(mode, "output"))
213 			encrypt_mode = ep->type;
214 		else {
215 			printf("%s: invalid encryption mode\n", mode);
216 			return(0);
217 		}
218 	} else
219 		decrypt_mode = encrypt_mode = ep->type;
220 	return(1);
221 }
222 
223 	int
224 EncryptStart(mode)
225 	char *mode;
226 {
227 	register int ret = 0;
228 	if (mode) {
229 		if (isprefix(mode, "input"))
230 			return(EncryptStartInput());
231 		if (isprefix(mode, "output"))
232 			return(EncryptStartOutput());
233 		if (isprefix(mode, "help") || isprefix(mode, "?")) {
234 			printf("Usage: encrypt start [input|output]\n");
235 			return(0);
236 		}
237 		printf("%s: invalid encryption mode 'encrypt start ?' for help\n", mode);
238 		return(0);
239 	}
240 	ret += EncryptStartInput();
241 	ret += EncryptStartOutput();
242 	return(ret);
243 }
244 
245 	int
246 EncryptStartInput()
247 {
248 	if (decrypt_mode) {
249 		encrypt_send_request_start();
250 		return(1);
251 	}
252 	printf("No previous decryption mode, decryption not enabled\r\n");
253 	return(0);
254 }
255 
256 	int
257 EncryptStartOutput()
258 {
259 	if (encrypt_mode) {
260 		encrypt_start_output(encrypt_mode);
261 		return(1);
262 	}
263 	printf("No previous encryption mode, encryption not enabled\r\n");
264 	return(0);
265 }
266 
267 	int
268 EncryptStop(mode)
269 	char *mode;
270 {
271 	int ret = 0;
272 	if (mode) {
273 		if (isprefix(mode, "input"))
274 			return(EncryptStopInput());
275 		if (isprefix(mode, "output"))
276 			return(EncryptStopOutput());
277 		if (isprefix(mode, "help") || isprefix(mode, "?")) {
278 			printf("Usage: encrypt stop [input|output]\n");
279 			return(0);
280 		}
281 		printf("%s: invalid encryption mode 'encrypt stop ?' for help\n", mode);
282 		return(0);
283 	}
284 	ret += EncryptStopInput();
285 	ret += EncryptStopOutput();
286 	return(ret);
287 }
288 
289 	int
290 EncryptStopInput()
291 {
292 	encrypt_send_request_end();
293 	return(1);
294 }
295 
296 	int
297 EncryptStopOutput()
298 {
299 	encrypt_send_end();
300 	return(1);
301 }
302 
303 	void
304 encrypt_display()
305 {
306 	if (encrypt_output)
307 		printf("Currently encrypting output with %s\r\n",
308 			ENCTYPE_NAME(encrypt_mode));
309 	if (decrypt_input)
310 		printf("Currently decrypting input with %s\r\n",
311 			ENCTYPE_NAME(decrypt_mode));
312 }
313 
314 	int
315 EncryptStatus()
316 {
317 	if (encrypt_output)
318 		printf("Currently encrypting output with %s\r\n",
319 			ENCTYPE_NAME(encrypt_mode));
320 	else if (encrypt_mode) {
321 		printf("Currently output is clear text.\r\n");
322 		printf("Last encryption mode was %s\r\n",
323 			ENCTYPE_NAME(encrypt_mode));
324 	}
325 	if (decrypt_input) {
326 		printf("Currently decrypting input with %s\r\n",
327 			ENCTYPE_NAME(decrypt_mode));
328 	} else if (decrypt_mode) {
329 		printf("Currently input is clear text.\r\n");
330 		printf("Last decryption mode was %s\r\n",
331 			ENCTYPE_NAME(decrypt_mode));
332 	}
333 	return 1;
334 }
335 
336 	void
337 encrypt_send_support()
338 {
339 	if (str_suplen) {
340 		/*
341 		 * If the user has requested that decryption start
342 		 * immediatly, then send a "REQUEST START" before
343 		 * we negotiate the type.
344 		 */
345 		if (!Server && autodecrypt)
346 			encrypt_send_request_start();
347 		net_write(str_send, str_suplen);
348 		printsub('>', &str_send[2], str_suplen - 2);
349 		str_suplen = 0;
350 	}
351 }
352 
353 	int
354 EncryptTogDebug()
355 {
356 	encrypt_debug_mode ^= 1;
357 	printf("Encryption debugging %s\r\n",
358 		encrypt_debug_mode ? "enabled" : "disabled");
359 	return(1);
360 }
361 
362 	int
363 EncryptTogVerbose()
364 {
365 	encrypt_verbose ^= 1;
366 	printf("Encryption %s verbose\r\n",
367 		encrypt_verbose ? "is" : "is not");
368 	return(1);
369 }
370 
371 	int
372 EncryptTogAuto()
373 {
374 	autoencrypt ^= 1;
375 	autodecrypt ^= 1;
376 	printf("Automatic encryption of data is %s\r\n",
377 		autoencrypt ? "enabled" : "disabled");
378 	return(1);
379 }
380 
381 
382 /*
383  * Called when ENCRYPT SUPPORT is received.
384  */
385 	void
386 encrypt_support(typelist, cnt)
387 	unsigned char *typelist;
388 	int cnt;
389 {
390 	register int type, use_type = 0;
391 	Encryptions *ep;
392 
393 	/*
394 	 * Forget anything the other side has previously told us.
395 	 */
396 	remote_supports_decrypt = 0;
397 
398 	while (cnt-- > 0) {
399 		type = *typelist++;
400 		if (encrypt_debug_mode)
401 			printf(">>>%s: He is supporting %s (%d)\r\n",
402 				Name,
403 				ENCTYPE_NAME(type), type);
404 		if ((type < ENCTYPE_CNT) &&
405 		    (i_support_encrypt & typemask(type))) {
406 			remote_supports_decrypt |= typemask(type);
407 			if (use_type == 0)
408 				use_type = type;
409 		}
410 	}
411 	if (use_type) {
412 		ep = findencryption(use_type);
413 		if (!ep)
414 			return;
415 		type = ep->start ? (*ep->start)(DIR_ENCRYPT, Server) : 0;
416 		if (encrypt_debug_mode)
417 			printf(">>>%s: (*ep->start)() returned %d\r\n",
418 					Name, type);
419 		if (type < 0)
420 			return;
421 		encrypt_mode = type;
422 		if (type == 0)
423 			encrypt_start_output(use_type);
424 	}
425 }
426 
427 	void
428 encrypt_is(data, cnt)
429 	unsigned char *data;
430 	int cnt;
431 {
432 	Encryptions *ep;
433 	register int type, ret;
434 
435 	if (--cnt < 0)
436 		return;
437 	type = *data++;
438 	if (type < ENCTYPE_CNT)
439 		remote_supports_encrypt |= typemask(type);
440 	if (!(ep = finddecryption(type))) {
441 		if (encrypt_debug_mode)
442 			printf(">>>%s: Can't find type %s (%d) for initial negotiation\r\n",
443 				Name,
444 				ENCTYPE_NAME(data[-1]), data[1]);
445 		return;
446 	}
447 	if (!ep->is) {
448 		if (encrypt_debug_mode)
449 			printf(">>>%s: No initial negotiation needed for type %s (%d)\r\n",
450 				Name,
451 				ENCTYPE_NAME(type), type);
452 		ret = 0;
453 	} else {
454 		ret = (*ep->is)(data, cnt);
455 /*@*/		if (encrypt_debug_mode)
456 /*@*/			printf("(*ep->is)(%x, %d) returned %s(%d)\n", data, cnt,
457 /*@*/				(ret < 0) ? "FAIL " :
458 /*@*/				(ret == 0) ? "SUCCESS " : "MORE_TO_DO ", ret);
459 	}
460 	if (ret < 0) {
461 		autodecrypt = 0;
462 	} else {
463 		decrypt_mode = type;
464 		if (ret == 0 && autodecrypt)
465 			encrypt_send_request_start();
466 	}
467 }
468 
469 	void
470 encrypt_reply(data, cnt)
471 	unsigned char *data;
472 	int cnt;
473 {
474 	Encryptions *ep;
475 	register int ret, type;
476 
477 	if (--cnt < 0)
478 		return;
479 	type = *data++;
480 	if (!(ep = findencryption(type))) {
481 		if (encrypt_debug_mode)
482 			printf(">>>%s: Can't find type %s (%d) for initial negotiation\r\n",
483 				Name,
484 				ENCTYPE_NAME(data[-1]), data[1]);
485 		return;
486 	}
487 	if (!ep->reply) {
488 		if (encrypt_debug_mode)
489 			printf(">>>%s: No initial negotiation needed for type %s (%d)\r\n",
490 				Name,
491 				ENCTYPE_NAME(data[-1]), data[1]);
492 		ret = 0;
493 	} else {
494 		ret = (*ep->reply)(data, cnt);
495 /*@*/		if (encrypt_debug_mode)
496 /*@*/			printf("(*ep->reply)(%x, %d) returned %s(%d)\n",
497 /*@*/				data, cnt,
498 /*@*/				(ret < 0) ? "FAIL " :
499 /*@*/				(ret == 0) ? "SUCCESS " : "MORE_TO_DO ", ret);
500 	}
501 	if (encrypt_debug_mode)
502 		printf(">>>%s: encrypt_reply returned %d\n", Name, ret);
503 	if (ret < 0) {
504 		autoencrypt = 0;
505 	} else {
506 		encrypt_mode = type;
507 		if (ret == 0 && autoencrypt)
508 			encrypt_start_output(type);
509 	}
510 }
511 
512 /*
513  * Called when a ENCRYPT START command is received.
514  */
515 	void
516 encrypt_start()
517 {
518 	Encryptions *ep;
519 
520 	if (!decrypt_mode) {
521 		/*
522 		 * Something is wrong.  We should not get a START
523 		 * command without having already picked our
524 		 * decryption scheme.  Send a REQUEST-END to
525 		 * attempt to clear the channel...
526 		 */
527 		printf("%s: Warning, Cannot decrypt input stream!!!\r\n", Name);
528 		encrypt_send_request_end();
529 		return;
530 	}
531 
532 	if (ep = finddecryption(decrypt_mode)) {
533 		decrypt_input = ep->input;
534 		if (encrypt_verbose)
535 			printf("[ Input is now decrypted with type %s ]\r\n",
536 				ENCTYPE_NAME(decrypt_mode));
537 		if (encrypt_debug_mode)
538 			printf(">>>%s: Start to decrypt input with type %s\r\n",
539 				Name, ENCTYPE_NAME(decrypt_mode));
540 	} else {
541 		printf("%s: Warning, Cannot decrypt type %s (%d)!!!\r\n",
542 				Name, ENCTYPE_NAME(decrypt_mode), decrypt_mode);
543 		encrypt_send_request_end();
544 	}
545 }
546 
547 	void
548 encrypt_session_key(key, server)
549 	Session_Key *key;
550 	int server;
551 {
552 	Encryptions *ep = encryptions;
553 
554 	havesessionkey = 1;
555 
556 	while (ep->type) {
557 		if (ep->session)
558 			(*ep->session)(key, server);
559 		if (!encrypt_output && autoencrypt && !server)
560 			encrypt_start_output(ep->type);
561 		if (!decrypt_input && autodecrypt && !server)
562 			encrypt_send_request_start();
563 		++ep;
564 	}
565 }
566 
567 /*
568  * Called when ENCRYPT END is received.
569  */
570 	void
571 encrypt_end()
572 {
573 	decrypt_input = 0;
574 	if (encrypt_debug_mode)
575 		printf(">>>%s: Input is back to clear text\r\n", Name);
576 	if (encrypt_verbose)
577 		printf("[ Input is now clear text ]\r\n");
578 }
579 
580 /*
581  * Called when ENCRYPT REQUEST-END is received.
582  */
583 	void
584 encrypt_request_end()
585 {
586 	encrypt_send_end();
587 }
588 
589 /*
590  * Called when ENCRYPT REQUEST-START is received.  If we receive
591  * this before a type is picked, then that indicates that the
592  * other side wants us to start encrypting data as soon as we
593  * can.
594  */
595 	void
596 encrypt_request_start()
597 {
598 	if (!encrypt_mode && Server) {
599 		autoencrypt = 1;
600 		return;
601 	}
602 	encrypt_start_output(encrypt_mode);
603 }
604 
605 	void
606 encrypt_auto()
607 {
608 	autoencrypt = 1;
609 	autodecrypt = 1;
610 }
611 
612 	void
613 encrypt_start_output(type)
614 	int type;
615 {
616 	Encryptions *ep;
617 	register int ret;
618 
619 	if (!(ep = findencryption(type))) {
620 		if (encrypt_debug_mode) {
621 			printf(">>>%s: Marking type %s for later encryption use\r\n",
622 				Name,
623 				ENCTYPE_NAME(type));
624 		}
625 		encrypt_mark |= typemask(type);
626 		return;
627 	}
628 	if (ep->start) {
629 		ret = (*ep->start)(DIR_ENCRYPT, Server);
630 		if (ret) {
631 			if (encrypt_debug_mode) {
632 				if (ret < 0)
633 					printf(">>>%s: Start failed for %s\r\n",
634 						Name, ENCTYPE_NAME(type));
635 				else
636 					printf(">>>%s: Start: initial negotiation in progress%s\r\n",
637 						Name, ENCTYPE_NAME(type));
638 			}
639 
640 			return;
641 		}
642 	}
643 	str_start[3] = ENCRYPT_START;
644 	net_write(str_start, sizeof(str_start));
645 	net_encrypt();
646 	printsub('>', &str_start[2], sizeof(str_start) - 2);
647 	/*
648 	 * If we are already encrypting in some mode, then
649 	 * encrypt the ring (which includes our request) in
650 	 * the old mode, mark it all as "clear text" and then
651 	 * switch to the new mode.
652 	 */
653 	encrypt_output = ep->output;
654 	encrypt_mode = type;
655 	if (encrypt_debug_mode)
656 		printf(">>>%s: Started to encrypt output with type %s\r\n",
657 			Name, ENCTYPE_NAME(type));
658 	if (encrypt_verbose)
659 		printf("[ Output is now encrypted with type %s ]\r\n",
660 			ENCTYPE_NAME(type));
661 }
662 
663 	void
664 encrypt_send_end()
665 {
666 	if (!encrypt_output)
667 		return;
668 
669 	str_end[3] = ENCRYPT_END;
670 	net_write(str_end, sizeof(str_end));
671 	net_encrypt();
672 	printsub('>', &str_end[2], sizeof(str_end) - 2);
673 	/*
674 	 * Encrypt the output buffer now because it will not be done by
675 	 * netflush...
676 	 */
677 	encrypt_output = 0;
678 	if (encrypt_debug_mode)
679 		printf(">>>%s: Output is back to clear text\r\n", Name);
680 	if (encrypt_verbose)
681 		printf("[ Output is now clear text ]\r\n");
682 }
683 
684 	void
685 encrypt_send_request_start()
686 {
687 #ifdef notdef
688 	Encryptions *ep;
689 
690 	if (!(ep = findencryption(type))) {
691 		if (encrypt_debug_mode) {
692 			printf(">>>%s: Marking type %s for later decryption use\r\n",
693 				Name,
694 				ENCTYPE_NAME(type));
695 		}
696 		decrypt_mark |= typemask(type);
697 		return;
698 	}
699 
700 	if (ep->start && (*ep->start)(DIR_DECRYPT, Server)) {
701 		if (encrypt_debug_mode) {
702 			printf(">>>%s: Request failed for %s\r\n",
703 				Name,
704 				ENCTYPE_NAME(type));
705 		}
706 		return;
707 	}
708 #endif
709 
710 	str_start[3] = ENCRYPT_REQSTART;
711 	net_write(str_start, sizeof(str_start));
712 	printsub('>', &str_start[2], sizeof(str_start) - 2);
713 	if (encrypt_debug_mode)
714 		printf(">>>%s: Request input to be encrypted\r\n", Name);
715 }
716 
717 	void
718 encrypt_send_request_end()
719 {
720 	str_end[3] = ENCRYPT_REQEND;
721 	net_write(str_end, sizeof(str_end));
722 	printsub('>', &str_end[2], sizeof(str_end) - 2);
723 
724 	if (encrypt_debug_mode)
725 		printf(">>>%s: Request input to be clear text\r\n", Name);
726 }
727 
728 	void
729 encrypt_wait()
730 {
731 	register int encrypt, decrypt;
732 	if (encrypt_debug_mode)
733 		printf(">>>%s: in encrypt_wait\r\n", Name);
734 	if (!havesessionkey || !(i_support_encrypt & remote_supports_decrypt))
735 		return;
736 	while (autoencrypt && !encrypt_output)
737 		if (telnet_spin())
738 			return;
739 }
740 
741 	void
742 encrypt_debug(mode)
743 	int mode;
744 {
745 	encrypt_debug_mode = mode;
746 }
747 
748 	void
749 encrypt_gen_printsub(data, cnt, buf, buflen)
750 	unsigned char *data, *buf;
751 	int cnt, buflen;
752 {
753 	char tbuf[16], *cp;
754 
755 	cnt -= 2;
756 	data += 2;
757 	buf[buflen-1] = '\0';
758 	buf[buflen-2] = '*';
759 	buflen -= 2;;
760 	for (; cnt > 0; cnt--, data++) {
761 		sprintf(tbuf, " %d", *data);
762 		for (cp = tbuf; *cp && buflen > 0; --buflen)
763 			*buf++ = *cp++;
764 		if (buflen <= 0)
765 			return;
766 	}
767 	*buf = '\0';
768 }
769 
770 	void
771 encrypt_printsub(data, cnt, buf, buflen)
772 	unsigned char *data, *buf;
773 	int cnt, buflen;
774 {
775 	Encryptions *ep;
776 	register int type = data[1];
777 
778 	for (ep = encryptions; ep->type && ep->type != type; ep++)
779 		;
780 
781 	if (ep->printsub)
782 		(*ep->printsub)(data, cnt, buf, buflen);
783 	else
784 		encrypt_gen_printsub(data, cnt, buf, buflen);
785 }
786 #endif
787