manage_client.cc


      //  $Id: manage_client.cc,v 2.3 2002/05/18 22:15:05 vickery Exp $
      
      /*    Process to manage one remote client.
       *
       *    Functions:  signal_handler  Handle signals.
       *                do_prompt       Generate a prompt string.
       *                process_message Process a message from the client.
       *                manageClient    The function that manages one
       *                                client.
       *
       *    $Log: manage_client.cc,v $
       *    Revision 2.3  2002/05/18 22:15:05  vickery
       *    Changed to use of the new recv_msg utility function.
       *    Several cosmetic changes.
       *
       *    Revision 2.2  2002/05/14 04:18:29  vickery
       *    Added signal handler for SIGCHLD and SIGUSR1.  SIGUSR1 indicates
       *    server is shutting down.
       *
       *    Revision 2.1  2002/05/12 00:49:28  vickery
       *    Initial version.
       *
       */
      #define _GNU_SOURCE
      #include "qserver.h"
      
      //  Global: Set by server process.
      //  ------------------------------------------------------------------
      
      extern const char login_msg[];
      
      
      //  Global: Separate copy for each manager process.
      //  ------------------------------------------------------------------
      extern  char  user_name[ USERNAME_MAX ];
      extern  char  host_name[ HOSTNAME_MAX ];
      
      static  char  prompt_string[ PROMPTSTR_MAX ];
      static  int   client_fd       = -1;
      static  bool  authenticated   = false;
      
      char                  *client_ident   = 0;
      vector<const char *>  stdin_vec;    //  Typeahead buffer
      
      //  Global: Set by processCommand()
      //  ------------------------------------------------------------------
              int   command_pid     = 0;          //  Command child
              int   command_status  = 0;          //  Child exit code
      
      
      //  signal_handler()
      //  ------------------------------------------------------------------
      /*
       *    Handle signals received by this process.
       *
       *    In particular, the main (server) process sends SIGUSR1 to all
       *    manager processes when a user enters a shutdown command.  The
       *    response is to kill any command that is running, to send a
       *    SC_SHUT message to the client, and to exit.
       */
      static void
      signal_handler( int signum )
      {
        pid_t     signalling_child;
        int       status;
        
        switch ( signum )
        {
          case SIGUSR1:
      
            //  Parent is telling us to shut down.
            //  ------------------------------------------------------------
            //  Determine whether there is a child process to kill, and kill
            //  it.
            if ( command_pid != 0 )
            {
              status = kill( command_pid, SIGTERM );
              if ( status < 0 )
              {
                writeLog( "***KILL A COMMAND FAILED ***", 
                                                        strerror( errno ) );
                _exit( 1 );
              }
            }
          //  Tell client, and exit manager process.
          send_msg( client_fd, SC_SHUT, "Server shutting down." );
          _exit( 0 );
          break;
      
          case SIGCHLD:
      
            //  Child (a command) has exited.
            //  ------------------------------------------------------------
            signalling_child = wait( &command_status );
            if ( signalling_child != command_pid )
            {
              fprintf( stderr, 
                       "Manager %d: child (%d) != command_pid (%d)n",
                       getpid(), signalling_child, command_pid );
              _exit( SHUTDOWN_EXIT );
            }
            command_pid = 0;
            break;
      
          default:
            fprintf( stderr, "Bad switch at %s line %dn", __FILE__,
                                                                 __LINE__ );
            _exit( SHUTDOWN_EXIT );
        }
        return;
      }
      
      
      //  do_prompt()
      //  ------------------------------------------------------------------
      /*
       *    Generate prompt string.
       *
       *    Format:   [eee]uuu@hhh{ddd}>
       *              eee is exit status of previous command
       *              uuu is user name
       *              hhh is host name
       *              ddd is current directory
       */
      void 
      do_prompt( void )
      {
      
        //  Convert command status to an exit code.
        bool        exited      = false;
        int         exit_code   = command_status;
        const char *signal_name = 0;
      
        if ( WIFEXITED(command_status) )
        {
          exit_code = WEXITSTATUS(command_status);
          exited = true;
        }
        else
        {
          if ( WIFSIGNALED(command_status) )
          {
            exit_code = WTERMSIG(command_status);
            signal_name = strsignal( exit_code ); 
          }
          else
          {
            exit_code = WSTOPSIG(command_status);
            signal_name = strsignal( exit_code );
          }
        }
        
        //  Reduce pathname of cwd to directory name only
        char  path_buf[ PATHNAME_MAX ];
        if ( getcwd( path_buf, PATHNAME_MAX ) < 0 )
        {
          perror( "getcwd" );
          exit( 1 );
        }
        char *dir = strrchr( path_buf, '/' );
        if ( 0 == dir )
        {
          dir = path_buf;
        }
        else
        {
          dir++;
        }
      
        //  Reduce fqdn of local host to hostname only
        char host_buf[ HOSTNAME_MAX ];
        strcpy( host_buf, host_name );
        char *ptr = strchr( host_buf, '.' );
        if ( ptr != 0 )
        {
          *ptr = 0;
        }
        
        if ( exited )
        {
          sprintf( prompt_string, "[%d]%s@%s{%s}> ",
                                      exit_code, user_name, host_buf, dir );
        }
        else
        {
          sprintf( prompt_string, "[%d(%s)]%s@%s{%s}> ",
                         exit_code, signal_name, user_name, host_buf, dir );
        }
        return;
      }
      
      
      //  process_message()
      //  ------------------------------------------------------------------
      /*
       *    Process a message received from a client.
       *
       *    Arguments:    fd          Socket for writing to client.
       *                  msg_header  Message header.
       *                  msg_body    Message body.
       *    Returns:      void
       */
      void
      process_message( int fd, msg_header_t msg_header, char *msg_body )
      {
        client_fd = fd;   //  Make it global
        char  *stdin_buf_ptr;
      
        switch ( msg_header.msg_type )
        {
          case  CS_USER:
                writeLog( "CS_USER", client_ident );
                if ( 0 == strcmp( user_name, msg_body ) )
                {
                  writeLog( "AUTHENTICATED", client_ident );
                  authenticated = true;
                  do_prompt();
                  send_msg( fd, SC_PROMPT, prompt_string );
                }
                else
                {
                  writeLog( "DENIED", client_ident );
                  authenticated = false;
                  send_msg( fd, SC_DENY, "" );
                }
                break;
      
          case  CS_SHUT:
                writeLog( "CS_SHUT", client_ident );
                _exit( SHUTDOWN_EXIT );
                break;
      
          case  CS_EXIT:
                writeLog( "CS_EXIT", client_ident );
                close( fd );
                _exit( 0 );
                break;
      
          case  CS_CMD:
                writeLog( "CS_CMD", client_ident );
                //  Process this command
                processCommandLine( fd, msg_body );
                //  Prompt for the next one
                do_prompt();
                send_msg( fd, SC_PROMPT, prompt_string );
                break;
      
          case  CS_STDIN:
                writeLog( "CS_STDIN", client_ident );
                stdin_buf_ptr = new char[ msg_header.msg_length ];
                memmove( stdin_buf_ptr, msg_body, msg_header.msg_length );
                stdin_vec.insert( stdin_vec.end(), stdin_buf_ptr );
                break;
      
          default:
            fprintf( stderr, "Bad switch (%d) at %s line %dn",
                                  msg_header.msg_type, __FILE__, __LINE__ );
            _exit( 1 );
        }
        return;
      }
      
      
      //  manageClient()
      //  ------------------------------------------------------------------
      /*
       *    This is the initial function for the process that manages each
       *    client.
       *    
       *    After initialization, this process issues command prompts, reads
       *    command lines, and executes them.
       *
       *    This function never returns; it exits.
       */
      
      void
      manageClient( int client_fd, const char *cname )
      {
        char msg_buf[ MESSAGE_MAX ];
        //  Clear signal handling inherited from parent process.
        signal( SIGINT,  SIG_DFL );
        signal( SIGTERM, SIG_DFL );
      
        //  Set up signal processing for this process.
        signal( SIGCHLD, signal_handler );
        signal( SIGUSR1, signal_handler );
        signal( SIGPIPE, SIG_IGN );
      
        //  Client identification: host(pid)
        client_ident = new char[ strlen(cname) + 10 ];
        sprintf( client_ident, "%s(%d)", cname, getpid() );
      
        //  Initiate authentication.
        int           result;
        msg_header_t  msg_header;
        msg_header.msg_type   = SC_LOGIN;
        msg_header.msg_length = strlen( login_msg );
        result = write_msg( client_fd, msg_header, login_msg );
        if ( result < 0 )
        {
          sprintf( msg_buf, "WRITE ERROR: %s", strerror( errno ) );
          writeLog( msg_buf, client_ident );
          close( client_fd );
          _exit( 1 );
        }
      
        //  Read and process messages from the connection until
        //  client closes it, sends an EXIT message, or sends a SHUT
        //  message.
        while ( true )
        {
          char *msg_body = 0;
          int result = recv_msg( client_fd, msg_header, &msg_body );
          if ( result < 0 )
          {
            sprintf( msg_buf, "RECEIVE ERROR: %s", strerror( errno ) );
            writeLog( msg_buf, client_ident );
            close ( client_fd );
            _exit( 1 );
          }
      
          //  Got a message; now process it
          process_message( client_fd, msg_header, msg_body );
          delete [] msg_body;
      
        }
      
      }