/
roa/
roa/lib/boards/
roa/lib/config/
roa/lib/edits/
roa/lib/help/
roa/lib/misc/
roa/lib/plrobjs/
roa/lib/quests/
roa/lib/socials/
roa/lib/www/
roa/lib/www/LEDSign/
roa/lib/www/LEDSign/fonts/
roa/lib/www/LEDSign/scripts/
roa/src/s_inc/
roa/src/sclient/
roa/src/sclient/binary/
roa/src/sclient/text/
roa/src/util/
/************************************************************************
	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_