source: MondoRescue/branches/2.2.5/mindi-busybox/networking/telnetd.c@ 1765

Last change on this file since 1765 was 1765, checked in by Bruno Cornec, 16 years ago

Update to busybox 1.7.2

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