Magellan Linux

Contents of /trunk/mkinitrd-magellan/busybox/networking/telnetd.c

Parent Directory Parent Directory | Revision Log Revision Log


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