Style, error_msg
[oweals/busybox.git] / networking / telnetd.c
1 /* $Id: telnetd.c,v 1.2 2002/11/10 22:26:19 bug1 Exp $
2  *
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
51 static const char *loginpath = "/bin/sh";
52
53 /* shell name and arguments */
54
55 static const char *argv_init[] = {NULL, NULL};
56
57 /* structure that describes a session */
58
59 struct 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
86 static int maxfd;
87
88 static 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   */
110 static char *
111 remove_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
155 static int
156 getpty(char *line)
157 {
158         int p;
159 #ifdef CONFIG_FEATURE_DEVPTS
160         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         }
188 #endif /* CONFIG_FEATURE_DEVPTS */
189         return -1;
190 }
191
192
193 static void
194 send_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
206 static struct tsession *
207 make_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
294 static void
295 free_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
323 int
324 telnetd_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 }