move utmp.h include to libbb.h
[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 source tree.
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 #define DEBUG 0
24
25 #include "libbb.h"
26 #include <syslog.h>
27
28 #if DEBUG
29 # define TELCMDS
30 # define TELOPTS
31 #endif
32 #include <arpa/telnet.h>
33
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 } FIX_ALIASING;
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 #if !ENABLE_FEATURE_TELNETD_STANDALONE
226         enum { sock = 0 };
227 #endif
228         const char *login_argv[2];
229         struct termios termbuf;
230         int fd, pid;
231         char tty_name[GETPTY_BUFSIZE];
232         struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
233
234         /*ts->buf1 = (char *)(ts + 1);*/
235         /*ts->buf2 = ts->buf1 + BUFSIZE;*/
236
237         /* Got a new connection, set up a tty */
238         fd = xgetpty(tty_name);
239         if (fd > G.maxfd)
240                 G.maxfd = fd;
241         ts->ptyfd = fd;
242         ndelay_on(fd);
243         close_on_exec_on(fd);
244
245         /* SO_KEEPALIVE by popular demand */
246         setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
247 #if ENABLE_FEATURE_TELNETD_STANDALONE
248         ts->sockfd_read = sock;
249         ndelay_on(sock);
250         if (sock == 0) { /* We are called with fd 0 - we are in inetd mode */
251                 sock++; /* so use fd 1 for output */
252                 ndelay_on(sock);
253         }
254         ts->sockfd_write = sock;
255         if (sock > G.maxfd)
256                 G.maxfd = sock;
257 #else
258         /* ts->sockfd_read = 0; - done by xzalloc */
259         ts->sockfd_write = 1;
260         ndelay_on(0);
261         ndelay_on(1);
262 #endif
263
264         /* Make the telnet client understand we will echo characters so it
265          * should not do it locally. We don't tell the client to run linemode,
266          * because we want to handle line editing and tab completion and other
267          * stuff that requires char-by-char support. */
268         {
269                 static const char iacs_to_send[] ALIGN1 = {
270                         IAC, DO, TELOPT_ECHO,
271                         IAC, DO, TELOPT_NAWS,
272                         /* This requires telnetd.ctrlSQ.patch (incomplete) */
273                         /*IAC, DO, TELOPT_LFLOW,*/
274                         IAC, WILL, TELOPT_ECHO,
275                         IAC, WILL, TELOPT_SGA
276                 };
277                 /* This confuses iac_safe_write(), it will try to duplicate
278                  * each IAC... */
279                 //memcpy(TS_BUF2(ts), iacs_to_send, sizeof(iacs_to_send));
280                 //ts->rdidx2 = sizeof(iacs_to_send);
281                 //ts->size2 = sizeof(iacs_to_send);
282                 /* So just stuff it into TCP stream! (no error check...) */
283 #if ENABLE_FEATURE_TELNETD_STANDALONE
284                 safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
285 #else
286                 safe_write(1, iacs_to_send, sizeof(iacs_to_send));
287 #endif
288                 /*ts->rdidx2 = 0; - xzalloc did it */
289                 /*ts->size2 = 0;*/
290         }
291
292         fflush_all();
293         pid = vfork(); /* NOMMU-friendly */
294         if (pid < 0) {
295                 free(ts);
296                 close(fd);
297                 /* sock will be closed by caller */
298                 bb_perror_msg("vfork");
299                 return NULL;
300         }
301         if (pid > 0) {
302                 /* Parent */
303                 ts->shell_pid = pid;
304                 return ts;
305         }
306
307         /* Child */
308         /* Careful - we are after vfork! */
309
310         /* Restore default signal handling ASAP */
311         bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
312
313         pid = getpid();
314
315         if (ENABLE_FEATURE_UTMP) {
316                 len_and_sockaddr *lsa = get_peer_lsa(sock);
317                 char *hostname = NULL;
318                 if (lsa) {
319                         hostname = xmalloc_sockaddr2dotted(&lsa->u.sa);
320                         free(lsa);
321                 }
322                 write_new_utmp(pid, LOGIN_PROCESS, tty_name, /*username:*/ "LOGIN", hostname);
323                 free(hostname);
324         }
325
326         /* Make new session and process group */
327         setsid();
328
329         /* Open the child's side of the tty */
330         /* NB: setsid() disconnects from any previous ctty's. Therefore
331          * we must open child's side of the tty AFTER setsid! */
332         close(0);
333         xopen(tty_name, O_RDWR); /* becomes our ctty */
334         xdup2(0, 1);
335         xdup2(0, 2);
336         tcsetpgrp(0, pid); /* switch this tty's process group to us */
337
338         /* The pseudo-terminal allocated to the client is configured to operate
339          * in cooked mode, and with XTABS CRMOD enabled (see tty(4)) */
340         tcgetattr(0, &termbuf);
341         termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
342         termbuf.c_oflag |= ONLCR | XTABS;
343         termbuf.c_iflag |= ICRNL;
344         termbuf.c_iflag &= ~IXOFF;
345         /*termbuf.c_lflag &= ~ICANON;*/
346         tcsetattr_stdin_TCSANOW(&termbuf);
347
348         /* Uses FILE-based I/O to stdout, but does fflush_all(),
349          * so should be safe with vfork.
350          * I fear, though, that some users will have ridiculously big
351          * issue files, and they may block writing to fd 1,
352          * (parent is supposed to read it, but parent waits
353          * for vforked child to exec!) */
354         print_login_issue(G.issuefile, tty_name);
355
356         /* Exec shell / login / whatever */
357         login_argv[0] = G.loginpath;
358         login_argv[1] = NULL;
359         /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
360          * exec external program.
361          * NB: sock is either 0 or has CLOEXEC set on it.
362          * fd has CLOEXEC set on it too. These two fds will be closed here.
363          */
364         BB_EXECVP(G.loginpath, (char **)login_argv);
365         /* _exit is safer with vfork, and we shouldn't send message
366          * to remote clients anyway */
367         _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", G.loginpath);*/
368 }
369
370 #if ENABLE_FEATURE_TELNETD_STANDALONE
371
372 static void
373 free_session(struct tsession *ts)
374 {
375         struct tsession *t;
376
377         if (option_mask32 & OPT_INETD)
378                 exit(EXIT_SUCCESS);
379
380         /* Unlink this telnet session from the session list */
381         t = G.sessions;
382         if (t == ts)
383                 G.sessions = ts->next;
384         else {
385                 while (t->next != ts)
386                         t = t->next;
387                 t->next = ts->next;
388         }
389
390 #if 0
391         /* It was said that "normal" telnetd just closes ptyfd,
392          * doesn't send SIGKILL. When we close ptyfd,
393          * kernel sends SIGHUP to processes having slave side opened. */
394         kill(ts->shell_pid, SIGKILL);
395         waitpid(ts->shell_pid, NULL, 0);
396 #endif
397         close(ts->ptyfd);
398         close(ts->sockfd_read);
399         /* We do not need to close(ts->sockfd_write), it's the same
400          * as sockfd_read unless we are in inetd mode. But in inetd mode
401          * we do not reach this */
402         free(ts);
403
404         /* Scan all sessions and find new maxfd */
405         G.maxfd = 0;
406         ts = G.sessions;
407         while (ts) {
408                 if (G.maxfd < ts->ptyfd)
409                         G.maxfd = ts->ptyfd;
410                 if (G.maxfd < ts->sockfd_read)
411                         G.maxfd = ts->sockfd_read;
412 #if 0
413                 /* Again, sockfd_write == sockfd_read here */
414                 if (G.maxfd < ts->sockfd_write)
415                         G.maxfd = ts->sockfd_write;
416 #endif
417                 ts = ts->next;
418         }
419 }
420
421 #else /* !FEATURE_TELNETD_STANDALONE */
422
423 /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
424 #define free_session(ts) return 0
425
426 #endif
427
428 static void handle_sigchld(int sig UNUSED_PARAM)
429 {
430         pid_t pid;
431         struct tsession *ts;
432         int save_errno = errno;
433
434         /* Looping: more than one child may have exited */
435         while (1) {
436                 pid = wait_any_nohang(NULL);
437                 if (pid <= 0)
438                         break;
439                 ts = G.sessions;
440                 while (ts) {
441                         if (ts->shell_pid == pid) {
442                                 ts->shell_pid = -1;
443 // man utmp:
444 // When init(8) finds that a process has exited, it locates its utmp entry
445 // by ut_pid, sets ut_type to DEAD_PROCESS, and clears ut_user, ut_host
446 // and ut_time with null bytes.
447 // [same applies to other processes which maintain utmp entries, like telnetd]
448 //
449 // We do not bother actually clearing fields:
450 // it might be interesting to know who was logged in and from where
451                                 update_utmp(pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
452                                 break;
453                         }
454                         ts = ts->next;
455                 }
456         }
457
458         errno = save_errno;
459 }
460
461 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
462 int telnetd_main(int argc UNUSED_PARAM, char **argv)
463 {
464         fd_set rdfdset, wrfdset;
465         unsigned opt;
466         int count;
467         struct tsession *ts;
468 #if ENABLE_FEATURE_TELNETD_STANDALONE
469 #define IS_INETD (opt & OPT_INETD)
470         int master_fd = master_fd; /* for compiler */
471         int sec_linger = sec_linger;
472         char *opt_bindaddr = NULL;
473         char *opt_portnbr;
474 #else
475         enum {
476                 IS_INETD = 1,
477                 master_fd = -1,
478         };
479 #endif
480         INIT_G();
481
482         /* -w NUM, and implies -F. -w and -i don't mix */
483         IF_FEATURE_TELNETD_INETD_WAIT(opt_complementary = "wF:w+:i--w:w--i";)
484         /* Even if !STANDALONE, we accept (and ignore) -i, thus people
485          * don't need to guess whether it's ok to pass -i to us */
486         opt = getopt32(argv, "f:l:Ki"
487                         IF_FEATURE_TELNETD_STANDALONE("p:b:F")
488                         IF_FEATURE_TELNETD_INETD_WAIT("Sw:"),
489                         &G.issuefile, &G.loginpath
490                         IF_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)
491                         IF_FEATURE_TELNETD_INETD_WAIT(, &sec_linger)
492         );
493         if (!IS_INETD /*&& !re_execed*/) {
494                 /* inform that we start in standalone mode?
495                  * May be useful when people forget to give -i */
496                 /*bb_error_msg("listening for connections");*/
497                 if (!(opt & OPT_FOREGROUND)) {
498                         /* DAEMON_CHDIR_ROOT was giving inconsistent
499                          * behavior with/without -F, -i */
500                         bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
501                 }
502         }
503         /* Redirect log to syslog early, if needed */
504         if (IS_INETD || (opt & OPT_SYSLOG) || !(opt & OPT_FOREGROUND)) {
505                 openlog(applet_name, LOG_PID, LOG_DAEMON);
506                 logmode = LOGMODE_SYSLOG;
507         }
508 #if ENABLE_FEATURE_TELNETD_STANDALONE
509         if (IS_INETD) {
510                 G.sessions = make_new_session(0);
511                 if (!G.sessions) /* pty opening or vfork problem, exit */
512                         return 1; /* make_new_session printed error message */
513         } else {
514                 master_fd = 0;
515                 if (!(opt & OPT_WAIT)) {
516                         unsigned portnbr = 23;
517                         if (opt & OPT_PORT)
518                                 portnbr = xatou16(opt_portnbr);
519                         master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
520                         xlisten(master_fd, 1);
521                 }
522                 close_on_exec_on(master_fd);
523         }
524 #else
525         G.sessions = make_new_session();
526         if (!G.sessions) /* pty opening or vfork problem, exit */
527                 return 1; /* make_new_session printed error message */
528 #endif
529
530         /* We don't want to die if just one session is broken */
531         signal(SIGPIPE, SIG_IGN);
532
533         if (opt & OPT_WATCHCHILD)
534                 signal(SIGCHLD, handle_sigchld);
535         else /* prevent dead children from becoming zombies */
536                 signal(SIGCHLD, SIG_IGN);
537
538 /*
539    This is how the buffers are used. The arrows indicate data flow.
540
541    +-------+     wridx1++     +------+     rdidx1++     +----------+
542    |       | <--------------  | buf1 | <--------------  |          |
543    |       |     size1--      +------+     size1++      |          |
544    |  pty  |                                            |  socket  |
545    |       |     rdidx2++     +------+     wridx2++     |          |
546    |       |  --------------> | buf2 |  --------------> |          |
547    +-------+     size2++      +------+     size2--      +----------+
548
549    size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
550    size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
551
552    Each session has got two buffers. Buffers are circular. If sizeN == 0,
553    buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
554    rdidxN == wridxN.
555 */
556  again:
557         FD_ZERO(&rdfdset);
558         FD_ZERO(&wrfdset);
559
560         /* Select on the master socket, all telnet sockets and their
561          * ptys if there is room in their session buffers.
562          * NB: scalability problem: we recalculate entire bitmap
563          * before each select. Can be a problem with 500+ connections. */
564         ts = G.sessions;
565         while (ts) {
566                 struct tsession *next = ts->next; /* in case we free ts */
567                 if (ts->shell_pid == -1) {
568                         /* Child died and we detected that */
569                         free_session(ts);
570                 } else {
571                         if (ts->size1 > 0)       /* can write to pty */
572                                 FD_SET(ts->ptyfd, &wrfdset);
573                         if (ts->size1 < BUFSIZE) /* can read from socket */
574                                 FD_SET(ts->sockfd_read, &rdfdset);
575                         if (ts->size2 > 0)       /* can write to socket */
576                                 FD_SET(ts->sockfd_write, &wrfdset);
577                         if (ts->size2 < BUFSIZE) /* can read from pty */
578                                 FD_SET(ts->ptyfd, &rdfdset);
579                 }
580                 ts = next;
581         }
582         if (!IS_INETD) {
583                 FD_SET(master_fd, &rdfdset);
584                 /* This is needed because free_session() does not
585                  * take master_fd into account when it finds new
586                  * maxfd among remaining fd's */
587                 if (master_fd > G.maxfd)
588                         G.maxfd = master_fd;
589         }
590
591         {
592                 struct timeval *tv_ptr = NULL;
593 #if ENABLE_FEATURE_TELNETD_INETD_WAIT
594                 struct timeval tv;
595                 if ((opt & OPT_WAIT) && !G.sessions) {
596                         tv.tv_sec = sec_linger;
597                         tv.tv_usec = 0;
598                         tv_ptr = &tv;
599                 }
600 #endif
601                 count = select(G.maxfd + 1, &rdfdset, &wrfdset, NULL, tv_ptr);
602         }
603         if (count == 0) /* "telnetd -w SEC" timed out */
604                 return 0;
605         if (count < 0)
606                 goto again; /* EINTR or ENOMEM */
607
608 #if ENABLE_FEATURE_TELNETD_STANDALONE
609         /* Check for and accept new sessions */
610         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
611                 int fd;
612                 struct tsession *new_ts;
613
614                 fd = accept(master_fd, NULL, NULL);
615                 if (fd < 0)
616                         goto again;
617                 close_on_exec_on(fd);
618
619                 /* Create a new session and link it into active list */
620                 new_ts = make_new_session(fd);
621                 if (new_ts) {
622                         new_ts->next = G.sessions;
623                         G.sessions = new_ts;
624                 } else {
625                         close(fd);
626                 }
627         }
628 #endif
629
630         /* Then check for data tunneling */
631         ts = G.sessions;
632         while (ts) { /* For all sessions... */
633                 struct tsession *next = ts->next; /* in case we free ts */
634
635                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
636                         int num_totty;
637                         unsigned char *ptr;
638                         /* Write to pty from buffer 1 */
639                         ptr = remove_iacs(ts, &num_totty);
640                         count = safe_write(ts->ptyfd, ptr, num_totty);
641                         if (count < 0) {
642                                 if (errno == EAGAIN)
643                                         goto skip1;
644                                 goto kill_session;
645                         }
646                         ts->size1 -= count;
647                         ts->wridx1 += count;
648                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
649                                 ts->wridx1 = 0;
650                 }
651  skip1:
652                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
653                         /* Write to socket from buffer 2 */
654                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
655                         count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2(ts) + ts->wridx2), count);
656                         if (count < 0) {
657                                 if (errno == EAGAIN)
658                                         goto skip2;
659                                 goto kill_session;
660                         }
661                         ts->size2 -= count;
662                         ts->wridx2 += count;
663                         if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
664                                 ts->wridx2 = 0;
665                 }
666  skip2:
667                 /* Should not be needed, but... remove_iacs is actually buggy
668                  * (it cannot process iacs which wrap around buffer's end)!
669                  * Since properly fixing it requires writing bigger code,
670                  * we rely instead on this code making it virtually impossible
671                  * to have wrapped iac (people don't type at 2k/second).
672                  * It also allows for bigger reads in common case. */
673                 if (ts->size1 == 0) {
674                         ts->rdidx1 = 0;
675                         ts->wridx1 = 0;
676                 }
677                 if (ts->size2 == 0) {
678                         ts->rdidx2 = 0;
679                         ts->wridx2 = 0;
680                 }
681
682                 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
683                         /* Read from socket to buffer 1 */
684                         count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
685                         count = safe_read(ts->sockfd_read, TS_BUF1(ts) + ts->rdidx1, count);
686                         if (count <= 0) {
687                                 if (count < 0 && errno == EAGAIN)
688                                         goto skip3;
689                                 goto kill_session;
690                         }
691                         /* Ignore trailing NUL if it is there */
692                         if (!TS_BUF1(ts)[ts->rdidx1 + count - 1]) {
693                                 --count;
694                         }
695                         ts->size1 += count;
696                         ts->rdidx1 += count;
697                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
698                                 ts->rdidx1 = 0;
699                 }
700  skip3:
701                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
702                         /* Read from pty to buffer 2 */
703                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
704                         count = safe_read(ts->ptyfd, TS_BUF2(ts) + ts->rdidx2, count);
705                         if (count <= 0) {
706                                 if (count < 0 && errno == EAGAIN)
707                                         goto skip4;
708                                 goto kill_session;
709                         }
710                         ts->size2 += count;
711                         ts->rdidx2 += count;
712                         if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
713                                 ts->rdidx2 = 0;
714                 }
715  skip4:
716                 ts = next;
717                 continue;
718  kill_session:
719                 if (ts->shell_pid > 0)
720                         update_utmp(ts->shell_pid, DEAD_PROCESS, /*tty_name:*/ NULL, /*username:*/ NULL, /*hostname:*/ NULL);
721                 free_session(ts);
722                 ts = next;
723         }
724
725         goto again;
726 }