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