Magellan Linux

Contents of /tags/mkinitrd-6_1_11/busybox/networking/telnetd.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 928 - (show annotations) (download)
Wed Oct 28 13:31:19 2009 UTC (14 years, 7 months ago) by niro
File MIME type: text/plain
File size: 17621 byte(s)
tagged 'mkinitrd-6_1_11'
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 0
25
26 #include "libbb.h"
27 #include <syslog.h>
28
29 #if DEBUG
30 #define TELCMDS
31 #define TELOPTS
32 #endif
33 #include <arpa/telnet.h>
34
35 /* Structure that describes a session */
36 struct tsession {
37 struct tsession *next;
38 int sockfd_read, sockfd_write, ptyfd;
39 int shell_pid;
40
41 /* two circular buffers */
42 /*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;
48 int rdidx2, wridx2, size2;
49 };
50
51 /* Two buffers are directly after tsession in malloced memory.
52 * Make whole thing fit in 4k */
53 enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
54
55
56 /* Globals */
57 static int maxfd;
58 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 buf1 (received IACs are ignored and must be removed
65 so as to not be interpreted by the terminal). Make an uninterrupted
66 string of characters fit for the terminal. Do this by packing
67 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.
70 and make *num_totty the number of characters that should be sent to
71 the terminal.
72
73 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
75 will be less than len.
76
77 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.
79
80 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 unsigned char *
85 remove_iacs(struct tsession *ts, int *pnum_totty)
86 {
87 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
88 unsigned char *ptr = ptr0;
89 unsigned char *totty = ptr;
90 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
91 int num_totty;
92
93 while (ptr < end) {
94 if (*ptr != IAC) {
95 char c = *ptr;
96
97 *totty++ = c;
98 ptr++;
99 /* We map \r\n ==> \r for pragmatic reasons.
100 * Many client implementations send \r\n when
101 * the user hits the CarriageReturn key.
102 */
103 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
104 ptr++;
105 continue;
106 }
107
108 if ((ptr+1) >= end)
109 break;
110 if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */
111 ptr += 2;
112 continue;
113 }
114 if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */
115 *totty++ = ptr[1];
116 ptr += 2;
117 continue;
118 }
119
120 /*
121 * TELOPT_NAWS support!
122 */
123 if ((ptr+2) >= end) {
124 /* only the beginning of the IAC is in the
125 buffer we were asked to process, we can't
126 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 }
149
150 num_totty = totty - ptr0;
151 *pnum_totty = num_totty;
152 /* the difference between ptr and totty is number of iacs
153 we removed from the stream. Adjust buf1 accordingly. */
154 if ((ptr - totty) == 0) /* 99.999% of cases */
155 return ptr0;
156 ts->wridx1 += ptr - totty;
157 ts->size1 -= ptr - totty;
158 /* move chars meant for the terminal towards the end of the buffer */
159 return memmove(ptr - num_totty, ptr0, num_totty);
160 }
161
162
163 static struct tsession *
164 make_new_session(
165 USE_FEATURE_TELNETD_STANDALONE(int sock)
166 SKIP_FEATURE_TELNETD_STANDALONE(void)
167 ) {
168 const char *login_argv[2];
169 struct termios termbuf;
170 int fd, pid;
171 char tty_name[GETPTY_BUFSIZE];
172 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
173
174 /*ts->buf1 = (char *)(ts + 1);*/
175 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
176
177 /* Got a new connection, set up a tty. */
178 fd = xgetpty(tty_name);
179 if (fd > maxfd)
180 maxfd = fd;
181 ts->ptyfd = fd;
182 ndelay_on(fd);
183 #if ENABLE_FEATURE_TELNETD_STANDALONE
184 ts->sockfd_read = sock;
185 /* SO_KEEPALIVE by popular demand */
186 setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
187 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
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;
200 ndelay_on(0);
201 ndelay_on(1);
202 #endif
203 /* Make the telnet client understand we will echo characters so it
204 * 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
206 * stuff that requires char-by-char support. */
207 {
208 static const char iacs_to_send[] ALIGN1 = {
209 IAC, DO, TELOPT_ECHO,
210 IAC, DO, TELOPT_NAWS,
211 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 fflush(NULL); /* flush all streams */
221 pid = vfork(); /* NOMMU-friendly */
222 if (pid < 0) {
223 free(ts);
224 close(fd);
225 /* sock will be closed by caller */
226 bb_perror_msg("vfork");
227 return NULL;
228 }
229 if (pid > 0) {
230 /* Parent */
231 ts->shell_pid = pid;
232 return ts;
233 }
234
235 /* Child */
236 /* Careful - we are after vfork! */
237
238 /* make new session and process group */
239 setsid();
240
241 /* Restore default signal handling */
242 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
243
244 /* open the child's side of the tty. */
245 /* NB: setsid() disconnects from any previous ctty's. Therefore
246 * we must open child's side of the tty AFTER setsid! */
247 close(0);
248 xopen(tty_name, O_RDWR); /* becomes our ctty */
249 xdup2(0, 1);
250 xdup2(0, 2);
251 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
254 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
255 tcgetattr(0, &termbuf);
256 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
257 termbuf.c_oflag |= ONLCR | XTABS;
258 termbuf.c_iflag |= ICRNL;
259 termbuf.c_iflag &= ~IXOFF;
260 /*termbuf.c_lflag &= ~ICANON;*/
261 tcsetattr_stdin_TCSANOW(&termbuf);
262
263 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
264 * so should be safe with vfork.
265 * I fear, though, that some users will have ridiculously big
266 * issue files, and they may block writing to fd 1,
267 * (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
291
292 static void
293 free_session(struct tsession *ts)
294 {
295 struct tsession *t = sessions;
296
297 if (option_mask32 & OPT_INETD)
298 exit(EXIT_SUCCESS);
299
300 /* Unlink this telnet session from the session list */
301 if (t == ts)
302 sessions = ts->next;
303 else {
304 while (t->next != ts)
305 t = t->next;
306 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);
314 wait4(ts->shell_pid, NULL, 0, NULL);
315 #endif
316 close(ts->ptyfd);
317 close(ts->sockfd_read);
318 /* We do not need to close(ts->sockfd_write), it's the same
319 * as sockfd_read unless we are in inetd mode. But in inetd mode
320 * we do not reach this */
321 free(ts);
322
323 /* Scan all sessions and find new maxfd */
324 maxfd = 0;
325 ts = sessions;
326 while (ts) {
327 if (maxfd < ts->ptyfd)
328 maxfd = ts->ptyfd;
329 if (maxfd < ts->sockfd_read)
330 maxfd = ts->sockfd_read;
331 #if 0
332 /* Again, sockfd_write == sockfd_read here */
333 if (maxfd < ts->sockfd_write)
334 maxfd = ts->sockfd_write;
335 #endif
336 ts = ts->next;
337 }
338 }
339
340 #else /* !FEATURE_TELNETD_STANDALONE */
341
342 /* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
343 #define free_session(ts) return 0
344
345 #endif
346
347 static void handle_sigchld(int sig UNUSED_PARAM)
348 {
349 pid_t pid;
350 struct tsession *ts;
351
352 /* Looping: more than one child may have exited */
353 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;
372 unsigned opt;
373 int count;
374 struct tsession *ts;
375 #if ENABLE_FEATURE_TELNETD_STANDALONE
376 #define IS_INETD (opt & OPT_INETD)
377 int master_fd = master_fd; /* be happy, gcc */
378 unsigned portnbr = 23;
379 char *opt_bindaddr = NULL;
380 char *opt_portnbr;
381 #else
382 enum {
383 IS_INETD = 1,
384 master_fd = -1,
385 portnbr = 23,
386 };
387 #endif
388 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
389 * don't need to guess whether it's ok to pass -i to us */
390 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
391 &issuefile, &loginpath
392 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 */
404 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
405 openlog(applet_name, 0, LOG_USER);
406 logmode = LOGMODE_SYSLOG;
407 }
408 USE_FEATURE_TELNETD_STANDALONE(
409 if (opt & OPT_PORT)
410 portnbr = xatou16(opt_portnbr);
411 );
412
413 /* Used to check access(loginpath, X_OK) here. Pointless.
414 * exec will do this for us for free later. */
415
416 #if ENABLE_FEATURE_TELNETD_STANDALONE
417 if (IS_INETD) {
418 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 {
422 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
423 xlisten(master_fd, 1);
424 }
425 #else
426 sessions = make_new_session();
427 if (!sessions) /* pty opening or vfork problem, exit */
428 return 1; /* make_new_session prints error message */
429 #endif
430
431 /* We don't want to die if just one session is broken */
432 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:
458 FD_ZERO(&rdfdset);
459 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) {
484 FD_SET(master_fd, &rdfdset);
485 /* This is needed because free_session() does not
486 * take master_fd into account when it finds new
487 * maxfd among remaining fd's */
488 if (master_fd > maxfd)
489 maxfd = master_fd;
490 }
491
492 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
493 if (count < 0)
494 goto again; /* EINTR or ENOMEM */
495
496 #if ENABLE_FEATURE_TELNETD_STANDALONE
497 /* First check for and accept new sessions. */
498 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
499 int fd;
500 struct tsession *new_ts;
501
502 fd = accept(master_fd, NULL, NULL);
503 if (fd < 0)
504 goto again;
505 /* Create a new session and link it into our active list */
506 new_ts = make_new_session(fd);
507 if (new_ts) {
508 new_ts->next = sessions;
509 sessions = new_ts;
510 } else {
511 close(fd);
512 }
513 }
514 #endif
515
516 /* Then check for data tunneling. */
517 ts = sessions;
518 while (ts) { /* For all sessions... */
519 struct tsession *next = ts->next; /* in case we free ts. */
520
521 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
522 int num_totty;
523 unsigned char *ptr;
524 /* Write to pty from buffer 1. */
525 ptr = remove_iacs(ts, &num_totty);
526 count = safe_write(ts->ptyfd, ptr, num_totty);
527 if (count < 0) {
528 if (errno == EAGAIN)
529 goto skip1;
530 goto kill_session;
531 }
532 ts->size1 -= count;
533 ts->wridx1 += count;
534 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
535 ts->wridx1 = 0;
536 }
537 skip1:
538 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
539 /* Write to socket from buffer 2. */
540 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
541 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
542 if (count < 0) {
543 if (errno == EAGAIN)
544 goto skip2;
545 goto kill_session;
546 }
547 ts->size2 -= count;
548 ts->wridx2 += count;
549 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
550 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)) {
569 /* Read from socket to buffer 1. */
570 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
571 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
572 if (count <= 0) {
573 if (count < 0 && errno == EAGAIN)
574 goto skip3;
575 goto kill_session;
576 }
577 /* Ignore trailing NUL if it is there */
578 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
579 --count;
580 }
581 ts->size1 += count;
582 ts->rdidx1 += count;
583 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
584 ts->rdidx1 = 0;
585 }
586 skip3:
587 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
588 /* Read from pty to buffer 2. */
589 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
590 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
591 if (count <= 0) {
592 if (count < 0 && errno == EAGAIN)
593 goto skip4;
594 goto kill_session;
595 }
596 ts->size2 += count;
597 ts->rdidx2 += count;
598 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
599 ts->rdidx2 = 0;
600 }
601 skip4:
602 ts = next;
603 continue;
604 kill_session:
605 free_session(ts);
606 ts = next;
607 }
608
609 goto again;
610 }