1350 lines
36 KiB
C
1350 lines
36 KiB
C
/*
|
||
* 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 "<Hello World>"
|
||
*
|
||
* 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
|