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