/* ....[@@@..[@@@..............[@.................. 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