ntpd: speed up reaction to poll interval decrease
[oweals/busybox.git] / networking / telnetd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Simple telnet server
4  * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
5  *
6  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7  *
8  * ---------------------------------------------------------------------------
9  * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
10  ****************************************************************************
11  *
12  * The telnetd manpage says it all:
13  *
14  * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
15  * a client, then creating a login process which has the slave side of the
16  * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
17  * master side of the pseudo-terminal, implementing the telnet protocol and
18  * passing characters between the remote client and the login process.
19  *
20  * Vladimir Oleynik <dzo@simtreas.ru> 2001
21  * Set process group corrections, initial busybox port
22  */
23
24 #define DEBUG 0
25
26 #include "libbb.h"
27 #include <syslog.h>
28
29 #if DEBUG
30 #define TELCMDS
31 #define TELOPTS
32 #endif
33 #include <arpa/telnet.h>
34
35 struct tsession {
36         struct tsession *next;
37         pid_t shell_pid;
38         int sockfd_read;
39         int sockfd_write;
40         int ptyfd;
41
42         /* two circular buffers */
43         /*char *buf1, *buf2;*/
44 /*#define TS_BUF1(ts) ts->buf1*/
45 /*#define TS_BUF2(ts) TS_BUF2(ts)*/
46 #define TS_BUF1(ts) ((unsigned char*)(ts + 1))
47 #define TS_BUF2(ts) (((unsigned char*)(ts + 1)) + BUFSIZE)
48         int rdidx1, wridx1, size1;
49         int rdidx2, wridx2, size2;
50 };
51
52 /* Two buffers are directly after tsession in malloced memory.
53  * Make whole thing fit in 4k */
54 enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
55
56
57 /* Globals */
58 struct globals {
59         struct tsession *sessions;
60         const char *loginpath;
61         const char *issuefile;
62         int maxfd;
63 };
64 #define G (*(struct globals*)&bb_common_bufsiz1)
65 #define INIT_G() do { \
66         G.loginpath = "/bin/login"; \
67         G.issuefile = "/etc/issue.net"; \
68 } while (0)
69
70
71 /*
72    Remove all IAC's from buf1 (received IACs are ignored and must be removed
73    so as to not be interpreted by the terminal).  Make an uninterrupted
74    string of characters fit for the terminal.  Do this by packing
75    all characters meant for the terminal sequentially towards the end of buf.
76
77    Return a pointer to the beginning of the characters meant for the terminal.
78    and make *num_totty the number of characters that should be sent to
79    the terminal.
80
81    Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
82    past (bf + len) then that IAC will be left unprocessed and *processed
83    will be less than len.
84
85    CR-LF ->'s CR mapping is also done here, for convenience.
86
87    NB: may fail to remove iacs which wrap around buffer!
88  */
89 static unsigned char *
90 remove_iacs(struct tsession *ts, int *pnum_totty)
91 {
92         unsigned char *ptr0 = TS_BUF1(ts) + ts->wridx1;
93         unsigned char *ptr = ptr0;
94         unsigned char *totty = ptr;
95         unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
96         int num_totty;
97
98         while (ptr < end) {
99                 if (*ptr != IAC) {
100                         char c = *ptr;
101
102                         *totty++ = c;
103                         ptr++;
104                         /* We map \r\n ==> \r for pragmatic reasons.
105                          * Many client implementations send \r\n when
106                          * the user hits the CarriageReturn key.
107                          */
108                         if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
109                                 ptr++;
110                         continue;
111                 }
112
113                 if ((ptr+1) >= end)
114                         break;
115                 if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */
116                         ptr += 2;
117                         continue;
118                 }
119                 if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */
120                         *totty++ = ptr[1];
121                         ptr += 2;
122                         continue;
123                 }
124
125                 /*
126                  * TELOPT_NAWS support!
127                  */
128                 if ((ptr+2) >= end) {
129                         /* Only the beginning of the IAC is in the
130                         buffer we were asked to process, we can't
131                         process this char */
132                         break;
133                 }
134                 /*
135                  * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
136                  */
137                 if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
138                         struct winsize ws;
139                         if ((ptr+8) >= end)
140                                 break;  /* incomplete, can't process */
141                         ws.ws_col = (ptr[3] << 8) | ptr[4];
142                         ws.ws_row = (ptr[5] << 8) | ptr[6];
143                         ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
144                         ptr += 9;
145                         continue;
146                 }
147                 /* skip 3-byte IAC non-SB cmd */
148 #if DEBUG
149                 fprintf(stderr, "Ignoring IAC %s,%s\n",
150                                 TELCMD(ptr[1]), TELOPT(ptr[2]));
151 #endif
152                 ptr += 3;
153         }
154
155         num_totty = totty - ptr0;
156         *pnum_totty = num_totty;
157         /* The difference between ptr and totty is number of iacs
158            we removed from the stream. Adjust buf1 accordingly */
159         if ((ptr - totty) == 0) /* 99.999% of cases */
160                 return ptr0;
161         ts->wridx1 += ptr - totty;
162         ts->size1 -= ptr - totty;
163         /* Move chars meant for the terminal towards the end of the buffer */
164         return memmove(ptr - num_totty, ptr0, num_totty);
165 }
166
167 /*
168  * Converting single IAC into double on output
169  */
170 static size_t iac_safe_write(int fd, const char *buf, size_t count)
171 {
172         const char *IACptr;
173         size_t wr, rc, total;
174
175         total = 0;
176         while (1) {
177                 if (count == 0)
178                         return total;
179                 if (*buf == (char)IAC) {
180                         static const char IACIAC[] ALIGN1 = { IAC, IAC };
181                         rc = safe_write(fd, IACIAC, 2);
182                         if (rc != 2)
183                                 break;
184                         buf++;
185                         total++;
186                         count--;
187                         continue;
188                 }
189                 /* count != 0, *buf != IAC */
190                 IACptr = memchr(buf, IAC, count);
191                 wr = count;
192                 if (IACptr)
193                         wr = IACptr - buf;
194                 rc = safe_write(fd, buf, wr);
195                 if (rc != wr)
196                         break;
197                 buf += rc;
198                 total += rc;
199                 count -= rc;
200         }
201         /* here: rc - result of last short write */
202         if ((ssize_t)rc < 0) { /* error? */
203                 if (total == 0)
204                         return rc;
205                 rc = 0;
206         }
207         return total + rc;
208 }
209
210 /* Must match getopt32 string */
211 enum {
212         OPT_WATCHCHILD = (1 << 2), /* -K */
213         OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
214         OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p PORT */
215         OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
216         OPT_SYSLOG     = (1 << 7) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -S */
217         OPT_WAIT       = (1 << 8) * ENABLE_FEATURE_TELNETD_INETD_WAIT, /* -w SEC */
218 };
219
220 static struct tsession *
221 make_new_session(
222                 IF_FEATURE_TELNETD_STANDALONE(int sock)
223                 IF_NOT_FEATURE_TELNETD_STANDALONE(void)
224 ) {
225         const char *login_argv[2];
226         struct termios termbuf;
227         int fd, pid;
228         char tty_name[GETPTY_BUFSIZE];
229         struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
230
231         /*ts->buf1 = (char *)(ts + 1);*/
232         /*ts->buf2 = ts->buf1 + BUFSIZE;*/
233
234         /* Got a new connection, set up a tty */
235         fd = xgetpty(tty_name);
236         if (fd > G.maxfd)
237                 G.maxfd = fd;
238         ts->ptyfd = fd;
239         ndelay_on(fd);
240         close_on_exec_on(fd);
241
242 #if ENABLE_FEATURE_TELNETD_STANDALONE
243         /* SO_KEEPALIVE by popular demand */
244         setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
245         ts->sockfd_read = sock;
246         ndelay_on(sock);
247         if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */
248                 sock++; /* so use fd 1 for output */
249                 ndelay_on(sock);
250         }
251         ts->sockfd_write = sock;
252         if (sock > G.maxfd)
253                 G.maxfd = sock;
254 #else
255         /* SO_KEEPALIVE by popular demand */
256         setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
257         /* ts->sockfd_read = 0; - done by xzalloc */
258         ts->sockfd_write = 1;
259         ndelay_on(0);
260         ndelay_on(1);
261 #endif
262
263         /* Make the telnet client understand we will echo characters so it
264          * should not do it locally. We don't tell the client to run linemode,
265          * because we want to handle line editing and tab completion and other
266          * stuff that requires char-by-char support. */
267         {
268                 static const char iacs_to_send[] ALIGN1 = {
269                         IAC, DO, TELOPT_ECHO,
270                         IAC, DO, TELOPT_NAWS,
271                 /* This requires telnetd.ctrlSQ.patch (incomplete) */
272                 /*      IAC, DO, TELOPT_LFLOW, */
273                         IAC, WILL, TELOPT_ECHO,
274                         IAC, WILL, TELOPT_SGA
275                 };
276                 /* This confuses iac_safe_write(), it will try to duplicate
277                  * each IAC... */
278                 //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send));
279                 //ts->rdidx2 = sizeof(iacs_to_send);
280                 //ts->size2 = sizeof(iacs_to_send);
281                 /* So just stuff it into TCP stream! (no error check...) */
282 #if ENABLE_FEATURE_TELNETD_STANDALONE
283                 safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
284 #else
285                 safe_write(1, iacs_to_send, sizeof(iacs_to_send));
286 #endif
287                 /*ts->rdidx2 = 0; - xzalloc did it */
288                 /*ts->size2 = 0;*/
289         }
290
291         fflush_all();
292         pid = vfork(); /* NOMMU-friendly */
293         if (pid < 0) {
294                 free(ts);
295                 close(fd);
296                 /* sock will be closed by caller */
297                 bb_perror_msg("vfork");
298                 return NULL;
299         }
300         if (pid > 0) {
301                 /* Parent */
302                 ts->shell_pid = pid;
303                 return ts;
304         }
305
306         /* Child */
307         /* Careful - we are after vfork! */
308
309         /* Restore default signal handling ASAP */
310         bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
311
312         /* Make new session and process group */
313         setsid();
314
315         /* Open the child's side of the tty */
316         /* NB: setsid() disconnects from any previous ctty's. Therefore
317          * we must open child's side of the tty AFTER setsid! */
318         close(0);
319         xopen(tty_name, O_RDWR); /* becomes our ctty */
320         xdup2(0, 1);
321         xdup2(0, 2);
322         tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
323
324         /* The pseudo-terminal allocated to the client is configured to operate
325          * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */
326         tcgetattr(0, &termbuf);
327         termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
328         termbuf.c_oflag |= ONLCR | XTABS;
329         termbuf.c_iflag |= ICRNL;
330         termbuf.c_iflag &= ~IXOFF;
331         /*termbuf.c_lflag &= ~ICANON;*/
332         tcsetattr_stdin_TCSANOW(&termbuf);
333
334         /* Uses FILE-based I/O to stdout, but does fflush_all(),
335          * so should be safe with vfork.
336          * I fear, though, that some users will have ridiculously big
337          * issue files, and they may block writing to fd 1,
338          * (parent is supposed to read it, but parent waits
339          * for vforked child to exec!) */
340         print_login_issue(G.issuefile, tty_name);
341
342         /* Exec shell / login / whatever */
343         login_argv[0] = G.loginpath;
344         login_argv[1] = NULL;
345         /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
346          * exec external program.
347          * NB: sock is either 0 or has CLOEXEC set on it.
348          * fd has CLOEXEC set on it too. These two fds will be closed here.
349          */
350         BB_EXECVP(G.loginpath, (char **)login_argv);
351         /* _exit is safer with vfork, and we shouldn't send message
352          * to remote clients anyway */
353         _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/
354 }
355
356 #if ENABLE_FEATURE_TELNETD_STANDALONE
357
358 static void
359 free_session(struct tsession *ts)
360 {
361         struct tsession *t = G.sessions;
362
363         if (option_mask32 & OPT_INETD)
364                 exit(EXIT_SUCCESS);
365
366         /* Unlink this telnet session from the session list */
367         if (t == ts)
368                 G.sessions = ts->next;
369         else {
370                 while (t->next != ts)
371                         t = t->next;
372                 t->next = ts->next;
373         }
374
375 #if 0
376         /* It was said that "normal" telnetd just closes ptyfd,
377          * doesn't send SIGKILL. When we close ptyfd,
378          * kernel sends SIGHUP to processes having slave side opened. */
379         kill(ts->shell_pid, SIGKILL);
380         waitpid(ts->shell_pid, NULL, 0);
381 #endif
382         close(ts->ptyfd);
383         close(ts->sockfd_read);
384         /* We do not need to close(ts->sockfd_write), it's the same
385          * as sockfd_read unless we are in inetd mode. But in inetd mode
386          * we do not reach this */
387         free(ts);
388
389         /* Scan all sessions and find new maxfd */
390         G.maxfd = 0;
391         ts = G.sessions;
392         while (ts) {
393                 if (G.maxfd < ts->ptyfd)
394                         G.maxfd = ts->ptyfd;
395                 if (G.maxfd < ts->sockfd_read)
396                         G.maxfd = ts->sockfd_read;
397 #if 0
398                 /* Again, sockfd_write == sockfd_read here */
399                 if (G.maxfd < ts->sockfd_write)
400                         G.maxfd = ts->sockfd_write;
401 #endif
402                 ts = ts->next;
403         }
404 }
405
406 #else /* !FEATURE_TELNETD_STANDALONE */
407
408 /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
409 #define free_session(ts) return 0
410
411 #endif
412
413 static void handle_sigchld(int sig UNUSED_PARAM)
414 {
415         pid_t pid;
416         struct tsession *ts;
417
418         /* Looping: more than one child may have exited */
419         while (1) {
420                 pid = wait_any_nohang(NULL);
421                 if (pid <= 0)
422                         break;
423                 ts = G.sessions;
424                 while (ts) {
425                         if (ts->shell_pid == pid) {
426                                 ts->shell_pid = -1;
427                                 break;
428                         }
429                         ts = ts->next;
430                 }
431         }
432 }
433
434 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
435 int telnetd_main(int argc UNUSED_PARAM, char **argv)
436 {
437         fd_set rdfdset, wrfdset;
438         unsigned opt;
439         int count;
440         struct tsession *ts;
441 #if ENABLE_FEATURE_TELNETD_STANDALONE
442 #define IS_INETD (opt & OPT_INETD)
443         int master_fd = master_fd; /* for compiler */
444         int sec_linger = sec_linger;
445         char *opt_bindaddr = NULL;
446         char *opt_portnbr;
447 #else
448         enum {
449                 IS_INETD = 1,
450                 master_fd = -1,
451         };
452 #endif
453         INIT_G();
454
455         /* -w NUM, and implies -F. -w and -i don't mix */
456         IF_FEATURE_TELNETD_INETD_WAIT(opt_complementary = "wF:w+:i--w:w--i";)
457         /* Even if !STANDALONE, we accept (and ignore) -i, thus people
458          * don't need to guess whether it's ok to pass -i to us */
459         opt = getopt32(argv, "f:l:Ki"
460                         IF_FEATURE_TELNETD_STANDALONE("p:b:F")
461                         IF_FEATURE_TELNETD_INETD_WAIT("Sw:"),
462                         &G.issuefile, &G.loginpath
463                         IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)
464                         IF_FEATURE_TELNETD_INETD_WAIT(, &sec_linger)
465         );
466         if (!IS_INETD /*&& !re_execed*/) {
467                 /* inform that we start in standalone mode?
468                  * May be useful when people forget to give -i */
469                 /*bb_error_msg("listening for connections");*/
470                 if (!(opt & OPT_FOREGROUND)) {
471                         /* DAEMON_CHDIR_ROOT was giving inconsistent
472                          * behavior with/without -F, -i */
473                         bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
474                 }
475         }
476         /* Redirect log to syslog early, if needed */
477         if (IS_INETD || (opt & OPT_SYSLOG) || !(opt & OPT_FOREGROUND)) {
478                 openlog(applet_name, LOG_PID, LOG_DAEMON);
479                 logmode = LOGMODE_SYSLOG;
480         }
481 #if ENABLE_FEATURE_TELNETD_STANDALONE
482         if (IS_INETD) {
483                 G.sessions = make_new_session(0);
484                 if (!G.sessions) /* pty opening or vfork problem, exit */
485                         return 1; /* make_new_session printed error message */
486         } else {
487                 master_fd = 0;
488                 if (!(opt & OPT_WAIT)) {
489                         unsigned portnbr = 23;
490                         if (opt & OPT_PORT)
491                                 portnbr = xatou16(opt_portnbr);
492                         master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
493                         xlisten(master_fd, 1);
494                 }
495                 close_on_exec_on(master_fd);
496         }
497 #else
498         G.sessions = make_new_session();
499         if (!G.sessions) /* pty opening or vfork problem, exit */
500                 return 1; /* make_new_session printed error message */
501 #endif
502
503         /* We don't want to die if just one session is broken */
504         signal(SIGPIPE, SIG_IGN);
505
506         if (opt & OPT_WATCHCHILD)
507                 signal(SIGCHLD, handle_sigchld);
508         else /* prevent dead children from becoming zombies */
509                 signal(SIGCHLD, SIG_IGN);
510
511 /*
512    This is how the buffers are used. The arrows indicate data flow.
513
514    +-------+     wridx1++     +------+     rdidx1++     +----------+
515    |       | <--------------  | buf1 | <--------------  |          |
516    |       |     size1--      +------+     size1++      |          |
517    |  pty  |                                            |  socket  |
518    |       |     rdidx2++     +------+     wridx2++     |          |
519    |       |  --------------> | buf2 |  --------------> |          |
520    +-------+     size2++      +------+     size2--      +----------+
521
522    size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
523    size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
524
525    Each session has got two buffers. Buffers are circular. If sizeN == 0,
526    buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
527    rdidxN == wridxN.
528 */
529  again:
530         FD_ZERO(&rdfdset);
531         FD_ZERO(&wrfdset);
532
533         /* Select on the master socket, all telnet sockets and their
534          * ptys if there is room in their session buffers.
535          * NB: scalability problem: we recalculate entire bitmap
536          * before each select. Can be a problem with 500+ connections. */
537         ts = G.sessions;
538         while (ts) {
539                 struct tsession *next = ts->next; /* in case we free ts */
540                 if (ts->shell_pid == -1) {
541                         /* Child died and we detected that */
542                         free_session(ts);
543                 } else {
544                         if (ts->size1 > 0)       /* can write to pty */
545                                 FD_SET(ts->ptyfd, &wrfdset);
546                         if (ts->size1 < BUFSIZE) /* can read from socket */
547                                 FD_SET(ts->sockfd_read, &rdfdset);
548                         if (ts->size2 > 0)       /* can write to socket */
549                                 FD_SET(ts->sockfd_write, &wrfdset);
550                         if (ts->size2 < BUFSIZE) /* can read from pty */
551                                 FD_SET(ts->ptyfd, &rdfdset);
552                 }
553                 ts = next;
554         }
555         if (!IS_INETD) {
556                 FD_SET(master_fd, &rdfdset);
557                 /* This is needed because free_session() does not
558                  * take master_fd into account when it finds new
559                  * maxfd among remaining fd's */
560                 if (master_fd > G.maxfd)
561                         G.maxfd = master_fd;
562         }
563
564         {
565                 struct timeval *tv_ptr = NULL;
566 #if ENABLE_FEATURE_TELNETD_INETD_WAIT
567                 struct timeval tv;
568                 if ((opt & OPT_WAIT) && !G.sessions) {
569                         tv.tv_sec = sec_linger;
570                         tv.tv_usec = 0;
571                         tv_ptr = &tv;
572                 }
573 #endif
574                 count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr);
575         }
576         if (count == 0) /* "telnetd -w SEC" timed out */
577                 return 0;
578         if (count < 0)
579                 goto again; /* EINTR or ENOMEM */
580
581 #if ENABLE_FEATURE_TELNETD_STANDALONE
582         /* Check for and accept new sessions */
583         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
584                 int fd;
585                 struct tsession *new_ts;
586
587                 fd = accept(master_fd, NULL, NULL);
588                 if (fd < 0)
589                         goto again;
590                 close_on_exec_on(fd);
591
592                 /* Create a new session and link it into active list */
593                 new_ts = make_new_session(fd);
594                 if (new_ts) {
595                         new_ts->next = G.sessions;
596                         G.sessions = new_ts;
597                 } else {
598                         close(fd);
599                 }
600         }
601 #endif
602
603         /* Then check for data tunneling */
604         ts = G.sessions;
605         while (ts) { /* For all sessions... */
606                 struct tsession *next = ts->next; /* in case we free ts */
607
608                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
609                         int num_totty;
610                         unsigned char *ptr;
611                         /* Write to pty from buffer 1 */
612                         ptr = remove_iacs(ts, &num_totty);
613                         count = safe_write(ts->ptyfd, ptr, num_totty);
614                         if (count < 0) {
615                                 if (errno == EAGAIN)
616                                         goto skip1;
617                                 goto kill_session;
618                         }
619                         ts->size1 -= count;
620                         ts->wridx1 += count;
621                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
622                                 ts->wridx1 = 0;
623                 }
624  skip1:
625                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
626                         /* Write to socket from buffer 2 */
627                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
628                         count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count);
629                         if (count < 0) {
630                                 if (errno == EAGAIN)
631                                         goto skip2;
632                                 goto kill_session;
633                         }
634                         ts->size2 -= count;
635                         ts->wridx2 += count;
636                         if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
637                                 ts->wridx2 = 0;
638                 }
639  skip2:
640                 /* Should not be needed, but... remove_iacs is actually buggy
641                  * (it cannot process iacs which wrap around buffer's end)!
642                  * Since properly fixing it requires writing bigger code,
643                  * we rely instead on this code making it virtually impossible
644                  * to have wrapped iac (people don't type at 2k/second).
645                  * It also allows for bigger reads in common case. */
646                 if (ts->size1 == 0) {
647                         ts->rdidx1 = 0;
648                         ts->wridx1 = 0;
649                 }
650                 if (ts->size2 == 0) {
651                         ts->rdidx2 = 0;
652                         ts->wridx2 = 0;
653                 }
654
655                 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
656                         /* Read from socket to buffer 1 */
657                         count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
658                         count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count);
659                         if (count <= 0) {
660                                 if (count < 0 && errno == EAGAIN)
661                                         goto skip3;
662                                 goto kill_session;
663                         }
664                         /* Ignore trailing NUL if it is there */
665                         if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) {
666                                 --count;
667                         }
668                         ts->size1 += count;
669                         ts->rdidx1 += count;
670                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
671                                 ts->rdidx1 = 0;
672                 }
673  skip3:
674                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
675                         /* Read from pty to buffer 2 */
676                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
677                         count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count);
678                         if (count <= 0) {
679                                 if (count < 0 && errno == EAGAIN)
680                                         goto skip4;
681                                 goto kill_session;
682                         }
683                         ts->size2 += count;
684                         ts->rdidx2 += count;
685                         if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
686                                 ts->rdidx2 = 0;
687                 }
688  skip4:
689                 ts = next;
690                 continue;
691  kill_session:
692                 free_session(ts);
693                 ts = next;
694         }
695
696         goto again;
697 }