xref: /dragonfly/libexec/telnetd/utility.c (revision aa8d5dcb)
1 /*
2  * Copyright (c) 1989, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#)utility.c	8.4 (Berkeley) 5/30/95
34  * $FreeBSD: src/libexec/telnetd/utility.c,v 1.13.2.4 2002/04/13 11:07:12 markm Exp $
35  * $DragonFly: src/libexec/telnetd/utility.c,v 1.3 2004/02/13 03:49:50 dillon Exp $
36  */
37 
38 #if defined(__DragonFly__) || defined(__FreeBSD__)
39 #include <locale.h>
40 #include <sys/utsname.h>
41 #endif
42 #include <string.h>
43 #define PRINTOPTIONS
44 #include "telnetd.h"
45 
46 
47 /*
48  * utility functions performing io related tasks
49  */
50 
51 /*
52  * ttloop
53  *
54  *	A small subroutine to flush the network output buffer, get some data
55  * from the network, and pass it through the telnet state machine.  We
56  * also flush the pty input buffer (by dropping its data) if it becomes
57  * too full.
58  */
59 
60     void
61 ttloop()
62 {
63 
64     DIAG(TD_REPORT, output_data("td: ttloop\r\n"));
65     if (nfrontp - nbackp > 0) {
66 	netflush();
67     }
68     ncc = read(net, netibuf, sizeof netibuf);
69     if (ncc < 0) {
70 	syslog(LOG_INFO, "ttloop:  read: %m");
71 	exit(1);
72     } else if (ncc == 0) {
73 	syslog(LOG_INFO, "ttloop:  peer died: %m");
74 	exit(1);
75     }
76     DIAG(TD_REPORT, output_data("td: ttloop read %d chars\r\n", ncc));
77     netip = netibuf;
78     telrcv();			/* state machine */
79     if (ncc > 0) {
80 	pfrontp = pbackp = ptyobuf;
81 	telrcv();
82     }
83 }  /* end of ttloop */
84 
85 /*
86  * Check a descriptor to see if out of band data exists on it.
87  */
88 int
89 stilloob(int s)
90 {
91     static struct timeval timeout = { 0, 0 };
92     fd_set	excepts;
93     int value;
94 
95     do {
96 	FD_ZERO(&excepts);
97 	FD_SET(s, &excepts);
98 	memset((char *)&timeout, 0, sizeof timeout);
99 	value = select(s+1, (fd_set *)0, (fd_set *)0, &excepts, &timeout);
100     } while ((value == -1) && (errno == EINTR));
101 
102     if (value < 0) {
103 	fatalperror(pty, "select");
104     }
105     if (FD_ISSET(s, &excepts)) {
106 	return 1;
107     } else {
108 	return 0;
109     }
110 }
111 
112 void
113 ptyflush(void)
114 {
115 	int n;
116 
117 	if ((n = pfrontp - pbackp) > 0) {
118 		DIAG(TD_REPORT | TD_PTYDATA,
119 		    output_data("td: ptyflush %d chars\r\n", n));
120 		DIAG(TD_PTYDATA, printdata("pd", pbackp, n));
121 		n = write(pty, pbackp, n);
122 	}
123 	if (n < 0) {
124 		if (errno == EWOULDBLOCK || errno == EINTR)
125 			return;
126 		cleanup(0);
127 	}
128 	pbackp += n;
129 	if (pbackp == pfrontp)
130 		pbackp = pfrontp = ptyobuf;
131 }
132 
133 /*
134  * nextitem()
135  *
136  *	Return the address of the next "item" in the TELNET data
137  * stream.  This will be the address of the next character if
138  * the current address is a user data character, or it will
139  * be the address of the character following the TELNET command
140  * if the current address is a TELNET IAC ("I Am a Command")
141  * character.
142  */
143 static char *
144 nextitem(char *current)
145 {
146     if ((*current&0xff) != IAC) {
147 	return current+1;
148     }
149     switch (*(current+1)&0xff) {
150     case DO:
151     case DONT:
152     case WILL:
153     case WONT:
154 	return current+3;
155     case SB:		/* loop forever looking for the SE */
156 	{
157 	    char *look = current+2;
158 
159 	    for (;;) {
160 		if ((*look++&0xff) == IAC) {
161 		    if ((*look++&0xff) == SE) {
162 			return look;
163 		    }
164 		}
165 	    }
166 	}
167     default:
168 	return current+2;
169     }
170 }  /* end of nextitem */
171 
172 /*
173  * netclear()
174  *
175  *	We are about to do a TELNET SYNCH operation.  Clear
176  * the path to the network.
177  *
178  *	Things are a bit tricky since we may have sent the first
179  * byte or so of a previous TELNET command into the network.
180  * So, we have to scan the network buffer from the beginning
181  * until we are up to where we want to be.
182  *
183  *	A side effect of what we do, just to keep things
184  * simple, is to clear the urgent data pointer.  The principal
185  * caller should be setting the urgent data pointer AFTER calling
186  * us in any case.
187  */
188 void
189 netclear(void)
190 {
191     char *thisitem, *next;
192     char *good;
193 #define	wewant(p)	((nfrontp > p) && ((*p&0xff) == IAC) && \
194 				((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL))
195 
196     thisitem = netobuf;
197 
198     while ((next = nextitem(thisitem)) <= nbackp) {
199 	thisitem = next;
200     }
201 
202     /* Now, thisitem is first before/at boundary. */
203 
204     good = netobuf;	/* where the good bytes go */
205 
206     while (nfrontp > thisitem) {
207 	if (wewant(thisitem)) {
208 	    int length;
209 
210 	    next = thisitem;
211 	    do {
212 		next = nextitem(next);
213 	    } while (wewant(next) && (nfrontp > next));
214 	    length = next-thisitem;
215 	    memmove(good, thisitem, length);
216 	    good += length;
217 	    thisitem = next;
218 	} else {
219 	    thisitem = nextitem(thisitem);
220 	}
221     }
222 
223     nbackp = netobuf;
224     nfrontp = good;		/* next byte to be sent */
225     neturg = 0;
226 }  /* end of netclear */
227 
228 /*
229  *  netflush
230  *		Send as much data as possible to the network,
231  *	handling requests for urgent data.
232  */
233 void
234 netflush(void)
235 {
236     int n;
237     extern int not42;
238 
239     while ((n = nfrontp - nbackp) > 0) {
240 #if 0
241 	/* XXX This causes output_data() to recurse and die */
242 	DIAG(TD_REPORT, {
243 	    n += output_data("td: netflush %d chars\r\n", n);
244 	});
245 #endif
246 	/*
247 	 * if no urgent data, or if the other side appears to be an
248 	 * old 4.2 client (and thus unable to survive TCP urgent data),
249 	 * write the entire buffer in non-OOB mode.
250 	 */
251 	if ((neturg == 0) || (not42 == 0)) {
252 	    n = write(net, nbackp, n);	/* normal write */
253 	} else {
254 	    n = neturg - nbackp;
255 	    /*
256 	     * In 4.2 (and 4.3) systems, there is some question about
257 	     * what byte in a sendOOB operation is the "OOB" data.
258 	     * To make ourselves compatible, we only send ONE byte
259 	     * out of band, the one WE THINK should be OOB (though
260 	     * we really have more the TCP philosophy of urgent data
261 	     * rather than the Unix philosophy of OOB data).
262 	     */
263 	    if (n > 1) {
264 		n = send(net, nbackp, n-1, 0);	/* send URGENT all by itself */
265 	    } else {
266 		n = send(net, nbackp, n, MSG_OOB);	/* URGENT data */
267 	    }
268 	}
269 	if (n == -1) {
270 	    if (errno == EWOULDBLOCK || errno == EINTR)
271 		continue;
272 	    cleanup(0);
273 	    /* NOTREACHED */
274 	}
275 	nbackp += n;
276 	if (nbackp >= neturg) {
277 	    neturg = 0;
278 	}
279 	if (nbackp == nfrontp) {
280 	    nbackp = nfrontp = netobuf;
281 	}
282     }
283     return;
284 }  /* end of netflush */
285 
286 
287 /*
288  * miscellaneous functions doing a variety of little jobs follow ...
289  */
290 
291 
292 void
293 fatal(int f, const char *msg)
294 {
295 	char buf[BUFSIZ];
296 
297 	(void) snprintf(buf, sizeof(buf), "telnetd: %s.\r\n", msg);
298 	(void) write(f, buf, (int)strlen(buf));
299 	sleep(1);	/*XXX*/
300 	exit(1);
301 }
302 
303 void
304 fatalperror(int f, const char *msg)
305 {
306 	char buf[BUFSIZ];
307 
308 	(void) snprintf(buf, sizeof(buf), "%s: %s", msg, strerror(errno));
309 	fatal(f, buf);
310 }
311 
312 char editedhost[32];
313 
314 void
315 edithost(char *pat, char *host)
316 {
317 	char *res = editedhost;
318 
319 	if (!pat)
320 		pat = strdup("");
321 	while (*pat) {
322 		switch (*pat) {
323 
324 		case '#':
325 			if (*host)
326 				host++;
327 			break;
328 
329 		case '@':
330 			if (*host)
331 				*res++ = *host++;
332 			break;
333 
334 		default:
335 			*res++ = *pat;
336 			break;
337 		}
338 		if (res == &editedhost[sizeof editedhost - 1]) {
339 			*res = '\0';
340 			return;
341 		}
342 		pat++;
343 	}
344 	if (*host)
345 		(void) strncpy(res, host,
346 				sizeof editedhost - (res - editedhost) -1);
347 	else
348 		*res = '\0';
349 	editedhost[sizeof editedhost - 1] = '\0';
350 }
351 
352 static char *putlocation;
353 
354 static void
355 putstr(const char *s)
356 {
357 
358 	while (*s)
359 		putchr(*s++);
360 }
361 
362 void
363 putchr(int cc)
364 {
365 	*putlocation++ = cc;
366 }
367 
368 #if defined(__DragonFly__) || defined(__FreeBSD__)
369 static char fmtstr[] = { "%+" };
370 #else
371 static char fmtstr[] = { "%l:%M%P on %A, %d %B %Y" };
372 #endif
373 
374 void
375 putf(char *cp, char *where)
376 {
377 	char *slash;
378 	time_t t;
379 	char db[100];
380 #if defined(__DragonFly__) || defined(__FreeBSD__)
381 	static struct utsname kerninfo;
382 
383 	if (!*kerninfo.sysname)
384 		uname(&kerninfo);
385 #endif
386 
387 	putlocation = where;
388 
389 	while (*cp) {
390 		if (*cp =='\n') {
391 			putstr("\r\n");
392 			cp++;
393 			continue;
394 		} else if (*cp != '%') {
395 			putchr(*cp++);
396 			continue;
397 		}
398 		switch (*++cp) {
399 
400 		case 't':
401 #ifdef	STREAMSPTY
402 			/* names are like /dev/pts/2 -- we want pts/2 */
403 			slash = strchr(line+1, '/');
404 #else
405 			slash = strrchr(line, '/');
406 #endif
407 			if (slash == (char *) 0)
408 				putstr(line);
409 			else
410 				putstr(&slash[1]);
411 			break;
412 
413 		case 'h':
414 			putstr(editedhost);
415 			break;
416 
417 		case 'd':
418 #if defined(__DragonFly__) || defined(__FreeBSD__)
419 			setlocale(LC_TIME, "");
420 #endif
421 			(void)time(&t);
422 			(void)strftime(db, sizeof(db), fmtstr, localtime(&t));
423 			putstr(db);
424 			break;
425 
426 #if defined(__DragonFly__) || defined(__FreeBSD__)
427 		case 's':
428 			putstr(kerninfo.sysname);
429 			break;
430 
431 		case 'm':
432 			putstr(kerninfo.machine);
433 			break;
434 
435 		case 'r':
436 			putstr(kerninfo.release);
437 			break;
438 
439 		case 'v':
440 			putstr(kerninfo.version);
441 			break;
442 #endif
443 
444 		case '%':
445 			putchr('%');
446 			break;
447 		}
448 		cp++;
449 	}
450 }
451 
452 #ifdef DIAGNOSTICS
453 /*
454  * Print telnet options and commands in plain text, if possible.
455  */
456 void
457 printoption(const char *fmt, int option)
458 {
459 	if (TELOPT_OK(option))
460 		output_data("%s %s\r\n", fmt, TELOPT(option));
461 	else if (TELCMD_OK(option))
462 		output_data("%s %s\r\n", fmt, TELCMD(option));
463 	else
464 		output_data("%s %d\r\n", fmt, option);
465 	return;
466 }
467 
468 void
469 printsub(char direction, unsigned char *pointer, int length)
470 {
471     int i = 0;
472 
473 	if (!(diagnostic & TD_OPTIONS))
474 		return;
475 
476 	if (direction) {
477 	    output_data("td: %s suboption ",
478 					direction == '<' ? "recv" : "send");
479 	    if (length >= 3) {
480 		int j;
481 
482 		i = pointer[length-2];
483 		j = pointer[length-1];
484 
485 		if (i != IAC || j != SE) {
486 		    output_data("(terminated by ");
487 		    if (TELOPT_OK(i))
488 			output_data("%s ", TELOPT(i));
489 		    else if (TELCMD_OK(i))
490 			output_data("%s ", TELCMD(i));
491 		    else
492 			output_data("%d ", i);
493 		    if (TELOPT_OK(j))
494 			output_data("%s", TELOPT(j));
495 		    else if (TELCMD_OK(j))
496 			output_data("%s", TELCMD(j));
497 		    else
498 			output_data("%d", j);
499 		    output_data(", not IAC SE!) ");
500 		}
501 	    }
502 	    length -= 2;
503 	}
504 	if (length < 1) {
505 	    output_data("(Empty suboption??\?)");
506 	    return;
507 	}
508 	switch (pointer[0]) {
509 	case TELOPT_TTYPE:
510 	    output_data("TERMINAL-TYPE ");
511 	    switch (pointer[1]) {
512 	    case TELQUAL_IS:
513 		output_data("IS \"%.*s\"", length-2, (char *)pointer+2);
514 		break;
515 	    case TELQUAL_SEND:
516 		output_data("SEND");
517 		break;
518 	    default:
519 		output_data(
520 				"- unknown qualifier %d (0x%x).",
521 				pointer[1], pointer[1]);
522 	    }
523 	    break;
524 	case TELOPT_TSPEED:
525 	    output_data("TERMINAL-SPEED");
526 	    if (length < 2) {
527 		output_data(" (empty suboption??\?)");
528 		break;
529 	    }
530 	    switch (pointer[1]) {
531 	    case TELQUAL_IS:
532 		output_data(" IS %.*s", length-2, (char *)pointer+2);
533 		break;
534 	    default:
535 		if (pointer[1] == 1)
536 		    output_data(" SEND");
537 		else
538 		    output_data(" %d (unknown)", pointer[1]);
539 		for (i = 2; i < length; i++) {
540 		    output_data(" ?%d?", pointer[i]);
541 		}
542 		break;
543 	    }
544 	    break;
545 
546 	case TELOPT_LFLOW:
547 	    output_data("TOGGLE-FLOW-CONTROL");
548 	    if (length < 2) {
549 		output_data(" (empty suboption??\?)");
550 		break;
551 	    }
552 	    switch (pointer[1]) {
553 	    case LFLOW_OFF:
554 		output_data(" OFF"); break;
555 	    case LFLOW_ON:
556 		output_data(" ON"); break;
557 	    case LFLOW_RESTART_ANY:
558 		output_data(" RESTART-ANY"); break;
559 	    case LFLOW_RESTART_XON:
560 		output_data(" RESTART-XON"); break;
561 	    default:
562 		output_data(" %d (unknown)", pointer[1]);
563 	    }
564 	    for (i = 2; i < length; i++) {
565 		output_data(" ?%d?", pointer[i]);
566 	    }
567 	    break;
568 
569 	case TELOPT_NAWS:
570 	    output_data("NAWS");
571 	    if (length < 2) {
572 		output_data(" (empty suboption??\?)");
573 		break;
574 	    }
575 	    if (length == 2) {
576 		output_data(" ?%d?", pointer[1]);
577 		break;
578 	    }
579 	    output_data(" %d %d (%d)",
580 		pointer[1], pointer[2],
581 		(int)((((unsigned int)pointer[1])<<8)|((unsigned int)pointer[2])));
582 	    if (length == 4) {
583 		output_data(" ?%d?", pointer[3]);
584 		break;
585 	    }
586 	    output_data(" %d %d (%d)",
587 		pointer[3], pointer[4],
588 		(int)((((unsigned int)pointer[3])<<8)|((unsigned int)pointer[4])));
589 	    for (i = 5; i < length; i++) {
590 		output_data(" ?%d?", pointer[i]);
591 	    }
592 	    break;
593 
594 	case TELOPT_LINEMODE:
595 	    output_data("LINEMODE ");
596 	    if (length < 2) {
597 		output_data(" (empty suboption??\?)");
598 		break;
599 	    }
600 	    switch (pointer[1]) {
601 	    case WILL:
602 		output_data("WILL ");
603 		goto common;
604 	    case WONT:
605 		output_data("WONT ");
606 		goto common;
607 	    case DO:
608 		output_data("DO ");
609 		goto common;
610 	    case DONT:
611 		output_data("DONT ");
612 	    common:
613 		if (length < 3) {
614 		    output_data("(no option??\?)");
615 		    break;
616 		}
617 		switch (pointer[2]) {
618 		case LM_FORWARDMASK:
619 		    output_data("Forward Mask");
620 		    for (i = 3; i < length; i++) {
621 			output_data(" %x", pointer[i]);
622 		    }
623 		    break;
624 		default:
625 		    output_data("%d (unknown)", pointer[2]);
626 		    for (i = 3; i < length; i++) {
627 			output_data(" %d", pointer[i]);
628 		    }
629 		    break;
630 		}
631 		break;
632 
633 	    case LM_SLC:
634 		output_data("SLC");
635 		for (i = 2; i < length - 2; i += 3) {
636 		    if (SLC_NAME_OK(pointer[i+SLC_FUNC]))
637 			output_data(" %s", SLC_NAME(pointer[i+SLC_FUNC]));
638 		    else
639 			output_data(" %d", pointer[i+SLC_FUNC]);
640 		    switch (pointer[i+SLC_FLAGS]&SLC_LEVELBITS) {
641 		    case SLC_NOSUPPORT:
642 			output_data(" NOSUPPORT"); break;
643 		    case SLC_CANTCHANGE:
644 			output_data(" CANTCHANGE"); break;
645 		    case SLC_VARIABLE:
646 			output_data(" VARIABLE"); break;
647 		    case SLC_DEFAULT:
648 			output_data(" DEFAULT"); break;
649 		    }
650 		    output_data("%s%s%s",
651 			pointer[i+SLC_FLAGS]&SLC_ACK ? "|ACK" : "",
652 			pointer[i+SLC_FLAGS]&SLC_FLUSHIN ? "|FLUSHIN" : "",
653 			pointer[i+SLC_FLAGS]&SLC_FLUSHOUT ? "|FLUSHOUT" : "");
654 		    if (pointer[i+SLC_FLAGS]& ~(SLC_ACK|SLC_FLUSHIN|
655 						SLC_FLUSHOUT| SLC_LEVELBITS)) {
656 			output_data("(0x%x)", pointer[i+SLC_FLAGS]);
657 		    }
658 		    output_data(" %d;", pointer[i+SLC_VALUE]);
659 		    if ((pointer[i+SLC_VALUE] == IAC) &&
660 			(pointer[i+SLC_VALUE+1] == IAC))
661 				i++;
662 		}
663 		for (; i < length; i++) {
664 		    output_data(" ?%d?", pointer[i]);
665 		}
666 		break;
667 
668 	    case LM_MODE:
669 		output_data("MODE ");
670 		if (length < 3) {
671 		    output_data("(no mode??\?)");
672 		    break;
673 		}
674 		{
675 		    char tbuf[32];
676 		    sprintf(tbuf, "%s%s%s%s%s",
677 			pointer[2]&MODE_EDIT ? "|EDIT" : "",
678 			pointer[2]&MODE_TRAPSIG ? "|TRAPSIG" : "",
679 			pointer[2]&MODE_SOFT_TAB ? "|SOFT_TAB" : "",
680 			pointer[2]&MODE_LIT_ECHO ? "|LIT_ECHO" : "",
681 			pointer[2]&MODE_ACK ? "|ACK" : "");
682 		    output_data("%s", tbuf[1] ? &tbuf[1] : "0");
683 		}
684 		if (pointer[2]&~(MODE_EDIT|MODE_TRAPSIG|MODE_ACK)) {
685 		    output_data(" (0x%x)", pointer[2]);
686 		}
687 		for (i = 3; i < length; i++) {
688 		    output_data(" ?0x%x?", pointer[i]);
689 		}
690 		break;
691 	    default:
692 		output_data("%d (unknown)", pointer[1]);
693 		for (i = 2; i < length; i++) {
694 		    output_data(" %d", pointer[i]);
695 		}
696 	    }
697 	    break;
698 
699 	case TELOPT_STATUS: {
700 	    const char *cp;
701 	    int j, k;
702 
703 	    output_data("STATUS");
704 
705 	    switch (pointer[1]) {
706 	    default:
707 		if (pointer[1] == TELQUAL_SEND)
708 		    output_data(" SEND");
709 		else
710 		    output_data(" %d (unknown)", pointer[1]);
711 		for (i = 2; i < length; i++) {
712 		    output_data(" ?%d?", pointer[i]);
713 		}
714 		break;
715 	    case TELQUAL_IS:
716 		output_data(" IS\r\n");
717 
718 		for (i = 2; i < length; i++) {
719 		    switch(pointer[i]) {
720 		    case DO:	cp = "DO"; goto common2;
721 		    case DONT:	cp = "DONT"; goto common2;
722 		    case WILL:	cp = "WILL"; goto common2;
723 		    case WONT:	cp = "WONT"; goto common2;
724 		    common2:
725 			i++;
726 			if (TELOPT_OK(pointer[i]))
727 			    output_data(" %s %s", cp, TELOPT(pointer[i]));
728 			else
729 			    output_data(" %s %d", cp, pointer[i]);
730 
731 			output_data("\r\n");
732 			break;
733 
734 		    case SB:
735 			output_data(" SB ");
736 			i++;
737 			j = k = i;
738 			while (j < length) {
739 			    if (pointer[j] == SE) {
740 				if (j+1 == length)
741 				    break;
742 				if (pointer[j+1] == SE)
743 				    j++;
744 				else
745 				    break;
746 			    }
747 			    pointer[k++] = pointer[j++];
748 			}
749 			printsub(0, &pointer[i], k - i);
750 			if (i < length) {
751 			    output_data(" SE");
752 			    i = j;
753 			} else
754 			    i = j - 1;
755 
756 			output_data("\r\n");
757 
758 			break;
759 
760 		    default:
761 			output_data(" %d", pointer[i]);
762 			break;
763 		    }
764 		}
765 		break;
766 	    }
767 	    break;
768 	  }
769 
770 	case TELOPT_XDISPLOC:
771 	    output_data("X-DISPLAY-LOCATION ");
772 	    switch (pointer[1]) {
773 	    case TELQUAL_IS:
774 		output_data("IS \"%.*s\"", length-2, (char *)pointer+2);
775 		break;
776 	    case TELQUAL_SEND:
777 		output_data("SEND");
778 		break;
779 	    default:
780 		output_data("- unknown qualifier %d (0x%x).",
781 				pointer[1], pointer[1]);
782 	    }
783 	    break;
784 
785 	case TELOPT_NEW_ENVIRON:
786 	    output_data("NEW-ENVIRON ");
787 	    goto env_common1;
788 	case TELOPT_OLD_ENVIRON:
789 	    output_data("OLD-ENVIRON");
790 	env_common1:
791 	    switch (pointer[1]) {
792 	    case TELQUAL_IS:
793 		output_data("IS ");
794 		goto env_common;
795 	    case TELQUAL_SEND:
796 		output_data("SEND ");
797 		goto env_common;
798 	    case TELQUAL_INFO:
799 		output_data("INFO ");
800 	    env_common:
801 		{
802 		    int noquote = 2;
803 		    for (i = 2; i < length; i++ ) {
804 			switch (pointer[i]) {
805 			case NEW_ENV_VAR:
806 			    output_data("\" VAR " + noquote);
807 			    noquote = 2;
808 			    break;
809 
810 			case NEW_ENV_VALUE:
811 			    output_data("\" VALUE " + noquote);
812 			    noquote = 2;
813 			    break;
814 
815 			case ENV_ESC:
816 			    output_data("\" ESC " + noquote);
817 			    noquote = 2;
818 			    break;
819 
820 			case ENV_USERVAR:
821 			    output_data("\" USERVAR " + noquote);
822 			    noquote = 2;
823 			    break;
824 
825 			default:
826 			    if (isprint(pointer[i]) && pointer[i] != '"') {
827 				if (noquote) {
828 				    output_data("\"");
829 				    noquote = 0;
830 				}
831 				output_data("%c", pointer[i]);
832 			    } else {
833 				output_data("\" %03o " + noquote,
834 							pointer[i]);
835 				noquote = 2;
836 			    }
837 			    break;
838 			}
839 		    }
840 		    if (!noquote)
841 			output_data("\"");
842 		    break;
843 		}
844 	    }
845 	    break;
846 
847 
848 
849 	default:
850 	    if (TELOPT_OK(pointer[0]))
851 		output_data("%s (unknown)", TELOPT(pointer[0]));
852 	    else
853 		output_data("%d (unknown)", pointer[i]);
854 	    for (i = 1; i < length; i++) {
855 		output_data(" %d", pointer[i]);
856 	    }
857 	    break;
858 	}
859 	output_data("\r\n");
860 }
861 
862 /*
863  * Dump a data buffer in hex and ascii to the output data stream.
864  */
865 void
866 printdata(const char *tag, char *ptr, int cnt)
867 {
868 	int i;
869 	char xbuf[30];
870 
871 	while (cnt) {
872 		/* flush net output buffer if no room for new data) */
873 		if ((&netobuf[BUFSIZ] - nfrontp) < 80) {
874 			netflush();
875 		}
876 
877 		/* add a line of output */
878 		output_data("%s: ", tag);
879 		for (i = 0; i < 20 && cnt; i++) {
880 			output_data("%02x", *ptr);
881 			if (isprint(*ptr)) {
882 				xbuf[i] = *ptr;
883 			} else {
884 				xbuf[i] = '.';
885 			}
886 			if (i % 2) {
887 				output_data(" ");
888 			}
889 			cnt--;
890 			ptr++;
891 		}
892 		xbuf[i] = '\0';
893 		output_data(" %s\r\n", xbuf );
894 	}
895 }
896 #endif /* DIAGNOSTICS */
897