added uu* devices (tu58 on dl11 controller)
[unix-history] / usr / src / usr.bin / telnet / telnet.c
CommitLineData
f18adf63 1#ifndef lint
9c1dab9e 2static char sccsid[] = "@(#)telnet.c 4.19 (Berkeley) %G%";
f18adf63
SL
3#endif
4
a19db822
BJ
5/*
6 * User telnet program.
7 */
de3b21e8
SL
8#include <sys/types.h>
9#include <sys/socket.h>
fb8e28da 10#include <sys/ioctl.h>
de3b21e8
SL
11
12#include <netinet/in.h>
13
9c1dab9e
SL
14#define TELOPTS
15#include <arpa/telnet.h>
16
a19db822
BJ
17#include <stdio.h>
18#include <ctype.h>
19#include <errno.h>
20#include <signal.h>
a19db822 21#include <setjmp.h>
9f005877 22#include <netdb.h>
de3b21e8 23
a19db822 24#define strip(x) ((x)&0177)
a19db822
BJ
25
26char ttyobuf[BUFSIZ], *tfrontp = ttyobuf, *tbackp = ttyobuf;
a50d5753 27char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
a19db822
BJ
28
29char hisopts[256];
30char myopts[256];
31
32char doopt[] = { IAC, DO, '%', 'c', 0 };
33char dont[] = { IAC, DONT, '%', 'c', 0 };
34char will[] = { IAC, WILL, '%', 'c', 0 };
35char wont[] = { IAC, WONT, '%', 'c', 0 };
36
37int connected;
38int net;
fb8e28da 39int showoptions = 0;
19baf46e 40int options;
4971ba8c 41int debug = 0;
fb8e28da 42int crmod = 0;
a19db822 43char *prompt;
fb8e28da 44char escape = CTRL(]);
a19db822
BJ
45
46char line[200];
47int margc;
48char *margv[20];
49
50jmp_buf toplevel;
51jmp_buf peerdied;
52
53extern int errno;
54
55int tn(), quit(), suspend(), bye(), help();
de372207 56int setescape(), status(), toggle(), setoptions();
5d6648c9 57int setcrmod(), setdebug();
a19db822 58
a50d5753 59#define HELPINDENT (sizeof ("connect"))
a19db822
BJ
60
61struct cmd {
f18adf63
SL
62 char *name; /* command name */
63 char *help; /* help string */
64 int (*handler)(); /* routine which executes command */
a19db822
BJ
65};
66
f18adf63
SL
67char openhelp[] = "connect to a site";
68char closehelp[] = "close current connection";
69char quithelp[] = "exit telnet";
70char zhelp[] = "suspend telnet";
71char debughelp[] = "toggle debugging";
72char escapehelp[] = "set escape character";
73char statushelp[] = "print status information";
74char helphelp[] = "print help information";
75char optionshelp[] = "toggle viewing of options processing";
76char crmodhelp[] = "toggle mapping of received carriage returns";
a19db822
BJ
77
78struct cmd cmdtab[] = {
f18adf63
SL
79 { "open", openhelp, tn },
80 { "close", closehelp, bye },
81 { "quit", quithelp, quit },
a19db822 82 { "z", zhelp, suspend },
f18adf63
SL
83 { "escape", escapehelp, setescape },
84 { "status", statushelp, status },
85 { "options", optionshelp, setoptions },
86 { "crmod", crmodhelp, setcrmod },
87 { "debug", debughelp, setdebug },
88 { "?", helphelp, help },
a19db822
BJ
89 0
90};
91
fb8e28da 92struct sockaddr_in sin;
a19db822
BJ
93
94int intr(), deadpeer();
95char *control();
96struct cmd *getcmd();
9f005877 97struct servent *sp;
a19db822 98
fb8e28da
SL
99struct ttychars otc;
100int oflags;
a50d5753 101
a19db822
BJ
102main(argc, argv)
103 int argc;
104 char *argv[];
105{
9f005877
SL
106 sp = getservbyname("telnet", "tcp");
107 if (sp == 0) {
108 fprintf(stderr, "telnet: tcp/telnet: unknown service\n");
109 exit(1);
110 }
fb8e28da
SL
111 ioctl(0, TIOCGET, (char *)&oflags);
112 ioctl(0, TIOCCGET, (char *)&otc);
a19db822
BJ
113 setbuf(stdin, 0);
114 setbuf(stdout, 0);
115 prompt = argv[0];
19baf46e
BF
116 if (argc > 1 && !strcmp(argv[1], "-d"))
117 options = SO_DEBUG, argv++, argc--;
a19db822
BJ
118 if (argc != 1) {
119 if (setjmp(toplevel) != 0)
120 exit(0);
121 tn(argc, argv);
122 }
123 setjmp(toplevel);
124 for (;;)
125 command(1);
126}
127
150ad7e5
SL
128char *hostname;
129char hnamebuf[32];
a19db822
BJ
130
131tn(argc, argv)
132 int argc;
133 char *argv[];
134{
135 register int c;
150ad7e5 136 register struct hostent *host;
a19db822
BJ
137
138 if (connected) {
150ad7e5 139 printf("?Already connected to %s\n", hostname);
a19db822
BJ
140 return;
141 }
142 if (argc < 2) {
143 strcpy(line, "Connect ");
144 printf("(to) ");
145 gets(&line[strlen(line)]);
146 makeargv();
147 argc = margc;
148 argv = margv;
149 }
150 if (argc > 3) {
151 printf("usage: %s host-name [port]\n", argv[0]);
152 return;
153 }
9f005877 154 host = gethostbyname(argv[1]);
150ad7e5 155 if (host) {
de3b21e8
SL
156 sin.sin_family = host->h_addrtype;
157 bcopy(host->h_addr, (caddr_t)&sin.sin_addr, host->h_length);
150ad7e5
SL
158 hostname = host->h_name;
159 } else {
de3b21e8 160 sin.sin_family = AF_INET;
150ad7e5
SL
161 sin.sin_addr.s_addr = inet_addr(argv[1]);
162 if (sin.sin_addr.s_addr == -1) {
163 printf("%s: unknown host\n", argv[1]);
164 return;
165 }
166 strcpy(hnamebuf, argv[1]);
167 hostname = hnamebuf;
a19db822 168 }
9f005877 169 sin.sin_port = sp->s_port;
de372207
SL
170 if (argc == 3) {
171 sin.sin_port = atoi(argv[2]);
172 if (sin.sin_port < 0) {
173 printf("%s: bad port number\n", argv[2]);
174 return;
175 }
5eba30a3 176 sin.sin_port = htons(sin.sin_port);
de372207 177 }
9de2cbb6 178 net = socket(AF_INET, SOCK_STREAM, 0, 0);
de3b21e8
SL
179 if (net < 0) {
180 perror("telnet: socket");
a19db822
BJ
181 return;
182 }
f18adf63
SL
183 if (debug && setsockopt(net, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
184 perror("setsockopt (SO_DEBUG)");
a19db822
BJ
185 sigset(SIGINT, intr);
186 sigset(SIGPIPE, deadpeer);
187 printf("Trying...\n");
de3b21e8
SL
188 if (connect(net, (caddr_t)&sin, sizeof (sin), 0) < 0) {
189 perror("telnet: connect");
a19db822
BJ
190 sigset(SIGINT, SIG_DFL);
191 return;
192 }
a19db822
BJ
193 connected++;
194 call(status, "status", 0);
195 if (setjmp(peerdied) == 0)
196 telnet(net);
197 fprintf(stderr, "Connection closed by foreign host.\n");
198 exit(1);
199}
200
201/*
202 * Print status about the connection.
203 */
204/*VARARGS*/
205status()
206{
207 if (connected)
150ad7e5 208 printf("Connected to %s.\n", hostname);
a19db822
BJ
209 else
210 printf("No connection.\n");
211 printf("Escape character is '%s'.\n", control(escape));
fb8e28da 212 fflush(stdout);
a19db822
BJ
213}
214
215makeargv()
216{
217 register char *cp;
218 register char **argp = margv;
219
220 margc = 0;
221 for (cp = line; *cp;) {
222 while (isspace(*cp))
223 cp++;
224 if (*cp == '\0')
225 break;
226 *argp++ = cp;
227 margc += 1;
228 while (*cp != '\0' && !isspace(*cp))
229 cp++;
230 if (*cp == '\0')
231 break;
232 *cp++ = '\0';
233 }
234 *argp++ = 0;
235}
236
237/*VARARGS*/
238suspend()
239{
240 register int save;
241
242 save = mode(0);
a50d5753
SL
243 kill(0, SIGTSTP);
244 /* reget parameters in case they were changed */
fb8e28da
SL
245 ioctl(0, TIOCGET, (char *)&oflags);
246 ioctl(0, TIOCCGET, (char *)&otc);
a50d5753 247 (void) mode(save);
a19db822
BJ
248}
249
250/*VARARGS*/
251bye()
252{
a50d5753 253 register char *op;
a19db822 254
a50d5753 255 (void) mode(0);
a19db822 256 if (connected) {
f18adf63 257 shutdown(net, 2);
a19db822
BJ
258 printf("Connection closed.\n");
259 close(net);
260 connected = 0;
a50d5753
SL
261 /* reset his options */
262 for (op = hisopts; op < &hisopts[256]; op++)
263 *op = 0;
a19db822
BJ
264 }
265}
266
267/*VARARGS*/
268quit()
269{
270 call(bye, "bye", 0);
271 exit(0);
272}
273
274/*
275 * Help command.
a19db822
BJ
276 */
277help(argc, argv)
278 int argc;
279 char *argv[];
280{
281 register struct cmd *c;
282
283 if (argc == 1) {
284 printf("Commands may be abbreviated. Commands are:\n\n");
285 for (c = cmdtab; c->name; c++)
286 printf("%-*s\t%s\n", HELPINDENT, c->name, c->help);
287 return;
288 }
289 while (--argc > 0) {
290 register char *arg;
291 arg = *++argv;
292 c = getcmd(arg);
293 if (c == (struct cmd *)-1)
294 printf("?Ambiguous help command %s\n", arg);
295 else if (c == (struct cmd *)0)
296 printf("?Invalid help command %s\n", arg);
297 else
298 printf("%s\n", c->help);
299 }
300}
301
302/*
303 * Call routine with argc, argv set from args (terminated by 0).
304 * VARARGS2
305 */
306call(routine, args)
307 int (*routine)();
308 int args;
309{
310 register int *argp;
311 register int argc;
312
313 for (argc = 0, argp = &args; *argp++ != 0; argc++)
314 ;
315 (*routine)(argc, &args);
316}
317
fb8e28da
SL
318struct ttychars notc = {
319 -1, -1, -1, -1, -1,
320 -1, -1, -1, -1, -1,
321 -1, -1, -1, -1
322};
323
a19db822
BJ
324mode(f)
325 register int f;
326{
a50d5753 327 static int prevmode = 0;
fb8e28da
SL
328 struct ttychars *tc;
329 int onoff, old, flags;
a50d5753
SL
330
331 if (prevmode == f)
332 return (f);
333 old = prevmode;
334 prevmode = f;
fb8e28da 335 flags = oflags;
a19db822 336 switch (f) {
a50d5753 337
a19db822 338 case 0:
a19db822 339 onoff = 0;
fb8e28da 340 tc = &otc;
a19db822
BJ
341 break;
342
343 case 1:
a19db822 344 case 2:
fb8e28da 345 flags |= CBREAK;
a50d5753 346 if (f == 1)
fb8e28da 347 flags &= ~(ECHO|CRMOD);
a50d5753 348 else
fb8e28da
SL
349 flags |= ECHO|CRMOD;
350 tc = &notc;
a19db822 351 onoff = 1;
fb8e28da
SL
352 break;
353
354 default:
355 return;
a19db822 356 }
fb8e28da
SL
357 ioctl(fileno(stdin), TIOCCSET, (char *)tc);
358 ioctl(fileno(stdin), TIOCSET, (char *)&flags);
a19db822
BJ
359 ioctl(fileno(stdin), FIONBIO, &onoff);
360 ioctl(fileno(stdout), FIONBIO, &onoff);
361 return (old);
362}
363
364char sibuf[BUFSIZ], *sbp;
365char tibuf[BUFSIZ], *tbp;
366int scc, tcc;
367
368/*
369 * Select from tty and network...
370 */
371telnet(s)
372 int s;
373{
374 register int c;
375 int tin = fileno(stdin), tout = fileno(stdout);
376 int on = 1;
377
a50d5753 378 (void) mode(2);
a19db822
BJ
379 ioctl(s, FIONBIO, &on);
380 for (;;) {
381 int ibits = 0, obits = 0;
382
383 if (nfrontp - nbackp)
384 obits |= (1 << s);
385 else
386 ibits |= (1 << tin);
387 if (tfrontp - tbackp)
388 obits |= (1 << tout);
389 else
390 ibits |= (1 << s);
391 if (scc < 0 && tcc < 0)
392 break;
de3b21e8 393 select(16, &ibits, &obits, 0, 0);
a19db822
BJ
394 if (ibits == 0 && obits == 0) {
395 sleep(5);
396 continue;
397 }
398
399 /*
400 * Something to read from the network...
401 */
402 if (ibits & (1 << s)) {
a50d5753 403 scc = read(s, sibuf, sizeof (sibuf));
a19db822
BJ
404 if (scc < 0 && errno == EWOULDBLOCK)
405 scc = 0;
406 else {
407 if (scc <= 0)
408 break;
409 sbp = sibuf;
410 }
411 }
412
413 /*
414 * Something to read from the tty...
415 */
416 if (ibits & (1 << tin)) {
a50d5753 417 tcc = read(tin, tibuf, sizeof (tibuf));
a19db822
BJ
418 if (tcc < 0 && errno == EWOULDBLOCK)
419 tcc = 0;
420 else {
421 if (tcc <= 0)
422 break;
423 tbp = tibuf;
424 }
425 }
426
427 while (tcc > 0) {
428 register int c;
429
430 if ((&netobuf[BUFSIZ] - nfrontp) < 2)
431 break;
432 c = *tbp++ & 0377, tcc--;
433 if (strip(c) == escape) {
434 command(0);
435 tcc = 0;
436 break;
437 }
f18adf63
SL
438 if (c == IAC)
439 *nfrontp++ = c;
a19db822
BJ
440 *nfrontp++ = c;
441 }
442 if ((obits & (1 << s)) && (nfrontp - nbackp) > 0)
443 netflush(s);
444 if (scc > 0)
445 telrcv();
446 if ((obits & (1 << tout)) && (tfrontp - tbackp) > 0)
447 ttyflush(tout);
448 }
a50d5753 449 (void) mode(0);
a19db822
BJ
450}
451
452command(top)
453 int top;
454{
455 register struct cmd *c;
456 int oldmode, wasopen;
457
458 oldmode = mode(0);
459 if (!top)
460 putchar('\n');
461 else
462 sigset(SIGINT, SIG_DFL);
463 for (;;) {
464 printf("%s> ", prompt);
465 if (gets(line) == 0)
466 break;
467 if (line[0] == 0)
468 break;
469 makeargv();
470 c = getcmd(margv[0]);
471 if (c == (struct cmd *)-1) {
472 printf("?Ambiguous command\n");
473 continue;
474 }
475 if (c == 0) {
476 printf("?Invalid command\n");
477 continue;
478 }
479 (*c->handler)(margc, margv);
480 if (c->handler != help)
481 break;
482 }
483 if (!top) {
484 if (!connected)
485 longjmp(toplevel, 1);
a50d5753 486 (void) mode(oldmode);
a19db822
BJ
487 }
488}
489
490/*
491 * Telnet receiver states for fsm
492 */
493#define TS_DATA 0
494#define TS_IAC 1
495#define TS_WILL 2
496#define TS_WONT 3
497#define TS_DO 4
498#define TS_DONT 5
499
500telrcv()
501{
502 register int c;
503 static int state = TS_DATA;
504
505 while (scc > 0) {
506 c = *sbp++ & 0377, scc--;
507 switch (state) {
508
509 case TS_DATA:
fb8e28da 510 if (c == IAC) {
a19db822 511 state = TS_IAC;
fb8e28da
SL
512 continue;
513 }
514 *tfrontp++ = c;
515 /*
516 * This hack is needed since we can't set
517 * CRMOD on output only. Machines like MULTICS
518 * like to send \r without \n; since we must
519 * turn off CRMOD to get proper input, the mapping
520 * is done here (sigh).
521 */
522 if (c == '\r' && crmod)
523 *tfrontp++ = '\n';
a19db822
BJ
524 continue;
525
526 case TS_IAC:
527 switch (c) {
528
529 case WILL:
530 state = TS_WILL;
531 continue;
532
533 case WONT:
534 state = TS_WONT;
535 continue;
536
537 case DO:
538 state = TS_DO;
539 continue;
540
541 case DONT:
542 state = TS_DONT;
543 continue;
544
545 case DM:
546 ioctl(fileno(stdout), TIOCFLUSH, 0);
547 break;
548
549 case NOP:
550 case GA:
551 break;
552
553 default:
554 break;
555 }
556 state = TS_DATA;
557 continue;
558
559 case TS_WILL:
9f005877 560 printoption("RCVD", will, c, !hisopts[c]);
a19db822
BJ
561 if (!hisopts[c])
562 willoption(c);
563 state = TS_DATA;
564 continue;
565
566 case TS_WONT:
9f005877 567 printoption("RCVD", wont, c, hisopts[c]);
a19db822
BJ
568 if (hisopts[c])
569 wontoption(c);
570 state = TS_DATA;
571 continue;
572
573 case TS_DO:
9f005877 574 printoption("RCVD", doopt, c, !myopts[c]);
a19db822
BJ
575 if (!myopts[c])
576 dooption(c);
577 state = TS_DATA;
578 continue;
579
580 case TS_DONT:
9f005877 581 printoption("RCVD", dont, c, myopts[c]);
a19db822
BJ
582 if (myopts[c]) {
583 myopts[c] = 0;
584 sprintf(nfrontp, wont, c);
a50d5753 585 nfrontp += sizeof (wont) - 2;
9f005877 586 printoption("SENT", wont, c);
a19db822
BJ
587 }
588 state = TS_DATA;
589 continue;
590 }
591 }
592}
593
594willoption(option)
595 int option;
596{
597 char *fmt;
598
599 switch (option) {
600
601 case TELOPT_ECHO:
a50d5753 602 (void) mode(1);
a19db822
BJ
603
604 case TELOPT_SGA:
605 hisopts[option] = 1;
606 fmt = doopt;
607 break;
608
609 case TELOPT_TM:
610 fmt = dont;
611 break;
612
613 default:
614 fmt = dont;
615 break;
616 }
de372207 617 sprintf(nfrontp, fmt, option);
a50d5753 618 nfrontp += sizeof (dont) - 2;
9f005877 619 printoption("SENT", fmt, option);
a19db822
BJ
620}
621
622wontoption(option)
623 int option;
624{
625 char *fmt;
626
627 switch (option) {
628
629 case TELOPT_ECHO:
a50d5753 630 (void) mode(2);
a19db822
BJ
631
632 case TELOPT_SGA:
633 hisopts[option] = 0;
634 fmt = dont;
635 break;
636
637 default:
638 fmt = dont;
639 }
640 sprintf(nfrontp, fmt, option);
a50d5753 641 nfrontp += sizeof (doopt) - 2;
9f005877 642 printoption("SENT", fmt, option);
a19db822
BJ
643}
644
645dooption(option)
646 int option;
647{
648 char *fmt;
649
650 switch (option) {
651
652 case TELOPT_TM:
653 fmt = wont;
654 break;
655
656 case TELOPT_SGA:
657 fmt = will;
658 break;
659
660 default:
661 fmt = wont;
662 break;
663 }
664 sprintf(nfrontp, fmt, option);
a50d5753 665 nfrontp += sizeof (doopt) - 2;
9f005877 666 printoption("SENT", fmt, option);
a19db822
BJ
667}
668
669/*
670 * Set the escape character.
671 */
672setescape(argc, argv)
673 int argc;
674 char *argv[];
675{
676 register char *arg;
677 char buf[50];
678
679 if (argc > 2)
680 arg = argv[1];
681 else {
682 printf("new escape character: ");
683 gets(buf);
684 arg = buf;
685 }
686 if (arg[0] != '\0')
687 escape = arg[0];
688 printf("Escape character is '%s'.\n", control(escape));
fb8e28da 689 fflush(stdout);
a19db822
BJ
690}
691
de372207
SL
692/*VARARGS*/
693setoptions()
694{
fb8e28da 695
de372207
SL
696 showoptions = !showoptions;
697 printf("%s show option processing.\n", showoptions ? "Will" : "Wont");
fb8e28da
SL
698 fflush(stdout);
699}
700
701/*VARARGS*/
702setcrmod()
703{
704
705 crmod = !crmod;
706 printf("%s map carriage return on output.\n", crmod ? "Will" : "Wont");
707 fflush(stdout);
de372207
SL
708}
709
4971ba8c
SL
710/*VARARGS*/
711setdebug()
712{
713
714 debug = !debug;
715 printf("%s turn on socket level debugging.\n",
716 debug ? "Will" : "Wont");
717 fflush(stdout);
f18adf63
SL
718 if (debug && setsockopt(net, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
719 perror("setsockopt (SO_DEBUG)");
4971ba8c
SL
720}
721
a19db822
BJ
722/*
723 * Construct a control character sequence
724 * for a special character.
725 */
726char *
727control(c)
728 register int c;
729{
730 static char buf[3];
731
732 if (c == 0177)
733 return ("^?");
734 if (c >= 040) {
735 buf[0] = c;
736 buf[1] = 0;
737 } else {
738 buf[0] = '^';
739 buf[1] = '@'+c;
740 buf[2] = 0;
741 }
742 return (buf);
743}
744
745struct cmd *
746getcmd(name)
747 register char *name;
748{
749 register char *p, *q;
750 register struct cmd *c, *found;
751 register int nmatches, longest;
752
753 longest = 0;
754 nmatches = 0;
755 found = 0;
756 for (c = cmdtab; p = c->name; c++) {
757 for (q = name; *q == *p++; q++)
758 if (*q == 0) /* exact match? */
759 return (c);
760 if (!*q) { /* the name was a prefix */
761 if (q - name > longest) {
762 longest = q - name;
763 nmatches = 1;
764 found = c;
765 } else if (q - name == longest)
766 nmatches++;
767 }
768 }
769 if (nmatches > 1)
770 return ((struct cmd *)-1);
771 return (found);
772}
773
774deadpeer()
775{
776 sigset(SIGPIPE, deadpeer);
a50d5753 777 (void) mode(0);
a19db822
BJ
778 longjmp(peerdied, -1);
779}
780
781intr()
782{
783 sigset(SIGINT, intr);
a50d5753 784 (void) mode(0);
a19db822
BJ
785 longjmp(toplevel, -1);
786}
787
788ttyflush(fd)
789{
790 int n;
791
792 if ((n = tfrontp - tbackp) > 0)
793 n = write(fd, tbackp, n);
9f005877
SL
794 if (n < 0)
795 return;
a19db822
BJ
796 tbackp += n;
797 if (tbackp == tfrontp)
798 tbackp = tfrontp = ttyobuf;
799}
800
801netflush(fd)
802{
803 int n;
804
805 if ((n = nfrontp - nbackp) > 0)
806 n = write(fd, nbackp, n);
f103d8a9
SL
807 if (n < 0) {
808 if (errno != ENOBUFS && errno != EWOULDBLOCK) {
a50d5753 809 (void) mode(0);
150ad7e5 810 perror(hostname);
f103d8a9
SL
811 close(fd);
812 longjmp(peerdied, -1);
813 /*NOTREACHED*/
814 }
a19db822 815 n = 0;
f103d8a9 816 }
a19db822
BJ
817 nbackp += n;
818 if (nbackp == nfrontp)
819 nbackp = nfrontp = netobuf;
820}
de372207 821
6cebba9f
BJ
822/*VARARGS*/
823printoption(direction, fmt, option, what)
de372207 824 char *direction, *fmt;
6cebba9f 825 int option, what;
de372207 826{
9f005877
SL
827 if (!showoptions)
828 return;
de372207
SL
829 printf("%s ", direction);
830 if (fmt == doopt)
831 fmt = "do";
832 else if (fmt == dont)
833 fmt = "dont";
834 else if (fmt == will)
835 fmt = "will";
836 else if (fmt == wont)
837 fmt = "wont";
838 else
839 fmt = "???";
840 if (option < TELOPT_SUPDUP)
6cebba9f 841 printf("%s %s", fmt, telopts[option]);
de372207 842 else
6cebba9f
BJ
843 printf("%s %d", fmt, option);
844 if (*direction == '<') {
845 printf("\r\n");
846 return;
847 }
848 printf(" (%s)\r\n", what ? "reply" : "don't reply");
de372207 849}