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