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