7b3c698981e4f26050e1ae8acfbecdd0e656941e
[oweals/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 GPL, 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 #undef DEBUG
26
27 #include <sys/time.h>
28 #include <sys/socket.h>
29 #include <sys/wait.h>
30 #include <sys/ioctl.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <fcntl.h>
38 #include <stdio.h>
39 #include <signal.h>
40 #include <termios.h>
41 #ifdef DEBUG
42 #define TELCMDS
43 #define TELOPTS
44 #endif
45 #include <arpa/telnet.h>
46 #include <ctype.h>
47 #include <sys/syslog.h>
48
49 #include "busybox.h"
50
51 #define BUFSIZE 4000
52
53 #ifdef CONFIG_FEATURE_IPV6
54 #define SOCKET_TYPE     AF_INET6
55 typedef struct sockaddr_in6 sockaddr_type;
56 #else
57 #define SOCKET_TYPE     AF_INET
58 typedef struct sockaddr_in sockaddr_type;
59 #endif
60
61
62 #ifdef CONFIG_LOGIN
63 static const char *loginpath = "/bin/login";
64 #else
65 static const char *loginpath;
66 #endif
67 static const char *issuefile = "/etc/issue.net";
68
69 /* shell name and arguments */
70
71 static const char *argv_init[] = {NULL, NULL};
72
73 /* structure that describes a session */
74
75 struct tsession {
76 #ifdef CONFIG_FEATURE_TELNETD_INETD
77         int sockfd_read, sockfd_write, ptyfd;
78 #else /* CONFIG_FEATURE_TELNETD_INETD */
79         struct tsession *next;
80         int sockfd, ptyfd;
81 #endif /* CONFIG_FEATURE_TELNETD_INETD */
82         int shell_pid;
83         /* two circular buffers */
84         char *buf1, *buf2;
85         int rdidx1, wridx1, size1;
86         int rdidx2, wridx2, size2;
87 };
88
89 /*
90
91    This is how the buffers are used. The arrows indicate the movement
92    of data.
93
94    +-------+     wridx1++     +------+     rdidx1++     +----------+
95    |       | <--------------  | buf1 | <--------------  |          |
96    |       |     size1--      +------+     size1++      |          |
97    |  pty  |                                        |  socket  |
98    |       |     rdidx2++     +------+     wridx2++     |          |
99    |       |  --------------> | buf2 |  --------------> |          |
100    +-------+     size2++      +------+     size2--      +----------+
101
102    Each session has got two buffers.
103
104 */
105
106 static int maxfd;
107
108 static struct tsession *sessions;
109
110
111 /*
112
113    Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored
114    and must be removed so as to not be interpreted by the terminal).  Make an
115    uninterrupted string of characters fit for the terminal.  Do this by packing
116    all characters meant for the terminal sequentially towards the end of bf.
117
118    Return a pointer to the beginning of the characters meant for the terminal.
119    and make *num_totty the number of characters that should be sent to
120    the terminal.
121
122    Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
123    past (bf + len) then that IAC will be left unprocessed and *processed will be
124    less than len.
125
126    FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
127    what is the escape character?  We aren't handling that situation here.
128
129    CR-LF ->'s CR mapping is also done here, for convenience
130
131   */
132 static char *
133 remove_iacs(struct tsession *ts, int *pnum_totty) {
134         unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1;
135         unsigned char *ptr = ptr0;
136         unsigned char *totty = ptr;
137         unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
138         int processed;
139         int num_totty;
140
141         while (ptr < end) {
142                 if (*ptr != IAC) {
143                         int c = *ptr;
144                         *totty++ = *ptr++;
145                         /* We now map \r\n ==> \r for pragmatic reasons.
146                          * Many client implementations send \r\n when
147                          * the user hits the CarriageReturn key.
148                          */
149                         if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end)
150                                 ptr++;
151                 }
152                 else {
153                         /*
154                          * TELOPT_NAWS support!
155                          */
156                         if ((ptr+2) >= end) {
157                                 /* only the beginning of the IAC is in the
158                                 buffer we were asked to process, we can't
159                                 process this char. */
160                                 break;
161                         }
162
163                         /*
164                          * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
165                          */
166                         else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
167                                 struct winsize ws;
168                                 if ((ptr+8) >= end)
169                                         break;  /* incomplete, can't process */
170                                 ws.ws_col = (ptr[3] << 8) | ptr[4];
171                                 ws.ws_row = (ptr[5] << 8) | ptr[6];
172                                 (void) ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
173                                 ptr += 9;
174                         }
175                         else {
176                                 /* skip 3-byte IAC non-SB cmd */
177 #ifdef DEBUG
178                                 fprintf(stderr, "Ignoring IAC %s,%s\n",
179                                         TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
180 #endif
181                                 ptr += 3;
182                         }
183                 }
184         }
185
186         processed = ptr - ptr0;
187         num_totty = totty - ptr0;
188         /* the difference between processed and num_to tty
189            is all the iacs we removed from the stream.
190            Adjust buf1 accordingly. */
191         ts->wridx1 += processed - num_totty;
192         ts->size1 -= processed - num_totty;
193         *pnum_totty = num_totty;
194         /* move the chars meant for the terminal towards the end of the
195         buffer. */
196         return memmove(ptr - num_totty, ptr0, num_totty);
197 }
198
199
200 static int
201 getpty(char *line)
202 {
203         int p;
204 #ifdef CONFIG_FEATURE_DEVPTS
205         p = open("/dev/ptmx", 2);
206         if (p > 0) {
207                 grantpt(p);
208                 unlockpt(p);
209                 strcpy(line, ptsname(p));
210                 return(p);
211         }
212 #else
213         struct stat stb;
214         int i;
215         int j;
216
217         strcpy(line, "/dev/ptyXX");
218
219         for (i = 0; i < 16; i++) {
220                 line[8] = "pqrstuvwxyzabcde"[i];
221                 line[9] = '0';
222                 if (stat(line, &stb) < 0) {
223                         continue;
224                 }
225                 for (j = 0; j < 16; j++) {
226                         line[9] = j < 10 ? j + '0' : j - 10 + 'a';
227 #ifdef DEBUG
228                         fprintf(stderr, "Trying to open device: %s\n", line);
229 #endif
230                         if ((p = open(line, O_RDWR | O_NOCTTY)) >= 0) {
231                                 line[5] = 't';
232                                 return p;
233                         }
234                 }
235         }
236 #endif /* CONFIG_FEATURE_DEVPTS */
237         return -1;
238 }
239
240
241 static void
242 send_iac(struct tsession *ts, unsigned char command, int option)
243 {
244         /* We rely on that there is space in the buffer for now.  */
245         char *b = ts->buf2 + ts->rdidx2;
246         *b++ = IAC;
247         *b++ = command;
248         *b++ = option;
249         ts->rdidx2 += 3;
250         ts->size2 += 3;
251 }
252
253
254 static struct tsession *
255 #ifdef CONFIG_FEATURE_TELNETD_INETD
256 make_new_session(void)
257 #else /* CONFIG_FEATURE_TELNETD_INETD */
258 make_new_session(int sockfd)
259 #endif /* CONFIG_FEATURE_TELNETD_INETD */
260 {
261         struct termios termbuf;
262         int pty, pid;
263         char tty_name[32];
264         struct tsession *ts = malloc(sizeof(struct tsession) + BUFSIZE * 2);
265
266         ts->buf1 = (char *)(&ts[1]);
267         ts->buf2 = ts->buf1 + BUFSIZE;
268
269 #ifdef CONFIG_FEATURE_TELNETD_INETD
270         ts->sockfd_read = 0;
271         ts->sockfd_write = 1;
272 #else /* CONFIG_FEATURE_TELNETD_INETD */
273         ts->sockfd = sockfd;
274 #endif /* CONFIG_FEATURE_TELNETD_INETD */
275
276         ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
277         ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
278
279         /* Got a new connection, set up a tty and spawn a shell.  */
280
281         pty = getpty(tty_name);
282
283         if (pty < 0) {
284                 syslog(LOG_ERR, "All terminals in use!");
285                 return 0;
286         }
287
288         if (pty > maxfd)
289                 maxfd = pty;
290
291         ts->ptyfd = pty;
292
293         /* Make the telnet client understand we will echo characters so it
294          * should not do it locally. We don't tell the client to run linemode,
295          * because we want to handle line editing and tab completion and other
296          * stuff that requires char-by-char support.
297          */
298
299         send_iac(ts, DO, TELOPT_ECHO);
300         send_iac(ts, DO, TELOPT_NAWS);
301         send_iac(ts, DO, TELOPT_LFLOW);
302         send_iac(ts, WILL, TELOPT_ECHO);
303         send_iac(ts, WILL, TELOPT_SGA);
304
305         if ((pid = fork()) < 0) {
306                 syslog(LOG_ERR, "Could not fork");
307         }
308         if (pid == 0) {
309                 /* In child, open the child's side of the tty.  */
310                 int i;
311
312                 for(i = 0; i <= maxfd; i++)
313                         close(i);
314                 /* make new process group */
315                 setsid();
316
317                 if (open(tty_name, O_RDWR /*| O_NOCTTY*/) < 0) {
318                         syslog(LOG_ERR, "Could not open tty");
319                         exit(1);
320                 }
321                 dup(0);
322                 dup(0);
323
324                 tcsetpgrp(0, getpid());
325
326                 /* The pseudo-terminal allocated to the client is configured to operate in
327                  * cooked mode, and with XTABS CRMOD enabled (see tty(4)).
328                  */
329
330                 tcgetattr(0, &termbuf);
331                 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
332                 termbuf.c_oflag |= ONLCR|XTABS;
333                 termbuf.c_iflag |= ICRNL;
334                 termbuf.c_iflag &= ~IXOFF;
335                 /*termbuf.c_lflag &= ~ICANON;*/
336                 tcsetattr(0, TCSANOW, &termbuf);
337
338                 print_login_issue(issuefile, NULL);
339
340                 /* exec shell, with correct argv and env */
341                 execv(loginpath, (char *const *)argv_init);
342
343                 /* NOT REACHED */
344                 syslog(LOG_ERR, "execv error");
345                 exit(1);
346         }
347
348         ts->shell_pid = pid;
349
350         return ts;
351 }
352
353 #ifndef CONFIG_FEATURE_TELNETD_INETD
354 static void
355 free_session(struct tsession *ts)
356 {
357         struct tsession *t = sessions;
358
359         /* Unlink this telnet session from the session list.  */
360         if (t == ts)
361                 sessions = ts->next;
362         else {
363                 while(t->next != ts)
364                         t = t->next;
365                 t->next = ts->next;
366         }
367
368         kill(ts->shell_pid, SIGKILL);
369
370         wait4(ts->shell_pid, NULL, 0, NULL);
371
372         close(ts->ptyfd);
373         close(ts->sockfd);
374
375         if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
376                 maxfd--;
377         if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
378                 maxfd--;
379
380         free(ts);
381 }
382 #endif /* CONFIG_FEATURE_TELNETD_INETD */
383
384 int
385 telnetd_main(int argc, char **argv)
386 {
387 #ifndef CONFIG_FEATURE_TELNETD_INETD
388         sockaddr_type sa;
389         int master_fd;
390 #endif /* CONFIG_FEATURE_TELNETD_INETD */
391         fd_set rdfdset, wrfdset;
392         int selret;
393 #ifndef CONFIG_FEATURE_TELNETD_INETD
394         int on = 1;
395         int portnbr = 23;
396         struct in_addr bind_addr = { .s_addr = 0x0 };
397 #endif /* CONFIG_FEATURE_TELNETD_INETD */
398         int c;
399         static const char options[] =
400 #ifdef CONFIG_FEATURE_TELNETD_INETD
401                 "f:l:";
402 #else /* CONFIG_EATURE_TELNETD_INETD */
403                 "f:l:p:b:";
404 #endif /* CONFIG_FEATURE_TELNETD_INETD */
405         int maxlen, w, r;
406
407 #ifndef CONFIG_LOGIN
408         loginpath = DEFAULT_SHELL;
409 #endif
410
411         for (;;) {
412                 c = getopt( argc, argv, options);
413                 if (c == EOF) break;
414                 switch (c) {
415                         case 'f':
416                                 issuefile = optarg;
417                                 break;
418                         case 'l':
419                                 loginpath = optarg;
420                                 break;
421 #ifndef CONFIG_FEATURE_TELNETD_INETD
422                         case 'p':
423                                 portnbr = atoi(optarg);
424                                 break;
425                         case 'b':
426                                 if (inet_aton(optarg, &bind_addr) == 0)
427                                         bb_show_usage();
428                                 break;
429 #endif /* CONFIG_FEATURE_TELNETD_INETD */
430                         default:
431                                 bb_show_usage();
432                 }
433         }
434
435         if (access(loginpath, X_OK) < 0) {
436                 bb_error_msg_and_die ("'%s' unavailable.", loginpath);
437         }
438
439         argv_init[0] = loginpath;
440
441         openlog(bb_applet_name, 0, LOG_USER);
442
443 #ifdef CONFIG_FEATURE_TELNETD_INETD
444         maxfd = 1;
445         sessions = make_new_session();
446 #else /* CONFIG_EATURE_TELNETD_INETD */
447         sessions = 0;
448
449         /* Grab a TCP socket.  */
450
451         master_fd = bb_xsocket(SOCKET_TYPE, SOCK_STREAM, 0);
452         (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
453
454         /* Set it to listen to specified port.  */
455
456         memset((void *)&sa, 0, sizeof(sa));
457 #ifdef CONFIG_FEATURE_IPV6
458         sa.sin6_family = AF_INET6;
459         sa.sin6_port = htons(portnbr);
460         /* sa.sin6_addr = bind_addr6; */
461 #else
462         sa.sin_family = AF_INET;
463         sa.sin_port = htons(portnbr);
464         sa.sin_addr = bind_addr;
465 #endif
466
467         bb_xbind(master_fd, (struct sockaddr *) &sa, sizeof(sa));
468         bb_xlisten(master_fd, 1);
469         bb_xdaemon(0, 0);
470
471         maxfd = master_fd;
472 #endif /* CONFIG_FEATURE_TELNETD_INETD */
473
474         do {
475                 struct tsession *ts;
476
477                 FD_ZERO(&rdfdset);
478                 FD_ZERO(&wrfdset);
479
480                 /* select on the master socket, all telnet sockets and their
481                  * ptys if there is room in their respective session buffers.
482                  */
483
484 #ifndef CONFIG_FEATURE_TELNETD_INETD
485                 FD_SET(master_fd, &rdfdset);
486 #endif /* CONFIG_FEATURE_TELNETD_INETD */
487
488                 ts = sessions;
489 #ifndef CONFIG_FEATURE_TELNETD_INETD
490                 while (ts) {
491 #endif /* CONFIG_FEATURE_TELNETD_INETD */
492                         /* buf1 is used from socket to pty
493                          * buf2 is used from pty to socket
494                          */
495                         if (ts->size1 > 0) {
496                                 FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
497                         }
498                         if (ts->size1 < BUFSIZE) {
499 #ifdef CONFIG_FEATURE_TELNETD_INETD
500                                 FD_SET(ts->sockfd_read, &rdfdset); /* can read from socket */
501 #else /* CONFIG_FEATURE_TELNETD_INETD */
502                                 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
503 #endif /* CONFIG_FEATURE_TELNETD_INETD */
504                         }
505                         if (ts->size2 > 0) {
506 #ifdef CONFIG_FEATURE_TELNETD_INETD
507                                 FD_SET(ts->sockfd_write, &wrfdset); /* can write to socket */
508 #else /* CONFIG_FEATURE_TELNETD_INETD */
509                                 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
510 #endif /* CONFIG_FEATURE_TELNETD_INETD */
511                         }
512                         if (ts->size2 < BUFSIZE) {
513                                 FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
514                         }
515 #ifndef CONFIG_FEATURE_TELNETD_INETD
516                         ts = ts->next;
517                 }
518 #endif /* CONFIG_FEATURE_TELNETD_INETD */
519
520                 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
521
522                 if (!selret)
523                         break;
524
525 #ifndef CONFIG_FEATURE_TELNETD_INETD
526                 /* First check for and accept new sessions.  */
527                 if (FD_ISSET(master_fd, &rdfdset)) {
528                         int fd, salen;
529
530                         salen = sizeof(sa);
531                         if ((fd = accept(master_fd, (struct sockaddr *)&sa,
532                                                 &salen)) < 0) {
533                                 continue;
534                         } else {
535                                 /* Create a new session and link it into
536                                         our active list.  */
537                                 struct tsession *new_ts = make_new_session(fd);
538                                 if (new_ts) {
539                                         new_ts->next = sessions;
540                                         sessions = new_ts;
541                                         if (fd > maxfd)
542                                                 maxfd = fd;
543                                 } else {
544                                         close(fd);
545                                 }
546                         }
547                 }
548
549                 /* Then check for data tunneling.  */
550
551                 ts = sessions;
552                 while (ts) { /* For all sessions...  */
553 #endif /* CONFIG_FEATURE_TELNETD_INETD */
554 #ifndef CONFIG_FEATURE_TELNETD_INETD
555                         struct tsession *next = ts->next; /* in case we free ts. */
556 #endif /* CONFIG_FEATURE_TELNETD_INETD */
557
558                         if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
559                                 int num_totty;
560                                 char *ptr;
561                                 /* Write to pty from buffer 1.  */
562
563                                 ptr = remove_iacs(ts, &num_totty);
564
565                                 w = write(ts->ptyfd, ptr, num_totty);
566                                 if (w < 0) {
567 #ifdef CONFIG_FEATURE_TELNETD_INETD
568                                         exit(0);
569 #else /* CONFIG_FEATURE_TELNETD_INETD */
570                                         free_session(ts);
571                                         ts = next;
572                                         continue;
573 #endif /* CONFIG_FEATURE_TELNETD_INETD */
574                                 }
575                                 ts->wridx1 += w;
576                                 ts->size1 -= w;
577                                 if (ts->wridx1 == BUFSIZE)
578                                         ts->wridx1 = 0;
579                         }
580
581 #ifdef CONFIG_FEATURE_TELNETD_INETD
582                         if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) {
583 #else /* CONFIG_FEATURE_TELNETD_INETD */
584                         if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
585 #endif /* CONFIG_FEATURE_TELNETD_INETD */
586                                 /* Write to socket from buffer 2.  */
587                                 maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
588 #ifdef CONFIG_FEATURE_TELNETD_INETD
589                                 w = write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen);
590                                 if (w < 0)
591                                         exit(0);
592 #else /* CONFIG_FEATURE_TELNETD_INETD */
593                                 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
594                                 if (w < 0) {
595                                         free_session(ts);
596                                         ts = next;
597                                         continue;
598                                 }
599 #endif /* CONFIG_FEATURE_TELNETD_INETD */
600                                 ts->wridx2 += w;
601                                 ts->size2 -= w;
602                                 if (ts->wridx2 == BUFSIZE)
603                                         ts->wridx2 = 0;
604                         }
605
606 #ifdef CONFIG_FEATURE_TELNETD_INETD
607                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) {
608 #else /* CONFIG_FEATURE_TELNETD_INETD */
609                         if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
610 #endif /* CONFIG_FEATURE_TELNETD_INETD */
611                                 /* Read from socket to buffer 1. */
612                                 maxlen = MIN(BUFSIZE - ts->rdidx1,
613                                                 BUFSIZE - ts->size1);
614 #ifdef CONFIG_FEATURE_TELNETD_INETD
615                                 r = read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen);
616                                 if (!r || (r < 0 && errno != EINTR))
617                                         exit(0);
618 #else /* CONFIG_FEATURE_TELNETD_INETD */
619                                 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
620                                 if (!r || (r < 0 && errno != EINTR)) {
621                                         free_session(ts);
622                                         ts = next;
623                                         continue;
624                                 }
625 #endif /* CONFIG_FEATURE_TELNETD_INETD */
626                                 if (!*(ts->buf1 + ts->rdidx1 + r - 1)) {
627                                         r--;
628                                         if (!r)
629                                                 continue;
630                                 }
631                                 ts->rdidx1 += r;
632                                 ts->size1 += r;
633                                 if (ts->rdidx1 == BUFSIZE)
634                                         ts->rdidx1 = 0;
635                         }
636
637                         if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
638                                 /* Read from pty to buffer 2.  */
639                                 maxlen = MIN(BUFSIZE - ts->rdidx2,
640                                                 BUFSIZE - ts->size2);
641                                 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
642                                 if (!r || (r < 0 && errno != EINTR)) {
643 #ifdef CONFIG_FEATURE_TELNETD_INETD
644                                         exit(0);
645 #else /* CONFIG_FEATURE_TELNETD_INETD */
646                                         free_session(ts);
647                                         ts = next;
648                                         continue;
649 #endif /* CONFIG_FEATURE_TELNETD_INETD */
650                                 }
651                                 ts->rdidx2 += r;
652                                 ts->size2 += r;
653                                 if (ts->rdidx2 == BUFSIZE)
654                                         ts->rdidx2 = 0;
655                         }
656
657                         if (ts->size1 == 0) {
658                                 ts->rdidx1 = 0;
659                                 ts->wridx1 = 0;
660                         }
661                         if (ts->size2 == 0) {
662                                 ts->rdidx2 = 0;
663                                 ts->wridx2 = 0;
664                         }
665 #ifndef CONFIG_FEATURE_TELNETD_INETD
666                         ts = next;
667                 }
668 #endif /* CONFIG_FEATURE_TELNETD_INETD */
669
670         } while (1);
671
672         return 0;
673 }