Magellan Linux

Contents of /trunk/mkinitrd-magellan/busybox/networking/telnetd.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 532 - (show annotations) (download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 14607 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd

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
364 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 }