/*
 * httpd implementation for busybox
 *
 * Copyright (C) 2002 Glenn Engel <glenne@engel.org>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *****************************************************************************
 *
 * Typical usage:  
 *   cd /var/www
 *   httpd 
 * This is equivalent to
 *    cd /var/www
 *    httpd -p 80 -c /etc/httpd.conf -r "Web Server Authentication"
 *
 * When a url contains "cgi-bin" it is assumed to be a cgi script.  The
 * server changes directory to the location of the script and executes it
 * after setting QUERY_STRING and other environment variables.  If url args
 * are included in the url or as a post, the args are placed into decoded
 * environment variables.  e.g. /cgi-bin/setup?foo=Hello%20World  will set
 * the $CGI_foo environment variable to "Hello World".
 *
 * The server can also be invoked as a url arg decoder and html text encoder
 * as follows:
 *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
 *  bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
 *
 * httpd.conf has the following format:
 
ip:10.10.         # Allow any address that begins with 10.10.
ip:172.20.        # Allow 172.20.x.x
ip:127.0.0.1      # Allow local loopback connections
/cgi-bin:foo:bar  # Require user foo, pwd bar on urls starting with /cgi-bin
/:admin:setup     # Require user admin, pwd setup on urls starting with /

 *
 * To open up the server: 
 * ip:*              # Allow any IP address
 * /:*               # no password required for urls starting with / (all)
 *
 * Processing of the file stops on the first sucessful match.  If the file
 * is not found, the server is assumed to be wide open.
 *
 *****************************************************************************
 *
 * Desired enhancements:
 *   cache httpd.conf
 *   support tinylogin
 *
 */
#include <stdio.h>
#include <ctype.h>         /* for isspace           */
#include <stdarg.h>        /* for varargs           */
#include <string.h>        /* for strerror          */
#include <stdlib.h>        /* for malloc            */
#include <time.h>
#include <errno.h>
#include <unistd.h>        /* for close             */
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>    /* for connect and socket*/
#include <netinet/in.h>    /* for sockaddr_in       */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>

static const char httpdVersion[] = "busybox httpd/1.13 3-Jan-2003";

// #define DEBUG 1 
#ifndef HTTPD_STANDALONE
#include <config.h>
#include <busybox.h>
// Note: xfuncs are not used because we want the server to keep running
//       if something bad happens due to a malformed user request.
//       As a result, all memory allocation is checked rigorously
#else
/* standalone */
#define CONFIG_FEATURE_HTTPD_BASIC_AUTH
void show_usage()
{
  fprintf(stderr,"Usage: httpd [-p <port>] [-c configFile] [-d/-e <string>] [-r realm]\n");
}
#endif

/* minimal global vars for busybox */
#ifndef ENVSIZE
#define ENVSIZE 50
#endif
int debugHttpd;
static char **envp;
static int envCount;
static char *realm = "Web Server Authentication";
static char *configFile;

static const char* const suffixTable [] = {
  ".htm.html", "text/html",
  ".jpg.jpeg", "image/jpeg",
  ".gif", "image/gif",
  ".png", "image/png",
  ".txt.h.c.cc.cpp", "text/plain",
  0,0
  };

typedef enum
{
  HTTP_OK = 200,
  HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
  HTTP_NOT_FOUND = 404,
  HTTP_INTERNAL_SERVER_ERROR = 500,
  HTTP_NOT_IMPLEMENTED = 501,  /* used for unrecognized requests */
  HTTP_BAD_REQUEST = 400,  /* malformed syntax */
#if 0 /* future use */
  HTTP_CONTINUE = 100,
  HTTP_SWITCHING_PROTOCOLS = 101,
  HTTP_CREATED = 201,
  HTTP_ACCEPTED = 202,
  HTTP_NON_AUTHORITATIVE_INFO = 203,
  HTTP_NO_CONTENT = 204,
  HTTP_MULTIPLE_CHOICES = 300,
  HTTP_MOVED_PERMANENTLY = 301,
  HTTP_MOVED_TEMPORARILY = 302,
  HTTP_NOT_MODIFIED = 304,
  HTTP_PAYMENT_REQUIRED = 402,
  HTTP_FORBIDDEN = 403,
  HTTP_BAD_GATEWAY = 502,
  HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
  HTTP_RESPONSE_SETSIZE=0xffffffff
#endif
} HttpResponseNum;

typedef struct
{
  HttpResponseNum type;
  const char *name;
  const char *info;
} HttpEnumString;

static const HttpEnumString httpResponseNames[] = {
  { HTTP_OK, "OK" },
  { HTTP_NOT_IMPLEMENTED, "Not Implemented", 
    "The requested method is not recognized by this server." },
  { HTTP_UNAUTHORIZED, "Unauthorized", "" },
  { HTTP_NOT_FOUND, "Not Found",
    "The requested URL was not found on this server." },
  { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error"
    "Internal Server Error" },
  { HTTP_BAD_REQUEST, "Bad Request" ,
    "Unsupported method.\n" },
#if 0
  { HTTP_CREATED, "Created" },
  { HTTP_ACCEPTED, "Accepted" },
  { HTTP_NO_CONTENT, "No Content" },
  { HTTP_MULTIPLE_CHOICES, "Multiple Choices" },
  { HTTP_MOVED_PERMANENTLY, "Moved Permanently" },
  { HTTP_MOVED_TEMPORARILY, "Moved Temporarily" },
  { HTTP_NOT_MODIFIED, "Not Modified" },
  { HTTP_FORBIDDEN, "Forbidden", "" },
  { HTTP_BAD_GATEWAY, "Bad Gateway", "" },
  { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" },
#endif
};

/****************************************************************************
 *
 > $Function: encodeString()
 *
 * $Description: Given a string, html encode special characters.
 *   This is used for the -e command line option to provide an easy way
 *   for scripts to encode result data without confusing browsers.  The
 *   returned string pointer is memory allocated by malloc().
 *
 * $Parameters:
 *      (const char *) string . . The first string to encode.
 *
 * $Return: (char *) . . . .. . . A pointer to the encoded string.
 *
 * $Errors: Returns a null string ("") if memory is not available.
 *
 ****************************************************************************/
static char *encodeString(const char *string)
{
  /* take the simple route and encode everything */
  /* could possibly scan once to get length.     */
  int len = strlen(string);
  char *out = (char*)malloc(len*5 +1); 
  char *p=out;
  char ch;
  if (!out) return "";
  while ((ch = *string++))
  {
    // very simple check for what to encode
    if (isalnum(ch)) *p++ = ch;
    else p += sprintf(p,"&#%d", (unsigned char) ch);
  }
  *p=0;
  return out;
}

/****************************************************************************
 *
 > $Function: decodeString()
 *
 * $Description: Given a URL encoded string, convert it to plain ascii.
 *   Since decoding always makes strings smaller, the decode is done in-place.
 *   Thus, callers should strdup() the argument if they do not want the
 *   argument modified.  The return is the original pointer, allowing this
 *   function to be easily used as arguments to other functions.
 *
 * $Parameters:
 *      (char *) string . . . The first string to decode.
 *
 * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
 *
 * $Errors: None
 *
 ****************************************************************************/
static char *decodeString(char *string)
{
  /* note that decoded string is always shorter than original */
  char *orig = string;
  char *ptr = string;
  while (*ptr)
  {
    if (*ptr == '+') { *string++ = ' '; ptr++; }
    else if (*ptr != '%') *string++ = *ptr++;
    else
    {
      unsigned int value;
      sscanf(ptr+1,"%2X",&value);
      *string++ = value;
      ptr += 3;
    }
  }
  *string = '\0';
  return orig;
}


/****************************************************************************
 *
 > $Function: addEnv()
 *
 * $Description: Add an enviornment variable setting to the global list.
 *    A NAME=VALUE string is allocated, filled, and added to the list of
 *    environment settings passed to the cgi execution script.
 *
 * $Parameters:
 *      (char *) name . . . The environment variable name.
 *      (char *) value  . . The value to which the env variable is set.
 *
 * $Return: (void)  
 *
 * $Errors: Silently returns if the env runs out of space to hold the new item
 *
 ****************************************************************************/
static void addEnv(const char *name, const char *value)
{
  char *s;
  if (envCount >= ENVSIZE) return;
  if (!value) value = "";
  s=(char*)malloc(strlen(name)+strlen(value)+2);
  if (s)
  {
    sprintf(s,"%s=%s",name, value);
    envp[envCount++]=s;
    envp[envCount]=0;
  }
}

/****************************************************************************
 *
 > $Function: addEnvCgi
 *
 * $Description: Create environment variables given a URL encoded arg list.
 *   For each variable setting the URL encoded arg list, create a corresponding
 *   environment variable.  URL encoded arguments have the form
 *      name1=value1&name2=value2&name3=value3
 *
 * $Parameters:
 *      (char *) pargs . . . . A pointer to the URL encoded arguments.
 *
 * $Return: None
 *
 * $Errors: None
 *
 ****************************************************************************/
static void addEnvCgi(const char *pargs)
{
  char *args;
  if (pargs==0) return;
  
  /* args are a list of name=value&name2=value2 sequences */
  args = strdup(pargs);
  while (args && *args)
  {
    char *sep;
    char *name=args;
    char *value=strchr(args,'=');
    char *cginame;
    if (!value) break;
    *value++=0;
    sep=strchr(value,'&');
    if (sep)
    {
      *sep=0;
      args=sep+1;
    }
    else
    {
      sep = value + strlen(value);
      args = 0; /* no more */
    }
    cginame=(char*)malloc(strlen(decodeString(name))+5);
    if (!cginame) break;
    sprintf(cginame,"CGI_%s",name);
    addEnv(cginame,decodeString(value));
    free(cginame);
  }
}

#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
static const unsigned char base64ToBin[] = {
    255 /* ' ' */,  255 /* '!' */,   255 /* '"' */,   255 /* '#' */,
     1 /* '$' */,  255 /* '%' */,    255 /* '&' */,   255 /* ''' */,
    255 /* '(' */,  255 /* ')' */,   255 /* '*' */,    62 /* '+' */,
    255 /* ',' */,  255 /* '-' */,   255 /* '.' */,    63 /* '/' */,
    52 /* '0' */,    53 /* '1' */,    54 /* '2' */,    55 /* '3' */,
    56 /* '4' */,    57 /* '5' */,    58 /* '6' */,    59 /* '7' */,
    60 /* '8' */,    61 /* '9' */,   255 /* ':' */,   255 /* ';' */,
    255 /* '<' */,   00 /* '=' */,   255 /* '>' */,   255 /* '?' */,
    255 /* '@' */,   00 /* 'A' */,    01 /* 'B' */,    02 /* 'C' */,
    03 /* 'D' */,    04 /* 'E' */,    05 /* 'F' */,    06 /* 'G' */,
     7 /* 'H' */,     8 /* 'I' */,     9 /* 'J' */,    10 /* 'K' */,
    11 /* 'L' */,    12 /* 'M' */,    13 /* 'N' */,    14 /* 'O' */,
    15 /* 'P' */,    16 /* 'Q' */,    17 /* 'R' */,    18 /* 'S' */,
    19 /* 'T' */,    20 /* 'U' */,    21 /* 'V' */,    22 /* 'W' */,
    23 /* 'X' */,    24 /* 'Y' */,    25 /* 'Z' */,   255 /* '[' */,
    255 /* '\' */,  255 /* ']' */,   255 /* '^' */,   255 /* '_' */,
    255 /* '`' */,   26 /* 'a' */,    27 /* 'b' */,    28 /* 'c' */,
    29 /* 'd' */,    30 /* 'e' */,    31 /* 'f' */,    32 /* 'g' */,
    33 /* 'h' */,    34 /* 'i' */,    35 /* 'j' */,    36 /* 'k' */,
    37 /* 'l' */,    38 /* 'm' */,    39 /* 'n' */,    40 /* 'o' */,
    41 /* 'p' */,    42 /* 'q' */,    43 /* 'r' */,    44 /* 's' */,
    45 /* 't' */,    46 /* 'u' */,    47 /* 'v' */,    48 /* 'w' */,
    49 /* 'x' */,    50 /* 'y' */,    51 /* 'z' */,   255 /* '{' */,
    255 /* '|' */,  255 /* '}' */,   255 /* '~' */,   255 /* '' */
};

/****************************************************************************
 *
 > $Function: decodeBase64()
 *
 > $Description: Decode a base 64 data stream as per rfc1521.
 *    Note that the rfc states that none base64 chars are to be ignored.
 *    Since the decode always results in a shorter size than the input, it is
 *    OK to pass the input arg as an output arg.
 *
 * $Parameters:
 *      (void *) outData. . . Where to place the decoded data.
 *      (size_t) outDataLen . The length of the output data string.
 *      (void *) inData . . . A pointer to a base64 encoded string.
 *      (size_t) inDataLen  . The length of the input data string.
 *
 * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
 *
 * $Errors: None
 *
 ****************************************************************************/
static size_t decodeBase64(void *outData, size_t outDataLen,
			   void *inData, size_t inDataLen)
{
  int i = 0;
  unsigned char *in = inData;
  unsigned char *out = outData;
  unsigned long ch = 0;
  while (inDataLen && outDataLen)
  {
    unsigned char conv = 0;
    unsigned char newch;
    
    while (inDataLen)
    {
      inDataLen--;
      newch = *in++;
      if ((newch < '0') || (newch > 'z')) continue;
      conv = base64ToBin[newch - 32];
      if (conv == 255) continue;
      break;
    }
    ch = (ch << 6) | conv;
    i++;
    if (i== 4)
    {
      if (outDataLen >= 3)
      {
	*(out++) = (unsigned char) (ch >> 16);
	*(out++) = (unsigned char) (ch >> 8);
	*(out++) = (unsigned char) ch;
	outDataLen-=3;
      }
      
      i = 0;
    }
    
    if ((inDataLen == 0) && (i != 0))
    {
      /* error - non multiple of 4 chars on input */
      break;
    }

  }

  /* return the actual number of chars in output array */
  return out-(unsigned char*) outData;
}
#endif

/****************************************************************************
 *
 > $Function: perror_and_exit()
 *
 > $Description: A helper function to print an error and exit.
 *
 * $Parameters:
 *      (const char *) msg . . . A 'context' message to include.
 *
 * $Return: None
 *
 * $Errors: None
 *
 ****************************************************************************/
static void perror_exit(const char *msg)
{
  perror(msg);
  exit(1);
}


/****************************************************************************
 *
 > $Function: strncmpi()
 *
 * $Description: compare two strings without regard to case.
 *
 * $Parameters:
 *      (char *) a . . . . . The first string.
 *      (char *) b . . . . . The second string.
 *      (int) n  . . . . . . The number of chars to compare.
 *
 * $Return: (int) . . . . . . 0 if strings equal. 1 if a>b, -1 if b < a.
 *
 * $Errors: None
 *
 ****************************************************************************/
#define __toupper(c)   ((('a' <= (c))&&((c) <= 'z')) ? ((c) - 'a' + 'A') : (c))
#define __tolower(c)   ((('A' <= (c))&&((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
static int strncmpi(const char *a, const char *b,int n)
{
  char a1,b1;
  a1 = b1 = 0;

  while(n-- && ((a1 = *a++) != '\0') && ((b1 = *b++) != '\0'))
  {
    if(a1 == b1) continue;       /* No need to convert */
    a1 = __tolower(a1);
    b1 = __tolower(b1);
    if(a1 != b1) break;          /* No match, abort */
  }
  if (n>=0) 
  {
    if(a1 > b1) return 1;
    if(a1 < b1) return -1;
  }
  return 0;
}

/****************************************************************************
 *
 > $Function: openServer()
 *
 * $Description: create a listen server socket on the designated port.
 *
 * $Parameters:
 *      (int) port . . . The port to listen on for connections.
 *
 * $Return: (int)  . . . A connection socket. -1 for errors.
 *
 * $Errors: None
 *
 ****************************************************************************/
static int openServer(int port)
{
  struct sockaddr_in lsocket;
  int fd;
  
  /* create the socket right now */
  /* inet_addr() returns a value that is already in network order */
  memset(&lsocket, 0, sizeof(lsocket));
  lsocket.sin_family = AF_INET;
  lsocket.sin_addr.s_addr = INADDR_ANY;
  lsocket.sin_port = htons(port) ;
  fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd >= 0)
  {
    /* tell the OS it's OK to reuse a previous address even though */
    /* it may still be in a close down state.  Allows bind to succeed. */
    int one = 1;
#ifdef SO_REUSEPORT
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&one, sizeof(one)) ;
#else
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) ;
#endif
    if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0)
    {
      listen(fd, 9);
      signal(SIGCHLD, SIG_IGN);   /* prevent zombie (defunct) processes */
    }
    else
    {
      perror("failure to bind to server port");
      shutdown(fd,0);
      close(fd);
      fd = -1;
      }
    }
    else
    {
      fprintf(stderr,"httpd: unable to create socket \n");
    }
  return fd;
}

static int sendBuf(int s, char *buf, int len)
{
  if (len == -1) len = strlen(buf);
  return send(s, buf, len, 0);
}

/****************************************************************************
 *
 > $Function: sendHeaders()
 *
 * $Description: Create and send HTTP response headers.
 *   The arguments are combined and sent as one write operation.  Note that
 *   IE will puke big-time if the headers are not sent in one packet and the
 *   second packet is delayed for any reason.  If contentType is null the
 *   content type is assumed to be text/html
 *
 * $Parameters:
 *      (int) s . . . The http socket.
 *      (HttpResponseNum) responseNum . . . The result code to send.
 *      (const char *) contentType  . . . . A string indicating the type.
 *      (int) contentLength . . . . . . . . Content length.  -1 if unknown.
 *      (time_t) expire . . . . . . . . . . Expiration time (secs since 1970)
 *
 * $Return: (int)  . . . . Always 0
 *
 * $Errors: None
 *
 ****************************************************************************/
static int sendHeaders(int s, HttpResponseNum responseNum ,
		       const char *contentType,
		       int contentLength, time_t expire)
{
  char buf[1200];
  const char *responseString = "";
  const char *infoString = 0;
  unsigned int i;
  time_t timer = time(0);
  char timeStr[80];
  for (i=0;
       i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++)
  {
    if (httpResponseNames[i].type != responseNum) continue;
    responseString = httpResponseNames[i].name;
    infoString = httpResponseNames[i].info;
    break;
  }
  if (infoString || !contentType)
  {
    contentType = "text/html";
  }
  
  sprintf(buf, "HTTP/1.0 %d %s\nContent-type: %s\r\n",
	  responseNum, responseString, contentType);

  /* emit the current date */
  strftime(timeStr, sizeof(timeStr),
           "%a, %d %b %Y %H:%M:%S GMT",gmtime(&timer));
  sprintf(buf+strlen(buf), "Date: %s\r\n", timeStr);
  sprintf(buf+strlen(buf), "Connection: close\r\n");
  if (expire)
  {
    strftime(timeStr, sizeof(timeStr),
             "%a, %d %b %Y %H:%M:%S GMT",gmtime(&expire));
    sprintf(buf+strlen(buf), "Expire: %s\r\n", timeStr);
  }

  if (responseNum == HTTP_UNAUTHORIZED)
  {
    sprintf(buf+strlen(buf),
	    "WWW-Authenticate: Basic realm=\"%s\"\r\n", realm);
  }
  if (contentLength != -1)
  {
    int len = strlen(buf);
    sprintf(buf+len,"Content-length: %d\r\n", contentLength);
  }
  strcat(buf,"\r\n");
  if (infoString)
  {
    sprintf(buf+strlen(buf),
	    "<HEAD><TITLE>%d %s</TITLE></HEAD>\n"
	    "<BODY><H1>%d %s</H1>\n%s\n</BODY>\n",
	    responseNum, responseString,
	    responseNum, responseString,
	    infoString);
  }
#ifdef DEBUG
  if (debugHttpd) fprintf(stderr,"Headers:'%s'", buf);
#endif
  sendBuf(s, buf,-1);
  return 0;
}

/****************************************************************************
 *
 > $Function: getLine()
 *
 * $Description: Read from the socket until an end of line char found.
 *
 *   Characters are read one at a time until an eol sequence is found.
 *
 * $Parameters:
 *      (int) s . . . . . The socket fildes.
 *      (char *) buf  . . Where to place the read result.
 *      (int) maxBuf  . . Maximum number of chars to fit in buf.
 *
 * $Return: (int) . . . . number of characters read.  -1 if error.
 *
 ****************************************************************************/
static int getLine(int s, char *buf, int maxBuf)
{
  int  count = 0;
  while (recv(s, buf+count, 1, 0) == 1)
  {
    if (buf[count] == '\r') continue;
    if (buf[count] == '\n')
    {
      buf[count] = 0;
      return count;
    }
    count++;
  }
  if (count) return count;
  else return -1;
}

/****************************************************************************
 *
 > $Function: sendCgi()
 *
 * $Description: Execute a CGI script and send it's stdout back
 *
 *   Environment variables are set up and the script is invoked with pipes
 *   for stdin/stdout.  If a post is being done the script is fed the POST
 *   data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
 *
 * $Parameters:
 *      (int ) s . . . . . . . . The session socket.
 *      (const char *) url . . . The requested URL (with leading /).
 *      (const char *urlArgs). . Any URL arguments.
 *      (const char *body) . . . POST body contents.
 *      (int bodyLen)  . . . . . Length of the post body.
 
 *
 * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
 *
 * $Errors: None
 *
 ****************************************************************************/
static int sendCgi(int s, const char *url, 
                   const char *request, const char *urlArgs,
		   const char *body, int bodyLen)
{
  int fromCgi[2];  /* pipe for reading data from CGI */
  int toCgi[2];    /* pipe for sending data to CGI */
  
  char *argp[] = { 0, 0 };
  int pid=0;
  int inFd=inFd;
  int outFd;
  int firstLine=1;

  do
  {
    if (pipe(fromCgi) != 0)
    {
      break;
    }
    if (pipe(toCgi) != 0)
    {
      break;
    }

    pid = fork();
    if (pid < 0)
    {
	pid = 0;
	break;;
    }
    
    if (!pid)   
    {
      /* child process */
      char *script;
      char *directory;
      inFd=toCgi[0];
      outFd=fromCgi[1];

      dup2(inFd, 0);  // replace stdin with the pipe
      dup2(outFd, 1);  // replace stdout with the pipe
      if (!debugHttpd) dup2(outFd, 2);  // replace stderr with the pipe
      close(toCgi[0]);
      close(toCgi[1]);
      close(fromCgi[0]);
      close(fromCgi[1]);

#if 0
      fcntl(0,F_SETFD, 1);
      fcntl(1,F_SETFD, 1);
      fcntl(2,F_SETFD, 1);
#endif
      
      script = (char*) malloc(strlen(url)+2);
      if (!script) _exit(242);
      sprintf(script,".%s",url);
	
      envCount=0;
      addEnv("SCRIPT_NAME",script);
      addEnv("REQUEST_METHOD",request);
      addEnv("QUERY_STRING",urlArgs);
      addEnv("SERVER_SOFTWARE",httpdVersion);
      if (strncmpi(request,"POST",4)==0) addEnvCgi(body);
      else addEnvCgi(urlArgs);
      
      /*
       * Most HTTP servers chdir to the cgi directory.
       */
      while (*url == '/') url++;  // skip leading slash(s)
      directory = strdup( url );  
      if ( directory == (char*) 0 )
	script = (char*) (url);	/* ignore errors */
      else
      {
	script = strrchr( directory, '/' );
	if ( script == (char*) 0 )
	  script = directory;
	else
	{
	  *script++ = '\0';
	  (void) chdir( directory );	/* ignore errors */
	}
      }
      // now run the program.  If it fails, use _exit() so no destructors
      // get called and make a mess.
      execve(script, argp, envp);
      
#ifdef DEBUG
      fprintf(stderr, "exec failed\n");
#endif
      close(2);
      close(1);
      close(0);
      _exit(242);
    } /* end child */

    /* parent process */
    inFd=fromCgi[0];
    outFd=toCgi[1];
    close(fromCgi[1]);
    close(toCgi[0]);
    if (body) write(outFd, body, bodyLen);
    close(outFd);

  } while (0);

  if (pid)
  {
    int status;
    pid_t dead_pid;
    
    while (1)
    {
      struct timeval timeout;
      fd_set readSet;
      char buf[160];
      int nfound;
      int count;
  
      FD_ZERO(&readSet);
      FD_SET(inFd, &readSet);
  
      /* Now wait on the set of sockets! */
      timeout.tv_sec = 0;
      timeout.tv_usec = 10000;
      nfound = select(inFd+1, &readSet, 0, 0, &timeout);

      if (nfound <= 0)
      {
	dead_pid = waitpid(pid, &status, WNOHANG);
	if (dead_pid != 0)
	{
	  close(fromCgi[0]);
	  close(fromCgi[1]);
	  close(toCgi[0]);
	  close(toCgi[1]);
#ifdef DEBUG
	  if (debugHttpd)
	  {
	    if (WIFEXITED(status))
	      fprintf(stderr,"piped has exited with status=%d\n", WEXITSTATUS(status));
	    if (WIFSIGNALED(status))
	      fprintf(stderr,"piped has exited with signal=%d\n", WTERMSIG(status));
	  }
#endif  
	  pid = -1;
	  break;
	}
      }
      else
      {
	// There is something to read
	count = read(inFd,buf,sizeof(buf)-1);
	// If a read returns 0 at this point then some type of error has
	// occurred.  Bail now.
	if (count == 0) break;
	if (count > 0)
	{
	  if (firstLine)
	  {
	    /* check to see if the user script added headers */
	    if (strcmp(buf,"HTTP")!= 0)
	    {
	      write(s,"HTTP/1.0 200 OK\n", 16);
	    }
	    if (strstr(buf,"ontent-") == 0)
	    {
	      write(s,"Content-type: text/plain\n\n", 26);
	    }
	    
	    firstLine=0;
	  }
	  write(s,buf,count);
#ifdef DEBUG
	  if (debugHttpd) fprintf(stderr,"cgi read %d bytes\n", count);
#endif
	}
      }
    }
  }
  return 0;
}

/****************************************************************************
 *
 > $Function: sendFile()
 *
 * $Description: Send a file response to an HTTP request
 *
 * $Parameters:
 *      (int) s  . . . . . . . The http session socket.
 *      (const char *) url . . The URL requested.
 *
 * $Return: (int)  . . . . . . Always 0.
 *
 ****************************************************************************/
static int sendFile(int s, const char *url)
{
  char *suffix = strrchr(url,'.');
  const char *content = "application/octet-stream";
  int  f;

  if (suffix)
  {
    const char ** table;
    for (table = (const char **) &suffixTable[0]; 
         *table && (strstr(*table, suffix) == 0); table+=2);
    if (table) content = *(table+1);
  }
  
  if (*url == '/') url++;
  suffix = strchr(url,'?');
  if (suffix) *suffix = 0;

#ifdef DEBUG
    fprintf(stderr,"Sending file '%s'\n", url);
#endif
  
  f = open(url,O_RDONLY, 0444);
  if (f >= 0)
  {
    char buf[1450];
    int count;
    sendHeaders(s, HTTP_OK, content, -1, 0 );
    while ((count = read(f, buf, sizeof(buf))))
    {
      sendBuf(s, buf, count);
    }
    close(f);
  }
  else
  {
#ifdef DEBUG
    fprintf(stderr,"Unable to open '%s'\n", url);
#endif
    sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0);
  }
  
  return 0;
}

/****************************************************************************
 *
 > $Function: checkPerm()
 *
 * $Description: Check the permission file for access.
 *
 *   Both IP addresses as well as url pathnames can be specified.  If an IP
 *   address check is desired, the 'path' should be specified as "ip" and the
 *   dotted decimal IP address placed in request.
 *
 *   For url pathnames, place the url (with leading /) in 'path' and any
 *   authentication information in request.  e.g. "user:pass"
 *
 *******
 *
 *   Keep the algorithm simple.
 *   If config file isn't present, everything is allowed.
 *   Run down /etc/httpd.hosts a line at a time.
 *   Stop if match is found.
 *   Entries are of the form:
 *   ip:10.10    # any address that begins with 10.10
 *   dir:user:pass  # dir security for dirs that start with 'dir'
 *
 * httpd.conf has the following format:
 *   ip:10.10.         # Allow any address that begins with 10.10.
 *   ip:172.20.        # Allow 172.20.x.x
 *   /cgi-bin:foo:bar  # Require user foo, pwd bar on urls starting with /cgi-bin
 *   /:foo:bar         # Require user foo, pwd bar on urls starting with /
 *
 * To open up the server: 
 *   ip:*              # Allow any IP address
 *   /:*               # no password required for urls starting with / (all)
 *
 * $Parameters:
 *      (const char *) path  . . . . The file path or "ip" for ip addresses.
 *      (const char *) request . . . User information to validate.
 *
 * $Return: (int)  . . . . . . . . . 1 if request OK, 0 otherwise.
 *
 ****************************************************************************/
static int checkPerm(const char *path, const char *request)
{
    FILE *f=NULL;
    int rval;
    char buf[80];
    char *p;
    int ipaddr=0;

    /* If httpd.conf not there assume anyone can get in */
    if (configFile) f = fopen(configFile,"r");
    if(f == NULL) f = fopen("/etc/httpd.conf","r");
    if(f == NULL) f = fopen("httpd.conf","r");
    if(f == NULL) {
        return(1);
    }
    if (strcmp("ip",path) == 0) ipaddr=1;

    rval=0;

    /* This could stand some work */
    while ( fgets(buf, 80, f) != NULL)
    {
        if(buf[0] == '#') continue;
        if(buf[0] == '\0') continue;
        for(p = buf + (strlen(buf) - 1); p >= buf; p--)
	{
            if(isspace(*p)) *p = 0;
        }
	
        p = strchr(buf,':');
	if (!p) continue;
	*p++=0;
#ifdef DEBUG
        fprintf(stderr,"checkPerm: '%s' ? '%s'\n",buf,path);
#endif
	if((ipaddr ? strcmp(buf,path) : strncmp(buf, path, strlen(buf))) == 0)
	{
	  /* match found.  Check request */
	  if ((strcmp("*",p) == 0) ||
	      (strcmp(p, request) == 0) ||
	      (ipaddr && (strncmp(p, request, strlen(p)) == 0)))
	  {
	    rval = 1;
	    break;
	  }

	  /* reject on first failure for non ipaddresses */
	  if (!ipaddr) break;
	}
    };
    fclose(f);
    return(rval);
};


/****************************************************************************
 *
 > $Function: handleIncoming()
 *
 * $Description: Handle an incoming http request.
 *
 * $Parameters:
 *      (s) s . . . . . The http request socket.
 *
 * $Return: (int) . . . Always 0.
 *
 ****************************************************************************/
static int handleIncoming(int s)
{
  char buf[8192];
  char url[8192];  /* hold args too initially */
  char credentials[80];
  char request[20];
  long length=0;
  int  major;
  int  minor;
  char *urlArgs;
  char *body=0;

  credentials[0] = 0;
  do
  {
    int  count = getLine(s, buf, sizeof(buf));
    int blank;
    if (count <= 0)  break;
    count = sscanf(buf, "%9s %1000s HTTP/%d.%d", request,
		   url, &major, &minor);
    
    if (count < 2) 
    {
      /* Garbled request/URL */
#if 0
      genHttpHeader(&requestInfo,
		    HTTP_BAD_REQUEST, requestInfo.dataType,
		    HTTP_LENGTH_UNKNOWN);
#endif
      break;
    }
    
    /* If no version info, assume 0.9 */
    if (count != 4)
    {
      major = 0;
      minor = 9;
    }

    /* extract url args if present */
    urlArgs = strchr(url,'?');
    if (urlArgs)
    {
      *urlArgs=0;
      urlArgs++;
    }
    
#ifdef DEBUG
    if (debugHttpd) fprintf(stderr,"url='%s', args=%s\n", url, urlArgs);
#endif  

    // read until blank line(s)
    blank = 0;
    while ((count = getLine(s, buf, sizeof(buf))) >= 0)
    {
      if (count == 0)
      {
	if (major > 0) break;
	blank++;
	if (blank == 2) break;
      }
#ifdef DEBUG
      if (debugHttpd) fprintf(stderr,"Header: '%s'\n", buf);
#endif  

      /* try and do our best to parse more lines */
      if ((strncmpi(buf, "Content-length:", 15) == 0))
      {
	sscanf(buf, "%*s %ld", &length);
      }    
#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
      else if (strncmpi(buf, "Authorization:", 14) == 0)
      {
	/* We only allow Basic credentials.
	 * It shows up as "Authorization: Basic <userid:password>" where
	 * the userid:password is base64 encoded.
	 */
	char *ptr = buf+14;
	while (*ptr == ' ') ptr++;
	if (strncmpi(ptr, "Basic", 5) != 0) break;
	ptr += 5;
	while (*ptr == ' ') ptr++;
	memset(credentials, 0, sizeof(credentials));
	decodeBase64(credentials,
		     sizeof(credentials)-1,
		     ptr,
		     strlen(ptr) );
    
      }
    }
    if (!checkPerm(url, credentials))
    {
      sendHeaders(s, HTTP_UNAUTHORIZED, 0, -1, 0);
      length=-1;
      break; /* no more processing */
    }
#else
    }
#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */

    /* we are done if an error occurred */
    if (length == -1) break;

    if (strcmp(url,"/") == 0) strcpy(url,"/index.html");

    if (length>0)
    {
      body=(char*) malloc(length+1);
      if (body)
      {
	length = read(s,body,length);
	body[length]=0; // always null terminate for safety
	urlArgs=body;
      }
    }
    
    if (strstr(url,"..") || strstr(url, "httpd.conf")) 
    {
      /* protect from .. path creep */
      sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0);
    }
    else if (strstr(url,"cgi-bin")) 
    { 
      sendCgi(s, url, request, urlArgs, body, length);
    }
    else if (strncmpi(request,"GET",3) == 0)
    {
      sendFile(s, url);
    }
    else
    {
      sendHeaders(s, HTTP_NOT_IMPLEMENTED, 0, -1, 0);
    }
  } while (0);

#ifdef DEBUG
  if (debugHttpd) fprintf(stderr,"closing socket\n");
#endif  
  if (body) free(body);
  shutdown(s,SHUT_WR);
  shutdown(s,SHUT_RD);
  close(s);
  
  return 0;
}

/****************************************************************************
 *
 > $Function: miniHttpd()
 *
 * $Description: The main http server function.
 *
 *   Given an open socket fildes, listen for new connections and farm out
 *   the processing as a forked process.
 *
 * $Parameters:
 *      (int) server. . . The server socket fildes.
 *
 * $Return: (int) . . . . Always 0.
 *
 ****************************************************************************/
static int miniHttpd(int server)
{
  fd_set readfd, portfd;
  int nfound;
  
  FD_ZERO(&portfd);
  FD_SET(server, &portfd);
  
  /* copy the ports we are watching to the readfd set */
  while (1) 
  {
    readfd = portfd ;
    
    /* Now wait INDEFINATELY on the set of sockets! */
    nfound = select(server+1, &readfd, 0, 0, 0);
    
    switch (nfound)
    {
    case 0:
      /* select timeout error! */
      break ;
    case -1:
      /* select error */
      break;
    default:
      if (FD_ISSET(server, &readfd))
      {
	char on;
	struct sockaddr_in fromAddr;
	char rmt_ip[20];
	int addr;
	socklen_t fromAddrLen = sizeof(fromAddr);
	int s = accept(server,
		       (struct sockaddr *)&fromAddr, &fromAddrLen) ;
	if (s < 0) 
	{
	  continue;
	}
	addr = ntohl(fromAddr.sin_addr.s_addr);
	sprintf(rmt_ip,"%u.%u.%u.%u",
                (unsigned char)(addr >> 24),
                (unsigned char)(addr >> 16),
                (unsigned char)(addr >> 8),
                (unsigned char)(addr >> 0));
#ifdef DEBUG
	if (debugHttpd)
	{
	  fprintf(stderr,"httpMini.cpp: connection from IP=%s, port %d\n",
		 rmt_ip, ntohs(fromAddr.sin_port));
	}
#endif  
	if(checkPerm("ip", rmt_ip) == 0)
	{
	  close(s);
	  continue;
	}
	
	/*  set the KEEPALIVE option to cull dead connections */
	on = 1;
	setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
		   sizeof (on));

	if (fork() == 0) 
	{
	  /* This is the spawned thread */
	  handleIncoming(s);
	  exit(0);
	}
	close(s);
      }
    }
  } // while (1)
  return 0;
}

int httpd_main(int argc, char *argv[])
{
  int server;
  int port = 80;
  int c;
  
  /* check if user supplied a port number */
  for (;;) {
    c = getopt( argc, argv, "p:ve:d:"
#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
    "r:c:"
#endif
    );
    if (c == EOF) break;
    switch (c) {
    case 'v':
      debugHttpd=1;
      break;
    case 'p':
      port = atoi(optarg);
      break;
    case 'd':
      printf("%s",decodeString(optarg));
      return 0;
    case 'e':
      printf("%s",encodeString(optarg));
      return 0;
    case 'r':
      realm = optarg;
      break;
    case 'c':
      configFile = optarg;
      break;
    default:
      fprintf(stderr,"%s\n", httpdVersion);
      show_usage();
      exit(1);
    }
  }

  envp = (char**) malloc((ENVSIZE+1)*sizeof(char*));
  if (envp == 0) perror_exit("envp alloc");

  server = openServer(port);
  if (server < 0) exit(1);

  if (!debugHttpd)
  {
    /* remember our current pwd, daemonize, chdir back */
    char *dir = (char *) malloc(256);
    if (dir == 0) perror_exit("out of memory for getpwd");
    if (getcwd(dir, 256) == 0) perror_exit("getcwd failed");
    if (daemon(0, 1) < 0) perror_exit("daemon");
    chdir(dir);
    free(dir);
  }

  miniHttpd(server);
  
  return 0;
}

#ifdef HTTPD_STANDALONE
int main(int argc, char *argv[])
{ 
  return httpd_main(argc, argv);
}

#endif