]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - telnet.c
Removing one bug, and hunting another
[PuTTY.git] / telnet.c
1 #ifdef macintosh
2 #else /* not macintosh */
3 #include <windows.h>
4 #include <winsock.h>
5 #endif /* not macintosh */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #include "putty.h"
11
12 #ifdef macintosh
13 #pragma segment Telnet
14 #endif
15
16 static SOCKET s = INVALID_SOCKET;
17 /* kludge till we decide where to put telnet state */
18 static Session *sess;
19
20 #define IAC     255             /* interpret as command: */
21 #define DONT    254             /* you are not to use option */
22 #define DO      253             /* please, you use option */
23 #define WONT    252             /* I won't use option */
24 #define WILL    251             /* I will use option */
25 #define SB      250             /* interpret as subnegotiation */
26 #define SE      240             /* end sub negotiation */
27
28 #define GA      249             /* you may reverse the line */
29 #define EL      248             /* erase the current line */
30 #define EC      247             /* erase the current character */
31 #define AYT     246             /* are you there */
32 #define AO      245             /* abort output--but let prog finish */
33 #define IP      244             /* interrupt process--permanently */
34 #define BREAK   243             /* break */
35 #define DM      242             /* data mark--for connect. cleaning */
36 #define NOP     241             /* nop */
37 #define EOR     239             /* end of record (transparent mode) */
38 #define ABORT   238             /* Abort process */
39 #define SUSP    237             /* Suspend process */
40 #define xEOF    236             /* End of file: EOF is already used... */
41
42 #define TELOPT_BINARY   0       /* 8-bit data path */
43 #define TELOPT_ECHO     1       /* echo */
44 #define TELOPT_RCP      2       /* prepare to reconnect */
45 #define TELOPT_SGA      3       /* suppress go ahead */
46 #define TELOPT_NAMS     4       /* approximate message size */
47 #define TELOPT_STATUS   5       /* give status */
48 #define TELOPT_TM       6       /* timing mark */
49 #define TELOPT_RCTE     7       /* remote controlled transmission and echo */
50 #define TELOPT_NAOL     8       /* negotiate about output line width */
51 #define TELOPT_NAOP     9       /* negotiate about output page size */
52 #define TELOPT_NAOCRD   10      /* negotiate about CR disposition */
53 #define TELOPT_NAOHTS   11      /* negotiate about horizontal tabstops */
54 #define TELOPT_NAOHTD   12      /* negotiate about horizontal tab disposition */
55 #define TELOPT_NAOFFD   13      /* negotiate about formfeed disposition */
56 #define TELOPT_NAOVTS   14      /* negotiate about vertical tab stops */
57 #define TELOPT_NAOVTD   15      /* negotiate about vertical tab disposition */
58 #define TELOPT_NAOLFD   16      /* negotiate about output LF disposition */
59 #define TELOPT_XASCII   17      /* extended ascic character set */
60 #define TELOPT_LOGOUT   18      /* force logout */
61 #define TELOPT_BM       19      /* byte macro */
62 #define TELOPT_DET      20      /* data entry terminal */
63 #define TELOPT_SUPDUP   21      /* supdup protocol */
64 #define TELOPT_SUPDUPOUTPUT 22  /* supdup output */
65 #define TELOPT_SNDLOC   23      /* send location */
66 #define TELOPT_TTYPE    24      /* terminal type */
67 #define TELOPT_EOR      25      /* end or record */
68 #define TELOPT_TUID     26      /* TACACS user identification */
69 #define TELOPT_OUTMRK   27      /* output marking */
70 #define TELOPT_TTYLOC   28      /* terminal location number */
71 #define TELOPT_3270REGIME 29    /* 3270 regime */
72 #define TELOPT_X3PAD    30      /* X.3 PAD */
73 #define TELOPT_NAWS     31      /* window size */
74 #define TELOPT_TSPEED   32      /* terminal speed */
75 #define TELOPT_LFLOW    33      /* remote flow control */
76 #define TELOPT_LINEMODE 34      /* Linemode option */
77 #define TELOPT_XDISPLOC 35      /* X Display Location */
78 #define TELOPT_OLD_ENVIRON 36   /* Old - Environment variables */
79 #define TELOPT_AUTHENTICATION 37/* Authenticate */
80 #define TELOPT_ENCRYPT  38      /* Encryption option */
81 #define TELOPT_NEW_ENVIRON 39   /* New - Environment variables */
82 #define TELOPT_EXOPL    255     /* extended-options-list */
83
84 #define TELQUAL_IS      0       /* option is... */
85 #define TELQUAL_SEND    1       /* send option */
86 #define TELQUAL_INFO    2       /* ENVIRON: informational version of IS */
87 #define BSD_VAR 1
88 #define BSD_VALUE 0
89 #define RFC_VAR 0
90 #define RFC_VALUE 1
91
92 #define CR 13
93 #define LF 10
94 #define NUL 0
95
96 #define iswritable(x) ( (x) != IAC && (x) != CR )
97
98 static char *telopt(int opt) {
99 #define i(x) if (opt == TELOPT_ ## x) return #x;
100     i(BINARY); i(ECHO); i(RCP); i(SGA); i(NAMS); i(STATUS); i(TM); i(RCTE);
101     i(NAOL); i(NAOP); i(NAOCRD); i(NAOHTS); i(NAOHTD); i(NAOFFD); i(NAOVTS);
102     i(NAOVTD); i(NAOLFD); i(XASCII); i(LOGOUT); i(BM); i(DET); i(SUPDUP);
103     i(SUPDUPOUTPUT); i(SNDLOC); i(TTYPE); i(EOR); i(TUID); i(OUTMRK);
104     i(TTYLOC); i(X3PAD); i(NAWS); i(TSPEED); i(LFLOW); i(LINEMODE);
105     i(XDISPLOC); i(OLD_ENVIRON); i(AUTHENTICATION); i(ENCRYPT);
106     i(NEW_ENVIRON); i(EXOPL);
107 #undef i
108     return "<unknown>";
109 }
110
111 static void telnet_size(Session *ignored);
112
113 struct Opt {
114     int send;                          /* what we initially send */
115     int nsend;                         /* -ve send if requested to stop it */
116     int ack, nak;                      /* +ve and -ve acknowledgements */
117     int option;                        /* the option code */
118     enum {
119         REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
120     } state;
121 };
122
123 static struct Opt o_naws = {WILL, WONT, DO, DONT, TELOPT_NAWS, REQUESTED};
124 static struct Opt o_tspeed = {WILL, WONT, DO, DONT, TELOPT_TSPEED, REQUESTED};
125 static struct Opt o_ttype = {WILL, WONT, DO, DONT, TELOPT_TTYPE, REQUESTED};
126 static struct Opt o_oenv = {WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON,
127     INACTIVE};
128 static struct Opt o_nenv = {WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON,
129     REQUESTED};
130 static struct Opt o_echo = {DO, DONT, WILL, WONT, TELOPT_ECHO, REQUESTED};
131 static struct Opt o_we_sga = {WILL, WONT, DO, DONT, TELOPT_SGA, REQUESTED};
132 static struct Opt o_they_sga = {DO, DONT, WILL, WONT, TELOPT_SGA, REQUESTED};
133
134 static struct Opt *opts[] = {
135     &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,
136     &o_we_sga, &o_they_sga, NULL
137 };
138
139 #if 0
140 static int in_synch;
141 #endif
142
143 static int sb_opt, sb_len;
144 static unsigned char *sb_buf = NULL;
145 static int sb_size = 0;
146 #define SB_DELTA 1024
147
148 static void try_write (void) {
149     while (sess->outbuf_head != sess->outbuf_reap) {
150         int end = (sess->outbuf_reap < sess->outbuf_head ? sess->outbuf_head : OUTBUF_SIZE);
151         int len = end - sess->outbuf_reap;
152         int ret;
153
154         ret = net_send (s, sess->outbuf+sess->outbuf_reap, len, 0);
155         if (ret > 0)
156             sess->outbuf_reap = (sess->outbuf_reap + ret) & OUTBUF_MASK;
157         if (ret < len)
158             return;
159     }
160 }
161
162 static void s_write (void *buf, int len) {
163     unsigned char *p = buf;
164     while (len--) {
165         int new_head = (sess->outbuf_head + 1) & OUTBUF_MASK;
166         if (new_head != sess->outbuf_reap) {
167             sess->outbuf[sess->outbuf_head] = *p++;
168             sess->outbuf_head = new_head;
169         }
170     }
171     try_write();
172 }
173
174 static void c_write (char *buf, int len) {
175     while (len--) {
176         int new_head = (sess->inbuf_head + 1) & INBUF_MASK;
177         int c = (unsigned char) *buf;
178         if (new_head != sess->inbuf_reap) {
179             sess->inbuf[sess->inbuf_head] = *buf++;
180             sess->inbuf_head = new_head;
181         }
182     }
183 }
184
185 static void log_option (char *sender, int cmd, int option) {
186     char buf[50];
187     sprintf(buf, "%s:\t%s %s", sender,
188             (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :
189              cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<??>"),
190             telopt(option));
191     lognegot(buf);
192 }
193
194 static void send_opt (int cmd, int option) {
195     unsigned char b[3];
196
197     b[0] = IAC; b[1] = cmd; b[2] = option;
198     s_write (b, 3);
199     log_option("client", cmd, option);
200 }
201
202 static void deactivate_option (struct Opt *o) {
203     if (o->state == REQUESTED || o->state == ACTIVE)
204         send_opt (o->nsend, o->option);
205     o->state = REALLY_INACTIVE;
206 }
207
208 static void activate_option (struct Opt *o) {
209     if (o->send == WILL && o->option == TELOPT_NAWS)
210         telnet_size(sess);
211     if (o->send == WILL &&
212         (o->option == TELOPT_NEW_ENVIRON ||
213          o->option == TELOPT_OLD_ENVIRON)) {
214         /*
215          * We may only have one kind of ENVIRON going at a time.
216          * This is a hack, but who cares.
217          */
218         deactivate_option (o->option==TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);
219     }
220 }
221
222 static void refused_option (struct Opt *o) {
223     if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
224         o_oenv.state == INACTIVE) {
225         send_opt (WILL, TELOPT_OLD_ENVIRON);
226         o_oenv.state = REQUESTED;
227     }
228 }
229
230 static void proc_rec_opt (int cmd, int option) {
231     struct Opt **o;
232
233     log_option ("server", cmd, option);
234     for (o = opts; *o; o++) {
235         if ((*o)->option == option && (*o)->ack == cmd) {
236             switch ((*o)->state) {
237               case REQUESTED:
238                 (*o)->state = ACTIVE;
239                 activate_option (*o);
240                 break;
241               case ACTIVE:
242                 break;
243               case INACTIVE:
244                 (*o)->state = ACTIVE;
245                 send_opt ((*o)->send, option);
246                 activate_option (*o);
247                 break;
248               case REALLY_INACTIVE:
249                 send_opt ((*o)->nsend, option);
250                 break;
251             }
252             return;
253         } else if ((*o)->option == option && (*o)->nak == cmd) {
254             switch ((*o)->state) {
255               case REQUESTED:
256                 (*o)->state = INACTIVE;
257                 refused_option (*o);
258                 break;
259               case ACTIVE:
260                 (*o)->state = INACTIVE;
261                 send_opt ((*o)->nsend, option);
262                 break;
263               case INACTIVE:
264               case REALLY_INACTIVE:
265                 break;
266             }
267             return;
268         }
269     }
270     /*
271      * If we reach here, the option was one we weren't prepared to
272      * cope with. So send a negative ack.
273      */
274     send_opt ((cmd == WILL ? DONT : WONT), option);
275 }
276
277 static void process_subneg (void) {
278     unsigned char b[2048], *p, *q;
279     int var, value, n;
280     char *e;
281
282     switch (sb_opt) {
283       case TELOPT_TSPEED:
284         if (sb_len == 1 && sb_buf[0] == TELQUAL_SEND) {
285             char logbuf[sizeof(sess->cfg.termspeed)+80];
286             b[0] = IAC; b[1] = SB; b[2] = TELOPT_TSPEED;
287             b[3] = TELQUAL_IS;
288             strcpy((char *)b+4, sess->cfg.termspeed);
289             n = 4 + strlen(sess->cfg.termspeed);
290             b[n] = IAC; b[n+1] = SE;
291             s_write (b, n+2);
292             lognegot("server:\tSB TSPEED SEND");
293             sprintf(logbuf, "client:\tSB TSPEED IS %s", sess->cfg.termspeed);
294             lognegot (logbuf);
295         } else
296             lognegot ("server:\tSB TSPEED <something weird>");
297         break;
298       case TELOPT_TTYPE:
299         if (sb_len == 1 && sb_buf[0] == TELQUAL_SEND) {
300             char logbuf[sizeof(sess->cfg.termtype)+80];
301             b[0] = IAC; b[1] = SB; b[2] = TELOPT_TTYPE;
302             b[3] = TELQUAL_IS;
303             for (n = 0; sess->cfg.termtype[n]; n++)
304                 b[n+4] = (sess->cfg.termtype[n] >= 'a' && sess->cfg.termtype[n] <= 'z' ?
305                           sess->cfg.termtype[n] + 'A'-'a' : sess->cfg.termtype[n]);
306             b[n+4] = IAC; b[n+5] = SE;
307             s_write (b, n+6);
308             b[n+4] = 0;
309             lognegot("server:\tSB TTYPE SEND");
310             sprintf(logbuf, "client:\tSB TTYPE IS %s", b+4);
311             lognegot(logbuf);
312         } else
313             lognegot("server:\tSB TTYPE <something weird>\r\n");
314         break;
315       case TELOPT_OLD_ENVIRON:
316       case TELOPT_NEW_ENVIRON:  
317         p = sb_buf;
318         q = p + sb_len;
319         if (p < q && *p == TELQUAL_SEND) {
320             char logbuf[50];
321             p++;
322             sprintf (logbuf, "server:\tSB %s SEND", telopt(sb_opt));
323             lognegot (logbuf);
324             if (sb_opt == TELOPT_OLD_ENVIRON) {
325                 if (sess->cfg.rfc_environ) {
326                     value = RFC_VALUE;
327                     var = RFC_VAR;
328                 } else {
329                     value = BSD_VALUE;
330                     var = BSD_VAR;
331                 }
332                 /*
333                  * Try to guess the sense of VAR and VALUE.
334                  */
335                 while (p < q) {
336                     if (*p == RFC_VAR) {
337                         value = RFC_VALUE;
338                         var = RFC_VAR;
339                     } else if (*p == BSD_VAR) {
340                         value = BSD_VALUE;
341                         var = BSD_VAR;
342                     }
343                     p++;
344                 }
345             } else {
346                 /*
347                  * With NEW_ENVIRON, the sense of VAR and VALUE
348                  * isn't in doubt.
349                  */
350                 value = RFC_VALUE;
351                 var = RFC_VAR;
352             }
353             b[0] = IAC; b[1] = SB; b[2] = sb_opt;
354             b[3] = TELQUAL_IS;
355             n = 4;
356           e = sess->cfg.environmt;
357             while (*e) {
358                 b[n++] = var;
359                 while (*e && *e != '\t') b[n++] = *e++;
360                 if (*e == '\t') e++;
361                 b[n++] = value;
362                 while (*e) b[n++] = *e++;
363                 e++;
364             }
365             if (*sess->cfg.username) {
366                 b[n++] = var; b[n++] = 'U'; b[n++] = 'S';
367                 b[n++] = 'E'; b[n++] = 'R'; b[n++] = value;
368                 e = sess->cfg.username;
369                 while (*e) b[n++] = *e++;
370             }
371             b[n++] = IAC; b[n++] = SE;
372             s_write (b, n);
373             sprintf(logbuf, "client:\tSB %s IS %s", telopt(sb_opt),
374                     n==6 ? "<nothing>" : "<stuff>");
375             lognegot (logbuf);
376         }
377         break;
378     }
379 }
380
381 static enum {
382     TELNET_TOPLEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
383     SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
384 } telnet_state = TELNET_TOPLEVEL;
385
386 static void do_telnet_read (char *buf, int len) {
387     unsigned char b[10];
388
389     while (len--) {
390         int c = (unsigned char) *buf++;
391
392         switch (telnet_state) {
393           case TELNET_TOPLEVEL:
394           case SEENCR:
395             if (c == NUL && telnet_state == SEENCR)
396                 telnet_state = TELNET_TOPLEVEL;
397             else if (c == IAC)
398                 telnet_state = SEENIAC;
399             else {
400                 b[0] = c;
401 #if 0
402                 if (!in_synch)
403 #endif
404                     c_write ((char *)b, 1);
405                 if (c == CR)
406                     telnet_state = SEENCR;
407                 else
408                     telnet_state = TELNET_TOPLEVEL;
409             }
410             break;
411           case SEENIAC:
412             if (c == DO) telnet_state = SEENDO;
413             else if (c == DONT) telnet_state = SEENDONT;
414             else if (c == WILL) telnet_state = SEENWILL;
415             else if (c == WONT) telnet_state = SEENWONT;
416             else if (c == SB) telnet_state = SEENSB;
417             else telnet_state = TELNET_TOPLEVEL;/* ignore _everything_ else! */
418             break;
419           case SEENWILL:
420             proc_rec_opt (WILL, c);
421             telnet_state = TELNET_TOPLEVEL;
422             break;
423           case SEENWONT:
424             proc_rec_opt (WONT, c);
425             telnet_state = TELNET_TOPLEVEL;
426             break;
427           case SEENDO:
428             proc_rec_opt (DO, c);
429             telnet_state = TELNET_TOPLEVEL;
430             break;
431           case SEENDONT:
432             proc_rec_opt (DONT, c);
433             telnet_state = TELNET_TOPLEVEL;
434             break;
435           case SEENSB:
436             sb_opt = c;
437             sb_len = 0;
438             telnet_state = SUBNEGOT;
439             break;
440           case SUBNEGOT:
441             if (c == IAC)
442                 telnet_state = SUBNEG_IAC;
443             else {
444                 subneg_addchar:
445                 if (sb_len >= sb_size) {
446                     unsigned char *newbuf;
447                     sb_size += SB_DELTA;
448                     newbuf = (sb_buf ?
449                               realloc(sb_buf, sb_size) :
450                               malloc(sb_size));
451                     if (newbuf)
452                         sb_buf = newbuf;
453                     else
454                         sb_size -= SB_DELTA;
455                 }
456                 if (sb_len < sb_size)
457                     sb_buf[sb_len++] = c;
458                 telnet_state = SUBNEGOT;/* in case we came here by goto */
459             }
460             break;
461           case SUBNEG_IAC:
462             if (c != SE)
463                 goto subneg_addchar;   /* yes, it's a hack, I know, but... */
464             else {
465                 process_subneg();
466                 telnet_state = TELNET_TOPLEVEL;
467             }
468             break;
469         }
470     }
471 }
472
473 /*
474  * Called to set up the Telnet connection. Will arrange for
475  * WM_NETEVENT messages to be passed to the specified window, whose
476  * window procedure should then call telnet_msg().
477  *
478  * Returns an error message, or NULL on success.
479  *
480  */
481 static char *telnet_init (Session *this_sess) {
482
483     sess = this_sess;
484     s = net_open(sess, sess->cfg.host, sess->cfg.port);
485
486     return NULL;
487 }
488
489 static void telnet_opened(Session *sess) {
490     /*
491      * Initialise option states.
492      */
493     {
494         struct Opt **o;
495
496         for (o = opts; *o; o++)
497             if ((*o)->state == REQUESTED)
498                 send_opt ((*o)->send, (*o)->option);
499     }
500
501 #if 0
502     /*
503      * Set up SYNCH state.
504      */
505     in_synch = FALSE;
506 #endif
507 }
508
509 /*
510  * Process a WM_NETEVENT message. Will return 0 if the connection
511  * has closed, or <0 for a socket error.
512  */
513 static int telnet_msg (Session *sess, SOCKET sock, Net_Event_Type ne) {
514     int ret;
515     char buf[256];
516
517     if (s == INVALID_SOCKET)           /* how the hell did we get here?! */
518         return -5000;
519
520     switch (ne) {
521       case NE_OPEN:
522         telnet_opened(sess);
523         return 1;
524       case NE_DATA:
525         ret = net_recv(s, buf, sizeof(buf), 0);
526         if (ret < 0)                   /* any _other_ error */
527             return -1;
528         if (ret == 0) {
529             s = INVALID_SOCKET;
530             return 0;                  /* can't happen, in theory */
531         }
532 #if 0
533         if (in_synch) {
534             BOOL i;
535             if (ioctlsocket (s, SIOCATMARK, &i) < 0) {
536                 return -20000-WSAGetLastError();
537             }
538             if (i)
539                 in_synch = FALSE;
540         }
541 #endif
542         do_telnet_read (buf, ret);
543         return 1;
544       case NE_URGENT:
545         do {
546             ret = net_recv(s, buf, sizeof(buf), 0);
547         } while (ret > 0);
548         telnet_state = TELNET_TOPLEVEL;
549         do {
550             ret = net_recv(s, buf, 1, /*MSG_OOB*/ 0);
551             if (ret > 0)
552                 do_telnet_read (buf, ret);
553         } while (ret > 0);
554         if (ret < 0)
555             return -3;
556         return 1;
557       case NE_CLOSING:
558         s = INVALID_SOCKET;
559         return 0;
560       case NE_NOHOST:
561         fatalbox("Host not found");
562       case NE_REFUSED:
563         fatalbox("Connection refused");
564       case NE_NOOPEN:
565         fatalbox("Unable to open connection");
566       case NE_TIMEOUT:
567         fatalbox("Connection timed out");
568       case NE_ABORT:
569         fatalbox("Connection reset by peer");
570       case NE_DIED:
571         fatalbox("Connection died");
572     }
573     return 1;                          /* shouldn't happen, but WTF */
574 }
575
576 /*
577  * Called to send data down the Telnet connection.
578  */
579 static void telnet_send (Session *this_sess, char *buf, int len) {
580     char *p;
581     static unsigned char iac[2] = { IAC, IAC };
582     static unsigned char cr[2] = { CR, NUL };
583
584     if (s == INVALID_SOCKET)
585         return;
586
587     p = buf;
588     while (p < buf+len) {
589         char *q = p;
590
591         while (iswritable((unsigned char)*p) && p < buf+len) p++;
592         s_write (q, p-q);
593
594         while (p < buf+len && !iswritable((unsigned char)*p)) {
595             s_write ((unsigned char)*p == IAC ? iac : cr, 2);
596             p++;
597         }
598     }
599 }
600
601 /*
602  * Called to set the size of the window from Telnet's POV.
603  */
604 static void telnet_size(Session *sess) {
605     unsigned char b[16];
606     char logbuf[50];
607
608     if (s == INVALID_SOCKET || o_naws.state != ACTIVE)
609         return;
610     b[0] = IAC; b[1] = SB; b[2] = TELOPT_NAWS;
611     b[3] = sess->cols >> 8; b[4] = sess->cols & 0xFF;
612     b[5] = sess->rows >> 8; b[6] = sess->rows & 0xFF;
613     b[7] = IAC; b[8] = SE;
614     s_write (b, 9);
615     sprintf(logbuf, "client:\tSB NAWS %d,%d",
616             ((unsigned char)b[3] << 8) + (unsigned char)b[4],
617             ((unsigned char)b[5] << 8) + (unsigned char)b[6]);
618     lognegot (logbuf);
619 }
620
621 /*
622  * Send Telnet special codes.
623  */
624 static void telnet_special (Session *sess, Telnet_Special code) {
625     unsigned char b[2];
626
627     if (s == INVALID_SOCKET)
628         return;
629
630     b[0] = IAC;
631     switch (code) {
632       case TS_AYT: b[1] = AYT; s_write (b, 2); break;
633       case TS_BRK: b[1] = BREAK; s_write (b, 2); break;
634       case TS_EC: b[1] = EC; s_write (b, 2); break;
635       case TS_EL: b[1] = EL; s_write (b, 2); break;
636       case TS_GA: b[1] = GA; s_write (b, 2); break;
637       case TS_NOP: b[1] = NOP; s_write (b, 2); break;
638       case TS_ABORT: b[1] = ABORT; s_write (b, 2); break;
639       case TS_AO: b[1] = AO; s_write (b, 2); break;
640       case TS_IP: b[1] = IP; s_write (b, 2); break;
641       case TS_SUSP: b[1] = SUSP; s_write (b, 2); break;
642       case TS_EOR: b[1] = EOR; s_write (b, 2); break;
643       case TS_EOF: b[1] = xEOF; s_write (b, 2); break;
644       case TS_SYNCH:
645         sess->outbuf_head = sess->outbuf_reap = 0;
646         b[0] = DM;
647         net_send (s, b, 1, SEND_URG);
648         break;
649     }
650 }
651
652 Backend telnet_backend = {
653     telnet_init,
654     telnet_msg,
655     telnet_send,
656     telnet_size,
657     telnet_special
658 };