Magellan Linux

Diff of /trunk/mkinitrd-magellan/busybox/networking/telnetd.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.532  
changed lines
  Added in v.816