summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn L McGrath2003-01-05 04:01:56 +0000
committerGlenn L McGrath2003-01-05 04:01:56 +0000
commit58c708af23d6ad855ea0cd1b61c2b87f26fc6988 (patch)
treef685aca3eb4a763dd0c58a291c766f018f8656c3
parentc2e74ca934860d7cd4b02ab51e4466ae34871223 (diff)
downloadbusybox-58c708af23d6ad855ea0cd1b61c2b87f26fc6988.zip
busybox-58c708af23d6ad855ea0cd1b61c2b87f26fc6988.tar.gz
New applet: httpd, by Glenn Engel
-rw-r--r--include/applets.h3
-rw-r--r--include/usage.h15
-rw-r--r--networking/Config.in14
-rw-r--r--networking/Makefile.in1
-rw-r--r--networking/httpd.c1349
5 files changed, 1381 insertions, 1 deletions
diff --git a/include/applets.h b/include/applets.h
index 5d8e7bb..177e823 100644
--- a/include/applets.h
+++ b/include/applets.h
@@ -236,6 +236,9 @@
#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
diff --git a/include/usage.h b/include/usage.h
index b568711..dfcc896 100644
--- a/include/usage.h
+++ b/include/usage.h
@@ -775,7 +775,20 @@
#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 \
diff --git a/networking/Config.in b/networking/Config.in
index b622b65..ecd3e57 100644
--- a/networking/Config.in
+++ b/networking/Config.in
@@ -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
diff --git a/networking/Makefile.in b/networking/Makefile.in
index c2ae451..7e9a6fd 100644
--- a/networking/Makefile.in
+++ b/networking/Makefile.in
@@ -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
index 0000000..bceb89b
--- /dev/null
+++ b/networking/httpd.c
@@ -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 /* '' */
+};
+
+/****************************************************************************
+ *
+ > $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