New applet: httpd, by Glenn Engel
authorGlenn L McGrath <bug1@ihug.co.nz>
Sun, 5 Jan 2003 04:01:56 +0000 (04:01 -0000)
committerGlenn L McGrath <bug1@ihug.co.nz>
Sun, 5 Jan 2003 04:01:56 +0000 (04:01 -0000)
include/applets.h
include/usage.h
networking/Config.in
networking/Makefile.in
networking/httpd.c [new file with mode: 0644]

index 5d8e7bb6835c94be159974cb6ea86e557db7f81e..177e82352936c2b1e6e8ad2f8e6f137ba203ed7a 100644 (file)
 #ifdef CONFIG_HOSTNAME
        APPLET(hostname, hostname_main, _BB_DIR_BIN, _BB_SUID_NEVER)
 #endif
+#ifdef CONFIG_HTTPD
+       APPLET(httpd, httpd_main, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)
+#endif
 #ifdef CONFIG_HUSH
        APPLET_NOUSAGE("hush", hush_main, _BB_DIR_BIN, _BB_SUID_NEVER)
 #endif
index b5687115d4dcf2a65a14ab847d92bcf3a2a92e13..dfcc89626488f3c8ab4763332168d872ed0aa9f2 100644 (file)
 #define hostname_example_usage \
        "$ hostname\n" \
        "sage \n"
-
+#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
+  #define USAGE_HTTPD_BASIC_AUTH(a) a
+#else
+  #define USAGE_HTTPD_BASIC_AUTH(a)
+#endif
+#define httpd_trivial_usage \
+       "[-p <port>] [-d/-e <string>]" USAGE_HTTPD_BASIC_AUTH(" [-c <conf file>] [-r <realm>]")
+#define httpd_full_usage \
+       "Listens for incoming http server requests.\n"\
+       "Options:\n" \
+       "\t-p PORT\tServer port (default 80).\n" \
+       USAGE_HTTPD_BASIC_AUTH("\t-c FILE\tSpecifies configuration file.  (default httpd.conf)\n\t-r REALM\tAuthentication Realm for Basic Authentication\n") \
+       "\t-e STRING\tHtml encode STRING\n" \
+       "\t-d STRING\tURL decode STRING\n" 
 #define hwclock_trivial_usage \
        "[-r|--show] [-s|--hctosys] [-w|--systohc] [-l|--localtime] [-u|--utc]"
 #define hwclock_full_usage \
index b622b65f372339420918c1a4d77cdec3f7f9cc03..ecd3e570a5766c4f33bd248c83413bf26dfc9d83 100644 (file)
@@ -29,6 +29,20 @@ config CONFIG_HOSTNAME
        help
          Please submit a patch to add help text for this item.
 
+config CONFIG_HTTPD
+       bool "httpd"
+       default n
+       help
+         Serve web pages via an HTTP server.
+
+config CONFIG_FEATURE_HTTPD_BASIC_AUTH
+       bool "  Enable Basic Authentication and IP address checking"
+       default n
+       depends on CONFIG_HTTPD
+       help
+         Utilizes /etc/httpd.conf for security settings allowing 
+          ip address filtering and basic authentication on a per url basis.
+
 config CONFIG_IFCONFIG
        bool "ifconfig"
        default n
index c2ae451a43367730e33f2af2cd02a8fbcd6c67e2..7e9a6fdd2f864420f385a6834ab501e9a82bdb1f 100644 (file)
@@ -26,6 +26,7 @@ NETWORKING-y:=
 NETWORKING-$(CONFIG_FTPGET)    += ftpgetput.o
 NETWORKING-$(CONFIG_FTPPUT)    += ftpgetput.o
 NETWORKING-$(CONFIG_HOSTNAME)  += hostname.o
+NETWORKING-$(CONFIG_HTTPD)     += httpd.o
 NETWORKING-$(CONFIG_IFCONFIG)  += ifconfig.o
 NETWORKING-$(CONFIG_IFUPDOWN)  += ifupdown.o
 NETWORKING-$(CONFIG_IP)                        += ip.o
diff --git a/networking/httpd.c b/networking/httpd.c
new file mode 100644 (file)
index 0000000..bceb89b
--- /dev/null
@@ -0,0 +1,1349 @@
+/*
+ * 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 /* '\7f' */
+};
+
+/****************************************************************************
+ *
+ > $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