blob: d208319a385fecec26c01c2ed091760f3936a498 [file] [log] [blame]
Glenn L McGrathf234e7c2002-11-10 22:26:19 +00001/* $Id: telnetd.c,v 1.2 2002/11/10 22:26:19 bug1 Exp $
Eric Andersen08a72202002-09-30 20:52:10 +00002 *
3 * Simple telnet server
4 * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
5 *
6 * This file is distributed under the Gnu Public License (GPL),
7 * please see the file LICENSE for further information.
8 *
9 * ---------------------------------------------------------------------------
10 * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
11 ****************************************************************************
12 *
13 * The telnetd manpage says it all:
14 *
15 * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
16 * a client, then creating a login process which has the slave side of the
17 * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
18 * master side of the pseudo-terminal, implementing the telnet protocol and
19 * passing characters between the remote client and the login process.
20 *
21 * Vladimir Oleynik <dzo@simtreas.ru> 2001
22 * Set process group corrections, initial busybox port
23 */
24
25/*#define DEBUG 1 */
26
27#include <sys/time.h>
28#include <sys/socket.h>
29#include <sys/wait.h>
30#include <string.h>
31#include <stdlib.h>
32#include <unistd.h>
33#include <errno.h>
34#include <netinet/in.h>
35#include <fcntl.h>
36#include <stdio.h>
37#include <signal.h>
38#include <termios.h>
39#ifdef DEBUG
40#define TELCMDS
41#define TELOPTS
42#endif
43#include <arpa/telnet.h>
44#include <ctype.h>
45#include <sys/syslog.h>
46
47#include "busybox.h"
48
49#define BUFSIZE 4000
50
51static const char *loginpath = "/bin/sh";
52
53/* shell name and arguments */
54
55static const char *argv_init[] = {NULL, NULL};
56
57/* structure that describes a session */
58
59struct tsession {
60 struct tsession *next;
61 int sockfd, 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
71 This is how the buffers are used. The arrows indicate the movement
72 of data.
73
74 +-------+ wridx1++ +------+ rdidx1++ +----------+
75 | | <-------------- | buf1 | <-------------- | |
76 | | size1-- +------+ size1++ | |
77 | pty | | socket |
78 | | rdidx2++ +------+ wridx2++ | |
79 | | --------------> | buf2 | --------------> | |
80 +-------+ size2++ +------+ size2-- +----------+
81
82 Each session has got two buffers.
83
84*/
85
86static int maxfd;
87
88static struct tsession *sessions;
89
90
91/*
92
93 Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored
94 and must be removed so as to not be interpreted by the terminal). Make an
95 uninterrupted string of characters fit for the terminal. Do this by packing
96 all characters meant for the terminal sequentially towards the end of bf.
97
98 Return a pointer to the beginning of the characters meant for the terminal.
99 and make *num_totty the number of characters that should be sent to
100 the terminal.
101
102 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
103 past (bf + len) then that IAC will be left unprocessed and *processed will be
104 less than len.
105
106 FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
107 what is the escape character? We aren't handling that situation here.
108
109 */
110static char *
111remove_iacs(struct tsession *ts, int *pnum_totty) {
112 unsigned char *ptr0 = ts->buf1 + ts->wridx1;
113 unsigned char *ptr = ptr0;
114 unsigned char *totty = ptr;
115 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
116 int processed;
117 int num_totty;
118
119 while (ptr < end) {
120 if (*ptr != IAC) {
121 *totty++ = *ptr++;
122 }
123 else {
124 if ((ptr+2) < end) {
125 /* the entire IAC is contained in the buffer
126 we were asked to process. */
127#ifdef DEBUG
128 fprintf(stderr, "Ignoring IAC %s,%s\n",
129 *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
130#endif
131 ptr += 3;
132 } else {
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
141 processed = ptr - ptr0;
142 num_totty = totty - ptr0;
143 /* the difference between processed and num_to tty
144 is all the iacs we removed from the stream.
145 Adjust buf1 accordingly. */
146 ts->wridx1 += processed - num_totty;
147 ts->size1 -= processed - num_totty;
148 *pnum_totty = num_totty;
149 /* move the chars meant for the terminal towards the end of the
150 buffer. */
151 return memmove(ptr - num_totty, ptr0, num_totty);
152}
153
154
155static int
156getpty(char *line)
157{
158 int p;
Glenn L McGrathf234e7c2002-11-10 22:26:19 +0000159#ifdef CONFIG_FEATURE_DEVPTS
Eric Andersen08a72202002-09-30 20:52:10 +0000160 p = open("/dev/ptmx", 2);
161 if (p > 0) {
162 grantpt(p);
163 unlockpt(p);
164 strcpy(line, ptsname(p));
165 return(p);
166 }
167#else
168 struct stat stb;
169 int i;
170 int j;
171
172 strcpy(line, "/dev/ptyXX");
173
174 for (i = 0; i < 16; i++) {
175 line[8] = "pqrstuvwxyzabcde"[i];
176 line[9] = '0';
177 if (stat(line, &stb) < 0) {
178 continue;
179 }
180 for (j = 0; j < 16; j++) {
181 line[9] = j < 10 ? j + '0' : j - 10 + 'a';
182 if ((p = open(line, O_RDWR | O_NOCTTY)) >= 0) {
183 line[5] = 't';
184 return p;
185 }
186 }
187 }
Glenn L McGrathf234e7c2002-11-10 22:26:19 +0000188#endif /* CONFIG_FEATURE_DEVPTS */
Eric Andersen08a72202002-09-30 20:52:10 +0000189 return -1;
190}
191
192
193static void
194send_iac(struct tsession *ts, unsigned char command, int option)
195{
196 /* We rely on that there is space in the buffer for now. */
197 char *b = ts->buf2 + ts->rdidx2;
198 *b++ = IAC;
199 *b++ = command;
200 *b++ = option;
201 ts->rdidx2 += 3;
202 ts->size2 += 3;
203}
204
205
206static struct tsession *
207make_new_session(int sockfd)
208{
209 struct termios termbuf;
210 int pty, pid;
211 char tty_name[32];
212 struct tsession *ts = malloc(sizeof(struct tsession) + BUFSIZE * 2);
213
214 ts->buf1 = (char *)(&ts[1]);
215 ts->buf2 = ts->buf1 + BUFSIZE;
216
217 ts->sockfd = sockfd;
218
219 ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
220 ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
221
222 /* Got a new connection, set up a tty and spawn a shell. */
223
224 pty = getpty(tty_name);
225
226 if (pty < 0) {
227 syslog_msg(LOG_USER, LOG_ERR, "All network ports in use!");
228 return 0;
229 }
230
231 if (pty > maxfd)
232 maxfd = pty;
233
234 ts->ptyfd = pty;
235
236 /* Make the telnet client understand we will echo characters so it
237 * should not do it locally. We don't tell the client to run linemode,
238 * because we want to handle line editing and tab completion and other
239 * stuff that requires char-by-char support.
240 */
241
242 send_iac(ts, DO, TELOPT_ECHO);
243 send_iac(ts, DO, TELOPT_LFLOW);
244 send_iac(ts, WILL, TELOPT_ECHO);
245 send_iac(ts, WILL, TELOPT_SGA);
246
247
248 if ((pid = fork()) < 0) {
249 syslog_msg(LOG_USER, LOG_ERR, "Can`t forking");
250 }
251 if (pid == 0) {
252 /* In child, open the child's side of the tty. */
253 int i;
254
255 for(i = 0; i <= maxfd; i++)
256 close(i);
257 /* make new process group */
258 setsid();
259
260 if (open(tty_name, O_RDWR /*| O_NOCTTY*/) < 0) {
261 syslog_msg(LOG_USER, LOG_ERR, "Could not open tty");
262 exit(1);
263 }
264 dup(0);
265 dup(0);
266
267 tcsetpgrp(0, getpid());
268
269 /* The pseudo-terminal allocated to the client is configured to operate in
270 * cooked mode, and with XTABS CRMOD enabled (see tty(4)).
271 */
272
273 tcgetattr(0, &termbuf);
274 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
275 termbuf.c_oflag |= ONLCR|XTABS;
276 termbuf.c_iflag |= ICRNL;
277 termbuf.c_iflag &= ~IXOFF;
278 /*termbuf.c_lflag &= ~ICANON;*/
279 tcsetattr(0, TCSANOW, &termbuf);
280
281 /* exec shell, with correct argv and env */
282 execv(loginpath, (char *const *)argv_init);
283
284 /* NOT REACHED */
285 syslog_msg(LOG_USER, LOG_ERR, "execv error");
286 exit(1);
287 }
288
289 ts->shell_pid = pid;
290
291 return ts;
292}
293
294static void
295free_session(struct tsession *ts)
296{
297 struct tsession *t = sessions;
298
299 /* Unlink this telnet session from the session list. */
300 if(t == ts)
301 sessions = ts->next;
302 else {
303 while(t->next != ts)
304 t = t->next;
305 t->next = ts->next;
306 }
307
308 kill(ts->shell_pid, SIGKILL);
309
310 wait4(ts->shell_pid, NULL, 0, NULL);
311
312 close(ts->ptyfd);
313 close(ts->sockfd);
314
315 if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
316 maxfd--;
317 if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
318 maxfd--;
319
320 free(ts);
321}
322
323int
324telnetd_main(int argc, char **argv)
325{
326 struct sockaddr_in sa;
327 int master_fd;
328 fd_set rdfdset, wrfdset;
329 int selret;
330 int on = 1;
331 int portnbr = 23;
332 int c;
333
334 /* check if user supplied a port number */
335
336 for (;;) {
337 c = getopt( argc, argv, "p:l:");
338 if (c == EOF) break;
339 switch (c) {
340 case 'p':
341 portnbr = atoi(optarg);
342 break;
343 case 'l':
344 loginpath = strdup (optarg);
345 break;
346 default:
347 show_usage();
348 }
349 }
350
351 if (access(loginpath, X_OK) < 0) {
352 error_msg_and_die ("'%s' unavailable.", loginpath);
353 }
354
355 argv_init[0] = loginpath;
356 sessions = 0;
357
358 /* Grab a TCP socket. */
359
360 master_fd = socket(AF_INET, SOCK_STREAM, 0);
361 if (master_fd < 0) {
362 perror_msg_and_die("socket");
363 }
364 (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
365
366 /* Set it to listen to specified port. */
367
368 memset((void *)&sa, 0, sizeof(sa));
369 sa.sin_family = AF_INET;
370 sa.sin_port = htons(portnbr);
371
372 if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
373 perror_msg_and_die("bind");
374 }
375
376 if (listen(master_fd, 1) < 0) {
377 perror_msg_and_die("listen");
378 }
379
380 if (daemon(0, 0) < 0)
381 perror_msg_and_die("daemon");
382
383
384 maxfd = master_fd;
385
386 do {
387 struct tsession *ts;
388
389 FD_ZERO(&rdfdset);
390 FD_ZERO(&wrfdset);
391
392 /* select on the master socket, all telnet sockets and their
393 * ptys if there is room in their respective session buffers.
394 */
395
396 FD_SET(master_fd, &rdfdset);
397
398 ts = sessions;
399 while (ts) {
400 /* buf1 is used from socket to pty
401 * buf2 is used from pty to socket
402 */
403 if (ts->size1 > 0) {
404 FD_SET(ts->ptyfd, &wrfdset); /* can write to pty */
405 }
406 if (ts->size1 < BUFSIZE) {
407 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
408 }
409 if (ts->size2 > 0) {
410 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
411 }
412 if (ts->size2 < BUFSIZE) {
413 FD_SET(ts->ptyfd, &rdfdset); /* can read from pty */
414 }
415 ts = ts->next;
416 }
417
418 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
419
420 if (!selret)
421 break;
422
423 /* First check for and accept new sessions. */
424 if (FD_ISSET(master_fd, &rdfdset)) {
425 int fd, salen;
426
427 salen = sizeof(sa);
428 if ((fd = accept(master_fd, (struct sockaddr *)&sa,
429 &salen)) < 0) {
430 continue;
431 } else {
432 /* Create a new session and link it into
433 our active list. */
434 struct tsession *new_ts = make_new_session(fd);
435 if (new_ts) {
436 new_ts->next = sessions;
437 sessions = new_ts;
438 if (fd > maxfd)
439 maxfd = fd;
440 } else {
441 close(fd);
442 }
443 }
444 }
445
446 /* Then check for data tunneling. */
447
448 ts = sessions;
449 while (ts) { /* For all sessions... */
450 int maxlen, w, r;
451 struct tsession *next = ts->next; /* in case we free ts. */
452
453 if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
454 int num_totty;
455 char *ptr;
456 /* Write to pty from buffer 1. */
457
458 ptr = remove_iacs(ts, &num_totty);
459
460 w = write(ts->ptyfd, ptr, num_totty);
461 if (w < 0) {
462 free_session(ts);
463 ts = next;
464 continue;
465 }
466 ts->wridx1 += w;
467 ts->size1 -= w;
468 if (ts->wridx1 == BUFSIZE)
469 ts->wridx1 = 0;
470 }
471
472 if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
473 /* Write to socket from buffer 2. */
474 maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
475 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
476 if (w < 0) {
477 free_session(ts);
478 ts = next;
479 continue;
480 }
481 ts->wridx2 += w;
482 ts->size2 -= w;
483 if (ts->wridx2 == BUFSIZE)
484 ts->wridx2 = 0;
485 }
486
487 if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
488 /* Read from socket to buffer 1. */
489 maxlen = MIN(BUFSIZE - ts->rdidx1,
490 BUFSIZE - ts->size1);
491 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
492 if (!r || (r < 0 && errno != EINTR)) {
493 free_session(ts);
494 ts = next;
495 continue;
496 }
497 if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
498 r--;
499 if(!r)
500 continue;
501 }
502 ts->rdidx1 += r;
503 ts->size1 += r;
504 if (ts->rdidx1 == BUFSIZE)
505 ts->rdidx1 = 0;
506 }
507
508 if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
509 /* Read from pty to buffer 2. */
510 maxlen = MIN(BUFSIZE - ts->rdidx2,
511 BUFSIZE - ts->size2);
512 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
513 if (!r || (r < 0 && errno != EINTR)) {
514 free_session(ts);
515 ts = next;
516 continue;
517 }
518 ts->rdidx2 += r;
519 ts->size2 += r;
520 if (ts->rdidx2 == BUFSIZE)
521 ts->rdidx2 = 0;
522 }
523
524 if (ts->size1 == 0) {
525 ts->rdidx1 = 0;
526 ts->wridx1 = 0;
527 }
528 if (ts->size2 == 0) {
529 ts->rdidx2 = 0;
530 ts->wridx2 = 0;
531 }
532 ts = next;
533 }
534
535 } while (1);
536
537 return 0;
538}