/************************************************************************
Realms of Aurealis James Rhone aka Vall of RoA
exshell.c External editor code which uses
pseudo terminals as a way to
spawn off an editing session.
Used primarily for builders who
need the services of an actual
editor. Lots of networking
things in here, as well as some
good ole fork/exec code. Ahhh,
nothing like Unix. :)
******** 100% Completely Original Code ********
*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
******** 100% Completely Original Code ********
All rights reserved henceforth.
Please note that no guarantees are associated with any code from
Realms of Aurealis. All code which has been released to the general
public has been done so with an 'as is' pretense. RoA is based on both
Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well
as the RoA license. *** Read, Learn, Understand, Improve ***
// This code has a bunch of origins, most of which I (-jtrhone)
// have got working, but a lot of which has been derived from various
// unix networkin books, I do not claim I wrote this from scratch.
*************************************************************************/
#define _EXSHELL_C_
#include "sysdep.h"
#include "conf.h"
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <netinet/in.h>
#include <arpa/telnet.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "structures.h"
#include "utils.h"
#include "comm.h"
#include "clicomm.h"
#include "interpreter.h"
#include "db.h"
#include "acmd.h"
#include "screen.h"
#include "roaolc.h"
#include "exshell.h"
#include "lists.h"
#include "global.h"
// externals
extern int MASTER_DESCRIPTOR;
extern int CLIENT_DESCRIPTOR;
extern FILE *player_fl;
extern void string_add(dsdata *d, char *str, BOOL term);
extern void nonblock(int s);
// This is RoA's SIGCHLD signal handler, used when builder exits external
// editor, the death of it will signal the mud and trigger this function...
//
// Since the server has other children now besides just editors, add
// functionality to take care of them too (client downloads...)
// 12/5/97 -jtrhone
//
// if the cli_router dies, restart it... 8/4/98 -jtrhone
RETSIGTYPE grim_reaper(int sig_received)
{
int status = 0, pid;
dsdata *d, *next_d;
static char tmpfile[85000];
struct rusage r;
#ifdef CLIENT_FORK
clpid_info *cl;
#endif
*tmpfile = '\0';
// could use WNOHANG if we didn't wanna wait
pid = wait3(&status, WNOHANG, &r);
// for some reason, no more children out there...
// usually occurs when a system call returns, no biggie... ignore and reset
if (pid == -1)
{
my_signal(SIGCHLD, grim_reaper);
return;
}
#ifdef DEBUG_MAX
sprintf(buf, "SYSUPD: GrimReaper (%d) has fielded PID: %d", getpid(), pid);
mudlog(buf, BUG, LEV_IMM, TRUE);
#endif
// if our router dies, we need to restart it 8/4/98 -jtrhone
if (pid == cli_router_pid)
{
start_cli_router();
my_signal(SIGCHLD, grim_reaper);
return;
}
// return our friend who was in shell to the mud world :)
for (d=descriptor_list; d; d=next_d)
{
next_d = d->next;
if (d->rerouted && d->child_pid && d->child_pid == pid)
{
d->child_pid = -1;
d->rerouted = FALSE; // needed so we can start readin their input again
if (d->character)
{
sprintf(buf, "edits/%s.edit",GET_NAME(d->character));
huge_file_to_string(buf, tmpfile);
if (!*tmpfile)
strcpy(tmpfile, "%1WARNING%0: String not loaded or too long.\n");
string_add(d, tmpfile, TRUE); // TRUE means terminate now
#ifdef DEBUG_MAX
sprintf(buf, "SYSUPD: GrimReaper (%d) matched ExShell PID: %d", getpid(), pid);
mudlog(buf, BUG, LEV_IMM, TRUE);
#endif
}
}
}
#ifdef CLIENT_FORK
// ELSE it's some other dead child... client download perhaps
// compare against the global client PID list
// if we get a match, free it's associated string 12/5/97 -jtrhone
for (cl = cl_pids; cl; cl=cl->next)
if (cl->pid == pid)
{
delete_clpid_entry(cl);
#ifdef DEBUG_MAX
sprintf(buf, "SYSUPD: GrimReaper (%d) matched clpid PID: %d", getpid(), pid);
mudlog(buf, BUG, LEV_IMM, TRUE);
#endif
break;
}
#endif
// reset handler...
my_signal(SIGCHLD, grim_reaper);
}
// i need a specialized system capability... ignore all system child exits...
// 3/6/98 -jtrhone
// Hmm, should really block rather than ignore, ah well
// ehhh.... we'll just ignore these children in reaper...3/15/98 -jtrhone
int roa_system(char *comm)
{
int retval;
// ignore the sigchld...
// my_signal(SIGCHLD, SIG_IGN);
// call the real system (will fork, exec, wait for it to finish, then return)
retval = system(comm);
// reinstate the reaper :)
// my_signal(SIGCHLD, grim_reaper);
// return it to maintain sanity
return retval;
}
int route_io( int fdnet, int fdpty )
{
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[2048];
int netbytes = 0;
int ptybytes = 0;
int maxdesc = fdnet; // note, fdnet == d->descriptor
// find our largest descriptor number
if (maxdesc < fdpty)
maxdesc = fdpty;
// clear out all our sets, no descriptors are in them after this
FD_ZERO( &r_set );
FD_ZERO( &w_set );
FD_ZERO( &o_set );
// begin the server (mini telnetd)
for( ; ; )
{
sleep_time.tv_sec = 0;
sleep_time.tv_usec = 1;
// include the fdnet descriptor in our read, write, and exception sets
FD_SET( (int)fdnet, &r_set );
FD_SET( (int)fdnet, &w_set );
FD_SET( (int)fdnet, &o_set );
// include the fdpty descriptor in our read and write sets
FD_SET( (int)fdpty, &r_set );
FD_SET( (int)fdpty, &w_set );
// look for anything to be read from 0 - maxdesc
select( maxdesc+1, &r_set, 0, 0, &sleep_time );
// keep sitting here until something comes in write set or
// exception set, breaks out on reception or error
while( select( maxdesc+1, 0, &w_set, &o_set, &sleep_time ) < 0 )
{
if( errno == EINTR )
continue;
perror( "route_io:select" );
return -1;
}
// if fdnet is in the readset and we havent read anything yet
if( FD_ISSET(fdnet, &r_set) && netbytes == 0 )
{
if( ( netbytes = read(fdnet, netbuf, 1024 )) < 0 )
{
perror( "route_io:read-from-fdnet" );
return -1;
}
else
if( netbytes == 0 )
if( fdnet < 0 )
break;
}
// see if fdpty is still around
// if not, its not an error, just return for now
if (fdpty < 0) return 1;
// see if the pty is in the write set, and we have read stuff
if( FD_ISSET( (int)fdpty, &w_set ) && netbytes > 0 )
{
int i = 0;
int j = 0;
ubyte ch;
// stick our written stuff from fdnet into netbuf2
// translating along the way
// we will write netbuf2 to fdpty a bit later
while( i < netbytes )
{
ch = netbuf[i++] & 0377;
switch(state)
{
case ST_DATA:
if( ch == IAC )
{
state = ST_IAC;
continue;
}
else
if( ch == '\r' )
state = ST_CRLF;
netbuf2[j++] = ch;
continue;
default:
case ST_CRLF: // Skip '\n' if '\r\n'
state = ST_DATA;
if( ch == '\n' || ch == '\0' )
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
// write netbuf2 to the pty, assuming its still open
if( write( fdpty, netbuf2, j ) < 0 )
{
if (errno == EFAULT)
perror( "writing outside of fdpty process space" );
if (errno == EINVAL)
perror( "fdpty was negative" );
if (errno == EPIPE)
perror( "attempted to write to descriptor which nobody is readin" );
if (errno == EBADF)
perror( "attempted to write to invalid descriptor" );
return -1;
}
netbytes = 0;
}
// Pty has data
// fdpty is in read_set and we havent read anything yet
if( FD_ISSET(fdpty, &r_set ) && ptybytes == 0 )
{
if( ( ptybytes = read(fdpty, ptybuf, 1024 )) < 0 )
{
perror( "fdpty.read" );
return -1;
}
else
if( ptybytes == 0 )
if( fdpty < 0 )
break;
}
// if our net, fdnet is in write set and we have read stuff from pty
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.
// stick all our read pty bytes into ptybuf2...
// translating along the way
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 we cannot write what we read from the pty to the fdnet
// we have a problem :)
if( write( fdnet, ptybuf2, j ) < 0 )
{
perror( "route_io:write-to-fdnet" );
return -1;
}
// ok, set our pty # of read bytes back to 0 and loop back
ptybytes = 0;
}
}
return 0;
}
// OK THIS IS THE MINI TELNETD BRANCH, this will take care of routing
void run_mini_telnetd(int master, int desc)
{
// make sure our master doesnt block
nonblock(master);
// let em know we are offering to do the echoing and we will
// suppress any go aheads
write(desc, WILL_ECHO, 3);
write(desc, WILL_SUPPRESS_GA, 3);
// run mini telnetd between pty and mud
// note, will not return until the session is over
route_io(desc, master);
// reset our previous offers after we return from mini telnetd
write(desc, WONT_ECHO, 5);
write(desc, WONT_SUPPRESS_GA, 3);
// when we're done routing, exit this child
exit(0);
}
/* 4.3 BSD */
// BSD ptys 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 * Ychars = "0123456789abcdefghijklmnopqrstuv";
int j;
strcpy( pty, "/dev/ptyXY" );
pty[8] = 'a'; // this is only X char allowed on our system, scan if you
// have more -roa
pty[9] = '0';
// See if system has /dev entry for ptyX0, if not we are done
if( stat( pty, &s ) < 0 )
{
perror("open master pty cannot find /dev/ptya0:");
return -1;
}
// just go to u, blah should find an empty pty out of 31 tries -roa
for( j=0; j < 30; j++ )
{
pty[9] = Ychars[j];
if( ( master_fd = open( pty, O_RDWR ) ) >= 0 )
return master_fd; // grab it and run
else
{
sprintf(buf, "Could not open master pty %s.",pty);
mudlog(buf, BUG, LEV_IMM, TRUE);
}
}
return -1;
}
// Going to have to play with this to scan for next available
// slave pty -about to be updated as soon as possible
// currently only allows for one person to be shelled out
int open_pty_slave( int master_fd, const char *pty )
{
int slave_fd;
char tty[12];
// uses same the pty string just replaces the p with t
// ie. if we grabbed /dev/ptyp5 for master
// the slave tries for /dev/ttyp5 -roa
strcpy( tty, pty );
tty[5] = 't';
// should we scan here? for an open slave tty -roa
if( (slave_fd = open(tty, O_RDWR)) < 0)
{
close( master_fd );
sprintf(buf, "Could not open slave pty %s.",tty);
mudlog(buf, BUG, LEV_IMM, TRUE);
return -1;
}
else
return slave_fd;
}
// do a stat on the thought to be location of the editor...
BOOL no_spico(void)
{
struct stat s;
if (stat(spico_path, &s) < 0)
{
perror("statting: ");
return TRUE;
}
else
return FALSE;
}
// This starts up all the server stuff to be documented much better as I
// become more comfortable with it... note: all the following code is
// probably very OS specific for the most part, and is only being written
// for BSDi systems, perhaps a System V addition sometime in the future
// to make it at least semi portable...
// for now however, RoA runs on FreeBSD, so I stick with that type OS :)
// update: This code has been run successfully on FreeBSD, BSDi, and RH Linux...
// Kudos to the MUD++ dev team for giving me something to look at
// jtrhone aka Vall RoA
int startshell(dsdata *d, char *editor, char *fname)
{
int master_fd;
int slave_fd;
char pty[12];
int pid;
dsdata *td;
// if no editor... mudlog and bail...
if (no_spico())
{
send_to_char("Unable to locate external editor (check configuration file).\n\r", d->character);
mudlog("SYSERR: Unable to locate external editor.", BRF, LEV_IMM, TRUE);
return 0;
}
fflush(NULL); // flush ALL open buffered streams
/* fork a server off to take care of shell and IO */
if((pid = fork()) > 0)
{
// Original parent
d->rerouted = TRUE;
d->child_pid = pid;
return 0;
}
else
if(pid < 0) // error on fork
{
perror("fork");
perror("system error forking shell.\n\r");
return -1;
}
// RoA
// SERVER BEGINS HERE, child of main mud, this will eventually
// split again into two branches:
// A) parent branch, a route_io server (mini telnetd) and
// B) the exec branch, the branch that gets blasted by the shell
// first we have to secure this so it doesnt have access to the main
// mud things, like the player_fl
fclose(player_fl);
// close our main descriptor, or the mother_desc
// also close client master now 5/19/98 -jtrhone
close(MASTER_DESCRIPTOR);
close(CLIENT_DESCRIPTOR);
// close every descriptor in the mud except the one were workin with
for (td = descriptor_list; td; td = td->next)
if (td != d)
close(td->descriptor);
// make sure files are r/w/e by group
umask(0070);
// grab a pseudo terminal master
if((master_fd = open_pty_master(pty)) < 0 )
{
perror( "Failed to open pty master for shell.\n\r" );
return -1;
}
// WE SPLIT HERE AGAIN, into server & exec branch
pid = fork();
if(pid == 0)
{
// this is the exec branch, it will soon wax everything with an exec
// make SURE we will send a SIGCHLD to its parent when we die
// thats how grim_reaper knows we have died
struct sigaction sa;
sa.sa_flags = SA_RESETHAND;
sa.sa_handler = grim_reaper;
if (sigaction(SIGCHLD, &sa, 0) < 0)
{
// yuikes, we failed for some reason
// send a kill to the parent process of this one (minitelnetd)
if ( kill(getppid(), SIGKILL) < 0 )
perror( "open_pty_slave, parent kill:" );
perror("sigaction: cant reset SIGCHLD");
exit(0);
}
// ok, now have this exec branch grab a slave pty sending our master
if((slave_fd = open_pty_slave(master_fd, pty)) < 0)
{
if ( kill(getppid(), SIGKILL) < 0 )
perror( "open_pty_slave, parent kill:" );
perror( "open_pty_slave:" );
exit(0);
}
// ok we grabbed a slave, now close the exec branches master as well
// as stdin, stdout, and stderr
close(master_fd); close(0); close(1); close(2);
// ok, we wanna dupe our slave into stdin AND stdout AND stderr
if(dup(slave_fd) != 0 || dup(slave_fd) != 1 )// || dup(slave_fd) != 2)
{
if ( kill(getppid(), SIGKILL) < 0 )
perror( "open_pty_slave, parent kill:" );
perror( "dup failed\n\r" );
exit(0);
}
// kewl, now the exec branch can only communicate thru this slave
// descriptor, we have copies of it in stdin stdout and stderr
// so we dont need the original anymore
close(slave_fd);
// do the exec, replaces entire code with new code, but does not
// reset stdin stdout and stderr, so whatever is ran can only
// communicate thru the slave_fd
chdir("edits");
execl(spico_path, "spico", fname, (char*)0);
if ( kill(getppid(), SIGKILL) < 0 )
perror( "open_pty_slave, parent kill:" );
perror( "execl" );
exit(0);
}
else
if( pid < 0 ) // fork error, bail
{
perror( "fork slave pty failed" );
return -1;
}
// send this branch into telnet mode...
// this exits before it ever returns here.. -roa
run_mini_telnetd(master_fd, d->descriptor);
// never get here, avoid compiler warning though
return -1;
}
#undef _EXSHELL_C_