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