|
|
researchv8 Dan Cross
#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);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.