Initial import from FreeBSD RELENG_4:
[dragonfly.git] / libexec / telnetd / state.c
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
34 #include <sys/cdefs.h>
35
36 __FBSDID("$FreeBSD: src/libexec/telnetd/state.c,v 1.9.2.4 2002/04/13 11:07:12 markm Exp $");
37
38 #ifndef lint
39 static const char sccsid[] = "@(#)state.c       8.5 (Berkeley) 5/30/95";
40 #endif
41
42 #include <stdarg.h>
43 #include "telnetd.h"
44
45 unsigned char   doopt[] = { IAC, DO, '%', 'c', 0 };
46 unsigned char   dont[] = { IAC, DONT, '%', 'c', 0 };
47 unsigned char   will[] = { IAC, WILL, '%', 'c', 0 };
48 unsigned char   wont[] = { IAC, WONT, '%', 'c', 0 };
49 int     not42 = 1;
50
51 /*
52  * Buffer for sub-options, and macros
53  * for suboptions buffer manipulations
54  */
55 unsigned char subbuffer[512], *subpointer= subbuffer, *subend= subbuffer;
56
57 #define SB_CLEAR()      subpointer = subbuffer
58 #define SB_TERM()       { subend = subpointer; SB_CLEAR(); }
59 #define SB_ACCUM(c)     if (subpointer < (subbuffer+sizeof subbuffer)) { \
60                                 *subpointer++ = (c); \
61                         }
62 #define SB_GET()        ((*subpointer++)&0xff)
63 #define SB_EOF()        (subpointer >= subend)
64 #define SB_LEN()        (subend - subpointer)
65
66 #ifdef  ENV_HACK
67 unsigned char *subsave;
68 #define SB_SAVE()       subsave = subpointer;
69 #define SB_RESTORE()    subpointer = subsave;
70 #endif
71
72
73 /*
74  * State for recv fsm
75  */
76 #define TS_DATA         0       /* base state */
77 #define TS_IAC          1       /* look for double IAC's */
78 #define TS_CR           2       /* CR-LF ->'s CR */
79 #define TS_SB           3       /* throw away begin's... */
80 #define TS_SE           4       /* ...end's (suboption negotiation) */
81 #define TS_WILL         5       /* will option negotiation */
82 #define TS_WONT         6       /* wont " */
83 #define TS_DO           7       /* do " */
84 #define TS_DONT         8       /* dont " */
85
86 static void doclientstat(void);
87
88 void
89 telrcv(void)
90 {
91         int c;
92         static int state = TS_DATA;
93
94         while (ncc > 0) {
95                 if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
96                         break;
97                 c = *netip++ & 0377, ncc--;
98                 switch (state) {
99
100                 case TS_CR:
101                         state = TS_DATA;
102                         /* Strip off \n or \0 after a \r */
103                         if ((c == 0) || (c == '\n')) {
104                                 break;
105                         }
106                         /* FALL THROUGH */
107
108                 case TS_DATA:
109                         if (c == IAC) {
110                                 state = TS_IAC;
111                                 break;
112                         }
113                         /*
114                          * We now map \r\n ==> \r for pragmatic reasons.
115                          * Many client implementations send \r\n when
116                          * the user hits the CarriageReturn key.
117                          *
118                          * We USED to map \r\n ==> \n, since \r\n says
119                          * that we want to be in column 1 of the next
120                          * printable line, and \n is the standard
121                          * unix way of saying that (\r is only good
122                          * if CRMOD is set, which it normally is).
123                          */
124                         if ((c == '\r') && his_state_is_wont(TELOPT_BINARY)) {
125                                 int nc = *netip;
126 #ifdef  LINEMODE
127                                 /*
128                                  * If we are operating in linemode,
129                                  * convert to local end-of-line.
130                                  */
131                                 if (linemode && (ncc > 0) && (('\n' == nc) ||
132                                          ((0 == nc) && tty_iscrnl())) ) {
133                                         netip++; ncc--;
134                                         c = '\n';
135                                 } else
136 #endif
137                                 {
138                                         state = TS_CR;
139                                 }
140                         }
141                         *pfrontp++ = c;
142                         break;
143
144                 case TS_IAC:
145 gotiac:                 switch (c) {
146
147                         /*
148                          * Send the process on the pty side an
149                          * interrupt.  Do this with a NULL or
150                          * interrupt char; depending on the tty mode.
151                          */
152                         case IP:
153                                 DIAG(TD_OPTIONS,
154                                         printoption("td: recv IAC", c));
155                                 interrupt();
156                                 break;
157
158                         case BREAK:
159                                 DIAG(TD_OPTIONS,
160                                         printoption("td: recv IAC", c));
161                                 sendbrk();
162                                 break;
163
164                         /*
165                          * Are You There?
166                          */
167                         case AYT:
168                                 DIAG(TD_OPTIONS,
169                                         printoption("td: recv IAC", c));
170                                 recv_ayt();
171                                 break;
172
173                         /*
174                          * Abort Output
175                          */
176                         case AO:
177                             {
178                                 DIAG(TD_OPTIONS,
179                                         printoption("td: recv IAC", c));
180                                 ptyflush();     /* half-hearted */
181                                 init_termbuf();
182
183                                 if (slctab[SLC_AO].sptr &&
184                                     *slctab[SLC_AO].sptr != (cc_t)(_POSIX_VDISABLE)) {
185                                     *pfrontp++ =
186                                         (unsigned char)*slctab[SLC_AO].sptr;
187                                 }
188
189                                 netclear();     /* clear buffer back */
190                                 output_data("%c%c", IAC, DM);
191                                 neturg = nfrontp-1; /* off by one XXX */
192                                 DIAG(TD_OPTIONS,
193                                         printoption("td: send IAC", DM));
194                                 break;
195                             }
196
197                         /*
198                          * Erase Character and
199                          * Erase Line
200                          */
201                         case EC:
202                         case EL:
203                             {
204                                 cc_t ch;
205
206                                 DIAG(TD_OPTIONS,
207                                         printoption("td: recv IAC", c));
208                                 ptyflush();     /* half-hearted */
209                                 init_termbuf();
210                                 if (c == EC)
211                                         ch = *slctab[SLC_EC].sptr;
212                                 else
213                                         ch = *slctab[SLC_EL].sptr;
214                                 if (ch != (cc_t)(_POSIX_VDISABLE))
215                                         *pfrontp++ = (unsigned char)ch;
216                                 break;
217                             }
218
219                         /*
220                          * Check for urgent data...
221                          */
222                         case DM:
223                                 DIAG(TD_OPTIONS,
224                                         printoption("td: recv IAC", c));
225                                 SYNCHing = stilloob(net);
226                                 settimer(gotDM);
227                                 break;
228
229
230                         /*
231                          * Begin option subnegotiation...
232                          */
233                         case SB:
234                                 state = TS_SB;
235                                 SB_CLEAR();
236                                 continue;
237
238                         case WILL:
239                                 state = TS_WILL;
240                                 continue;
241
242                         case WONT:
243                                 state = TS_WONT;
244                                 continue;
245
246                         case DO:
247                                 state = TS_DO;
248                                 continue;
249
250                         case DONT:
251                                 state = TS_DONT;
252                                 continue;
253                         case EOR:
254                                 if (his_state_is_will(TELOPT_EOR))
255                                         doeof();
256                                 break;
257
258                         /*
259                          * Handle RFC 10xx Telnet linemode option additions
260                          * to command stream (EOF, SUSP, ABORT).
261                          */
262                         case xEOF:
263                                 doeof();
264                                 break;
265
266                         case SUSP:
267                                 sendsusp();
268                                 break;
269
270                         case ABORT:
271                                 sendbrk();
272                                 break;
273
274                         case IAC:
275                                 *pfrontp++ = c;
276                                 break;
277                         }
278                         state = TS_DATA;
279                         break;
280
281                 case TS_SB:
282                         if (c == IAC) {
283                                 state = TS_SE;
284                         } else {
285                                 SB_ACCUM(c);
286                         }
287                         break;
288
289                 case TS_SE:
290                         if (c != SE) {
291                                 if (c != IAC) {
292                                         /*
293                                          * bad form of suboption negotiation.
294                                          * handle it in such a way as to avoid
295                                          * damage to local state.  Parse
296                                          * suboption buffer found so far,
297                                          * then treat remaining stream as
298                                          * another command sequence.
299                                          */
300
301                                         /* for DIAGNOSTICS */
302                                         SB_ACCUM(IAC);
303                                         SB_ACCUM(c);
304                                         subpointer -= 2;
305
306                                         SB_TERM();
307                                         suboption();
308                                         state = TS_IAC;
309                                         goto gotiac;
310                                 }
311                                 SB_ACCUM(c);
312                                 state = TS_SB;
313                         } else {
314                                 /* for DIAGNOSTICS */
315                                 SB_ACCUM(IAC);
316                                 SB_ACCUM(SE);
317                                 subpointer -= 2;
318
319                                 SB_TERM();
320                                 suboption();    /* handle sub-option */
321                                 state = TS_DATA;
322                         }
323                         break;
324
325                 case TS_WILL:
326                         willoption(c);
327                         state = TS_DATA;
328                         continue;
329
330                 case TS_WONT:
331                         wontoption(c);
332                         state = TS_DATA;
333                         continue;
334
335                 case TS_DO:
336                         dooption(c);
337                         state = TS_DATA;
338                         continue;
339
340                 case TS_DONT:
341                         dontoption(c);
342                         state = TS_DATA;
343                         continue;
344
345                 default:
346                         syslog(LOG_ERR, "panic state=%d", state);
347                         printf("telnetd: panic state=%d\n", state);
348                         exit(1);
349                 }
350         }
351 }  /* end of telrcv */
352
353 /*
354  * The will/wont/do/dont state machines are based on Dave Borman's
355  * Telnet option processing state machine.
356  *
357  * These correspond to the following states:
358  *      my_state = the last negotiated state
359  *      want_state = what I want the state to go to
360  *      want_resp = how many requests I have sent
361  * All state defaults are negative, and resp defaults to 0.
362  *
363  * When initiating a request to change state to new_state:
364  *
365  * if ((want_resp == 0 && new_state == my_state) || want_state == new_state) {
366  *      do nothing;
367  * } else {
368  *      want_state = new_state;
369  *      send new_state;
370  *      want_resp++;
371  * }
372  *
373  * When receiving new_state:
374  *
375  * if (want_resp) {
376  *      want_resp--;
377  *      if (want_resp && (new_state == my_state))
378  *              want_resp--;
379  * }
380  * if ((want_resp == 0) && (new_state != want_state)) {
381  *      if (ok_to_switch_to new_state)
382  *              want_state = new_state;
383  *      else
384  *              want_resp++;
385  *      send want_state;
386  * }
387  * my_state = new_state;
388  *
389  * Note that new_state is implied in these functions by the function itself.
390  * will and do imply positive new_state, wont and dont imply negative.
391  *
392  * Finally, there is one catch.  If we send a negative response to a
393  * positive request, my_state will be the positive while want_state will
394  * remain negative.  my_state will revert to negative when the negative
395  * acknowlegment arrives from the peer.  Thus, my_state generally tells
396  * us not only the last negotiated state, but also tells us what the peer
397  * wants to be doing as well.  It is important to understand this difference
398  * as we may wish to be processing data streams based on our desired state
399  * (want_state) or based on what the peer thinks the state is (my_state).
400  *
401  * This all works fine because if the peer sends a positive request, the data
402  * that we receive prior to negative acknowlegment will probably be affected
403  * by the positive state, and we can process it as such (if we can; if we
404  * can't then it really doesn't matter).  If it is that important, then the
405  * peer probably should be buffering until this option state negotiation
406  * is complete.
407  *
408  */
409 void
410 send_do(int option, int init)
411 {
412         if (init) {
413                 if ((do_dont_resp[option] == 0 && his_state_is_will(option)) ||
414                     his_want_state_is_will(option))
415                         return;
416                 /*
417                  * Special case for TELOPT_TM:  We send a DO, but pretend
418                  * that we sent a DONT, so that we can send more DOs if
419                  * we want to.
420                  */
421                 if (option == TELOPT_TM)
422                         set_his_want_state_wont(option);
423                 else
424                         set_his_want_state_will(option);
425                 do_dont_resp[option]++;
426         }
427         output_data((const char *)doopt, option);
428
429         DIAG(TD_OPTIONS, printoption("td: send do", option));
430 }
431
432 void
433 willoption(int option)
434 {
435         int changeok = 0;
436         void (*func)(void) = 0;
437
438         /*
439          * process input from peer.
440          */
441
442         DIAG(TD_OPTIONS, printoption("td: recv will", option));
443
444         if (do_dont_resp[option]) {
445                 do_dont_resp[option]--;
446                 if (do_dont_resp[option] && his_state_is_will(option))
447                         do_dont_resp[option]--;
448         }
449         if (do_dont_resp[option] == 0) {
450             if (his_want_state_is_wont(option)) {
451                 switch (option) {
452
453                 case TELOPT_BINARY:
454                         init_termbuf();
455                         tty_binaryin(1);
456                         set_termbuf();
457                         changeok++;
458                         break;
459
460                 case TELOPT_ECHO:
461                         /*
462                          * See comments below for more info.
463                          */
464                         not42 = 0;      /* looks like a 4.2 system */
465                         break;
466
467                 case TELOPT_TM:
468 #if     defined(LINEMODE) && defined(KLUDGELINEMODE)
469                         /*
470                          * This telnetd implementation does not really
471                          * support timing marks, it just uses them to
472                          * support the kludge linemode stuff.  If we
473                          * receive a will or wont TM in response to our
474                          * do TM request that may have been sent to
475                          * determine kludge linemode support, process
476                          * it, otherwise TM should get a negative
477                          * response back.
478                          */
479                         /*
480                          * Handle the linemode kludge stuff.
481                          * If we are not currently supporting any
482                          * linemode at all, then we assume that this
483                          * is the client telling us to use kludge
484                          * linemode in response to our query.  Set the
485                          * linemode type that is to be supported, note
486                          * that the client wishes to use linemode, and
487                          * eat the will TM as though it never arrived.
488                          */
489                         if (lmodetype < KLUDGE_LINEMODE) {
490                                 lmodetype = KLUDGE_LINEMODE;
491                                 clientstat(TELOPT_LINEMODE, WILL, 0);
492                                 send_wont(TELOPT_SGA, 1);
493                         } else if (lmodetype == NO_AUTOKLUDGE) {
494                                 lmodetype = KLUDGE_OK;
495                         }
496 #endif  /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
497                         /*
498                          * We never respond to a WILL TM, and
499                          * we leave the state WONT.
500                          */
501                         return;
502
503                 case TELOPT_LFLOW:
504                         /*
505                          * If we are going to support flow control
506                          * option, then don't worry peer that we can't
507                          * change the flow control characters.
508                          */
509                         slctab[SLC_XON].defset.flag &= ~SLC_LEVELBITS;
510                         slctab[SLC_XON].defset.flag |= SLC_DEFAULT;
511                         slctab[SLC_XOFF].defset.flag &= ~SLC_LEVELBITS;
512                         slctab[SLC_XOFF].defset.flag |= SLC_DEFAULT;
513                 case TELOPT_TTYPE:
514                 case TELOPT_SGA:
515                 case TELOPT_NAWS:
516                 case TELOPT_TSPEED:
517                 case TELOPT_XDISPLOC:
518                 case TELOPT_NEW_ENVIRON:
519                 case TELOPT_OLD_ENVIRON:
520                         changeok++;
521                         break;
522
523 #ifdef  LINEMODE
524                 case TELOPT_LINEMODE:
525 # ifdef KLUDGELINEMODE
526                         /*
527                          * Note client's desire to use linemode.
528                          */
529                         lmodetype = REAL_LINEMODE;
530 # endif /* KLUDGELINEMODE */
531                         func = doclientstat;
532                         changeok++;
533                         break;
534 #endif  /* LINEMODE */
535
536
537
538                 default:
539                         break;
540                 }
541                 if (changeok) {
542                         set_his_want_state_will(option);
543                         send_do(option, 0);
544                 } else {
545                         do_dont_resp[option]++;
546                         send_dont(option, 0);
547                 }
548             } else {
549                 /*
550                  * Option processing that should happen when
551                  * we receive conformation of a change in
552                  * state that we had requested.
553                  */
554                 switch (option) {
555                 case TELOPT_ECHO:
556                         not42 = 0;      /* looks like a 4.2 system */
557                         /*
558                          * Egads, he responded "WILL ECHO".  Turn
559                          * it off right now!
560                          */
561                         send_dont(option, 1);
562                         /*
563                          * "WILL ECHO".  Kludge upon kludge!
564                          * A 4.2 client is now echoing user input at
565                          * the tty.  This is probably undesireable and
566                          * it should be stopped.  The client will
567                          * respond WONT TM to the DO TM that we send to
568                          * check for kludge linemode.  When the WONT TM
569                          * arrives, linemode will be turned off and a
570                          * change propogated to the pty.  This change
571                          * will cause us to process the new pty state
572                          * in localstat(), which will notice that
573                          * linemode is off and send a WILL ECHO
574                          * so that we are properly in character mode and
575                          * all is well.
576                          */
577                         break;
578 #ifdef  LINEMODE
579                 case TELOPT_LINEMODE:
580 # ifdef KLUDGELINEMODE
581                         /*
582                          * Note client's desire to use linemode.
583                          */
584                         lmodetype = REAL_LINEMODE;
585 # endif /* KLUDGELINEMODE */
586                         func = doclientstat;
587                         break;
588 #endif  /* LINEMODE */
589
590
591                 case TELOPT_LFLOW:
592                         func = flowstat;
593                         break;
594                 }
595             }
596         }
597         set_his_state_will(option);
598         if (func)
599                 (*func)();
600 }  /* end of willoption */
601
602 void
603 send_dont(int option, int init)
604 {
605         if (init) {
606                 if ((do_dont_resp[option] == 0 && his_state_is_wont(option)) ||
607                     his_want_state_is_wont(option))
608                         return;
609                 set_his_want_state_wont(option);
610                 do_dont_resp[option]++;
611         }
612         output_data((const char *)dont, option);
613
614         DIAG(TD_OPTIONS, printoption("td: send dont", option));
615 }
616
617 void
618 wontoption(int option)
619 {
620         /*
621          * Process client input.
622          */
623
624         DIAG(TD_OPTIONS, printoption("td: recv wont", option));
625
626         if (do_dont_resp[option]) {
627                 do_dont_resp[option]--;
628                 if (do_dont_resp[option] && his_state_is_wont(option))
629                         do_dont_resp[option]--;
630         }
631         if (do_dont_resp[option] == 0) {
632             if (his_want_state_is_will(option)) {
633                 /* it is always ok to change to negative state */
634                 switch (option) {
635                 case TELOPT_ECHO:
636                         not42 = 1; /* doesn't seem to be a 4.2 system */
637                         break;
638
639                 case TELOPT_BINARY:
640                         init_termbuf();
641                         tty_binaryin(0);
642                         set_termbuf();
643                         break;
644
645 #ifdef  LINEMODE
646                 case TELOPT_LINEMODE:
647 # ifdef KLUDGELINEMODE
648                         /*
649                          * If real linemode is supported, then client is
650                          * asking to turn linemode off.
651                          */
652                         if (lmodetype != REAL_LINEMODE)
653                                 break;
654                         lmodetype = KLUDGE_LINEMODE;
655 # endif /* KLUDGELINEMODE */
656                         clientstat(TELOPT_LINEMODE, WONT, 0);
657                         break;
658 #endif  /* LINEMODE */
659
660                 case TELOPT_TM:
661                         /*
662                          * If we get a WONT TM, and had sent a DO TM,
663                          * don't respond with a DONT TM, just leave it
664                          * as is.  Short circut the state machine to
665                          * achive this.
666                          */
667                         set_his_want_state_wont(TELOPT_TM);
668                         return;
669
670                 case TELOPT_LFLOW:
671                         /*
672                          * If we are not going to support flow control
673                          * option, then let peer know that we can't
674                          * change the flow control characters.
675                          */
676                         slctab[SLC_XON].defset.flag &= ~SLC_LEVELBITS;
677                         slctab[SLC_XON].defset.flag |= SLC_CANTCHANGE;
678                         slctab[SLC_XOFF].defset.flag &= ~SLC_LEVELBITS;
679                         slctab[SLC_XOFF].defset.flag |= SLC_CANTCHANGE;
680                         break;
681
682
683                 /*
684                  * For options that we might spin waiting for
685                  * sub-negotiation, if the client turns off the
686                  * option rather than responding to the request,
687                  * we have to treat it here as if we got a response
688                  * to the sub-negotiation, (by updating the timers)
689                  * so that we'll break out of the loop.
690                  */
691                 case TELOPT_TTYPE:
692                         settimer(ttypesubopt);
693                         break;
694
695                 case TELOPT_TSPEED:
696                         settimer(tspeedsubopt);
697                         break;
698
699                 case TELOPT_XDISPLOC:
700                         settimer(xdisplocsubopt);
701                         break;
702
703                 case TELOPT_OLD_ENVIRON:
704                         settimer(oenvironsubopt);
705                         break;
706
707                 case TELOPT_NEW_ENVIRON:
708                         settimer(environsubopt);
709                         break;
710
711                 default:
712                         break;
713                 }
714                 set_his_want_state_wont(option);
715                 if (his_state_is_will(option))
716                         send_dont(option, 0);
717             } else {
718                 switch (option) {
719                 case TELOPT_TM:
720 #if     defined(LINEMODE) && defined(KLUDGELINEMODE)
721                         if (lmodetype < NO_AUTOKLUDGE) {
722                                 lmodetype = NO_LINEMODE;
723                                 clientstat(TELOPT_LINEMODE, WONT, 0);
724                                 send_will(TELOPT_SGA, 1);
725                                 send_will(TELOPT_ECHO, 1);
726                         }
727 #endif  /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
728                         break;
729
730                 default:
731                         break;
732                 }
733             }
734         }
735         set_his_state_wont(option);
736
737 }  /* end of wontoption */
738
739 void
740 send_will(int option, int init)
741 {
742         if (init) {
743                 if ((will_wont_resp[option] == 0 && my_state_is_will(option))||
744                     my_want_state_is_will(option))
745                         return;
746                 set_my_want_state_will(option);
747                 will_wont_resp[option]++;
748         }
749         output_data((const char *)will, option);
750
751         DIAG(TD_OPTIONS, printoption("td: send will", option));
752 }
753
754 #if     !defined(LINEMODE) || !defined(KLUDGELINEMODE)
755 /*
756  * When we get a DONT SGA, we will try once to turn it
757  * back on.  If the other side responds DONT SGA, we
758  * leave it at that.  This is so that when we talk to
759  * clients that understand KLUDGELINEMODE but not LINEMODE,
760  * we'll keep them in char-at-a-time mode.
761  */
762 int turn_on_sga = 0;
763 #endif
764
765 void
766 dooption(int option)
767 {
768         int changeok = 0;
769
770         /*
771          * Process client input.
772          */
773
774         DIAG(TD_OPTIONS, printoption("td: recv do", option));
775
776         if (will_wont_resp[option]) {
777                 will_wont_resp[option]--;
778                 if (will_wont_resp[option] && my_state_is_will(option))
779                         will_wont_resp[option]--;
780         }
781         if ((will_wont_resp[option] == 0) && (my_want_state_is_wont(option))) {
782                 switch (option) {
783                 case TELOPT_ECHO:
784 #ifdef  LINEMODE
785 # ifdef KLUDGELINEMODE
786                         if (lmodetype == NO_LINEMODE)
787 # else
788                         if (his_state_is_wont(TELOPT_LINEMODE))
789 # endif
790 #endif
791                         {
792                                 init_termbuf();
793                                 tty_setecho(1);
794                                 set_termbuf();
795                         }
796                         changeok++;
797                         break;
798
799                 case TELOPT_BINARY:
800                         init_termbuf();
801                         tty_binaryout(1);
802                         set_termbuf();
803                         changeok++;
804                         break;
805
806                 case TELOPT_SGA:
807 #if     defined(LINEMODE) && defined(KLUDGELINEMODE)
808                         /*
809                          * If kludge linemode is in use, then we must
810                          * process an incoming do SGA for linemode
811                          * purposes.
812                          */
813                         if (lmodetype == KLUDGE_LINEMODE) {
814                                 /*
815                                  * Receipt of "do SGA" in kludge
816                                  * linemode is the peer asking us to
817                                  * turn off linemode.  Make note of
818                                  * the request.
819                                  */
820                                 clientstat(TELOPT_LINEMODE, WONT, 0);
821                                 /*
822                                  * If linemode did not get turned off
823                                  * then don't tell peer that we did.
824                                  * Breaking here forces a wont SGA to
825                                  * be returned.
826                                  */
827                                 if (linemode)
828                                         break;
829                         }
830 #else
831                         turn_on_sga = 0;
832 #endif  /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
833                         changeok++;
834                         break;
835
836                 case TELOPT_STATUS:
837                         changeok++;
838                         break;
839
840                 case TELOPT_TM:
841                         /*
842                          * Special case for TM.  We send a WILL, but
843                          * pretend we sent a WONT.
844                          */
845                         send_will(option, 0);
846                         set_my_want_state_wont(option);
847                         set_my_state_wont(option);
848                         return;
849
850                 case TELOPT_LOGOUT:
851                         /*
852                          * When we get a LOGOUT option, respond
853                          * with a WILL LOGOUT, make sure that
854                          * it gets written out to the network,
855                          * and then just go away...
856                          */
857                         set_my_want_state_will(TELOPT_LOGOUT);
858                         send_will(TELOPT_LOGOUT, 0);
859                         set_my_state_will(TELOPT_LOGOUT);
860                         (void)netflush();
861                         cleanup(0);
862                         /* NOT REACHED */
863                         break;
864
865                 case TELOPT_LINEMODE:
866                 case TELOPT_TTYPE:
867                 case TELOPT_NAWS:
868                 case TELOPT_TSPEED:
869                 case TELOPT_LFLOW:
870                 case TELOPT_XDISPLOC:
871 #ifdef  TELOPT_ENVIRON
872                 case TELOPT_NEW_ENVIRON:
873 #endif
874                 case TELOPT_OLD_ENVIRON:
875                 default:
876                         break;
877                 }
878                 if (changeok) {
879                         set_my_want_state_will(option);
880                         send_will(option, 0);
881                 } else {
882                         will_wont_resp[option]++;
883                         send_wont(option, 0);
884                 }
885         }
886         set_my_state_will(option);
887
888 }  /* end of dooption */
889
890 void
891 send_wont(int option, int init)
892 {
893         if (init) {
894                 if ((will_wont_resp[option] == 0 && my_state_is_wont(option)) ||
895                     my_want_state_is_wont(option))
896                         return;
897                 set_my_want_state_wont(option);
898                 will_wont_resp[option]++;
899         }
900         output_data((const char *)wont, option);
901
902         DIAG(TD_OPTIONS, printoption("td: send wont", option));
903 }
904
905 void
906 dontoption(int option)
907 {
908         /*
909          * Process client input.
910          */
911
912
913         DIAG(TD_OPTIONS, printoption("td: recv dont", option));
914
915         if (will_wont_resp[option]) {
916                 will_wont_resp[option]--;
917                 if (will_wont_resp[option] && my_state_is_wont(option))
918                         will_wont_resp[option]--;
919         }
920         if ((will_wont_resp[option] == 0) && (my_want_state_is_will(option))) {
921                 switch (option) {
922                 case TELOPT_BINARY:
923                         init_termbuf();
924                         tty_binaryout(0);
925                         set_termbuf();
926                         break;
927
928                 case TELOPT_ECHO:       /* we should stop echoing */
929 #ifdef  LINEMODE
930 # ifdef KLUDGELINEMODE
931                         if ((lmodetype != REAL_LINEMODE) &&
932                             (lmodetype != KLUDGE_LINEMODE))
933 # else
934                         if (his_state_is_wont(TELOPT_LINEMODE))
935 # endif
936 #endif
937                         {
938                                 init_termbuf();
939                                 tty_setecho(0);
940                                 set_termbuf();
941                         }
942                         break;
943
944                 case TELOPT_SGA:
945 #if     defined(LINEMODE) && defined(KLUDGELINEMODE)
946                         /*
947                          * If kludge linemode is in use, then we
948                          * must process an incoming do SGA for
949                          * linemode purposes.
950                          */
951                         if ((lmodetype == KLUDGE_LINEMODE) ||
952                             (lmodetype == KLUDGE_OK)) {
953                                 /*
954                                  * The client is asking us to turn
955                                  * linemode on.
956                                  */
957                                 lmodetype = KLUDGE_LINEMODE;
958                                 clientstat(TELOPT_LINEMODE, WILL, 0);
959                                 /*
960                                  * If we did not turn line mode on,
961                                  * then what do we say?  Will SGA?
962                                  * This violates design of telnet.
963                                  * Gross.  Very Gross.
964                                  */
965                         }
966                         break;
967 #else
968                         set_my_want_state_wont(option);
969                         if (my_state_is_will(option))
970                                 send_wont(option, 0);
971                         set_my_state_wont(option);
972                         if (turn_on_sga ^= 1)
973                                 send_will(option, 1);
974                         return;
975 #endif  /* defined(LINEMODE) && defined(KLUDGELINEMODE) */
976
977                 default:
978                         break;
979                 }
980
981                 set_my_want_state_wont(option);
982                 if (my_state_is_will(option))
983                         send_wont(option, 0);
984         }
985         set_my_state_wont(option);
986
987 }  /* end of dontoption */
988
989 #ifdef  ENV_HACK
990 int env_ovar = -1;
991 int env_ovalue = -1;
992 #else   /* ENV_HACK */
993 # define env_ovar OLD_ENV_VAR
994 # define env_ovalue OLD_ENV_VALUE
995 #endif  /* ENV_HACK */
996
997 /*
998  * suboption()
999  *
1000  *      Look at the sub-option buffer, and try to be helpful to the other
1001  * side.
1002  *
1003  *      Currently we recognize:
1004  *
1005  *      Terminal type is
1006  *      Linemode
1007  *      Window size
1008  *      Terminal speed
1009  */
1010 void
1011 suboption(void)
1012 {
1013     int subchar;
1014
1015     DIAG(TD_OPTIONS, {netflush(); printsub('<', subpointer, SB_LEN()+2);});
1016
1017     subchar = SB_GET();
1018     switch (subchar) {
1019     case TELOPT_TSPEED: {
1020         int xspeed, rspeed;
1021
1022         if (his_state_is_wont(TELOPT_TSPEED))   /* Ignore if option disabled */
1023                 break;
1024
1025         settimer(tspeedsubopt);
1026
1027         if (SB_EOF() || SB_GET() != TELQUAL_IS)
1028                 return;
1029
1030         xspeed = atoi((char *)subpointer);
1031
1032         while (SB_GET() != ',' && !SB_EOF());
1033         if (SB_EOF())
1034                 return;
1035
1036         rspeed = atoi((char *)subpointer);
1037         clientstat(TELOPT_TSPEED, xspeed, rspeed);
1038
1039         break;
1040
1041     }  /* end of case TELOPT_TSPEED */
1042
1043     case TELOPT_TTYPE: {                /* Yaaaay! */
1044         static char terminalname[41];
1045
1046         if (his_state_is_wont(TELOPT_TTYPE))    /* Ignore if option disabled */
1047                 break;
1048         settimer(ttypesubopt);
1049
1050         if (SB_EOF() || SB_GET() != TELQUAL_IS) {
1051             return;             /* ??? XXX but, this is the most robust */
1052         }
1053
1054         terminaltype = terminalname;
1055
1056         while ((terminaltype < (terminalname + sizeof terminalname-1)) &&
1057                                                                     !SB_EOF()) {
1058             int c;
1059
1060             c = SB_GET();
1061             if (isupper(c)) {
1062                 c = tolower(c);
1063             }
1064             *terminaltype++ = c;    /* accumulate name */
1065         }
1066         *terminaltype = 0;
1067         terminaltype = terminalname;
1068         break;
1069     }  /* end of case TELOPT_TTYPE */
1070
1071     case TELOPT_NAWS: {
1072         int xwinsize, ywinsize;
1073
1074         if (his_state_is_wont(TELOPT_NAWS))     /* Ignore if option disabled */
1075                 break;
1076
1077         if (SB_EOF())
1078                 return;
1079         xwinsize = SB_GET() << 8;
1080         if (SB_EOF())
1081                 return;
1082         xwinsize |= SB_GET();
1083         if (SB_EOF())
1084                 return;
1085         ywinsize = SB_GET() << 8;
1086         if (SB_EOF())
1087                 return;
1088         ywinsize |= SB_GET();
1089         clientstat(TELOPT_NAWS, xwinsize, ywinsize);
1090
1091         break;
1092
1093     }  /* end of case TELOPT_NAWS */
1094
1095 #ifdef  LINEMODE
1096     case TELOPT_LINEMODE: {
1097         int request;
1098
1099         if (his_state_is_wont(TELOPT_LINEMODE)) /* Ignore if option disabled */
1100                 break;
1101         /*
1102          * Process linemode suboptions.
1103          */
1104         if (SB_EOF())
1105             break;              /* garbage was sent */
1106         request = SB_GET();     /* get will/wont */
1107
1108         if (SB_EOF())
1109             break;              /* another garbage check */
1110
1111         if (request == LM_SLC) {  /* SLC is not preceeded by WILL or WONT */
1112                 /*
1113                  * Process suboption buffer of slc's
1114                  */
1115                 start_slc(1);
1116                 do_opt_slc(subpointer, subend - subpointer);
1117                 (void) end_slc(0);
1118                 break;
1119         } else if (request == LM_MODE) {
1120                 if (SB_EOF())
1121                     return;
1122                 useeditmode = SB_GET();  /* get mode flag */
1123                 clientstat(LM_MODE, 0, 0);
1124                 break;
1125         }
1126
1127         if (SB_EOF())
1128             break;
1129         switch (SB_GET()) {  /* what suboption? */
1130         case LM_FORWARDMASK:
1131                 /*
1132                  * According to spec, only server can send request for
1133                  * forwardmask, and client can only return a positive response.
1134                  * So don't worry about it.
1135                  */
1136
1137         default:
1138                 break;
1139         }
1140         break;
1141     }  /* end of case TELOPT_LINEMODE */
1142 #endif
1143     case TELOPT_STATUS: {
1144         int mode;
1145
1146         if (SB_EOF())
1147             break;
1148         mode = SB_GET();
1149         switch (mode) {
1150         case TELQUAL_SEND:
1151             if (my_state_is_will(TELOPT_STATUS))
1152                 send_status();
1153             break;
1154
1155         case TELQUAL_IS:
1156             break;
1157
1158         default:
1159             break;
1160         }
1161         break;
1162     }  /* end of case TELOPT_STATUS */
1163
1164     case TELOPT_XDISPLOC: {
1165         if (SB_EOF() || SB_GET() != TELQUAL_IS)
1166                 return;
1167         settimer(xdisplocsubopt);
1168         subpointer[SB_LEN()] = '\0';
1169         (void)setenv("DISPLAY", (char *)subpointer, 1);
1170         break;
1171     }  /* end of case TELOPT_XDISPLOC */
1172
1173 #ifdef  TELOPT_NEW_ENVIRON
1174     case TELOPT_NEW_ENVIRON:
1175 #endif
1176     case TELOPT_OLD_ENVIRON: {
1177         int c;
1178         char *cp, *varp, *valp;
1179
1180         if (SB_EOF())
1181                 return;
1182         c = SB_GET();
1183         if (c == TELQUAL_IS) {
1184                 if (subchar == TELOPT_OLD_ENVIRON)
1185                         settimer(oenvironsubopt);
1186                 else
1187                         settimer(environsubopt);
1188         } else if (c != TELQUAL_INFO) {
1189                 return;
1190         }
1191
1192 #ifdef  TELOPT_NEW_ENVIRON
1193         if (subchar == TELOPT_NEW_ENVIRON) {
1194             while (!SB_EOF()) {
1195                 c = SB_GET();
1196                 if ((c == NEW_ENV_VAR) || (c == ENV_USERVAR))
1197                         break;
1198             }
1199         } else
1200 #endif
1201         {
1202 #ifdef  ENV_HACK
1203             /*
1204              * We only want to do this if we haven't already decided
1205              * whether or not the other side has its VALUE and VAR
1206              * reversed.
1207              */
1208             if (env_ovar < 0) {
1209                 int last = -1;          /* invalid value */
1210                 int empty = 0;
1211                 int got_var = 0, got_value = 0, got_uservar = 0;
1212
1213                 /*
1214                  * The other side might have its VALUE and VAR values
1215                  * reversed.  To be interoperable, we need to determine
1216                  * which way it is.  If the first recognized character
1217                  * is a VAR or VALUE, then that will tell us what
1218                  * type of client it is.  If the fist recognized
1219                  * character is a USERVAR, then we continue scanning
1220                  * the suboption looking for two consecutive
1221                  * VAR or VALUE fields.  We should not get two
1222                  * consecutive VALUE fields, so finding two
1223                  * consecutive VALUE or VAR fields will tell us
1224                  * what the client is.
1225                  */
1226                 SB_SAVE();
1227                 while (!SB_EOF()) {
1228                         c = SB_GET();
1229                         switch(c) {
1230                         case OLD_ENV_VAR:
1231                                 if (last < 0 || last == OLD_ENV_VAR
1232                                     || (empty && (last == OLD_ENV_VALUE)))
1233                                         goto env_ovar_ok;
1234                                 got_var++;
1235                                 last = OLD_ENV_VAR;
1236                                 break;
1237                         case OLD_ENV_VALUE:
1238                                 if (last < 0 || last == OLD_ENV_VALUE
1239                                     || (empty && (last == OLD_ENV_VAR)))
1240                                         goto env_ovar_wrong;
1241                                 got_value++;
1242                                 last = OLD_ENV_VALUE;
1243                                 break;
1244                         case ENV_USERVAR:
1245                                 /* count strings of USERVAR as one */
1246                                 if (last != ENV_USERVAR)
1247                                         got_uservar++;
1248                                 if (empty) {
1249                                         if (last == OLD_ENV_VALUE)
1250                                                 goto env_ovar_ok;
1251                                         if (last == OLD_ENV_VAR)
1252                                                 goto env_ovar_wrong;
1253                                 }
1254                                 last = ENV_USERVAR;
1255                                 break;
1256                         case ENV_ESC:
1257                                 if (!SB_EOF())
1258                                         c = SB_GET();
1259                                 /* FALL THROUGH */
1260                         default:
1261                                 empty = 0;
1262                                 continue;
1263                         }
1264                         empty = 1;
1265                 }
1266                 if (empty) {
1267                         if (last == OLD_ENV_VALUE)
1268                                 goto env_ovar_ok;
1269                         if (last == OLD_ENV_VAR)
1270                                 goto env_ovar_wrong;
1271                 }
1272                 /*
1273                  * Ok, the first thing was a USERVAR, and there
1274                  * are not two consecutive VAR or VALUE commands,
1275                  * and none of the VAR or VALUE commands are empty.
1276                  * If the client has sent us a well-formed option,
1277                  * then the number of VALUEs received should always
1278                  * be less than or equal to the number of VARs and
1279                  * USERVARs received.
1280                  *
1281                  * If we got exactly as many VALUEs as VARs and
1282                  * USERVARs, the client has the same definitions.
1283                  *
1284                  * If we got exactly as many VARs as VALUEs and
1285                  * USERVARS, the client has reversed definitions.
1286                  */
1287                 if (got_uservar + got_var == got_value) {
1288             env_ovar_ok:
1289                         env_ovar = OLD_ENV_VAR;
1290                         env_ovalue = OLD_ENV_VALUE;
1291                 } else if (got_uservar + got_value == got_var) {
1292             env_ovar_wrong:
1293                         env_ovar = OLD_ENV_VALUE;
1294                         env_ovalue = OLD_ENV_VAR;
1295                         DIAG(TD_OPTIONS,
1296                             output_data("ENVIRON VALUE and VAR are reversed!\r\n"));
1297
1298                 }
1299             }
1300             SB_RESTORE();
1301 #endif
1302
1303             while (!SB_EOF()) {
1304                 c = SB_GET();
1305                 if ((c == env_ovar) || (c == ENV_USERVAR))
1306                         break;
1307             }
1308         }
1309
1310         if (SB_EOF())
1311                 return;
1312
1313         cp = varp = (char *)subpointer;
1314         valp = 0;
1315
1316         while (!SB_EOF()) {
1317                 c = SB_GET();
1318                 if (subchar == TELOPT_OLD_ENVIRON) {
1319                         if (c == env_ovar)
1320                                 c = NEW_ENV_VAR;
1321                         else if (c == env_ovalue)
1322                                 c = NEW_ENV_VALUE;
1323                 }
1324                 switch (c) {
1325
1326                 case NEW_ENV_VALUE:
1327                         *cp = '\0';
1328                         cp = valp = (char *)subpointer;
1329                         break;
1330
1331                 case NEW_ENV_VAR:
1332                 case ENV_USERVAR:
1333                         *cp = '\0';
1334                         if (valp)
1335                                 (void)setenv(varp, valp, 1);
1336                         else
1337                                 unsetenv(varp);
1338                         cp = varp = (char *)subpointer;
1339                         valp = 0;
1340                         break;
1341
1342                 case ENV_ESC:
1343                         if (SB_EOF())
1344                                 break;
1345                         c = SB_GET();
1346                         /* FALL THROUGH */
1347                 default:
1348                         *cp++ = c;
1349                         break;
1350                 }
1351         }
1352         *cp = '\0';
1353         if (valp)
1354                 (void)setenv(varp, valp, 1);
1355         else
1356                 unsetenv(varp);
1357         break;
1358     }  /* end of case TELOPT_NEW_ENVIRON */
1359
1360     default:
1361         break;
1362     }  /* end of switch */
1363
1364 }  /* end of suboption */
1365
1366 static void
1367 doclientstat(void)
1368 {
1369         clientstat(TELOPT_LINEMODE, WILL, 0);
1370 }
1371
1372 #define ADD(c)   *ncp++ = c
1373 #define ADD_DATA(c) { *ncp++ = c; if (c == SE || c == IAC) *ncp++ = c; }
1374 void
1375 send_status(void)
1376 {
1377         unsigned char statusbuf[256];
1378         unsigned char *ncp;
1379         unsigned char i;
1380
1381         ncp = statusbuf;
1382
1383         netflush();     /* get rid of anything waiting to go out */
1384
1385         ADD(IAC);
1386         ADD(SB);
1387         ADD(TELOPT_STATUS);
1388         ADD(TELQUAL_IS);
1389
1390         /*
1391          * We check the want_state rather than the current state,
1392          * because if we received a DO/WILL for an option that we
1393          * don't support, and the other side didn't send a DONT/WONT
1394          * in response to our WONT/DONT, then the "state" will be
1395          * WILL/DO, and the "want_state" will be WONT/DONT.  We
1396          * need to go by the latter.
1397          */
1398         for (i = 0; i < (unsigned char)NTELOPTS; i++) {
1399                 if (my_want_state_is_will(i)) {
1400                         ADD(WILL);
1401                         ADD_DATA(i);
1402                         if (i == IAC)
1403                                 ADD(IAC);
1404                 }
1405                 if (his_want_state_is_will(i)) {
1406                         ADD(DO);
1407                         ADD_DATA(i);
1408                         if (i == IAC)
1409                                 ADD(IAC);
1410                 }
1411         }
1412
1413         if (his_want_state_is_will(TELOPT_LFLOW)) {
1414                 ADD(SB);
1415                 ADD(TELOPT_LFLOW);
1416                 if (flowmode) {
1417                         ADD(LFLOW_ON);
1418                 } else {
1419                         ADD(LFLOW_OFF);
1420                 }
1421                 ADD(SE);
1422
1423                 if (restartany >= 0) {
1424                         ADD(SB);
1425                         ADD(TELOPT_LFLOW);
1426                         if (restartany) {
1427                                 ADD(LFLOW_RESTART_ANY);
1428                         } else {
1429                                 ADD(LFLOW_RESTART_XON);
1430                         }
1431                         ADD(SE);
1432                 }
1433         }
1434
1435 #ifdef  LINEMODE
1436         if (his_want_state_is_will(TELOPT_LINEMODE)) {
1437                 unsigned char *cp, *cpe;
1438                 int len;
1439
1440                 ADD(SB);
1441                 ADD(TELOPT_LINEMODE);
1442                 ADD(LM_MODE);
1443                 ADD_DATA(editmode);
1444                 ADD(SE);
1445
1446                 ADD(SB);
1447                 ADD(TELOPT_LINEMODE);
1448                 ADD(LM_SLC);
1449                 start_slc(0);
1450                 send_slc();
1451                 len = end_slc(&cp);
1452                 for (cpe = cp + len; cp < cpe; cp++)
1453                         ADD_DATA(*cp);
1454                 ADD(SE);
1455         }
1456 #endif  /* LINEMODE */
1457
1458         ADD(IAC);
1459         ADD(SE);
1460
1461         output_datalen(statusbuf, ncp - statusbuf);
1462         netflush();     /* Send it on its way */
1463
1464         DIAG(TD_OPTIONS,
1465                 {printsub('>', statusbuf, ncp - statusbuf); netflush();});
1466 }
1467
1468 /*
1469  * This function appends data to nfrontp and advances nfrontp.
1470  * Returns the number of characters written altogether (the
1471  * buffer may have been flushed in the process).
1472  */
1473
1474 int
1475 output_data(const char *format, ...)
1476 {
1477         va_list args;
1478         int len;
1479         char *buf;
1480
1481         va_start(args, format);
1482         if ((len = vasprintf(&buf, format, args)) == -1)
1483                 return -1;
1484         output_datalen(buf, len);
1485         va_end(args);
1486         free(buf);
1487         return (len);
1488 }
1489
1490 void
1491 output_datalen(const char *buf, int len)
1492 {
1493         int remaining, copied;
1494         
1495         remaining = BUFSIZ - (nfrontp - netobuf);
1496         while (len > 0) {
1497                 /* Free up enough space if the room is too low*/
1498                 if ((len > BUFSIZ ? BUFSIZ : len) > remaining) {
1499                         netflush();
1500                         remaining = BUFSIZ - (nfrontp - netobuf);
1501                 }
1502
1503                 /* Copy out as much as will fit */
1504                 copied = remaining > len ? len : remaining;
1505                 memmove(nfrontp, buf, copied);
1506                 nfrontp += copied;
1507                 len -= copied;
1508                 remaining -= copied;
1509                 buf += copied;
1510         }
1511         return;
1512 }