diff options
Diffstat (limited to 'networking/telnetd.c')
-rw-r--r-- | networking/telnetd.c | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/networking/telnetd.c b/networking/telnetd.c new file mode 100644 index 000000000..604f65c91 --- /dev/null +++ b/networking/telnetd.c | |||
@@ -0,0 +1,578 @@ | |||
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 1 */ | ||
25 | #define DEBUG 0 | ||
26 | |||
27 | #include "busybox.h" | ||
28 | |||
29 | #if DEBUG | ||
30 | #define TELCMDS | ||
31 | #define TELOPTS | ||
32 | #endif | ||
33 | #include <arpa/telnet.h> | ||
34 | #include <sys/syslog.h> | ||
35 | |||
36 | |||
37 | #define BUFSIZE 4000 | ||
38 | |||
39 | #if ENABLE_FEATURE_IPV6 | ||
40 | typedef struct sockaddr_in6 sockaddr_type; | ||
41 | #else | ||
42 | typedef struct sockaddr_in sockaddr_type; | ||
43 | #endif | ||
44 | |||
45 | #if ENABLE_LOGIN | ||
46 | static const char *loginpath = "/bin/login"; | ||
47 | #else | ||
48 | static const char *loginpath = DEFAULT_SHELL; | ||
49 | #endif | ||
50 | |||
51 | static const char *issuefile = "/etc/issue.net"; | ||
52 | |||
53 | /* shell name and arguments */ | ||
54 | |||
55 | static const char *argv_init[2]; | ||
56 | |||
57 | /* structure that describes a session */ | ||
58 | |||
59 | struct tsession { | ||
60 | struct tsession *next; | ||
61 | int sockfd_read, sockfd_write, ptyfd; | ||
62 | int shell_pid; | ||
63 | /* two circular buffers */ | ||
64 | char *buf1, *buf2; | ||
65 | int rdidx1, wridx1, size1; | ||
66 | int rdidx2, wridx2, size2; | ||
67 | }; | ||
68 | |||
69 | /* | ||
70 | This is how the buffers are used. The arrows indicate the movement | ||
71 | of data. | ||
72 | |||
73 | +-------+ wridx1++ +------+ rdidx1++ +----------+ | ||
74 | | | <-------------- | buf1 | <-------------- | | | ||
75 | | | size1-- +------+ size1++ | | | ||
76 | | pty | | socket | | ||
77 | | | rdidx2++ +------+ wridx2++ | | | ||
78 | | | --------------> | buf2 | --------------> | | | ||
79 | +-------+ size2++ +------+ size2-- +----------+ | ||
80 | |||
81 | Each session has got two buffers. | ||
82 | */ | ||
83 | |||
84 | static int maxfd; | ||
85 | |||
86 | static struct tsession *sessions; | ||
87 | |||
88 | |||
89 | /* | ||
90 | Remove all IAC's from the buffer pointed to by bf (received IACs are ignored | ||
91 | and must be removed so as to not be interpreted by the terminal). Make an | ||
92 | uninterrupted string of characters fit for the terminal. Do this by packing | ||
93 | all characters meant for the terminal sequentially towards the end of bf. | ||
94 | |||
95 | Return a pointer to the beginning of the characters meant for the terminal. | ||
96 | and make *num_totty the number of characters that should be sent to | ||
97 | the terminal. | ||
98 | |||
99 | Note - If an IAC (3 byte quantity) starts before (bf + len) but extends | ||
100 | past (bf + len) then that IAC will be left unprocessed and *processed will be | ||
101 | less than len. | ||
102 | |||
103 | FIXME - if we mean to send 0xFF to the terminal then it will be escaped, | ||
104 | what is the escape character? We aren't handling that situation here. | ||
105 | |||
106 | CR-LF ->'s CR mapping is also done here, for convenience | ||
107 | */ | ||
108 | static char * | ||
109 | remove_iacs(struct tsession *ts, int *pnum_totty) | ||
110 | { | ||
111 | unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1; | ||
112 | unsigned char *ptr = ptr0; | ||
113 | unsigned char *totty = ptr; | ||
114 | unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); | ||
115 | int processed; | ||
116 | int num_totty; | ||
117 | |||
118 | while (ptr < end) { | ||
119 | if (*ptr != IAC) { | ||
120 | int c = *ptr; | ||
121 | *totty++ = *ptr++; | ||
122 | /* We now map \r\n ==> \r for pragmatic reasons. | ||
123 | * Many client implementations send \r\n when | ||
124 | * the user hits the CarriageReturn key. | ||
125 | */ | ||
126 | if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end) | ||
127 | ptr++; | ||
128 | } else { | ||
129 | /* | ||
130 | * TELOPT_NAWS support! | ||
131 | */ | ||
132 | if ((ptr+2) >= end) { | ||
133 | /* only the beginning of the IAC is in the | ||
134 | buffer we were asked to process, we can't | ||
135 | process this char. */ | ||
136 | break; | ||
137 | } | ||
138 | |||
139 | /* | ||
140 | * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE | ||
141 | */ | ||
142 | else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { | ||
143 | struct winsize ws; | ||
144 | if ((ptr+8) >= end) | ||
145 | break; /* incomplete, can't process */ | ||
146 | ws.ws_col = (ptr[3] << 8) | ptr[4]; | ||
147 | ws.ws_row = (ptr[5] << 8) | ptr[6]; | ||
148 | ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); | ||
149 | ptr += 9; | ||
150 | } else { | ||
151 | /* skip 3-byte IAC non-SB cmd */ | ||
152 | #if DEBUG | ||
153 | fprintf(stderr, "Ignoring IAC %s,%s\n", | ||
154 | TELCMD(ptr[1]), TELOPT(ptr[2])); | ||
155 | #endif | ||
156 | ptr += 3; | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | |||
161 | processed = ptr - ptr0; | ||
162 | num_totty = totty - ptr0; | ||
163 | /* the difference between processed and num_to tty | ||
164 | is all the iacs we removed from the stream. | ||
165 | Adjust buf1 accordingly. */ | ||
166 | ts->wridx1 += processed - num_totty; | ||
167 | ts->size1 -= processed - num_totty; | ||
168 | *pnum_totty = num_totty; | ||
169 | /* move the chars meant for the terminal towards the end of the | ||
170 | buffer. */ | ||
171 | return memmove(ptr - num_totty, ptr0, num_totty); | ||
172 | } | ||
173 | |||
174 | |||
175 | static int | ||
176 | getpty(char *line, int size) | ||
177 | { | ||
178 | int p; | ||
179 | #if ENABLE_FEATURE_DEVPTS | ||
180 | p = open("/dev/ptmx", O_RDWR); | ||
181 | if (p > 0) { | ||
182 | const char *name; | ||
183 | grantpt(p); | ||
184 | unlockpt(p); | ||
185 | name = ptsname(p); | ||
186 | if (!name) { | ||
187 | bb_perror_msg("ptsname error (is /dev/pts mounted?)"); | ||
188 | return -1; | ||
189 | } | ||
190 | safe_strncpy(line, name, size); | ||
191 | return p; | ||
192 | } | ||
193 | #else | ||
194 | struct stat stb; | ||
195 | int i; | ||
196 | int j; | ||
197 | |||
198 | strcpy(line, "/dev/ptyXX"); | ||
199 | |||
200 | for (i = 0; i < 16; i++) { | ||
201 | line[8] = "pqrstuvwxyzabcde"[i]; | ||
202 | line[9] = '0'; | ||
203 | if (stat(line, &stb) < 0) { | ||
204 | continue; | ||
205 | } | ||
206 | for (j = 0; j < 16; j++) { | ||
207 | line[9] = j < 10 ? j + '0' : j - 10 + 'a'; | ||
208 | if (DEBUG) | ||
209 | fprintf(stderr, "Trying to open device: %s\n", line); | ||
210 | p = open(line, O_RDWR | O_NOCTTY); | ||
211 | if (p >= 0) { | ||
212 | line[5] = 't'; | ||
213 | return p; | ||
214 | } | ||
215 | } | ||
216 | } | ||
217 | #endif /* FEATURE_DEVPTS */ | ||
218 | return -1; | ||
219 | } | ||
220 | |||
221 | |||
222 | static void | ||
223 | send_iac(struct tsession *ts, unsigned char command, int option) | ||
224 | { | ||
225 | /* We rely on that there is space in the buffer for now. */ | ||
226 | char *b = ts->buf2 + ts->rdidx2; | ||
227 | *b++ = IAC; | ||
228 | *b++ = command; | ||
229 | *b++ = option; | ||
230 | ts->rdidx2 += 3; | ||
231 | ts->size2 += 3; | ||
232 | } | ||
233 | |||
234 | |||
235 | static struct tsession * | ||
236 | make_new_session( | ||
237 | USE_FEATURE_TELNETD_STANDALONE(int sock_r, int sock_w) | ||
238 | SKIP_FEATURE_TELNETD_STANDALONE(void) | ||
239 | ) { | ||
240 | struct termios termbuf; | ||
241 | int fd, pid; | ||
242 | char tty_name[32]; | ||
243 | struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2); | ||
244 | |||
245 | ts->buf1 = (char *)(&ts[1]); | ||
246 | ts->buf2 = ts->buf1 + BUFSIZE; | ||
247 | |||
248 | /* Got a new connection, set up a tty. */ | ||
249 | fd = getpty(tty_name, 32); | ||
250 | if (fd < 0) { | ||
251 | bb_error_msg("all terminals in use"); | ||
252 | return NULL; | ||
253 | } | ||
254 | if (fd > maxfd) maxfd = fd; | ||
255 | ndelay_on(ts->ptyfd = fd); | ||
256 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
257 | if (sock_w > maxfd) maxfd = sock_w; | ||
258 | if (sock_r > maxfd) maxfd = sock_r; | ||
259 | ndelay_on(ts->sockfd_write = sock_w); | ||
260 | ndelay_on(ts->sockfd_read = sock_r); | ||
261 | #else | ||
262 | ts->sockfd_write = 1; | ||
263 | /* xzalloc: ts->sockfd_read = 0; */ | ||
264 | ndelay_on(0); | ||
265 | ndelay_on(1); | ||
266 | #endif | ||
267 | /* Make the telnet client understand we will echo characters so it | ||
268 | * should not do it locally. We don't tell the client to run linemode, | ||
269 | * because we want to handle line editing and tab completion and other | ||
270 | * stuff that requires char-by-char support. */ | ||
271 | send_iac(ts, DO, TELOPT_ECHO); | ||
272 | send_iac(ts, DO, TELOPT_NAWS); | ||
273 | send_iac(ts, DO, TELOPT_LFLOW); | ||
274 | send_iac(ts, WILL, TELOPT_ECHO); | ||
275 | send_iac(ts, WILL, TELOPT_SGA); | ||
276 | |||
277 | pid = fork(); | ||
278 | if (pid < 0) { | ||
279 | free(ts); | ||
280 | close(fd); | ||
281 | bb_perror_msg("fork"); | ||
282 | return NULL; | ||
283 | } | ||
284 | if (pid > 0) { | ||
285 | /* parent */ | ||
286 | ts->shell_pid = pid; | ||
287 | return ts; | ||
288 | } | ||
289 | |||
290 | /* child */ | ||
291 | |||
292 | /* open the child's side of the tty. */ | ||
293 | fd = xopen(tty_name, O_RDWR /*| O_NOCTTY*/); | ||
294 | dup2(fd, 0); | ||
295 | dup2(fd, 1); | ||
296 | dup2(fd, 2); | ||
297 | while (fd > 2) close(fd--); | ||
298 | /* make new process group */ | ||
299 | setsid(); | ||
300 | tcsetpgrp(0, getpid()); | ||
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(0, TCSANOW, &termbuf); | ||
311 | |||
312 | print_login_issue(issuefile, NULL); | ||
313 | |||
314 | /* exec shell, with correct argv and env */ | ||
315 | execv(loginpath, (char *const *)argv_init); | ||
316 | bb_perror_msg_and_die("execv"); | ||
317 | } | ||
318 | |||
319 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
320 | |||
321 | static void | ||
322 | free_session(struct tsession *ts) | ||
323 | { | ||
324 | struct tsession *t = sessions; | ||
325 | |||
326 | /* unlink this telnet session from the session list */ | ||
327 | if (t == ts) | ||
328 | sessions = ts->next; | ||
329 | else { | ||
330 | while (t->next != ts) | ||
331 | t = t->next; | ||
332 | t->next = ts->next; | ||
333 | } | ||
334 | |||
335 | kill(ts->shell_pid, SIGKILL); | ||
336 | wait4(ts->shell_pid, NULL, 0, NULL); | ||
337 | close(ts->ptyfd); | ||
338 | close(ts->sockfd_read); | ||
339 | /* error if ts->sockfd_read == ts->sockfd_write. So what? ;) */ | ||
340 | close(ts->sockfd_write); | ||
341 | free(ts); | ||
342 | |||
343 | /* scan all sessions and find new maxfd */ | ||
344 | ts = sessions; | ||
345 | maxfd = 0; | ||
346 | while (ts) { | ||
347 | if (maxfd < ts->ptyfd) | ||
348 | maxfd = ts->ptyfd; | ||
349 | if (maxfd < ts->sockfd_read) | ||
350 | maxfd = ts->sockfd_read; | ||
351 | if (maxfd < ts->sockfd_write) | ||
352 | maxfd = ts->sockfd_write; | ||
353 | ts = ts->next; | ||
354 | } | ||
355 | } | ||
356 | |||
357 | #else /* !FEATURE_TELNETD_STANDALONE */ | ||
358 | |||
359 | /* Never actually called */ | ||
360 | void free_session(struct tsession *ts); | ||
361 | |||
362 | #endif | ||
363 | |||
364 | |||
365 | int | ||
366 | telnetd_main(int argc, char **argv) | ||
367 | { | ||
368 | fd_set rdfdset, wrfdset; | ||
369 | unsigned opt; | ||
370 | int selret, maxlen, w, r; | ||
371 | struct tsession *ts; | ||
372 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
373 | #define IS_INETD (opt & OPT_INETD) | ||
374 | int master_fd = -1; /* be happy, gcc */ | ||
375 | unsigned portnbr = 23; | ||
376 | char *opt_bindaddr = NULL; | ||
377 | char *opt_portnbr; | ||
378 | #else | ||
379 | enum { | ||
380 | IS_INETD = 1, | ||
381 | master_fd = -1, | ||
382 | portnbr = 23, | ||
383 | }; | ||
384 | #endif | ||
385 | enum { | ||
386 | OPT_PORT = 4 * ENABLE_FEATURE_TELNETD_STANDALONE, | ||
387 | OPT_FOREGROUND = 0x10 * ENABLE_FEATURE_TELNETD_STANDALONE, | ||
388 | OPT_INETD = 0x20 * ENABLE_FEATURE_TELNETD_STANDALONE, | ||
389 | }; | ||
390 | |||
391 | opt = getopt32(argc, argv, "f:l:" USE_FEATURE_TELNETD_STANDALONE("p:b:Fi"), | ||
392 | &issuefile, &loginpath | ||
393 | USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); | ||
394 | /* Redirect log to syslog early, if needed */ | ||
395 | if (IS_INETD || !(opt & OPT_FOREGROUND)) { | ||
396 | openlog(applet_name, 0, LOG_USER); | ||
397 | logmode = LOGMODE_SYSLOG; | ||
398 | } | ||
399 | //if (opt & 1) // -f | ||
400 | //if (opt & 2) // -l | ||
401 | USE_FEATURE_TELNETD_STANDALONE( | ||
402 | if (opt & OPT_PORT) // -p | ||
403 | portnbr = xatou16(opt_portnbr); | ||
404 | //if (opt & 8) // -b | ||
405 | //if (opt & 0x10) // -F | ||
406 | //if (opt & 0x20) // -i | ||
407 | ); | ||
408 | |||
409 | /* Used to check access(loginpath, X_OK) here. Pointless. | ||
410 | * exec will do this for us for free later. */ | ||
411 | argv_init[0] = loginpath; | ||
412 | |||
413 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
414 | if (IS_INETD) { | ||
415 | sessions = make_new_session(0, 1); | ||
416 | } else { | ||
417 | master_fd = create_and_bind_socket_ip4or6(opt_bindaddr, portnbr); | ||
418 | xlisten(master_fd, 1); | ||
419 | if (!(opt & OPT_FOREGROUND)) | ||
420 | xdaemon(0, 0); | ||
421 | } | ||
422 | #else | ||
423 | sessions = make_new_session(); | ||
424 | #endif | ||
425 | |||
426 | /* We don't want to die if just one session is broken */ | ||
427 | signal(SIGPIPE, SIG_IGN); | ||
428 | |||
429 | again: | ||
430 | FD_ZERO(&rdfdset); | ||
431 | FD_ZERO(&wrfdset); | ||
432 | if (!IS_INETD) { | ||
433 | FD_SET(master_fd, &rdfdset); | ||
434 | /* This is needed because free_session() does not | ||
435 | * take into account master_fd when it finds new | ||
436 | * maxfd among remaining fd's: */ | ||
437 | if (master_fd > maxfd) | ||
438 | maxfd = master_fd; | ||
439 | } | ||
440 | |||
441 | /* select on the master socket, all telnet sockets and their | ||
442 | * ptys if there is room in their session buffers. */ | ||
443 | ts = sessions; | ||
444 | while (ts) { | ||
445 | /* buf1 is used from socket to pty | ||
446 | * buf2 is used from pty to socket */ | ||
447 | if (ts->size1 > 0) /* can write to pty */ | ||
448 | FD_SET(ts->ptyfd, &wrfdset); | ||
449 | if (ts->size1 < BUFSIZE) /* can read from socket */ | ||
450 | FD_SET(ts->sockfd_read, &rdfdset); | ||
451 | if (ts->size2 > 0) /* can write to socket */ | ||
452 | FD_SET(ts->sockfd_write, &wrfdset); | ||
453 | if (ts->size2 < BUFSIZE) /* can read from pty */ | ||
454 | FD_SET(ts->ptyfd, &rdfdset); | ||
455 | ts = ts->next; | ||
456 | } | ||
457 | |||
458 | selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0); | ||
459 | if (!selret) | ||
460 | return 0; | ||
461 | |||
462 | #if ENABLE_FEATURE_TELNETD_STANDALONE | ||
463 | /* First check for and accept new sessions. */ | ||
464 | if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { | ||
465 | sockaddr_type sa; | ||
466 | int fd; | ||
467 | socklen_t salen; | ||
468 | struct tsession *new_ts; | ||
469 | |||
470 | salen = sizeof(sa); | ||
471 | fd = accept(master_fd, (struct sockaddr *)&sa, &salen); | ||
472 | if (fd < 0) | ||
473 | goto again; | ||
474 | /* Create a new session and link it into our active list */ | ||
475 | new_ts = make_new_session(fd, fd); | ||
476 | if (new_ts) { | ||
477 | new_ts->next = sessions; | ||
478 | sessions = new_ts; | ||
479 | } else { | ||
480 | close(fd); | ||
481 | } | ||
482 | } | ||
483 | #endif | ||
484 | |||
485 | /* Then check for data tunneling. */ | ||
486 | ts = sessions; | ||
487 | while (ts) { /* For all sessions... */ | ||
488 | struct tsession *next = ts->next; /* in case we free ts. */ | ||
489 | |||
490 | if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) { | ||
491 | int num_totty; | ||
492 | char *ptr; | ||
493 | /* Write to pty from buffer 1. */ | ||
494 | ptr = remove_iacs(ts, &num_totty); | ||
495 | w = safe_write(ts->ptyfd, ptr, num_totty); | ||
496 | /* needed? if (w < 0 && errno == EAGAIN) continue; */ | ||
497 | if (w < 0) { | ||
498 | if (IS_INETD) | ||
499 | return 0; | ||
500 | free_session(ts); | ||
501 | ts = next; | ||
502 | continue; | ||
503 | } | ||
504 | ts->wridx1 += w; | ||
505 | ts->size1 -= w; | ||
506 | if (ts->wridx1 == BUFSIZE) | ||
507 | ts->wridx1 = 0; | ||
508 | } | ||
509 | |||
510 | if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) { | ||
511 | /* Write to socket from buffer 2. */ | ||
512 | maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2); | ||
513 | w = safe_write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen); | ||
514 | /* needed? if (w < 0 && errno == EAGAIN) continue; */ | ||
515 | if (w < 0) { | ||
516 | if (IS_INETD) | ||
517 | return 0; | ||
518 | free_session(ts); | ||
519 | ts = next; | ||
520 | continue; | ||
521 | } | ||
522 | ts->wridx2 += w; | ||
523 | ts->size2 -= w; | ||
524 | if (ts->wridx2 == BUFSIZE) | ||
525 | ts->wridx2 = 0; | ||
526 | } | ||
527 | |||
528 | if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) { | ||
529 | /* Read from socket to buffer 1. */ | ||
530 | maxlen = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); | ||
531 | r = safe_read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen); | ||
532 | if (r < 0 && errno == EAGAIN) continue; | ||
533 | if (r <= 0) { | ||
534 | if (IS_INETD) | ||
535 | return 0; | ||
536 | free_session(ts); | ||
537 | ts = next; | ||
538 | continue; | ||
539 | } | ||
540 | if (!ts->buf1[ts->rdidx1 + r - 1]) | ||
541 | if (!--r) | ||
542 | continue; | ||
543 | ts->rdidx1 += r; | ||
544 | ts->size1 += r; | ||
545 | if (ts->rdidx1 == BUFSIZE) | ||
546 | ts->rdidx1 = 0; | ||
547 | } | ||
548 | |||
549 | if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) { | ||
550 | /* Read from pty to buffer 2. */ | ||
551 | maxlen = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); | ||
552 | r = safe_read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen); | ||
553 | if (r < 0 && errno == EAGAIN) continue; | ||
554 | if (r <= 0) { | ||
555 | if (IS_INETD) | ||
556 | return 0; | ||
557 | free_session(ts); | ||
558 | ts = next; | ||
559 | continue; | ||
560 | } | ||
561 | ts->rdidx2 += r; | ||
562 | ts->size2 += r; | ||
563 | if (ts->rdidx2 == BUFSIZE) | ||
564 | ts->rdidx2 = 0; | ||
565 | } | ||
566 | |||
567 | if (ts->size1 == 0) { | ||
568 | ts->rdidx1 = 0; | ||
569 | ts->wridx1 = 0; | ||
570 | } | ||
571 | if (ts->size2 == 0) { | ||
572 | ts->rdidx2 = 0; | ||
573 | ts->wridx2 = 0; | ||
574 | } | ||
575 | ts = next; | ||
576 | } | ||
577 | goto again; | ||
578 | } | ||