Contents of /trunk/mkinitrd-magellan/busybox/networking/telnetd.c
Parent Directory | 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)
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 | } |