ouch.cc


      //  $Id: ouch.cc,v 1.4 2001/10/15 02:54:51 vickery Exp $
      /*
       *    $Log: ouch.cc,v $
       *    Revision 1.4  2001/10/15 02:54:51  vickery
       *    Updated comments for isExecutable() to acknowledge that
       *    access(2) does almost the same thing.
       *
       *    Revision 1.3  2001/10/12 16:14:10  vickery
       *    Used PATH_MAX to determine buffer size of full pathnames.
       *    Fixed stray usage message when no command line arguments.
       *
       *    Revision 1.2  2001/10/11 19:57:34  vickery
       *    Implemented -c and -? options.
       *
       *    Revision 1.1  2001/10/04 19:40:43  vickery
       *    Initial revision
       *
       */
     
      #include "ouch.h"
     
      //  Global Variables
      //  ------------------------------------------------------------------
      const uid_t     user_uid  = geteuid();  // Effective, not real.
      const gid_t     user_gid  = getegid();  // Effective, not real.
            char      *PATH     = getenv( "PATH" );
            dirNode_t *pathHead = 0;
     
      //  Local Prototypes
      //  ------------------------------------------------------------------
      char *findExecutable( const char * const );
      bool isExecutable( const char * const ); 
     
      //  main()
      //  ------------------------------------------------------------------
      /*
       *    Initialize pathDirs linked list.
       *    Process command line options.
       *      If -?
       *        Display Usage message
       *        Exit
       *      If -c
       *        Parse command line
       *        Locate the executable file
       *        Exec the command
       *      Else
       *        Exit
       */
        int
        main( int argc, char* argv[], char* envp[] )
        {
          char *fullPathname = 0;
          
          //  Initialize pathDirs linked list.
     
          ABORT_IF( 0 == PATH, "Ouch: No PATH" );
     
          char * pathCopy = (char*) malloc( 1 + strlen( PATH ) );
            ABORT_IF( 0 == pathCopy, "Ouch: malloc failed for pathCopy" );
          strcpy( pathCopy, PATH );
          pathHead = (dirNode_t *) malloc( sizeof(dirNode_t) );
            ABORT_IF( 0 == pathHead, "Ouch: malloc failed for pathHead" );
          pathHead->dirName = strtok( pathCopy, ":" );
            ABORT_IF( 0 == pathHead->dirName, "Ouch: Empty PATH" );
          pathHead->nextNode = 0;
     
          dirNode_t *nextNode = pathHead;
          char *dirName = 0;
          while ( 0 != (dirName = strtok( 0, ":" ) ) )
          {
            nextNode->nextNode = (dirNode_t*) malloc( sizeof(dirNode_t) );
              ABORT_IF( 0 == nextNode->nextNode, 
                  "Ouch: malloc failed for directory node" );
            nextNode = nextNode->nextNode;
            nextNode->dirName = dirName;
            nextNode->nextNode = 0;
          }
     
          //  Process command line options.
          int optChar;
          while ( -1 != (optChar = getopt( argc, argv, "c:?" ) ) )
          {
            switch ( optChar )
            {
              case '?'  :
                //  Display Usage message
                fprintf( stderr, "Usage: ouch [-?] | [-c subcommand]\n" );
                exit( 1 );
     
              case 'c' :
                //  Parse and execute a subcommand
                fullPathname = findExecutable( optarg );
                if ( 0 != fullPathname )
                {
                  char **argVector = 
                      (char**) malloc( (1 + argc) * sizeof( char* ) );
                  ABORT_IF( 0 == argVector, 
                      "Ouch: malloc failed for argVector" );
                  argVector[0] = optarg;
                  int vecIndex = 1;
                  for (int i = optind; i < argc; i++ )
                    argVector[vecIndex++] = argv[ i ];
                  argVector[ vecIndex ] = 0;
                  
                  //  Display full pathname and exec the subcommand
                  printf( "Full pathname: %s\n", fullPathname );
                  execve( fullPathname, argVector, envp );
                  perror( "Ouch: execve" );
                  exit( 1 );
                }
                fprintf( stderr, "%s: Command not found\n", optarg );
                exit( 1 );
     
              default :
                fprintf( stderr, "Bad switch at %s line %d\n",
                    __FILE__, __LINE__ );
                exit( 1 );
            }
          }
          //  No options found; be sure there were no arguments
          if ( argc != 1 )
          {
            fprintf( stderr, "Usage: ouch [-?] | [-c subcommand]\n" );
            exit( 1 );
          }
          exit( 0 );
        }
     
      //  findExecutable()
      //  -------------------------------------------------------------
      /*
       *    Find an executable file and return its full pathname, if
       *    possible.  Return 0 if not found.
       *
       *    Note:  Memory for returned string is malloc'd.  It is the
       *    caller's responsibility to free it.
       *    
       *    Cases: Explicit pathname given, starting with /, ./, or ../
       *           Otherwise search PATH directories.
       *           Found file must be executable by user.
       */
        char *
        findExecutable( const char * const fileName )
        {
          //  Check if absolute path is given.
          if ( '/' == *fileName ||
               '.' == *fileName && '/' == *(fileName + 1) ||
               '.' == *fileName && '.' == *(fileName + 1) && 
               '/' == *(fileName + 2) )
          {
            if ( isExecutable( fileName ) )
            {
              //  Absolute path: malloc memory to return it to be consistent
              //  with case where relative path is given.
              char *returnVal = (char*) malloc( 1 + strlen( fileName ) );
              ABORT_IF( 0 == returnVal,
                  "findExecutable: malloc failed for returnVal" );
              strcpy( returnVal, fileName );
              return returnVal;
            }
            else
              return 0;
          }
     
          // Not an absolute pathname: search PATH for possible executables
          dirNode_t *nextNode = pathHead;
          char *returnValue = 
              (char *) malloc( PATH_MAX + strlen(fileName + 2) );
            ABORT_IF( 0 == returnValue, 
            "findExecutable: malloc failed for returnVal" );
          while ( 0 != nextNode )
          {
            strcpy( returnValue, nextNode->dirName );
            strcat( returnValue, "/" );
            strcat( returnValue, fileName );
            if ( isExecutable( returnValue ) )
            {
              return returnValue;
            }
            nextNode = nextNode->nextNode;
          }
          //  Executable not found in any of the dirs in PATH
          free( returnValue );
          return 0;
        }
     
      //  isExecutable()
      //  ------------------------------------------------------------------
      /*
       *    Checks if a file is ... ummm ... executable.
       *    This is (supposed to be) the same as access(2), except we use
       *    the  effective user and group ids instead of the real user and
       *    group ids, as the system call does.
       */
        bool
        isExecutable( const char * const pathName )
        {
          struct stat statBuf;
          if ( -1 == stat( pathName, &statBuf ) )
            return false;
          
          // Check permissions
          int permissions = statBuf.st_mode & 07;
          if ( user_gid == statBuf.st_gid )
            permissions |= (statBuf.st_mode & 070) >> 3;
          if ( user_uid == statBuf.st_uid )
            permissions |= (statBuf.st_mode & 0700) >> 6;
          if ( (permissions & 05) == 05 )
            return true;
          else
            return false;
        }