1/* Modified by Broadcom Corp. Portions Copyright (c) Broadcom Corp, 2007. */
2/* utelnetd.c
3 *
4 * Simple telnet server
5 *
6 * Artur Bajor, Centrum Informatyki ROW
7 * <abajor@cirow.pl>
8 *
9 * Bjorn Wesen, Axis Communications AB
10 * <bjornw@axis.com>
11 *
12 * Joerg Schmitz-Linneweber, Aston GmbH
13 * <schmitz-linneweber@aston-technologie.de>
14 *
15 * Vladimir Oleynik
16 * <dzo@simtreas.ru>
17 *
18 * Robert Schwebel, Pengutronix
19 * <r.schwebel@pengutronix.de>
20 *
21 *
22 * This file is distributed under the GNU General Public License (GPL),
23 * please see the file LICENSE for further information.
24 *
25 * ---------------------------------------------------------------------------
26 * (C) 2000, 2001, 2002 by the authors mentioned above
27 * ---------------------------------------------------------------------------
28 *
29 * The telnetd manpage says it all:
30 *
31 *   Telnetd operates by allocating a pseudo-terminal device (see pty(4))  for
32 *   a client, then creating a login process which has the slave side of the
33 *   pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
34 *   master side of the pseudo-terminal, implementing the telnet protocol and
35 *   passing characters between the remote client and the login process.
36 */
37
38/* configuration - we'll separate this out later */
39#define USE_SYSLOG 	1
40#define USE_ISSUE	1
41
42
43#include <sys/time.h>
44#include <sys/socket.h>
45#include <sys/wait.h>
46#include <sys/stat.h>
47#include <sys/ioctl.h>
48#include <string.h>
49#include <unistd.h>
50
51/* we need getpty() */
52#define __USE_GNU 1
53#define __USE_XOPEN 1
54#include <stdlib.h>
55#undef __USE_GNU
56#undef __USE_XOPEN
57
58#include <errno.h>
59#include <netinet/in.h>
60#include <fcntl.h>
61#include <stdio.h>
62#include <signal.h>
63
64#ifdef USE_SYSLOG
65#include <syslog.h>
66#endif
67
68#include <termios.h>
69
70#ifdef DEBUG
71#define TELCMDS
72#define TELOPTS
73#endif
74
75#include <arpa/telnet.h>
76#include <arpa/inet.h>
77#include <ctype.h>
78#include <net/if.h>
79
80#define BUFSIZE 4000
81#ifdef USE_ISSUE
82#define ISSUE_FILE "/etc/issue.net"
83#endif
84
85#define MIN(a,b) ((a) > (b) ? (b) : (a))
86
87static char *loginpath = NULL;
88
89/* shell name and arguments */
90
91static char *argv_init[] = {NULL, NULL};
92
93/* structure that describes a session */
94
95struct tsession {
96	struct tsession *next;
97	int sockfd, ptyfd;
98	int shell_pid;
99	/* two circular buffers */
100	char *buf1, *buf2;
101	int rdidx1, wridx1, size1;
102	int rdidx2, wridx2, size2;
103};
104
105#ifdef DEBUG
106#define DEBUG_OUT(...) fprintf(stderr, __VA_ARGS__)
107#else
108#define DEBUG_OUT(...)
109//static inline void DEBUG_OUT(const char *format, ...) {};
110#endif
111
112/*
113
114   This is how the buffers are used. The arrows indicate the movement
115   of data.
116
117   +-------+     wridx1++     +------+     rdidx1++     +----------+
118   |       | <--------------  | buf1 | <--------------  |          |
119   |       |     size1--      +------+     size1++      |          |
120   |  pty  |                                            |  socket  |
121   |       |     rdidx2++     +------+     wridx2++     |          |
122   |       |  --------------> | buf2 |  --------------> |          |
123   +-------+     size2++      +------+     size2--      +----------+
124
125   Each session has got two buffers.
126
127*/
128
129static int maxfd;
130
131static struct tsession *sessions;
132
133/*
134 * This code was ported from a version which was made for busybox.
135 * So we have to define some helper functions which are originally
136 * available in busybox...
137 */
138
139void show_usage(void)
140{
141	printf("Usage: telnetd [-p port] [-i interface] [-l loginprogram] [-d]\n");
142	printf("\n");
143	printf("   -p port          specify the tcp port to connect to\n");
144	printf("   -i interface     specify the network interface to listen on\n");
145	printf("                    (default: all interfaces)\n");
146	printf("   -l loginprogram  program started by the server\n");
147	printf("   -d               daemonize\n");
148	printf("\n");
149	exit(1);
150}
151
152void perror_msg_and_die(char *text)
153{
154	fprintf(stderr,text);
155	exit(1);
156}
157
158void error_msg_and_die(char *text)
159{
160	perror_msg_and_die(text);
161}
162
163
164/*
165   Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored
166   and must be removed so as to not be interpreted by the terminal).  Make an
167   uninterrupted string of characters fit for the terminal.  Do this by packing
168   all characters meant for the terminal sequentially towards the end of bf.
169
170   Return a pointer to the beginning of the characters meant for the terminal.
171   and make *processed equal to the number of characters that were actually
172   processed and *num_totty the number of characters that should be sent to
173   the terminal.
174
175   Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
176   past (bf + len) then that IAC will be left unprocessed and *processed will be
177   less than len.
178
179   FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
180   what is the escape character?  We aren't handling that situation here.
181
182  */
183static char *
184remove_iacs(unsigned char *bf, int len, int *processed, int *num_totty) {
185    unsigned char *ptr = bf;
186    unsigned char *totty = bf;
187    unsigned char *end = bf + len;
188
189    while (ptr < end) {
190	if (*ptr != IAC) {
191	    *totty++ = *ptr++;
192	}
193	else {
194	    if ((ptr+2) < end) {
195		/* the entire IAC is contained in the buffer
196		   we were asked to process. */
197		DEBUG_OUT("Ignoring IAC 0x%02x, %s, %s\n", *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2)));
198		ptr += 3;
199	    } else {
200		/* only the beginning of the IAC is in the
201		   buffer we were asked to process, we can't
202		   process this char. */
203		break;
204	    }
205	}
206    }
207
208    *processed = ptr - bf;
209    *num_totty = totty - bf;
210    /* move the chars meant for the terminal towards the end of the
211       buffer. */
212    return memmove(ptr - *num_totty, bf, *num_totty);
213}
214
215
216static int getpty(char *line)
217{
218        int p;
219
220        p = getpt();
221        if (p < 0) {
222                DEBUG_OUT("getpty(): couldn't get pty\n");
223                close(p);
224                return -1;
225        }
226        if (grantpt(p)<0 || unlockpt(p)<0) {
227                DEBUG_OUT("getpty(): couldn't grant and unlock pty\n");
228                close(p);
229                return -1;
230        }
231        DEBUG_OUT("getpty(): got pty %s\n",ptsname(p));
232        strcpy(line, (const char*)ptsname(p));
233
234        return(p);
235}
236
237
238static void
239send_iac(struct tsession *ts, unsigned char command, int option)
240{
241	/* We rely on that there is space in the buffer for now.  */
242	char *b = ts->buf2 + ts->rdidx2;
243	*b++ = IAC;
244	*b++ = command;
245	*b++ = option;
246	ts->rdidx2 += 3;
247	ts->size2 += 3;
248}
249
250
251static struct tsession *
252make_new_session(int sockfd)
253{
254	struct termios termbuf;
255	int pty, pid;
256	static char tty_name[32];
257	struct tsession *ts = (struct tsession *)malloc(sizeof(struct tsession));
258	int t1, t2;
259#ifdef USE_ISSUE
260	FILE *fp;
261	int chr;
262#endif
263	ts->buf1 = (char *)malloc(BUFSIZE);
264	ts->buf2 = (char *)malloc(BUFSIZE);
265
266	ts->sockfd = sockfd;
267
268	ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
269	ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
270
271	/* Got a new connection, set up a tty and spawn a shell.  */
272
273	pty = getpty(tty_name);
274
275	if (pty < 0) {
276		fprintf(stderr, "All network ports in use!\n");
277		return 0;
278	}
279
280	if (pty > maxfd)
281		maxfd = pty;
282
283	ts->ptyfd = pty;
284
285	/* Make the telnet client understand we will echo characters so it
286	 * should not do it locally. We don't tell the client to run linemode,
287	 * because we want to handle line editing and tab completion and other
288	 * stuff that requires char-by-char support.
289	 */
290
291	send_iac(ts, DO, TELOPT_ECHO);
292	send_iac(ts, DO, TELOPT_LFLOW);
293	send_iac(ts, WILL, TELOPT_ECHO);
294	send_iac(ts, WILL, TELOPT_SGA);
295
296
297	if ((pid = fork()) < 0) {
298		perror("fork");
299	}
300	if (pid == 0) {
301		/* In child, open the child's side of the tty.  */
302		int i, t;
303
304		for(i = 0; i <= maxfd; i++)
305			close(i);
306
307		/* make new process group */
308		if (setsid() < 0)
309			perror_msg_and_die("setsid");
310		t = open(tty_name, O_RDWR | O_NOCTTY);
311		//t = open(tty_name, O_RDWR);
312		if (t < 0)
313			perror_msg_and_die("Could not open tty");
314
315		t1 = dup(0);
316		t2 = dup(1);
317
318		tcsetpgrp(0, getpid());
319		if (ioctl(t, TIOCSCTTY, NULL)) {
320			perror_msg_and_die("could not set controlling tty");
321		}
322
323		/* The pseudo-terminal allocated to the client is configured to operate in
324		 * cooked mode, and with XTABS CRMOD enabled (see tty(4)).
325		 */
326
327		tcgetattr(t, &termbuf);
328		termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
329		termbuf.c_oflag |= ONLCR|XTABS;
330		termbuf.c_iflag |= ICRNL;
331		termbuf.c_iflag &= ~IXOFF;
332		/* termbuf.c_lflag &= ~ICANON; */
333		tcsetattr(t, TCSANOW, &termbuf);
334
335		DEBUG_OUT("stdin, stdout, stderr: %d %d %d\n", t, t1, t2);
336#ifdef USE_ISSUE
337		/* Display ISSUE_FILE */
338		if ((fp = fopen(ISSUE_FILE, "r")) != NULL) {
339			DEBUG_OUT(" Open & start display %s\n", ISSUE_FILE);
340			while ((chr=fgetc(fp)) != EOF) {
341				if (chr == '\n') fputc('\r', stdout);
342				fputc(chr, stdout);
343			}
344			fclose(fp);
345		}
346#endif
347		/* exec shell, with correct argv and env */
348		execv(loginpath, argv_init);
349
350	        /* NOT REACHED */
351		perror_msg_and_die("execv");
352	}
353
354	ts->shell_pid = pid;
355
356	return ts;
357}
358
359static void
360free_session(struct tsession *ts)
361{
362	struct tsession *t = sessions;
363
364	/* Unlink this telnet session from the session list.  */
365	if(t == ts)
366		sessions = ts->next;
367	else {
368		while(t->next != ts)
369			t = t->next;
370		t->next = ts->next;
371	}
372
373	free(ts->buf1);
374	free(ts->buf2);
375
376	kill(ts->shell_pid, SIGKILL);
377
378	wait4(ts->shell_pid, NULL, 0, NULL);
379
380	close(ts->ptyfd);
381	close(ts->sockfd);
382
383	if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
384		maxfd--;
385	if(ts->ptyfd == maxfd || ts->sockfd == maxfd)
386		maxfd--;
387
388	free(ts);
389}
390
391int main(int argc, char **argv)
392{
393	struct sockaddr_in sa;
394	int master_fd;
395	fd_set rdfdset, wrfdset;
396	int selret;
397	int on = 1;
398	int portnbr = 23;
399	int c, ii;
400	int daemonize = 0;
401	char *interface_name = NULL;
402	struct ifreq interface;
403
404#ifdef USE_SYSLOG
405	char *appname;
406	appname = strrchr (argv [0], '/');
407	if (!appname) appname = argv [0];
408	    else appname++;
409#endif
410
411	/* check if user supplied a port number */
412
413	for (;;) {
414		c = getopt( argc, argv, "i:p:l:hd");
415		if (c == EOF) break;
416		switch (c) {
417			case 'p':
418				portnbr = atoi(optarg);
419				break;
420			case 'i':
421				interface_name = strdup(optarg);
422				break;
423			case 'l':
424				loginpath = strdup(optarg);
425				break;
426			case 'd':
427				daemonize = 1;
428				break;
429			case 'h':
430			default:
431				show_usage();
432				exit(1);
433		}
434	}
435
436	if (!loginpath) {
437	  loginpath = "/bin/login";
438	  if (access(loginpath, X_OK) < 0)
439	    loginpath = "/bin/sh";
440	}
441
442	if (access(loginpath, X_OK) < 0) {
443		/* workaround: error_msg_and_die has doesn't understand
444 		   variable argument lists yet */
445		fprintf(stderr,"\"%s\"",loginpath);
446		perror_msg_and_die(" is no valid executable!\n");
447	}
448
449	printf("telnetd: starting\n");
450	printf("  port: %i; interface: %s; login program: %s\n",
451		portnbr, (interface_name)?interface_name:"any", loginpath);
452
453	argv_init[0] = loginpath;
454	sessions = 0;
455
456	/* Grab a TCP socket.  */
457
458	master_fd = socket(AF_INET, SOCK_STREAM, 0);
459	if (master_fd < 0) {
460		perror("socket");
461		return 1;
462	}
463	(void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
464
465	/* Set it to listen to specified port.  */
466
467	memset((void *)&sa, 0, sizeof(sa));
468	sa.sin_family = AF_INET;
469	sa.sin_port = htons(portnbr);
470
471	/* Set it to listen on the specified interface */
472	if (interface_name) {
473		strncpy(interface.ifr_ifrn.ifrn_name, interface_name, IFNAMSIZ);
474		(void)setsockopt(master_fd, SOL_SOCKET,
475				SO_BINDTODEVICE, &interface, sizeof(interface));
476	}
477
478	if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
479		perror("bind");
480		return 1;
481	}
482
483	if (listen(master_fd, 1) < 0) {
484		perror("listen");
485		return 1;
486	}
487
488	if (daemonize)
489	{
490		DEBUG_OUT("  daemonizing\n");
491		if (daemon(0, 1) < 0) perror_msg_and_die("daemon");
492	}
493
494#ifdef USE_SYSLOG
495	openlog(appname , LOG_NDELAY | LOG_PID, LOG_DAEMON);
496	syslog(LOG_INFO, "%s (port: %i, ifname: %s, login: %s) startup succeeded\n"\
497	    , appname, portnbr, (interface_name)?interface_name:"any", loginpath);
498	closelog();
499#endif
500
501	maxfd = master_fd;
502
503	do {
504		struct tsession *ts;
505
506		FD_ZERO(&rdfdset);
507		FD_ZERO(&wrfdset);
508
509		/* select on the master socket, all telnet sockets and their
510		 * ptys if there is room in their respective session buffers.
511		 */
512
513		FD_SET(master_fd, &rdfdset);
514
515		ts = sessions;
516		while (ts) {
517			/* buf1 is used from socket to pty
518			 * buf2 is used from pty to socket
519			 */
520			if (ts->size1 > 0) {
521				FD_SET(ts->ptyfd, &wrfdset);  /* can write to pty */
522			}
523			if (ts->size1 < BUFSIZE) {
524				FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
525			}
526			if (ts->size2 > 0) {
527				FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
528			}
529			if (ts->size2 < BUFSIZE) {
530				FD_SET(ts->ptyfd, &rdfdset);  /* can read from pty */
531			}
532			ts = ts->next;
533		}
534
535		selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
536
537		if (!selret)
538			break;
539
540		/* First check for and accept new sessions.  */
541		if (FD_ISSET(master_fd, &rdfdset)) {
542			int fd, salen;
543
544			salen = sizeof(sa);
545			if ((fd = accept(master_fd, (struct sockaddr *)&sa,
546					 (socklen_t *)&salen)) < 0) {
547				continue;
548			} else {
549				/* Create a new session and link it into our active list. */
550				struct tsession *new_ts;
551#ifdef USE_SYSLOG
552				openlog(appname , LOG_NDELAY, LOG_DAEMON);
553				syslog(LOG_INFO, "connection from: %s\n", inet_ntoa(sa.sin_addr));
554				closelog();
555#endif
556				new_ts = make_new_session(fd);
557				if (new_ts) {
558					new_ts->next = sessions;
559					sessions = new_ts;
560					if (fd > maxfd)
561						maxfd = fd;
562				} else {
563					close(fd);
564				}
565			}
566		}
567
568		/* Then check for data tunneling.  */
569
570		ts = sessions;
571		while (ts) { /* For all sessions...  */
572			int maxlen, w, r;
573			struct tsession *next = ts->next; /* in case we free ts. */
574
575			if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) {
576			    int processed, num_totty;
577			    char *ptr;
578				/* Write to pty from buffer 1.  */
579
580				maxlen = MIN(BUFSIZE - ts->wridx1,
581					     ts->size1);
582				ptr = remove_iacs((unsigned char *)ts->buf1 + ts->wridx1, maxlen,
583					&processed, &num_totty);
584
585				/* the difference between processed and num_totty
586				   is all the iacs we removed from the stream.
587				   Adjust buf1 accordingly. */
588				ts->wridx1 += processed - num_totty;
589				ts->size1 -= processed - num_totty;
590
591				w = write(ts->ptyfd, ptr, num_totty);
592				if (w < 0) {
593					perror("write");
594					free_session(ts);
595					ts = next;
596					continue;
597				}
598				ts->wridx1 += w;
599				ts->size1 -= w;
600				if (ts->wridx1 == BUFSIZE)
601					ts->wridx1 = 0;
602			}
603
604			if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) {
605				/* Write to socket from buffer 2.  */
606				maxlen = MIN(BUFSIZE - ts->wridx2,
607					     ts->size2);
608				w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
609				if (w < 0) {
610					perror("write");
611					free_session(ts);
612					ts = next;
613					continue;
614				}
615				ts->wridx2 += w;
616				ts->size2 -= w;
617				if (ts->wridx2 == BUFSIZE)
618					ts->wridx2 = 0;
619			}
620
621			if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) {
622				/* Read from socket to buffer 1. */
623				maxlen = MIN(BUFSIZE - ts->rdidx1,
624					     BUFSIZE - ts->size1);
625				r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
626				if (!r || (r < 0 && errno != EINTR)) {
627					free_session(ts);
628					ts = next;
629					continue;
630				}
631				if(!*(ts->buf1 + ts->rdidx1 + r - 1)) {
632					r--;
633					if(!r)
634						continue;
635				}
636				ts->rdidx1 += r;
637				ts->size1 += r;
638				if (ts->rdidx1 == BUFSIZE)
639					ts->rdidx1 = 0;
640			}
641
642			if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) {
643				/* Read from pty to buffer 2.  */
644				maxlen = MIN(BUFSIZE - ts->rdidx2,
645					     BUFSIZE - ts->size2);
646				r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
647				if (!r || (r < 0 && errno != EINTR)) {
648					free_session(ts);
649					ts = next;
650					continue;
651				}
652				for (ii=0; ii < r; ii++)
653				  if (*(ts->buf2 + ts->rdidx2 + ii) == 3)
654				    fprintf(stderr, "found <CTRL>-<C> in data!\n");
655				ts->rdidx2 += r;
656				ts->size2 += r;
657				if (ts->rdidx2 == BUFSIZE)
658					ts->rdidx2 = 0;
659			}
660
661			if (ts->size1 == 0) {
662				ts->rdidx1 = 0;
663				ts->wridx1 = 0;
664			}
665			if (ts->size2 == 0) {
666				ts->rdidx2 = 0;
667				ts->wridx2 = 0;
668			}
669			ts = next;
670		}
671
672	} while (1);
673
674	return 0;
675}
676