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