/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project. All
................................................ contributions are welcome.
....Copyright(C).1995.Melvin.Smith.............. Enjoy.
------------------------------------------------------------------------------
Melvin Smith (aka Fusion) msmith@hom.net
MUD++ development mailing list mudpp@van.ml.org
------------------------------------------------------------------------------
shell.cc
*/
// Pseudo-terminals - Whheee!
// This code is not very obvious.
// It is concerned with handled pseudo-terminals (pty for short)
// To run terminal emulation across a standard network descriptor
// we open a master pty, fork a slave which opens the slave side of the pty,
// maps the pty onto stdin/sdtdout/stderr and then execs the shell process.
// Code for BSD is completely different from SYSV so please report problems.
// Recommended reading:
// -Stevens' UNIX Network Programming
// -Berkeley Software Distribution source code (telnetd and rlogind)
#if 1
#include <fcntl.h>
#include <termio.h>
#include "config.h"
#include "socket.h"
#if defined(SYSV) || defined(_SYSV)
#include <stropts.h>
#ifndef PTY_MASTER_DEV_SYSV
#define PTY_MASTER_DEV_SYSV "/dev/ptmx"
#endif
static struct termio tty;
extern "C" {
int grantpt( int );
int unlockpt( int );
char *ptsname( int );
}
#else /* BSD */
#include <sys/stat.h>
#ifndef WIN32
#include <sys/ioctl.h>
#endif
#endif
#if defined(WIN32)
#include <io.h>
#endif //WIN32
int route_io( int, int );
int open_pty_master( char * );
int open_pty_slave( int, const char * );
// Mini-telnetd
// This is not meant to be an attempt to implement telnetd
// Berkeley telnetd is about 7000 lines of code. A lot of it is
// conditional switches for various non-standard OS. I just want
// to implement enough to be functional. With time it will probably
// grow as reports come in from OS that need different stuff.
#define DEBUG 1
const int ST_DATA = 0;
const int ST_IAC = 1;
const int ST_SE = 2;
const int ST_OPT = 3;
const int ST_HOW = 4;
const int ST_WILL = 5;
const int ST_WONT = 6;
const int ST_DO = 7;
const int ST_DONT = 8;
const int ST_CRLF = 9;
const int ST_IP = 10;
int route_io( int fd1, int fd2 )
{
Descriptor fdnet( fd1 );
Descriptor fdpty( fd2 );
struct timeval sleep_time;
fd_set r_set, w_set, o_set;
int state = ST_DATA;
char netbuf[1024];
char netbuf2[1024];
char ptybuf[1024];
char ptybuf2[1024*2];
int netbytes = 0;
int ptybytes = 0;
int maxdesc = fdnet;
if( maxdesc < fdpty )
maxdesc = fdpty;
FD_ZERO( &r_set );
FD_ZERO( &w_set );
FD_ZERO( &o_set );
for( ; ; )
{
sleep_time.tv_sec = 1;
sleep_time.tv_usec = 0;
FD_SET( (int)fdnet, &r_set );
FD_SET( (int)fdnet, &w_set );
FD_SET( (int)fdnet, &o_set );
FD_SET( (int)fdpty, &r_set );
FD_SET( (int)fdpty, &w_set );
// First sleep until data to be read. Then poll the writeable
// descriptors too. This is best for conserving CPU and gives
// the best interactive performance from my tests. Of course
// data only gets written on 'read' pulses but testing hasn't
// shown this to be a problem.
// Don't bother checking error codes here.
select( maxdesc+1, &r_set, 0, 0, &sleep_time );
// Now do the write poll which almost always returns ready.
sleep_time.tv_sec = 1;
sleep_time.tv_usec = 0;
while( select( maxdesc+1, 0, &w_set, &o_set, &sleep_time ) < 0 )
{
if( errno == EINTR )
continue;
perror( "route_io:select" );
return -1;
}
if( FD_ISSET( (int)fdnet, &o_set ) )
{
printf( "Got OOB data.\n" );
// add code to deal with it soon
}
if( FD_ISSET( (int)fdnet, &r_set ) && netbytes == 0 )
{
if( ( netbytes = fdnet.read( netbuf, sizeof( netbuf ) ) ) < 0 )
{
#ifdef DEBUG
perror( "route_io:read-from-fdnet" );
#endif
return -1;
}
else if( netbytes == 0 )
if( fdnet.eof() )
break;
}
if( FD_ISSET( (int)fdpty, &w_set ) && netbytes > 0 )
{
int i = 0;
int j = 0;
int ch;
// After about 80% I looked at Berkeley telnetd and was surprised
// that it uses a similar state machine. Also the CRLF
// stuff here is based on my understanding of Berkeley sources.
while( i < netbytes )
{
ch = netbuf[i++] & 0377;
switch( state )
{
default:
case ST_CRLF: // Skip '\n' if '\r\n'
state = ST_DATA;
if( ch == '\n' || ch == '\0' )
continue;
// Fall through to data
case ST_DATA:
if( ch == IAC )
{
state = ST_IAC;
continue;
}
else if( ch == '\r' )
state = ST_CRLF;
netbuf2[j++] = ch;
continue;
case ST_IAC:
// Messy. Eat up IACs for now.
if( ch == SB )
state = ST_SE;
else if( ch == IP )
{
// Kludgy! Will handle data-mark and urgent
// data later, right now just kill the shell.
exit(0);
}
else if( ch == IAC ) // double IAC can happen
{
netbuf2[j++] = ch;
state = ST_DATA;
}
else state = ST_OPT;
break;
case ST_OPT:
case ST_DO:
case ST_DONT:
case ST_WILL:
case ST_WONT:
// will/wont/do/dont - discarding for now
state = ST_DATA;
break;
case ST_SE:
if( ch == SE )
state = ST_DATA;
break;
}
}
// If error assume shell exited
if( fdpty.write( netbuf2, j ) < 0 )
{
#ifdef DEBUG
perror( "fdpty.write" );
#endif
return -1;
}
netbytes = 0;
}
// Pty has data
if( FD_ISSET( (int)fdpty, &r_set ) && ptybytes == 0 )
{
if( ( ptybytes = fdpty.read( ptybuf, sizeof( ptybuf ) ) ) < 0 )
{
#ifdef DEBUG
perror( "fdpty.read" );
#endif
return -1;
}
else if( ptybytes == 0 )
if( fdpty.eof() )
break;
}
if( FD_ISSET( (int)fdnet, &w_set ) && ptybytes > 0 )
{
int i = 0;
int j = 0;
int ch;
// This little snippet is from reading Berkeley sources
// trying to understand the telnet protocol.
// If we were supporting binary mode (RFC 856) we would need
// more than what I have. IAC is a legal byte in binary
// mode so if we get double IAC we just pass one thru.
// ---
// Concerning CRLF, since we are the server (UNIX side)
// we only expect '\n' so translate for client. If we
// see '\r' from server side, ignore since it was probably
// part of some other app trying to do translation like us.
// This is only my understanding, which is very limited.
while( i < ptybytes )
{
ch = ptybuf[i++] & 0377;
if( ch == IAC )
ptybuf2[j++] = ch;
else if( ch == '\n' )
{
ptybuf2[j++] = '\r';
ptybuf2[j++] = ch;
}
else if( ch != '\r' )
ptybuf2[j++] = ch;
}
if( fdnet.write( ptybuf2, j ) < 0 )
{
#ifdef DEBUG
perror( "route_io:write-to-fdnet" );
#endif
return -1;
}
ptybytes = 0;
}
}
return 0;
}
#ifdef SYSV
// SYSV doesn't use the pty name
int open_pty_master( char * )
{
int fd;
if( ( fd = open( PTY_MASTER_DEV_SYSV, O_RDWR ) ) < 0 )
return -1;
return fd;
}
int open_pty_slave( int master_fd, const char * )
{
int fd;
char *slavepty;
if( grantpt( master_fd ) < 0 )
{
close( master_fd );
return -1;
}
if( unlockpt( master_fd ) < 0 )
{
close( master_fd );
return -1;
}
slavepty = ptsname( master_fd );
if( !slavepty )
{
close( master_fd );
return -1;
}
if( ( fd = open( slavepty, O_RDWR ) ) < 0 )
{
close( master_fd );
return -1;
}
// Push streams modules
// ptem = pseudo terminal emulation module
// ldterm = terminal line discipline
if( ioctl( fd, I_PUSH, "ptem" ) < 0 )
{
close( master_fd );
return -1;
}
if( ioctl( fd, I_PUSH, "ldterm" ) < 0 )
{
close( master_fd );
return -1;
}
return fd;
}
#else /* 4.3 BSD */
// BSD pty's are in form ptyXY ( ptyp0 to ptysf ) where
// X is p-s and Y is 0-f hex
// Increment through until we succeed in opening one.
// The slave side of a BSD ptyXY is ttyXY
int open_pty_master( char *pty )
{
int master_fd;
struct stat s;
const char * Xchars = "pqrs";
const char * Ychars = "0123456789abcdef";
int i,j;
strcpy( pty, "/dev/ptyXY" );
for( i = 0; Xchars[i]; i++ )
{
pty[8] = Xchars[i];
pty[9] = '0';
// See if system has /dev entry for ptyX0, if not we are done
if( stat( pty, &s ) < 0 )
break;
for( j=0; j < 16; j++ )
{
pty[9] = Ychars[j];
if( ( master_fd = open( pty, O_RDWR ) ) >= 0 )
return master_fd; // grab it and run
}
}
return -1;
}
int open_pty_slave( int master_fd, const char *pty )
{
int slave_fd;
char tty[12];
// Change /dev/ptyXY to /dev/ttyXY (the slave side)
strcpy( tty, pty );
tty[5] = 't';
if( ( slave_fd = open( tty, O_RDWR ) ) < 0 )
{
close( master_fd );
return -1;
}
return slave_fd;
}
#endif
#endif // remove this to try shell code