V8/usr/src/cmd/inet/etc/telnetd.c

Compare this file to the similar file:
Show the results in this format:

#ifndef lint
static char sccsid[] = "@(#)telnetd.c	4.26 (Berkeley) 83/08/06";
#endif

/*
 * Stripped-down telnet server.
 */
#include <stdio.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <sgtty.h>
#include <stdio.h>
#include <wait.h>
#include <sys/inet/in.h>
#include <sys/ttyld.h>
#include <sys/stream.h>
#include "config.h"
#include "telnet.h"

#define	BELL	'\07'
#define DEBUG if (debug)

/* option settings (remote and local) */
static char	hisopts[256];
static char	myopts[256];

/* formats for option messages */
static char	doopt[] = { IAC, DO, '%', 'c', 0 };
static char	dont[] = { IAC, DONT, '%', 'c', 0 };
static char	will[] = { IAC, WILL, '%', 'c', 0 };
static char	wont[] = { IAC, WONT, '%', 'c', 0 };

static int	ptno;		/* number of the pt */
static int 	ptfd, netfd;	/* fd's to pt and tcp */
static int	done;		/* true if session is to be ended */
static int	debug;		/* true if debugging is to be output */
static struct sgttyb ptb;	/* result of an IOCGETP on the pt */
static struct tchars ptt;	/* result of an IOCGETC on the pt */

/* predefined */
static 	reapchild();
static	catchint();	

/* imported */
extern	char **environ;
extern	int errno;
extern	char *strrchr(), *strchr();
 

/*
 *	The following macros and routines are used to
 *	manage the I/O buffers.  They're a cross between
 *	stream buffers and standard I/O.
 */
struct buffer {
	char *b_rp;		/* read pointer */
	char *b_wp;		/* write pointer */
	char b_buf[BUFSIZ];	/* the buffer */
};
struct buffer ptibuf, *ptin = &ptibuf;
struct buffer ptobuf, *ptout = &ptobuf;
struct buffer netibuf, *netin = &netibuf;
struct buffer netobuf, *netout = &netobuf;

#define binit(bp) (bp->b_rp = bp->b_wp = bp->b_buf)
#define bytes_filled(bp) (bp->b_wp - bp->b_rp)
#define space_left(bp) (bp->b_buf+sizeof(bp->b_buf) - bp->b_wp)
#define bput(bp, c) (*(bp->b_wp++) = c)
#define bget(bp) (*(bp->b_rp++) & 0377)

/* read whatever the buffer can take */
static int
bread(bp, fd)
	struct buffer *bp;
	int fd;
{
	int cc;

	/* normalize the buffer */
	if (bytes_filled(bp) == 0)
		bp->b_rp = bp->b_wp = bp->b_buf;

	/* fill it */
	cc = read(fd, bp->b_wp, space_left(bp));
	if (cc > 0)
		bp->b_wp += cc;
	return cc;
}

/* read at most n bytes */
static int
breadn(bp, fd, n)
	struct buffer *bp;
	int fd, n;
{
	int cc;

	/* normalize the buffer */
	if (bytes_filled(bp) == 0)
		bp->b_rp = bp->b_wp = bp->b_buf;
	if (n > space_left(bp))
		n = space_left(bp);

	/* fill it */
	cc = read(fd, bp->b_wp, n);
	if (cc > 0)
		bp->b_wp += cc;
	return cc;
}

/* empty the buffer */
static int
bwrite(bp, fd)
	struct buffer *bp;
	int fd;
{
	int cc;

	
	cc = write(fd, bp->b_rp, bytes_filled(bp));

	/* normalize the buffer */
	if (cc == bytes_filled(bp))
		binit(bp);
	return cc;
}
static int
bputs(bp, s)
	struct buffer *bp;
	char *s;
{
	while (*s)
		bput(bp, *s++);
}

/*
 *	Establish a tcp socket and fork off a process for each connection.
 */
main(argc, argv)
	char *argv[];
{
	int pid;
	int f, dev;
	unsigned long faddr;
	int myport, fport;
	struct in_service *sp;

	sp = in_service("telnet", "tcp", 0);
	if (sp == 0) {
		fprintf(stderr, "telnetd: tcp/telnet: unknown service\n");
		exit(1);
	}
	myport = sp->port;
	argc--, argv++;
	if (argc > 0 && !strcmp(*argv, "-d")) {
		debug++;
		argc--, argv++;
	}
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, SIG_IGN);

	do {
		f = tcp_sock();
		if (f < 0) {
			perror("telnetd: socket");;
			sleep(5);
			continue;
		}
		if (tcp_listen(f, myport, 0, 0) < 0) {
			close(f);
			f = -1;
			sleep(30);
			continue;
		}
	} while (f < 0);

	signal(SIGCHLD, reapchild);
	for (;;) {
		int s;

		s = tcp_accept(f, &faddr, &fport, &dev);
		if (s < 0) {
			if (errno == EINTR)
				continue;
			perror("telnetd: accept");
			sleep(1);
			continue;
		}
		switch(fork()) {
		case -1:
			printf("Out of processes\n");
			break;
		case 0:
			doit(s, faddr, fport, dev);
			exit(0);
		}
		close(s);
	}
	/*NOTREACHED*/
}

static
reapchild()
{
	union wait status;

	while (wait3(&status, WNOHANG, 0) > 0)
		;
}

char	*envinit[] = { "TERM=network", 0 };

/*
 *	Get a pt.  Put a login on one side and a telnet receiver on
 *	the other.
 */
static
doit(f, faddr, fport, dev)
	int f;
	unsigned long faddr;
{
	char *host, *in_ntoa(), *ptname();
	int pfd[2];
#	define TERMEND pfd[0]
#	define PROCEND pfd[1]
	extern int tty_ld, mesg_ld;

	/* get a pt pair */
	if ((ptno = ptpipe(pfd)) < 0)
		fatalperror(f, "out of pts", errno);

	host = (char *)in_host(faddr);
	if (host == 0)
		host = in_ntoa(faddr);
	DEBUG fprintf(stderr, "telnet from %s: login on %s\n", host, ptname(ptno));
	DEBUG fprintf(stderr, "telnetd pid = %d\n", getpid());


	/* get the pt's characteristics */
	ioctl(PROCEND, TIOCGETC, &ptt);
	ioctl(PROCEND, TIOCGETP, &ptb);

	/* make it really look like a terminal */
	ioctl(TERMEND, FIOPUSHLD, (struct sgttyb *)&mesg_ld);
	ioctl(PROCEND, FIOPUSHLD, (struct sgttyb *)&tty_ld);

	/* prepare for the death of a child */
	signal(SIGCHLD, catchint);
	switch (fork()) {
	case -1:
		fatalperror(f, "fork", errno);
	case 0:
		/* a process which is the remote login */
		getty(PROCEND);
	default:
		/* the protocol process */
		close(PROCEND);
		netfd = f;
		ptfd = TERMEND;
		telnet();
	}

	/*NOTREACHED*/
}

static
fatal(f, msg)
	int f;
	char *msg;
{
	char buf[BUFSIZ];

	(void) sprintf(buf, "telnetd: %s.\n", msg);
	(void) write(f, buf, strlen(buf));
	exit(1);
}

static
fatalperror(f, msg, errno)
	int f;
	char *msg;
	int errno;
{
	char buf[BUFSIZ];
	extern char *sys_errlist[];

	(void) sprintf(buf, "%s: %s", msg, sys_errlist[errno]);
	fatal(f, buf);
}

static
terminate(s)
	char *s;
{
	DEBUG fprintf(stderr, "session on %s terminated because of %s\n",
			ptname(ptno), s);
	done = 1;
}

/* loop on input from the pt and the network */
static
telnet()
{
	register int c;
	int n;
	fd_set ibits, obits;

	binit(ptin);
	binit(ptout);
	binit(netin);
	binit(netout);

	/* Request to do remote echo. */
	dooption(TELOPT_ECHO);
	myopts[TELOPT_ECHO] = 1;

	while(!done) {
		FD_ZERO(ibits);
		FD_ZERO(obits);

		/* process anything sitting in the input buffers */
		if (bytes_filled(ptin) > 0)
			ptprocess();
		if (bytes_filled(netin) > 0)
			netprocess();

		/* select for read only if there's room to read into */
		if (space_left(netin) > 0)
			FD_SET(netfd, ibits);
		if (space_left(ptin) > 0)
			FD_SET(ptfd, ibits);

		/* select for write only if there's something to write */
		if (bytes_filled(ptout) > 0)
			FD_SET(ptfd, obits);
		if (bytes_filled(netout) > 0)
			FD_SET(netfd, obits);

		n = select(NOFILE, &ibits, &obits, 100000);
		if (n < 0)
			break;
		else if (n == 0)
			continue;

		/* fill input buffers */
		if (FD_ISSET(netfd, ibits))
			netrcv();
		if (FD_ISSET(ptfd, ibits))
			ptrcv();

		/* flush output buffers */
		if (FD_ISSET(netfd, obits) && bytes_filled(netout) > 0)
			netflush();
		if (FD_ISSET(ptfd, obits) && bytes_filled(ptout) > 0)
			ptflush();
	}
	rmut();
	close(netfd);
	close(ptfd);
	exit(0);
}

struct mesg {
	short type;
	short size;
};
static struct mesg m;

/* read from the pt */
static
ptrcv()
{
	/* get the header if we don't already have one */
	if (m.size <= 0)
		if (read(ptfd, &m, sizeof(m)) != sizeof(m)) {
			terminate("pt read");
			return;
		}
	switch(m.type) {
	case M_HANGUP:
		terminate("pt hangup");
		break;
	case M_DATA:
		datamesg();
		break;
	case M_IOCTL:
		ioctlmesg();
		break;
	case M_IOCACK:
		fprintf(stderr, "IOCACK\n");
		flushmesg();
		break;
	case M_IOCNAK:
		fprintf(stderr, "IOCNAK\n");
		flushmesg();
		break;
	default:
		othermesg();
		break;
	}
}

/* flush a message from the pt */
static
flushmesg()
{
	union stmsg s;
	int cc;

	/* flush it */
	while (m.size > 0) {
		fprintf(stderr, "flush\n");
		cc = read(ptfd, &s, sizeof(s));
		if (cc < 0) {
			terminate("pt read");
			return;
		}
		m.size -= cc;
	}
}

/* handle an ioctl message from the pt */
static
ioctlmesg()
{
	struct mesg rm;
	union stmsg s;
	int cc;

	rm.size = m.size;
	if (m.size > 0) {
		cc = read(ptfd, &s, sizeof(s));
		if (cc < 0) {
			terminate("pt read");
			return;
		}
		m.size -= cc;
	}
	rm.type = M_IOCACK;
	switch (s.ioc0.com) {

	case TIOCSETN:
	case TIOCSETP:
		ptb = s.ioc1.sb;
		rm.size = 0;
		break;

	case TIOCGETP:
		s.ioc1.sb.sg_ispeed = B9600;
		s.ioc1.sb.sg_ospeed = B9600;
		ptb = s.ioc1.sb;
		break;

	default:
		rm.type = M_IOCNAK;
		rm.size = 0;
		break;
	}
	if (write(ptfd, &rm, sizeof(rm)) != sizeof(rm)) {
		terminate("pt write");
		return;
	}
	if (rm.size > 0)
		if (write(ptfd, &s, rm.size) != rm.size) {
			terminate("pt write");
			return;
		}

	flushmesg();
}

/* read bytes from the pt and write to the network connection */
static
datamesg()
{
	int cc;

	cc = breadn(ptin, ptfd, m.size);
	if (cc < 0)
		terminate("pt read");
	else
		m.size -= cc;
}

/* handle an unrecognized type of message */
static
othermesg()
{
	struct mesg rm;
	char buf[132];
	int rcc, wcc;

	wcc = write(ptfd, &m, sizeof(m));
	if (wcc != sizeof(m)) {
		terminate("pt write");
		return;
	}
	while (m.size > 0) {
		rcc = read(ptfd, buf, sizeof(buf));
		if (rcc < 0) {
			terminate("pt read");
			return;
		}
		wcc = write(ptfd, buf, rcc);
		if (wcc != rcc) {
			terminate("pt write");
			return;
		}
		m.size -= rcc;
	}
}

/* set pt mode */
static
mode(on, off)
	int on, off;
{
	ptflush();
	ptb.sg_flags |= on;
	ptb.sg_flags &= ~off;
	if(ioctl(ptfd, TIOCSETP, &ptb)<0)
		terminate("setting mode of pt");
}

/* flush the pt's output buffer */
ptflush()
{
	struct mesg rm;

	rm.type = M_DATA;
	rm.size = bytes_filled(ptout);
	if (rm.size > 0) {
		if (write(ptfd, &rm, sizeof(rm)) < sizeof(rm))
			terminate("pt write");
		else if (bwrite(ptout, ptfd) <= 0)
			terminate("pt write");
	}
}

/* process bytes from the pt */
static
ptprocess()
{
	register int c;

	while(bytes_filled(ptin) && space_left(netout) > 1) {
		c = bget(ptin);
		if (c == IAC)
			bput(netout, c);
		bput(netout, c);
	}
}

/* read bytes from the net */
static
netrcv()
{
	if (bread(netin, netfd) < 0)
		terminate("net read");
}

/* flush the netwrk's output buffer */
netflush()
{
	if (bwrite(netout, netfd) < 0)	
		terminate("net write");
}
	
/*
 * State for recv fsm
 */
#define	TS_DATA		0	/* base state */
#define	TS_IAC		1	/* look for double IAC's */
#define	TS_CR		2	/* CR-LF ->'s CR */
#define	TS_BEGINNEG	3	/* throw away begin's... */
#define	TS_ENDNEG	4	/* ...end's (suboption negotiation) */
#define	TS_WILL		5	/* will option negotiation */
#define	TS_WONT		6	/* wont " */
#define	TS_DO		7	/* do " */
#define	TS_DONT		8	/* dont " */

static
netprocess()
{
	static int state = TS_DATA;
	register int c;
	char buf[10];

	while (bytes_filled(netin)) {
		c = bget(netin);
		switch (state) {

		case TS_DATA:
			if (c == IAC) {
				state = TS_IAC;
				break;
			}
			bput(ptout, c);
			if (!myopts[TELOPT_BINARY] && c == '\r')
				state = TS_CR;
			break;

		case TS_CR:
			if (c && c != '\n')
				bput(ptout, c);
			state = TS_DATA;
			break;

		case TS_IAC:
			switch (c) {

			/*
			 * Send the process on the pty side an
			 * interrupt.  Do this with a NULL or
			 * interrupt char; depending on the tty mode.
			 */
			case BREAK:
			case IP:
				interrupt();
				break;

			/*
			 * Are You There?
			 */
			case AYT:
				bput(ptout, BELL);
				break;

			/*
			 * Erase Character and
			 * Erase Line
			 */
			case EC:
			case EL:
				ptflush();
				bput(ptout, (c == EC) ? ptb.sg_erase : ptb.sg_kill);
				break;

			/*
			 * Check for urgent data...
			 */
			case DM:
				break;

			/*
			 * Begin option subnegotiation...
			 */
			case SB:
				state = TS_BEGINNEG;
				continue;

			case WILL:
			case WONT:
			case DO:
			case DONT:
				state = TS_WILL + (c - WILL);
				continue;

			case IAC:
				bput(ptout, c);
				break;
			}
			state = TS_DATA;
			break;

		case TS_BEGINNEG:
			if (c == IAC)
				state = TS_ENDNEG;
			break;

		case TS_ENDNEG:
			state = c == SE ? TS_DATA : TS_BEGINNEG;
			break;

		case TS_WILL:
			if (!hisopts[c])
				willoption(c);
			state = TS_DATA;
			continue;

		case TS_WONT:
			if (hisopts[c])
				wontoption(c);
			state = TS_DATA;
			continue;

		case TS_DO:
			if (!myopts[c])
				dooption(c);
			state = TS_DATA;
			continue;

		case TS_DONT:
			if (myopts[c]) {
				myopts[c] = 0;
				sprintf(buf, wont, c);
				bputs(netout, buf);
			}
			state = TS_DATA;
			continue;

		default:
			printf("telnetd: panic state=%d\n", state);
			exit(1);
		}
	}
}

static
willoption(option)
	int option;
{
	char *fmt;
	char buf[10];

	switch (option) {

	case TELOPT_BINARY:
		mode(RAW, 0);
		goto common;

	case TELOPT_ECHO:
		mode(0, ECHO|CRMOD);
		/*FALL THRU*/

	case TELOPT_SGA:
	common:
		hisopts[option] = 1;
		fmt = doopt;
		break;

	case TELOPT_TM:
		fmt = dont;
		break;

	default:
		fmt = dont;
		break;
	}
	sprintf(buf, fmt, option);
	bputs(netout, buf);
}

static
wontoption(option)
	int option;
{
	char *fmt;
	char buf[10];

	switch (option) {

	case TELOPT_ECHO:
		mode(ECHO|CRMOD, 0);
		goto common;

	case TELOPT_BINARY:
		mode(0, RAW);
		/*FALL THRU*/

	case TELOPT_SGA:
	common:
		hisopts[option] = 0;
		fmt = dont;
		break;

	default:
		fmt = dont;
	}
	sprintf(buf, fmt, option);
	bputs(netout, buf);
}

static
dooption(option)
	int option;
{
	char *fmt;
	char buf[10];

	switch (option) {

	case TELOPT_TM:
		fmt = wont;
		break;

	case TELOPT_ECHO:
		mode(ECHO|CRMOD, 0);
		goto common;

	case TELOPT_BINARY:
		mode(RAW, 0);
		/*FALL THRU*/

	case TELOPT_SGA:
	common:
		fmt = will;
		break;

	default:
		fmt = wont;
		break;
	}
	sprintf(buf, fmt, option);
	bputs(netout, buf);
}

/*
 * Send interrupt to process on other side of pty.
 * If it is in raw mode, just write NULL;
 * otherwise, write intr char.
 */
static
interrupt()
{
	ptflush();
	ioctl(ptfd, TIOCGETC, &ptt);
	bput(ptout, ptb.sg_flags & RAW ? '\0' : ptt.t_intrc);
}

static
catchint()
{
	terminate("child death");
}

#include <utmp.h>

struct	utmp wtmp;
char	wtmpf[]	= "/usr/adm/wtmp";
char	utmp[] = "/etc/utmp";
#define SCPYN(a, b)	strncpy(a, b, sizeof (a))
#define SCMPN(a, b)	strncmp(a, b, sizeof (a))

static
rmut()
{
	register f;
	int found = 0;
	char *line, *dev;

	dev = ptname(ptno);
	line = dev + 5;

	DEBUG fprintf(stderr, "closing connection on %s\n", line);

	f = open(utmp, 2);
	if (f >= 0) {
		while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) {
			if (SCMPN(wtmp.ut_line, line) || wtmp.ut_name[0]==0)
				continue;
			lseek(f, -(long)sizeof (wtmp), 1);
			SCPYN(wtmp.ut_name, "");
			time(&wtmp.ut_time);
			write(f, (char *)&wtmp, sizeof (wtmp));
			found++;
		}
		close(f);
	}
	if (found) {
		f = open(wtmpf, 1);
		if (f >= 0) {
			SCPYN(wtmp.ut_line, line);
			SCPYN(wtmp.ut_name, "");
			time(&wtmp.ut_time);
			lseek(f, (long)0, 2);
			write(f, (char *)&wtmp, sizeof (wtmp));
			close(f);
		}
	}
	chown(dev, 0, 0);
	chmod(dev, 0666);
}

static
getty(f)
{
	int i;
	struct sgttyb b;
	char banner[128];

	ioctl(f, TIOCSPGRP, 0);
	signal(SIGTERM, SIG_DFL) ;
	signal(SIGPIPE, SIG_DFL) ;
	signal(SIGQUIT, SIG_DFL) ;
	signal(SIGINT, SIG_DFL) ;
	signal(SIGALRM, SIG_DFL) ;
	signal(SIGHUP, SIG_DFL) ;
	signal(SIGCHLD, SIG_DFL) ;
	close(0) ;
	close(1) ;
	close(2) ;
	close(3) ;
	dup(f) ;
	dup(f) ;
	dup(f) ;
	dup(f);		/* for /dev/tty */
	for (i=4; i<NOFILE; i++)
		close(i) ;
	ioctl(0, TIOCGETP, &b);
	b.sg_flags |= CRMOD|XTABS|ANYP|ECHO;
	b.sg_erase = '#';
	b.sg_kill = '@';
	ioctl(0, TIOCSETP, &b);
	sprintf(banner, "%s (%s) ", whoami(), strrchr(ptname(ptno), '/')+1);
	write (netfd, banner, strlen(banner));
	execl(LOGIN, "login", 0);
	fatalperror(2, LOGIN, errno);
	exit(1);

}