481c932db55f1723df158ea284e00bc256522fae
[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 /* Structure that describes a session */
36 struct tsession {
37         struct tsession *next;
38         int sockfd_read, sockfd_write, ptyfd;
39         int shell_pid;
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                 USE_FEATURE_TELNETD_STANDALONE(int sock)
205                 SKIP_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                         IAC, DO, TELOPT_LFLOW,
251                         IAC, WILL, TELOPT_ECHO,
252                         IAC, WILL, TELOPT_SGA
253                 };
254                 /* This confuses iac_safe_write(), it will try to duplicate
255                  * each IAC... */
256                 //memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
257                 //ts->rdidx2 = sizeof(iacs_to_send);
258                 //ts->size2 = sizeof(iacs_to_send);
259                 /* So just stuff it into TCP stream! (no error check...) */
260 #if ENABLE_FEATURE_TELNETD_STANDALONE
261                 safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
262 #else
263                 safe_write(1, iacs_to_send, sizeof(iacs_to_send));
264 #endif
265                 /*ts->rdidx2 = 0; - xzalloc did it */
266                 /*ts->size2 = 0;*/
267         }
268
269         fflush(NULL); /* flush all streams */
270         pid = vfork(); /* NOMMU-friendly */
271         if (pid < 0) {
272                 free(ts);
273                 close(fd);
274                 /* sock will be closed by caller */
275                 bb_perror_msg("vfork");
276                 return NULL;
277         }
278         if (pid > 0) {
279                 /* Parent */
280                 ts->shell_pid = pid;
281                 return ts;
282         }
283
284         /* Child */
285         /* Careful - we are after vfork! */
286
287         /* make new session and process group */
288         setsid();
289
290         /* Restore default signal handling */
291         bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
292
293         /* open the child's side of the tty. */
294         /* NB: setsid() disconnects from any previous ctty's. Therefore
295          * we must open child's side of the tty AFTER setsid! */
296         close(0);
297         xopen(tty_name, O_RDWR); /* becomes our ctty */
298         xdup2(0, 1);
299         xdup2(0, 2);
300         tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
301
302         /* The pseudo-terminal allocated to the client is configured to operate in
303          * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
304         tcgetattr(0, &termbuf);
305         termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
306         termbuf.c_oflag |= ONLCR | XTABS;
307         termbuf.c_iflag |= ICRNL;
308         termbuf.c_iflag &= ~IXOFF;
309         /*termbuf.c_lflag &= ~ICANON;*/
310         tcsetattr_stdin_TCSANOW(&termbuf);
311
312         /* Uses FILE-based I/O to stdout, but does fflush(stdout),
313          * so should be safe with vfork.
314          * I fear, though, that some users will have ridiculously big
315          * issue files, and they may block writing to fd 1,
316          * (parent is supposed to read it, but parent waits
317          * for vforked child to exec!) */
318         print_login_issue(issuefile, tty_name);
319
320         /* Exec shell / login / whatever */
321         login_argv[0] = loginpath;
322         login_argv[1] = NULL;
323         /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
324          * exec external program */
325         BB_EXECVP(loginpath, (char **)login_argv);
326         /* _exit is safer with vfork, and we shouldn't send message
327          * to remote clients anyway */
328         _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
329 }
330
331 /* Must match getopt32 string */
332 enum {
333         OPT_WATCHCHILD = (1 << 2), /* -K */
334         OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
335         OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
336         OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
337 };
338
339 #if ENABLE_FEATURE_TELNETD_STANDALONE
340
341 static void
342 free_session(struct tsession *ts)
343 {
344         struct tsession *t = sessions;
345
346         if (option_mask32 & OPT_INETD)
347                 exit(EXIT_SUCCESS);
348
349         /* Unlink this telnet session from the session list */
350         if (t == ts)
351                 sessions = ts->next;
352         else {
353                 while (t->next != ts)
354                         t = t->next;
355                 t->next = ts->next;
356         }
357
358 #if 0
359         /* It was said that "normal" telnetd just closes ptyfd,
360          * doesn't send SIGKILL. When we close ptyfd,
361          * kernel sends SIGHUP to processes having slave side opened. */
362         kill(ts->shell_pid, SIGKILL);
363         wait4(ts->shell_pid, NULL, 0, NULL);
364 #endif
365         close(ts->ptyfd);
366         close(ts->sockfd_read);
367         /* We do not need to close(ts->sockfd_write), it's the same
368          * as sockfd_read unless we are in inetd mode. But in inetd mode
369          * we do not reach this */
370         free(ts);
371
372         /* Scan all sessions and find new maxfd */
373         maxfd = 0;
374         ts = sessions;
375         while (ts) {
376                 if (maxfd < ts->ptyfd)
377                         maxfd = ts->ptyfd;
378                 if (maxfd < ts->sockfd_read)
379                         maxfd = ts->sockfd_read;
380 #if 0
381                 /* Again, sockfd_write == sockfd_read here */
382                 if (maxfd < ts->sockfd_write)
383                         maxfd = ts->sockfd_write;
384 #endif
385                 ts = ts->next;
386         }
387 }
388
389 #else /* !FEATURE_TELNETD_STANDALONE */
390
391 /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
392 #define free_session(ts) return 0
393
394 #endif
395
396 static void handle_sigchld(int sig UNUSED_PARAM)
397 {
398         pid_t pid;
399         struct tsession *ts;
400
401         /* Looping: more than one child may have exited */
402         while (1) {
403                 pid = wait_any_nohang(NULL);
404                 if (pid <= 0)
405                         break;
406                 ts = sessions;
407                 while (ts) {
408                         if (ts->shell_pid == pid) {
409                                 ts->shell_pid = -1;
410                                 break;
411                         }
412                         ts = ts->next;
413                 }
414         }
415 }
416
417 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
418 int telnetd_main(int argc UNUSED_PARAM, char **argv)
419 {
420         fd_set rdfdset, wrfdset;
421         unsigned opt;
422         int count;
423         struct tsession *ts;
424 #if ENABLE_FEATURE_TELNETD_STANDALONE
425 #define IS_INETD (opt & OPT_INETD)
426         int master_fd = master_fd; /* be happy, gcc */
427         unsigned portnbr = 23;
428         char *opt_bindaddr = NULL;
429         char *opt_portnbr;
430 #else
431         enum {
432                 IS_INETD = 1,
433                 master_fd = -1,
434                 portnbr = 23,
435         };
436 #endif
437         /* Even if !STANDALONE, we accept (and ignore) -i, thus people
438          * don't need to guess whether it's ok to pass -i to us */
439         opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
440                         &issuefile, &loginpath
441                         USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
442         if (!IS_INETD /*&& !re_execed*/) {
443                 /* inform that we start in standalone mode?
444                  * May be useful when people forget to give -i */
445                 /*bb_error_msg("listening for connections");*/
446                 if (!(opt & OPT_FOREGROUND)) {
447                         /* DAEMON_CHDIR_ROOT was giving inconsistent
448                          * behavior with/without -F, -i */
449                         bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
450                 }
451         }
452         /* Redirect log to syslog early, if needed */
453         if (IS_INETD || !(opt & OPT_FOREGROUND)) {
454                 openlog(applet_name, 0, LOG_USER);
455                 logmode = LOGMODE_SYSLOG;
456         }
457         USE_FEATURE_TELNETD_STANDALONE(
458                 if (opt & OPT_PORT)
459                         portnbr = xatou16(opt_portnbr);
460         );
461
462         /* Used to check access(loginpath, X_OK) here. Pointless.
463          * exec will do this for us for free later. */
464
465 #if ENABLE_FEATURE_TELNETD_STANDALONE
466         if (IS_INETD) {
467                 sessions = make_new_session(0);
468                 if (!sessions) /* pty opening or vfork problem, exit */
469                         return 1; /* make_new_session prints error message */
470         } else {
471                 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
472                 xlisten(master_fd, 1);
473         }
474 #else
475         sessions = make_new_session();
476         if (!sessions) /* pty opening or vfork problem, exit */
477                 return 1; /* make_new_session prints error message */
478 #endif
479
480         /* We don't want to die if just one session is broken */
481         signal(SIGPIPE, SIG_IGN);
482
483         if (opt & OPT_WATCHCHILD)
484                 signal(SIGCHLD, handle_sigchld);
485         else /* prevent dead children from becoming zombies */
486                 signal(SIGCHLD, SIG_IGN);
487
488 /*
489    This is how the buffers are used. The arrows indicate the movement
490    of data.
491    +-------+     wridx1++     +------+     rdidx1++     +----------+
492    |       | <--------------  | buf1 | <--------------  |          |
493    |       |     size1--      +------+     size1++      |          |
494    |  pty  |                                            |  socket  |
495    |       |     rdidx2++     +------+     wridx2++     |          |
496    |       |  --------------> | buf2 |  --------------> |          |
497    +-------+     size2++      +------+     size2--      +----------+
498
499    size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
500    size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
501
502    Each session has got two buffers. Buffers are circular. If sizeN == 0,
503    buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
504    rdidxN == wridxN.
505 */
506  again:
507         FD_ZERO(&rdfdset);
508         FD_ZERO(&wrfdset);
509
510         /* Select on the master socket, all telnet sockets and their
511          * ptys if there is room in their session buffers.
512          * NB: scalability problem: we recalculate entire bitmap
513          * before each select. Can be a problem with 500+ connections. */
514         ts = sessions;
515         while (ts) {
516                 struct tsession *next = ts->next; /* in case we free ts. */
517                 if (ts->shell_pid == -1) {
518                         /* Child died and we detected that */
519                         free_session(ts);
520                 } else {
521                         if (ts->size1 > 0)       /* can write to pty */
522                                 FD_SET(ts->ptyfd, &wrfdset);
523                         if (ts->size1 < BUFSIZE) /* can read from socket */
524                                 FD_SET(ts->sockfd_read, &rdfdset);
525                         if (ts->size2 > 0)       /* can write to socket */
526                                 FD_SET(ts->sockfd_write, &wrfdset);
527                         if (ts->size2 < BUFSIZE) /* can read from pty */
528                                 FD_SET(ts->ptyfd, &rdfdset);
529                 }
530                 ts = next;
531         }
532         if (!IS_INETD) {
533                 FD_SET(master_fd, &rdfdset);
534                 /* This is needed because free_session() does not
535                  * take master_fd into account when it finds new
536                  * maxfd among remaining fd's */
537                 if (master_fd > maxfd)
538                         maxfd = master_fd;
539         }
540
541         count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
542         if (count < 0)
543                 goto again; /* EINTR or ENOMEM */
544
545 #if ENABLE_FEATURE_TELNETD_STANDALONE
546         /* First check for and accept new sessions. */
547         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
548                 int fd;
549                 struct tsession *new_ts;
550
551                 fd = accept(master_fd, NULL, NULL);
552                 if (fd < 0)
553                         goto again;
554                 /* Create a new session and link it into our active list */
555                 new_ts = make_new_session(fd);
556                 if (new_ts) {
557                         new_ts->next = sessions;
558                         sessions = new_ts;
559                 } else {
560                         close(fd);
561                 }
562         }
563 #endif
564
565         /* Then check for data tunneling. */
566         ts = sessions;
567         while (ts) { /* For all sessions... */
568                 struct tsession *next = ts->next; /* in case we free ts. */
569
570                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
571                         int num_totty;
572                         unsigned char *ptr;
573                         /* Write to pty from buffer 1. */
574                         ptr = remove_iacs(ts, &num_totty);
575                         count = safe_write(ts->ptyfd, ptr, num_totty);
576                         if (count < 0) {
577                                 if (errno == EAGAIN)
578                                         goto skip1;
579                                 goto kill_session;
580                         }
581                         ts->size1 -= count;
582                         ts->wridx1 += count;
583                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
584                                 ts->wridx1 = 0;
585                 }
586  skip1:
587                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
588                         /* Write to socket from buffer 2. */
589                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
590                         count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2 + ts->wridx2), count);
591                         if (count < 0) {
592                                 if (errno == EAGAIN)
593                                         goto skip2;
594                                 goto kill_session;
595                         }
596                         ts->size2 -= count;
597                         ts->wridx2 += count;
598                         if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
599                                 ts->wridx2 = 0;
600                 }
601  skip2:
602                 /* Should not be needed, but... remove_iacs is actually buggy
603                  * (it cannot process iacs which wrap around buffer's end)!
604                  * Since properly fixing it requires writing bigger code,
605                  * we rely instead on this code making it virtually impossible
606                  * to have wrapped iac (people don't type at 2k/second).
607                  * It also allows for bigger reads in common case. */
608                 if (ts->size1 == 0) {
609                         ts->rdidx1 = 0;
610                         ts->wridx1 = 0;
611                 }
612                 if (ts->size2 == 0) {
613                         ts->rdidx2 = 0;
614                         ts->wridx2 = 0;
615                 }
616
617                 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
618                         /* Read from socket to buffer 1. */
619                         count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
620                         count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
621                         if (count <= 0) {
622                                 if (count < 0 && errno == EAGAIN)
623                                         goto skip3;
624                                 goto kill_session;
625                         }
626                         /* Ignore trailing NUL if it is there */
627                         if (!TS_BUF1[ts->rdidx1 + count - 1]) {
628                                 --count;
629                         }
630                         ts->size1 += count;
631                         ts->rdidx1 += count;
632                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
633                                 ts->rdidx1 = 0;
634                 }
635  skip3:
636                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
637                         /* Read from pty to buffer 2. */
638                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
639                         count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
640                         if (count <= 0) {
641                                 if (count < 0 && errno == EAGAIN)
642                                         goto skip4;
643                                 goto kill_session;
644                         }
645                         ts->size2 += count;
646                         ts->rdidx2 += count;
647                         if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
648                                 ts->rdidx2 = 0;
649                 }
650  skip4:
651                 ts = next;
652                 continue;
653  kill_session:
654                 free_session(ts);
655                 ts = next;
656         }
657
658         goto again;
659 }