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