/* * httpd implementation for busybox * * Copyright (C) 2002 Glenn Engel * * * 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 ""` # 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 #include /* for isspace */ #include /* for varargs */ #include /* for strerror */ #include /* for malloc */ #include #include #include /* for close */ #include #include #include /* for connect and socket*/ #include /* for sockaddr_in */ #include #include #include #include static const char httpdVersion[] = "busybox httpd/1.13 3-Jan-2003"; // #define DEBUG 1 #ifndef HTTPD_STANDALONE #include #include // 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 ] [-c configFile] [-d/-e ] [-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), "%d %s\n" "

%d %s

\n%s\n\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 " 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