V10/ipc/servers/telnetd.c
#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/ttyld.h>
#include <sys/stream.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 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 finish();
int f;
doit(0);
rmut();
/*NOTREACHED*/
}
/*
* Get a pt. Put a login on one side and a telnet receiver on
* the other.
*/
static
doit(f)
int f;
{
int pfd[2];
# define TERMEND pfd[0]
# define PROCEND pfd[1]
extern int tty_ld, mesg_ld;
/* get a pt pair */
if (pipe(pfd) < 0)
fatalperror(f, "out of pipes", errno);
/* make it really look like a terminal */
if (ioctl(TERMEND, FIOPUSHLD, (struct sgttyb *)&mesg_ld)<0)
fatalperror(f, "can't push mesgld", errno);
if (ioctl(PROCEND, FIOPUSHLD, (struct sgttyb *)&tty_ld)<0)
fatalperror(f, "can't push ttyld", errno);
/* prepare for the death of a child */
signal(SIGCHLD, catchint);
signal(SIGHUP, catchint);
signal(SIGPIPE, 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));
rmut();
exit(1);
}
static
finish()
{
if (debug) fprintf(stderr, "pipe closed\n");
rmut();
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 terminated because of %s\n", s);
done = 1;
}
/* loop on input from the pt and the network */
static
telnet()
{
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();
exit(0);
}
/* read from the pt */
static
ptrcv()
{
static struct mesg m;
static int size = 0;
/* get the header if we don't already have one */
if (size <= 0) {
if (read(ptfd, (char *)&m, MSGHLEN) != MSGHLEN) {
terminate("pt read");
return;
}
size = (m.losize & 0377) + ((m.hisize & 0377) << 8);
}
switch(m.type) {
case M_HANGUP:
terminate("pt hangup");
size=0;
break;
case M_DATA:
size=datamesg(size);
break;
case M_IOCTL:
ioctlmesg(size);
size=0;
break;
case M_IOCACK:
fprintf(stderr, "IOCACK\n");
flushmesg(size);
size=0;
break;
case M_IOCNAK:
fprintf(stderr, "IOCNAK\n");
flushmesg(size);
size=0;
break;
default:
othermesg(m, size);
size=0;
break;
}
}
/* flush a message from the pt */
static
flushmesg(size)
{
char buf[64];
int cc;
/* flush it */
while (size > 0) {
fprintf(stderr, "flush\n");
cc = size > sizeof(buf) ? sizeof(buf) : size;
cc = read(ptfd, buf, cc);
if (cc < 0) {
terminate("pt read");
return;
}
size -= cc;
}
}
/* handle an ioctl message from the pt */
static
ioctlmesg(size)
{
struct mesg rm;
union stmsg s;
int cc;
if (size > 0) {
cc = read(ptfd, (char *)&s, size > sizeof(s) ? sizeof(s) : size);
if (cc < 0) {
terminate("pt read");
return;
}
if (size > cc)
flushmesg(size - cc);
}
rm.type = M_IOCACK;
switch (s.ioc0.com) {
case TIOCSETN:
case TIOCSETP:
size = 0;
break;
case TIOCGETP:
s.ioc1.sb.sg_ispeed = B9600;
s.ioc1.sb.sg_ospeed = B9600;
ptb = s.ioc1.sb; /* sic; remember what ttyld said */
/* leave size as it was */
break;
default:
rm.type = M_IOCNAK;
size = 0;
break;
}
rm.magic = MSGMAGIC;
rm.losize = size;
rm.hisize = size>>8;
if (write(ptfd, (char *)&rm, MSGHLEN) != MSGHLEN) {
terminate("pt write");
return;
}
if (size > 0)
if (write(ptfd, (char *)&s, size) != size) {
terminate("pt write");
return;
}
}
/* read bytes from the pt and write to the network connection */
static
datamesg(size)
{
int cc;
cc = breadn(ptin, ptfd, size);
if (cc < 0)
terminate("pt read");
else
size -= cc;
return (size);
}
/* handle an unrecognized type of message */
static
othermesg(m, size)
struct mesg m;
{
char buf[132];
int rcc, wcc;
wcc = write(ptfd, (char *)&m, MSGHLEN);
if (wcc != MSGHLEN) {
terminate("pt write");
return;
}
while (size > 0) {
rcc = read(ptfd, buf, size > sizeof(buf) ? sizeof(buf) : size);
if (rcc <= 0) {
terminate("pt read");
return;
}
wcc = write(ptfd, buf, rcc);
if (wcc != rcc) {
terminate("pt write");
return;
}
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;
int size;
if ((size = bytes_filled(ptout)) > 0) {
rm.type = M_DATA;
rm.magic = MSGMAGIC;
rm.losize = size;
rm.hisize = size>>8;
if (write(ptfd, (char *)&rm, MSGHLEN) < MSGHLEN)
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;
long time();
dev = NULL;
if (dev == NULL)
return;
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];
char *envp[4];
char **p, **passenv();
char *whoami();
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) ;
for (i = 0; i < NSYSFILE; i++)
if (i != f)
close(i);
for (i = 0; i < NSYSFILE; i++)
dup(f);
for (i=NSYSFILE; 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);
p = envp;
p = passenv(p, "CSOURCE");
*p = (char *)0;
sprintf(banner, "%s\n", whoami());
write (netfd, banner, strlen(banner));
execle("/etc/login", "login", 0, envp);
execle("/etc/login", "login", 0, envp);
fatalperror(2, "/etc/login", errno);
exit(1);
}
char**
passenv(ep, var)
char **ep;
{
char *cp;
char *getenv();
cp = getenv(var);
if(cp){
*ep = cp-strlen(var)-1;
ep++;
}
return ep;
}
char *
whoami()
{
static char name[128];
int fd, n;
char *cp;
fd = open("/etc/whoami", 0);
if (fd < 0)
return ("Kremvax");
n = read(fd, name, sizeof(name)-1);
if (n <= 0)
return ("Kremvax");
name[n] = '\0';
for (cp=name; *cp; cp++)
if (*cp == '\n') {
*cp = '\0';
break;
}
return name;
}