telnetd: fix problem with zombies (by Paul Fox <pgf@brightstareng.com>)
[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 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
28 #if DEBUG
29 #define TELCMDS
30 #define TELOPTS
31 #endif
32 #include <arpa/telnet.h>
33 #include <sys/syslog.h>
34
35
36 /* Structure that describes a session */
37 struct tsession {
38         struct tsession *next;
39         int sockfd_read, sockfd_write, ptyfd;
40         int shell_pid;
41
42         /* two circular buffers */
43         /*char *buf1, *buf2;*/
44 /*#define TS_BUF1 ts->buf1*/
45 /*#define TS_BUF2 TS_BUF2*/
46 #define TS_BUF1 ((unsigned char*)(ts + 1))
47 #define TS_BUF2 (((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 static int maxfd;
59 static struct tsession *sessions;
60 #if ENABLE_LOGIN
61 static const char *loginpath = "/bin/login";
62 #else
63 static const char *loginpath = DEFAULT_SHELL;
64 #endif
65 static const char *issuefile = "/etc/issue.net";
66
67
68 /*
69    Remove all IAC's from buf1 (received IACs are ignored and must be removed
70    so as to not be interpreted by the terminal).  Make an uninterrupted
71    string of characters fit for the terminal.  Do this by packing
72    all characters meant for the terminal sequentially towards the end of buf.
73
74    Return a pointer to the beginning of the characters meant for the terminal.
75    and make *num_totty the number of characters that should be sent to
76    the terminal.
77
78    Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
79    past (bf + len) then that IAC will be left unprocessed and *processed
80    will be less than len.
81
82    FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
83    what is the escape character?  We aren't handling that situation here.
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->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 now 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                 } else {
111                         /*
112                          * TELOPT_NAWS support!
113                          */
114                         if ((ptr+2) >= end) {
115                                 /* only the beginning of the IAC is in the
116                                 buffer we were asked to process, we can't
117                                 process this char. */
118                                 break;
119                         }
120
121                         /*
122                          * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
123                          */
124                         else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
125                                 struct winsize ws;
126
127                                 if ((ptr+8) >= end)
128                                         break;  /* incomplete, can't process */
129                                 ws.ws_col = (ptr[3] << 8) | ptr[4];
130                                 ws.ws_row = (ptr[5] << 8) | ptr[6];
131                                 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
132                                 ptr += 9;
133                         } else {
134                                 /* skip 3-byte IAC non-SB cmd */
135 #if DEBUG
136                                 fprintf(stderr, "Ignoring IAC %s,%s\n",
137                                         TELCMD(ptr[1]), TELOPT(ptr[2]));
138 #endif
139                                 ptr += 3;
140                         }
141                 }
142         }
143
144         num_totty = totty - ptr0;
145         *pnum_totty = num_totty;
146         /* the difference between ptr and totty is number of iacs
147            we removed from the stream. Adjust buf1 accordingly. */
148         if ((ptr - totty) == 0) /* 99.999% of cases */
149                 return ptr0;
150         ts->wridx1 += ptr - totty;
151         ts->size1 -= ptr - totty;
152         /* move chars meant for the terminal towards the end of the buffer */
153         return memmove(ptr - num_totty, ptr0, num_totty);
154 }
155
156
157 static int
158 getpty(char *line, int size)
159 {
160         int p;
161 #if ENABLE_FEATURE_DEVPTS
162         p = open("/dev/ptmx", O_RDWR);
163         if (p > 0) {
164                 const char *name;
165                 grantpt(p);
166                 unlockpt(p);
167                 name = ptsname(p);
168                 if (!name) {
169                         bb_perror_msg("ptsname error (is /dev/pts mounted?)");
170                         return -1;
171                 }
172                 safe_strncpy(line, name, size);
173                 return p;
174         }
175 #else
176         struct stat stb;
177         int i;
178         int j;
179
180         strcpy(line, "/dev/ptyXX");
181
182         for (i = 0; i < 16; i++) {
183                 line[8] = "pqrstuvwxyzabcde"[i];
184                 line[9] = '0';
185                 if (stat(line, &stb) < 0) {
186                         continue;
187                 }
188                 for (j = 0; j < 16; j++) {
189                         line[9] = j < 10 ? j + '0' : j - 10 + 'a';
190                         if (DEBUG)
191                                 fprintf(stderr, "Trying to open device: %s\n", line);
192                         p = open(line, O_RDWR | O_NOCTTY);
193                         if (p >= 0) {
194                                 line[5] = 't';
195                                 return p;
196                         }
197                 }
198         }
199 #endif /* FEATURE_DEVPTS */
200         return -1;
201 }
202
203
204 static struct tsession *
205 make_new_session(
206                 USE_FEATURE_TELNETD_STANDALONE(int sock)
207                 SKIP_FEATURE_TELNETD_STANDALONE(void)
208 ) {
209         const char *login_argv[2];
210         struct termios termbuf;
211         int fd, pid;
212         char tty_name[32];
213         struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
214
215         /*ts->buf1 = (char *)(ts + 1);*/
216         /*ts->buf2 = ts->buf1 + BUFSIZE;*/
217
218         /* Got a new connection, set up a tty. */
219         fd = getpty(tty_name, 32);
220         if (fd < 0) {
221                 bb_error_msg("can't create pty");
222                 return NULL;
223         }
224         if (fd > maxfd)
225                 maxfd = fd;
226         ts->ptyfd = fd;
227         ndelay_on(fd);
228 #if ENABLE_FEATURE_TELNETD_STANDALONE
229         ts->sockfd_read = sock;
230         ndelay_on(sock);
231         if (!sock) { /* We are called with fd 0 - we are in inetd mode */
232                 sock++; /* so use fd 1 for output */
233                 ndelay_on(sock);
234         }
235         ts->sockfd_write = sock;
236         if (sock > maxfd)
237                 maxfd = sock;
238 #else
239         /* ts->sockfd_read = 0; - done by xzalloc */
240         ts->sockfd_write = 1;
241         ndelay_on(0);
242         ndelay_on(1);
243 #endif
244         /* Make the telnet client understand we will echo characters so it
245          * should not do it locally. We don't tell the client to run linemode,
246          * because we want to handle line editing and tab completion and other
247          * stuff that requires char-by-char support. */
248         {
249                 static const char iacs_to_send[] ALIGN1 = {
250                         IAC, DO, TELOPT_ECHO,
251                         IAC, DO, TELOPT_NAWS,
252                         IAC, DO, TELOPT_LFLOW,
253                         IAC, WILL, TELOPT_ECHO,
254                         IAC, WILL, TELOPT_SGA
255                 };
256                 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
257                 ts->rdidx2 = sizeof(iacs_to_send);
258                 ts->size2 = sizeof(iacs_to_send);
259         }
260
261         fflush(NULL); /* flush all streams */
262         pid = vfork(); /* NOMMU-friendly */
263         if (pid < 0) {
264                 free(ts);
265                 close(fd);
266                 /* sock will be closed by caller */
267                 bb_perror_msg("vfork");
268                 return NULL;
269         }
270         if (pid > 0) {
271                 /* Parent */
272                 ts->shell_pid = pid;
273                 return ts;
274         }
275
276         /* Child */
277         /* Careful - we are after vfork! */
278
279         /* make new session and process group */
280         setsid();
281
282         /* Restore default signal handling */
283         signal(SIGCHLD, SIG_DFL);
284         signal(SIGPIPE, SIG_DFL);
285
286         /* open the child's side of the tty. */
287         /* NB: setsid() disconnects from any previous ctty's. Therefore
288          * we must open child's side of the tty AFTER setsid! */
289         fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
290         dup2(fd, 0);
291         dup2(fd, 1);
292         dup2(fd, 2);
293         while (fd > 2) close(fd--);
294         tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
295
296         /* The pseudo-terminal allocated to the client is configured to operate in
297          * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
298         tcgetattr(0, &termbuf);
299         termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
300         termbuf.c_oflag |= ONLCR | XTABS;
301         termbuf.c_iflag |= ICRNL;
302         termbuf.c_iflag &= ~IXOFF;
303         /*termbuf.c_lflag &= ~ICANON;*/
304         tcsetattr(0, TCSANOW, &termbuf);
305
306         /* Uses FILE-based I/O to stdout, but does fflush(stdout),
307          * so should be safe with vfork.
308          * I fear, though, that some users will have ridiculously big
309          * issue files, and they may block writing to fd 1,
310          * (parent is supposed to read it, but parent waits
311          * for vforked child to exec!) */
312         print_login_issue(issuefile, NULL);
313
314         /* Exec shell / login / whatever */
315         login_argv[0] = loginpath;
316         login_argv[1] = NULL;
317         /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
318          * exec external program */
319         BB_EXECVP(loginpath, (char **)login_argv);
320         /* _exit is safer with vfork, and we shouldn't send message
321          * to remote clients anyway */
322         _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
323 }
324
325 /* Must match getopt32 string */
326 enum {
327         OPT_WATCHCHILD = (1 << 2), /* -K */
328         OPT_INETD      = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
329         OPT_PORT       = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
330         OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
331 };
332
333 #if ENABLE_FEATURE_TELNETD_STANDALONE
334
335 static void
336 free_session(struct tsession *ts)
337 {
338         struct tsession *t = sessions;
339
340         if (option_mask32 & OPT_INETD)
341                 exit(0);
342
343         /* Unlink this telnet session from the session list */
344         if (t == ts)
345                 sessions = ts->next;
346         else {
347                 while (t->next != ts)
348                         t = t->next;
349                 t->next = ts->next;
350         }
351
352 #if 0
353         /* It was said that "normal" telnetd just closes ptyfd,
354          * doesn't send SIGKILL. When we close ptyfd,
355          * kernel sends SIGHUP to processes having slave side opened. */
356         kill(ts->shell_pid, SIGKILL);
357         wait4(ts->shell_pid, NULL, 0, NULL);
358 #endif
359         close(ts->ptyfd);
360         close(ts->sockfd_read);
361         /* We do not need to close(ts->sockfd_write), it's the same
362          * as sockfd_read unless we are in inetd mode. But in inetd mode
363          * we do not reach this */
364         free(ts);
365
366         /* Scan all sessions and find new maxfd */
367         maxfd = 0;
368         ts = sessions;
369         while (ts) {
370                 if (maxfd < ts->ptyfd)
371                         maxfd = ts->ptyfd;
372                 if (maxfd < ts->sockfd_read)
373                         maxfd = ts->sockfd_read;
374 #if 0
375                 /* Again, sockfd_write == sockfd_read here */
376                 if (maxfd < ts->sockfd_write)
377                         maxfd = ts->sockfd_write;
378 #endif
379                 ts = ts->next;
380         }
381 }
382
383 #else /* !FEATURE_TELNETD_STANDALONE */
384
385 /* Used in main() only, thus "return 0" actually is exit(0). */
386 #define free_session(ts) return 0
387
388 #endif
389
390 static void handle_sigchld(int sig)
391 {
392         pid_t pid;
393         struct tsession *ts;
394
395         /* Looping: more than one child may have exited */
396         while (1) {
397                 pid = waitpid(-1, NULL, WNOHANG);
398                 if (pid <= 0)
399                         break;
400                 ts = sessions;
401                 while (ts) {
402                         if (ts->shell_pid == pid) {
403                                 ts->shell_pid = -1;
404                                 break;
405                         }
406                         ts = ts->next;
407                 }
408         }
409 }
410
411 int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
412 int telnetd_main(int argc, char **argv)
413 {
414         fd_set rdfdset, wrfdset;
415         unsigned opt;
416         int count;
417         struct tsession *ts;
418 #if ENABLE_FEATURE_TELNETD_STANDALONE
419 #define IS_INETD (opt & OPT_INETD)
420         int master_fd = master_fd; /* be happy, gcc */
421         unsigned portnbr = 23;
422         char *opt_bindaddr = NULL;
423         char *opt_portnbr;
424 #else
425         enum {
426                 IS_INETD = 1,
427                 master_fd = -1,
428                 portnbr = 23,
429         };
430 #endif
431         /* Even if !STANDALONE, we accept (and ignore) -i, thus people
432          * don't need to guess whether it's ok to pass -i to us */
433         opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
434                         &issuefile, &loginpath
435                         USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
436         if (!IS_INETD /*&& !re_execed*/) {
437                 /* inform that we start in standalone mode?
438                  * May be useful when people forget to give -i */
439                 /*bb_error_msg("listening for connections");*/
440                 if (!(opt & OPT_FOREGROUND)) {
441                         /* DAEMON_CHDIR_ROOT was giving inconsistent
442                          * behavior with/without -F, -i */
443                         bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
444                 }
445         }
446         /* Redirect log to syslog early, if needed */
447         if (IS_INETD || !(opt & OPT_FOREGROUND)) {
448                 openlog(applet_name, 0, LOG_USER);
449                 logmode = LOGMODE_SYSLOG;
450         }
451         USE_FEATURE_TELNETD_STANDALONE(
452                 if (opt & OPT_PORT)
453                         portnbr = xatou16(opt_portnbr);
454         );
455
456         /* Used to check access(loginpath, X_OK) here. Pointless.
457          * exec will do this for us for free later. */
458
459 #if ENABLE_FEATURE_TELNETD_STANDALONE
460         if (IS_INETD) {
461                 sessions = make_new_session(0);
462                 if (!sessions) /* pty opening or vfork problem, exit */
463                         return 1; /* make_new_session prints error message */
464         } else {
465                 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
466                 xlisten(master_fd, 1);
467         }
468 #else
469         sessions = make_new_session();
470         if (!sessions) /* pty opening or vfork problem, exit */
471                 return 1; /* make_new_session prints error message */
472 #endif
473
474         /* We don't want to die if just one session is broken */
475         signal(SIGPIPE, SIG_IGN);
476
477         if (opt & OPT_WATCHCHILD)
478                 signal(SIGCHLD, handle_sigchld);
479         else /* prevent dead children from becoming zombies */
480                 signal(SIGCHLD, SIG_IGN);
481
482 /*
483    This is how the buffers are used. The arrows indicate the movement
484    of data.
485    +-------+     wridx1++     +------+     rdidx1++     +----------+
486    |       | <--------------  | buf1 | <--------------  |          |
487    |       |     size1--      +------+     size1++      |          |
488    |  pty  |                                            |  socket  |
489    |       |     rdidx2++     +------+     wridx2++     |          |
490    |       |  --------------> | buf2 |  --------------> |          |
491    +-------+     size2++      +------+     size2--      +----------+
492
493    size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
494    size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
495
496    Each session has got two buffers. Buffers are circular. If sizeN == 0,
497    buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
498    rdidxN == wridxN.
499 */
500  again:
501         FD_ZERO(&rdfdset);
502         FD_ZERO(&wrfdset);
503
504         /* Select on the master socket, all telnet sockets and their
505          * ptys if there is room in their session buffers.
506          * NB: scalability problem: we recalculate entire bitmap
507          * before each select. Can be a problem with 500+ connections. */
508         ts = sessions;
509         while (ts) {
510                 struct tsession *next = ts->next; /* in case we free ts. */
511                 if (ts->shell_pid == -1) {
512                         /* Child died and we detected that */
513                         free_session(ts);
514                 } else {
515                         if (ts->size1 > 0)       /* can write to pty */
516                                 FD_SET(ts->ptyfd, &wrfdset);
517                         if (ts->size1 < BUFSIZE) /* can read from socket */
518                                 FD_SET(ts->sockfd_read, &rdfdset);
519                         if (ts->size2 > 0)       /* can write to socket */
520                                 FD_SET(ts->sockfd_write, &wrfdset);
521                         if (ts->size2 < BUFSIZE) /* can read from pty */
522                                 FD_SET(ts->ptyfd, &rdfdset);
523                 }
524                 ts = next;
525         }
526         if (!IS_INETD) {
527                 FD_SET(master_fd, &rdfdset);
528                 /* This is needed because free_session() does not
529                  * take master_fd into account when it finds new
530                  * maxfd among remaining fd's */
531                 if (master_fd > maxfd)
532                         maxfd = master_fd;
533         }
534
535         count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
536         if (count < 0)
537                 goto again; /* EINTR or ENOMEM */
538
539 #if ENABLE_FEATURE_TELNETD_STANDALONE
540         /* First check for and accept new sessions. */
541         if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
542                 int fd;
543                 struct tsession *new_ts;
544
545                 fd = accept(master_fd, NULL, NULL);
546                 if (fd < 0)
547                         goto again;
548                 /* Create a new session and link it into our active list */
549                 new_ts = make_new_session(fd);
550                 if (new_ts) {
551                         new_ts->next = sessions;
552                         sessions = new_ts;
553                 } else {
554                         close(fd);
555                 }
556         }
557 #endif
558
559         /* Then check for data tunneling. */
560         ts = sessions;
561         while (ts) { /* For all sessions... */
562                 struct tsession *next = ts->next; /* in case we free ts. */
563
564                 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
565                         int num_totty;
566                         unsigned char *ptr;
567                         /* Write to pty from buffer 1. */
568                         ptr = remove_iacs(ts, &num_totty);
569                         count = safe_write(ts->ptyfd, ptr, num_totty);
570                         if (count < 0) {
571                                 if (errno == EAGAIN)
572                                         goto skip1;
573                                 goto kill_session;
574                         }
575                         ts->size1 -= count;
576                         ts->wridx1 += count;
577                         if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
578                                 ts->wridx1 = 0;
579                 }
580  skip1:
581                 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
582                         /* Write to socket from buffer 2. */
583                         count = MIN(BUFSIZE - ts->wridx2, ts->size2);
584                         count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
585                         if (count < 0) {
586                                 if (errno == EAGAIN)
587                                         goto skip2;
588                                 goto kill_session;
589                         }
590                         ts->size2 -= count;
591                         ts->wridx2 += count;
592                         if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
593                                 ts->wridx2 = 0;
594                 }
595  skip2:
596                 /* Should not be needed, but... remove_iacs is actually buggy
597                  * (it cannot process iacs which wrap around buffer's end)!
598                  * Since properly fixing it requires writing bigger code,
599                  * we rely instead on this code making it virtually impossible
600                  * to have wrapped iac (people don't type at 2k/second).
601                  * It also allows for bigger reads in common case. */
602                 if (ts->size1 == 0) {
603                         ts->rdidx1 = 0;
604                         ts->wridx1 = 0;
605                 }
606                 if (ts->size2 == 0) {
607                         ts->rdidx2 = 0;
608                         ts->wridx2 = 0;
609                 }
610
611                 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
612                         /* Read from socket to buffer 1. */
613                         count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
614                         count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
615                         if (count <= 0) {
616                                 if (count < 0 && errno == EAGAIN)
617                                         goto skip3;
618                                 goto kill_session;
619                         }
620                         /* Ignore trailing NUL if it is there */
621                         if (!TS_BUF1[ts->rdidx1 + count - 1]) {
622                                 --count;
623                         }
624                         ts->size1 += count;
625                         ts->rdidx1 += count;
626                         if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
627                                 ts->rdidx1 = 0;
628                 }
629  skip3:
630                 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
631                         /* Read from pty to buffer 2. */
632                         count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
633                         count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
634                         if (count <= 0) {
635                                 if (count < 0 && errno == EAGAIN)
636                                         goto skip4;
637                                 goto kill_session;
638                         }
639                         ts->size2 += count;
640                         ts->rdidx2 += count;
641                         if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
642                                 ts->rdidx2 = 0;
643                 }
644  skip4:
645                 ts = next;
646                 continue;
647  kill_session:
648                 free_session(ts);
649                 ts = next;
650         }
651
652         goto again;
653 }