/*
//
//  wsng.c ( adapted from Bruce's ws.c ) - a web server
//
//      AUTHOR: Russell Lowke
//   CSCIE-215: Assignment Six
//
//       Usage: wsng [portnumber [rootdir] ]
//
// Description: Enhance Bruces ws.c web server so it will:
//                 1. Return the date, server name & version
//                   see head() function.
//                 2. Support the HEAD method command 
//                   see process_rq() and do() functions.
//                 3. reads filetype and extensions from external file
//                   see initExtTable() and  readTable() functions.
//                 4. Produce directory listings with links
//                   see do_ls() function.
//                 5. look for index.html
//                   see do_ls() function.
//                 6. Handle an extra error
//                   see process_rq() and do_403() functions.
//                 7. Cleanup Zombies
//                   see initAlarm() and killZombies() functions.
//                 8. handle QUERY_STRING
//                   see initVarTable() and extractQuery() functions.         
//
//      Method: Main sends calls to setup the alarm, the variable table, read
//              from the external text file the filetypes, and to initalize
//              the socket. Main then sits in a loop reading incoming socket
//              requests, which are sent to process_rq to be delt with.
//              Alarm is set to periodically trigger killZombies(), which
//              cleans up any dead(finished) processes.  
//              process_rq() forks to a new process, then breaks the call into
//              [cmd] and [item] parts.  The [cmd] should be either a GET or
//              HEAD while [item] contains the bulk of the call.
//              extractQuery() is called on [item] to extract any query
//              variables that may have been sent at the end of [item], these
//              being delimited by a '?'.  [item] is determined to be either
//              a directory, a cgi, or a file format, and sent to the
//              appropriate do() to be processed.
//              ...for directories:
//                 do_ls() will chdir into the directory, then search for an
//              index.html.  If no index.html is found then html is generated to
//              describe the contents of the directory, with links to each item,
//              including item size and last modified dates.
//              ...for cgi:
//                 do_exec() is called, which exec's the cgi
//              ...for files:
//                 do_cat() is called, which checks through the table
//              of extension names v filetypes.  When the right filetype is
//              known this information is passed to head() to be passed at
//              the beginning of the reply, then the rest of the file follows,
//              reading via getc() and sending via putc().
//              ...for errors.  Three important errors are handled.
//               501 - unimplemented command.
//                      basically a syntax error
//               403 - Permissions won't allow.(q 6, "add one more error case")
//                      uses stat(f,&info) and (S_IRUSR & info.st_mode) to
//                      check the mode of the file and see if permissionas
//                      are set to allow a read
//               404 - item not found.
//                      item requested is not found
//
//
//         General functions are...
//                main - Calls four initialization functions for setting
//                       the alarm, the var table, the .ext table, and to get a 
//                       socket. The Main loop then waits for socket requests.
//                       When a request is recieved via socket it goes to
//                       process_rq for processing
//           initAlarm - initialization for alarm, sets it to call killZombies
//                       every 40 seconds or so (ZOMBIEFIX)
//        initVarTable - initialization of environment table using varlib.c
//                       Adds all the external values from [environ]
//                       into the var table.  Decalres REQUEST_METHOD
//                       and QUERY_STRING as vars to be exported to environ
//          initSocket - Changes to the root dir, makes a server socket,
//                       gets the hostname.
//        initExtTable - An external text file called "txtTable" contains a
//                       list of all the extension (.ext) names and their
//                       corresponding file types (text/html).
//                       readTextFile reads this file and tabulates
//                       the data into a struct called mFileTypes.
//          set_ticker - Called by initAlarm
//                       arranges for the interval timer to issue
//                       SIGALRM's at regular intervals
//         killZombies - Called periodically via alarm ( see set_ticker() )
//                       uses waitpid with a WNOHANG option. This will return a 
//                       positive pid for all zombies,
//                       and clean them in the process.
//        extractQuery - Str [item] might contain a '?', after which a
//                       query string is passed as typically used for cgi
//                       scripts. extractQuery() finds the '?' delimeter and,
//                       if found sets the environment var QUERY_STRING =
//                       whatever is after the '?'
//          process_rq - Process all requests.
//                       Request is handled in a new child process (fork)
//                       the parent simply returns and looks for the next 
//                       request. The child must exit, so the exit(0); 
//                       calls are very important here. Scanf is used to
//                       break the request into command [cmd] and argument [arg]
//                       the arg is further passed through modify_argument,
//                       to verify path, and extractQuery, to extract cgi?query
//                       information at end of the request.
//                       A flag called cmdType stores if cmd is of type
//                       HEAD or GET. Finallly, [item] is passed to one of
//                       various "do" functions that handle the request.
//     modify_argument - For security, remove all ".." components in paths
//                       and cleanup, if arg is "/" convert to "."
//              header - Use by all the "do" functions
//                       Sends a standard header to fp as the beginning
//                       of a reply.
//           cannot_do - do_501 unimplemented HTTP command
//              do_403 - Permisions won't allow, so block
//              do_404 - Item requested is not found
//              do_cat - sends back contents after a header
//             do_exec - exec a program/cgi and send it down the pipe
//               do_ls - Looks for index.html in directory, if found, 
//                       sends a do_cat of the index. Otherwise do_ls creates 
//                       a menu of links of all the items in the 
//                       directory by generating html.
//                       The size and last modified date
//                       of each item is displayed also.
//           readTable - looks through the table [mFileTypes] for .ext [str]
//             usrRead - Tests if file [f] is user readable
//                       determined by the st_mode permissions
//                       i.e. chmod foo +r
//         rfc822_time - return a time string suitable for web servers
//                       such as:  Sun, 06 Nov 1994 08:49:37 GMT
//                       uses gmtime() to get struct
//                       then uses asctime to translate to English
//                       then rearranges using sprintf
//
//       Notes:          For question 6 "add one more error case" I added
//                       do_403(), which checks the permissions on a file
//                       to see if it may be read.
*/

#include	"wsng.h"
extern char **environ;


#define	PORTNUM         9666
#define	SERVER_ROOT	"/home/l/o/lowke/public_html/wsng"
#define TXTFILENAME     "txtTable"
#define	HEAD            0
#define	GET             1
#define	POST            2
#define MAXSTR          512
#define ZOMBIEFIX       1000
    
char	myhost[MAXHOSTNAMELEN];
int	myport;
char	*full_hostname();
struct fileTypes mFileTypes[MAXSTR];

/*
//  main
//
//     Calls four initialization functions for setting
//     the alarm, the var table, the .ext table, and to get a socket.
//     The Main loop then waits for socket requests.
//     When a request is recieved via socket it goes to process_rq for
//     processing
//
//     args:  ac         --  argument count
//            **arg      --  pointer to arguments
*/
int main(int ac, char *av[])
{
    int 	sock, fd;
    FILE	*fpin, *fpout;
    char	request[BUFSIZ];
    
    /*
    //   set up
    */
    initAlarm();                                      /* alarm                */
    initVarTable();                                   /* environ var table    */
    initExtTable();                                   /* .ext table           */
    sock = initSocket(ac, av, myhost, &myport);       /* get server socket    */
    printf("ws1 started.  host=%s port=%d\n",         /* print sign on        */
            myhost, myport);
    
    /* 
    // loop forever
    */
    while(1){                                                    
        fd = accept( sock, NULL, NULL );              /* take a call          */
        if ( fd == -1 )
            ;                      /* ...error, (probably system call alarm)  */
        else
        {
            fpin  = fdopen(fd, "r" );
            fpout = fdopen(fd, "w" );

            fgets(request, BUFSIZ, fpin);             /* read request         */
            printf("got a call: request = %s", request);
            read_til_crnl(fpin);

            process_rq(request, fpout);               /* do what client asks  */

            fclose(fpin);
            fclose(fpout);
        }
    }
    
    return 0;
}


/*
//  initAlarm
//
//     initialization for alarm, sets it to
//     call killZombies every 40 seconds or so (ZOMBIEFIX)
//
*/
void initAlarm()
{
    signal(SIGALRM, killZombies );      /* set alarm to trigger killZombies...*/
    set_ticker( ZOMBIEFIX );            /* every ZOMBIEFIX milliseconds.      */
}


/*
//  initVarTable
//
//     initialization of environment table using varlib.c
//     Adds all the external values from [environ] into the var table
//     Decalres REQUEST_METHOD and QUERY_STRING 
//     as vars to be exported to environ
*/
void initVarTable()
{
    if ( VLenviron2table(environ) == 0 )/* copy externals to var table        */
        exit(1);
    VLexport("REQUEST_METHOD");         /* externalize REQUEST_METHOD         */
    VLexport("QUERY_STRING");           /* externalize QUERY_STRING           */
}


/*
//  initSocket
//
//     Changes to the root dir, makes a server socket,
//     gets the hostname.
//
//     args: *ac         --  argument count (from main)
//           *av         --  argument values (from main)
//           *host       --  container for the host name
//           *portnump   --  container for the port number
//
//     returns: the server socket
*/
int initSocket(int ac, char *av[],char host[], int *portnump)
{
    int	sock;                                /* socket int                    */
    int	portnum = PORTNUM;
    char *rootdir = SERVER_ROOT;

    if ( ac > 1 )
        portnum = atoi(av[1]);               /* ensure as integer             */
    if ( ac > 2 )
        rootdir = av[2];

    if ( chdir(rootdir) == -1 )              /* change the directory          */
        oops(rootdir,3);

    sock = make_server_socket( portnum );    /* get the socket int            */
    if ( sock == -1 ) 
        oops("making socket",2);
    strcpy(myhost, full_hostname());
    *portnump = portnum;
    return sock;
}


/*
//  initExtTable
//
//     An external text file called "txtTable" contains a
//     list of all the extension (.ext) names and their
//     corresponding file types (text/html).
//     readTextFile reads this file and tabulates the data into
//     a struct called mFileTypes.
//
//     Note: this function could be improved by
//           finding the size of the text file and initializing
//           the mFileTypes[] array to the right size.
*/
void initExtTable()
{
    int i;                                          /* general purpose int    */
    char        txtline[MAXSTR];                    /* str to hold txt line   */
    FILE	*txtFile;                           /* ptr for txt file       */

    if ( ! (txtFile = fopen(TXTFILENAME, "r")))     /* open the text file     */
            perror( TXTFILENAME );                  /* ...couldn't open       */
    while ( getLine( txtline, txtFile ) ) {
        if ( sscanf(txtline, "%s%s",
                    mFileTypes[i].extension,
                    mFileTypes[i].fileType) != 2 )  /* parse txtline          */
            fatal("text file format bad");
        i++;             
    }
    strcpy(mFileTypes[i].extension, "\0");          /* conclude table         */
    fclose(txtFile);
}


/*
//  getLine
//
//     Used by initExtTable to verify lines of data.
//
//     args: *buffer         --  buffer to put string into
//           *input_stream   --  the input stream
//
//     returns: TRUE if line OK, FALSE if line not OK.
*/
int getLine( char *buffer, FILE *input_stream )
{
    if ( fgets(buffer, MAXSTR, input_stream) )     /* get line                */
    {
        buffer[strlen(buffer) - 1] = '\0';         /* remove nl               */
        return TRUE;                               /* say ok                  */
    }
    return FALSE;                                  /* no more                 */
}


/*
//  set_ticker (adapted from bruce's alarmlib.c)
//
//     Called by initAlarm
//     arranges for the interval timer to issue
//     SIGALRM's at regular intervals
//
//     args:  n_msecs      --  in milliseconds, converted into micro seoncds
*/
void set_ticker( int n_msecs )
{
    struct itimerval new_timeset, old_timeset;
    long	n_sec, n_usecs;

    n_sec = n_msecs / 1000;
    n_usecs = ( n_msecs % 1000 ) * 1000L;

    new_timeset.it_interval.tv_sec  = n_sec;       /* set reload              */
    new_timeset.it_interval.tv_usec = n_usecs;	   /* new ticker value        */
    new_timeset.it_value.tv_sec     = n_sec;       /* store this              */
    new_timeset.it_value.tv_usec    = n_usecs;     /* and this                */

    if ( setitimer( ITIMER_REAL, &new_timeset, &old_timeset ) != 0 ){
        printf("Error with timer..errno=%d\n", errno );
        exit(1);
    }
}


/*
//  killZombies
//
//     Called periodically via alarm ( see set_ticker() )
//     uses waitpid with a WNOHANG option. This will return a
//     positive pid for all zombies, and clean them up in the process.
*/
void killZombies()
{
   // waitpid(-1,0,WNOHANG) loops through cleaning dead processes,
   // WNOHANG option allows it NOT to wait for process, but proceed.
   while ( waitpid(-1,0,WNOHANG) > 0)
       ;
}


/*
//  read_til_crnl
//
//     skip over all request info until a CRNL is seen
//
//     args: *fp         --  file pointer
*/
void read_til_crnl(FILE *fp)
{
    char buf[BUFSIZ];
    while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 )
        ;
}


/*
//  extractQuery
//
//     Str [item] might contain a '?', after which a query string is passed
//     as typically used for cgi scripts.
//     extractQuery() finds the '?' delimeter and, if found
//     sets the environment var QUERY_STRING = whatever is after the '?'
//
//     args: *item         --  symbol to use for prompt
*/
void extractQuery(char *item)
{
    char *qStr;                                 /* query string pointer       */

    for (qStr = item; *qStr ; qStr++) {         /* parse out the query string */
        if (*qStr == '?') {
            *qStr++ = '\0';
            break;
        }
    }
    
    if (*qStr) {                                /* if query string found      */             
        VLstore ("QUERY_STRING", qStr);         /* save it to var table       */
        VLstore ("REQUEST_METHOD", "GET");
    }
    else {
        VLstore ("QUERY_STRING", "");           /* ...else clear QUERY_STRING */
        VLstore ("REQUEST_METHOD", "");
    }
    environ = VLtable2environ();                /* externalize                */
}


/*
//  process_rq
//
//     Process all requests.
//     Request is handled in a new child process (fork)
//     the parent simply returns and looks for the next request.
//     The child must exit, so the exit(0); calls are very important here.
//     Scanf is used to break the request into command [cmd] and argument [arg]
//     the arg is further passed through modify_argument, to verify path, and
//     extractQuery, to extract cgi?query information at end of the request.
//     A flag called cmdType stores if cmd is of type HEAD or GET.
//     Finallly, [item] is passed to one of various "do" functions that handle
//     the request.
//
//     args: *rq         --  the request
//           *fp         --  file pointer
*/
void process_rq( char *rq, FILE *fp)
{
    char	cmd[BUFSIZ], arg[BUFSIZ];
    char	*item, *modify_argument();
    int         cmdType;
    
    if ( sscanf(rq, "%s%s", cmd, arg) != 2 )    /* parses rq into cmd & arg   */
    return;                                     /* ...bad rq, return          */

    item = modify_argument(arg, BUFSIZ);        /* see pg 51 - ensure no ".." */
    
    extractQuery(item);                         /* extract query from item str*/
    
    if ( fork() != 0 )                          /* create a new process       */
        return;                                 /* return if parent           */
    
                                                /* determine command          */
    if ( strcmp(cmd, "HEAD") == 0) 
        cmdType = HEAD;
    else if ( strcmp(cmd, "GET") == 0)
        cmdType = GET;
    else {
        cannot_do(fp);                          /*      501 Not Implemented   */
        exit(0);
    }

                                                /* deal with item             */
    if ( not_exist( item ) )
        do_404(item, fp, cmdType );             /*      404 Not Found         */
    else if ( isadir( item ) )
        do_ls(item, fp, cmdType );              /* ls,  200 OK                */
    else if ( ends_in_cgi( item ) )
        do_exec(item, fp, cmdType );            /* cgi, 200 OK                */
    else if ( ! usrRead(item) )
        do_403(fp, cmdType );                   /*      403 Forbidden         */
    else
        do_cat(item, fp, cmdType );             /* cat, 200 OK                */
    
    exit(0);
}


/*
//  modify_argument
//
//     For security, remove all ".." components in paths
//          and cleanup - if arg is "/" convert to "."
//
//     args: *arg         --  argument string being modified
//           *len         --  length of arg
//
//     returns: pointer to modified string
*/
char *modify_argument(char *arg, int len)
{
    char *nexttoken;
    char *copy = malloc(len);

    if ( copy == NULL )
        oops("memory error", 1);

    /* remove all ".." components from path */
    /* by tokeninzing on "/" and rebuilding */
    /* the string without the ".." items    */

    *copy = '\0';

    nexttoken = strtok(arg, "/");
    while( nexttoken != NULL )
    {
        if ( strcmp(nexttoken,"..") != 0 )
        {
            if ( *copy )
                strcat(copy, "/");
                strcat(copy, nexttoken);
            }
        nexttoken = strtok(NULL, "/");
	}
    strcpy(arg, copy);
    free(copy);

    /* the array is now cleaned up */
    
    /* handle a special case       */
    if ( strcmp(arg,"") == 0 )
        strcpy(arg, ".");
    return arg;
}


/*
//  header
//
//     Use by all the "do" functions
//     Sends a standard header to fp as the beginning of a reply.
//
//     args: *fp              --  file pointer
//           *content_type    --  filetype string from mFileTypes table
//           *resultStr       --  status result
*/
void header( FILE *fp, char *content_type, char *resultStr )
{
    time_t      timeval;                             /* struct for the time   */
    
    fprintf(fp, "HTTP/1.0 %s\r\n", resultStr);       /* status line           */

    time(&timeval);
    fprintf(fp, "Date: %s", ctime(&timeval));        /* print date time       */
    fprintf(fp, "Server: Russ/1.0.0\r\n");           /* print server name     */
    
    if ( content_type )
        fprintf(fp, "Content-type: %s\r\n", content_type ); /* file type      */
}


/*
//  cannot_do
//
//     do_501 unimplemented HTTP command
//
//     args: *fp         --  file pointer
*/
void cannot_do(FILE *fp)
{
    header(fp, "text/plain", "501 Not Implemented");         /* header        */

    fprintf(fp,"\r\n");
    fprintf(fp, "That command is not yet implemented\r\n");  /* msg           */
    fclose(fp);                                              /* close         */
}


/*
//  do_403
//
//     Permisions won't allow, so block
//
//     args: *fp         --  file pointer
//           *cmdType    --  type of cmd,  GET or HEAD
*/
void do_403(FILE *fp, int cmdType)
{
    header(fp, "text/plain", "403 Forbidden");               /* header        */

    if ( cmdType == GET ) {
        fprintf(fp,"\r\n");
        fprintf(fp, "Not authorized to view item requested\r\n");
    }
    fclose(fp);
}


/*
//  do_404
//
//     Item requested is not found
//
//     args: *item       --  item requested
//           *fp         --  file pointer
//           *cmdType    --  type of cmd,  GET or HEAD
*/
void do_404(char *item, FILE *fp, int cmdType)
{
    header(fp, "text/plain", "404 Not Found");               /* header        */

    if ( cmdType == GET ) {
        fprintf(fp,"\r\n");
        fprintf(fp, "The item you requested: %s\r\nis not found\r\n", item);
    }
    fclose(fp);
}


/*
//  do_cat
//
//     sends back contents after a header
//
//     args: *f         --  filename
//           *fpsock    --  socket pointer
//           *cmdType   --  type of cmd,  GET or HEAD
*/
void do_cat(char *f, FILE *fpsock, int cmdType)
{
    char	*extension = file_type(f);
    char	*content;
    FILE	*fpfile;
    int	c;    
    
    /*
    //  read the filetype associated with file .ext from table
    */
    content = readTable(extension);                      /* try read .ext     */
    if (! content)
        content = readTable("DEFAULT");                  /* try read default  */
    if (! content)
        content = "text/plain";                          /* ...use text plain */

    fpfile = fopen( f , "r");
    if ( fpfile != NULL )
    {
        header( fpsock, content, "200 OK" );             /* header            */
        if ( cmdType == GET ) {
            fprintf(fpsock, "\r\n");
            while( (c = getc(fpfile) ) != EOF )          /* send the file     */
                putc(c, fpsock);
        }
        fclose(fpfile);                                  /* cleanup           */
        fclose(fpsock);        
    }
}


/*
//  do_exec
//
//     exec a program/cgi and send it down the pipe
//
//     args: *prog       --  prog name to exec
//           *fp         --  file pointer
//           *cmdType    --  type of cmd,  GET or HEAD
*/
void do_exec( char *prog, FILE *fp, int cmdType)
{
    int	fd = fileno(fp);

    header(fp, NULL, "200 OK");
    if ( cmdType == GET ) {
        fflush(fp);
        dup2(fd, 1);                                /* get plumbing ready     */
        dup2(fd, 2);
        fclose(fp);
        execl(prog,prog,NULL);                      /* exec the file
                                                      (this should auto exit) */
        perror(prog);                               /*...error if get to here */
    }
    else
       fclose(fp); 
}


/*
//  do_ls
//
//     Looks for index.html in directory, if found, 
//     sends a do_cat of the index. Otherwise do_ls creates 
//     a menu of links of all the items in the 
//     directory by generating html.
//     The size and last modified date
//     of each item is displayed also.
//
//     args: *dir         --  symbol to use for prompt
//           *fp         --  string buffer
//           *cmdType   --  type of cmd,  GET or HEAD
*/
void do_ls(char *dir, FILE *fp, int cmdType)
{    
    DIR           *dir_ptr;                            /* the directory       */
    struct dirent *direntp;                            /* each entry          */
    struct stat   info;
    char          *displayAs, dirSym;
    int           paddingSize;
    
    if (( dir_ptr = opendir( dir )) == NULL ){         /* bad directory?      */
            fprintf(stderr,"Couldn't open directory");
            perror( dir );
    }
    
    if ( chdir(dir) == -1 )                            /* change to directory */
        oops(dir, 3);

    while ((direntp = readdir( dir_ptr )) != NULL) {   /* find index.html     */
        if ( strcmp(direntp->d_name, "index.html" ) == 0) {
             do_cat(direntp->d_name, fp, cmdType );    /* switch to exec      */            
             exit(0);                                  /* exit after exec     */
        }
    }
    rewinddir(dir_ptr);                                /* ...need to rewind   */
     
    header(fp, "text/html", "200 OK");
    fprintf(fp,"\r\n");
    fflush(fp);                                        /* flush fp buffer     */

    if ( cmdType == GET ) {
        
        fprintf(fp,"<html>\n");
        fprintf(fp,"<head><title>Index of...</title>\n");
        fprintf(fp,"</head>\n");
        fprintf(fp,"<body bgcolor = white>\n");
        fprintf(fp,"<center> Index of %s </center>\n", dir);
        fprintf(fp,"<hr>\n");
        fprintf(fp,"<pre>\n");    /* use preformatted mode, <BR>'s not needed */
        fprintf(fp,"%-20s %-35s %-20s\n", "NAME", "LAST MODIFIED", "SIZE");

        
        /*
        // read each item in directory
        */
        while (( direntp = readdir( dir_ptr )) != NULL ) {  
        
            if ( stat(direntp->d_name, &info) == -1 ) {      /* can't stat    */
                fprintf(fp,"could not get info on file %s\n", direntp->d_name);
                continue;
            }
            
            displayAs = direntp->d_name;               /* name display var    */
            
            if ( strcmp(direntp->d_name, "." ) == 0)   /* ignore '.' directory*/
                continue;
            if ( strcmp(direntp->d_name, ".." ) == 0)  /* fix '..' directory  */
                displayAs = "Parent Directory";
            
            paddingSize = 20-strlen(displayAs);        /* calc padding        */
            if ( isadir( direntp->d_name ) ) {         /* test for directory  */
                paddingSize--;
                dirSym = '/';
            }
            else
                dirSym = '\0';
            
            fprintf(fp,"<A HREF='%s%c'>%s%c</A>%*s %-35s %d bytes\n",
                direntp->d_name, dirSym,
                displayAs, dirSym, paddingSize, " ",
                rfc822_time(info.st_mtime), (int)info.st_size);
        }
        closedir(dir_ptr);                             /* close directory     */
        
        fprintf(fp,"</pre>\n");
        fprintf(fp,"</body></html>\n");                /* close html          */
        
        fclose(fp);        
    }
    else
        fclose(fp);
}


/*
//  readTable
//
//     looks through the table [mFileTypes] for extension [str]
//
//     args: *str    -- str (usually ".ext") being looked for
//
//     returns:  string of filetype for extension [str]
*/
char *readTable(char *str)
{
    int i;
    
    /* loop through table of extensions */
    for ( i = 0; strcmp(mFileTypes[i].extension, "\0") ; i++ ) {  
        if ( ! strcmp(mFileTypes[i].extension, str) )  /* find the ext        */
            return mFileTypes[i].fileType;             /* return fileType     */
    }
    return NULL;                                       /* str not found       */
}


/*
//  isadir
//
//     Test if pathname [p] is a directory
//
//     args: *f         --  pathname to test
//
//     returns: TRUE if pathname [p] is a directory
*/
int isadir(char *f)
{
    struct stat info;
    return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) );
}


/*
//  not_exist
//
//     read next line from input_stream.
//
//     args: *f         --  filename
//
//     returns: FALSE on EOF
*/
int not_exist(char *f)
{
    struct stat info;
    return( stat(f,&info) == -1 && errno == ENOENT );
}


/*
//  usrRead
//
//     Tests if file [f] is user readable
//     determined by the st_mode stat permissions, 
//     stat(f,&info) and (S_IRUSR & info.st_mode)
//     checks the mode of the file and see if permissionas
//     are set to allow a read
//     i.e. chmod foo +r
//
//     args: *f         --  filename
//
//     returns: TRUE if the file permissions respond as readable 
*/
int usrRead(char *f)
{
    struct stat info;
        
    return( stat(f,&info) != -1 && (S_IRUSR & info.st_mode));
}


/*
//  ends_in_cgi
//
//     test if [f] ends with cgi
//
//     args: *f         --  filename
//
//     returns: TRUE if [f] ends in cgi
*/
int ends_in_cgi(char *f)
{
    return ( strcmp( file_type(f), "cgi" ) == 0 );
}


/*
//  rfc822_time
//
//     return a time string suitable for web servers
//     such as:  Sun, 06 Nov 1994 08:49:37 GMT
//     uses gmtime() to get struct
//     then uses asctime to translate to English
//     then rearranges using sprintf
//
//     args: *thetime         --  a time_t value
//
//     returns: a pointer to a static buffer string
*/
char *rfc822_time(time_t thetime)
{
        struct  tm *t;
        char    *str;
        int     d;
        static  char retval[36];

        t = gmtime( &thetime );                       /* break into parts     */
        str = asctime( t );                           /* create string        */
        d = atoi( str + 8 );
        sprintf(retval,"%.3s, %02d %.3s %.4s %.8s GMT", 
                        str ,   d, str+4, str+20, str+11 );
        return retval;
}


/*
//  file_type
//
//     return extension of file [f]
//
//     args: *f         --  filename
//
//     returns: extension on filename [f]
*/
char *file_type(char *f)
{
    char	*cp;                                  /* char ptr             */
    if ( (cp = strrchr(f, '.' )) != NULL )            /* look for '.'         */
        return cp+1;
    return "";
}


/*
//  full_hostname
//
// NOTE: this returns a ptr to a static buffer that is
//       overwritten with each call. ( you know what to do.)
//
//     returns: returns full `official' hostname for current machine
*/
char *full_hostname()
{
    struct	hostent		*hp;
    char	hname[MAXHOSTNAMELEN];
    static  char fullname[MAXHOSTNAMELEN];

    if ( gethostname(hname,MAXHOSTNAMELEN) == -1 )         /* get rel name    */
    {
        perror("gethostname");
        exit(1);
    }
    hp = gethostbyname( hname );		      /* get info about host  */
    if ( hp == NULL )			              /*   or die             */
		return NULL;
    strcpy( fullname, hp->h_name );		      /* store foo.bar.com    */
    return fullname;			              /* and return it        */
}


/*
//  fatal
//
//     Fatal error message to stderr then exit (never returns)
//
//     args:  errStr      --  error message to display
*/
void fatal(char *errStr)
{
    fprintf(stderr, "%s\n", errStr);
    exit(1);
}
