busybox/networking/httpd.c

1350 lines
36 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* 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