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