2 * httpd implementation for busybox
4 * Copyright (C) 2002 Glenn Engel <glenne@engel.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *****************************************************************************
26 * This is equivalent to
28 * httpd -p 80 -c /etc/httpd.conf -r "Web Server Authentication"
30 * When a url contains "cgi-bin" it is assumed to be a cgi script. The
31 * server changes directory to the location of the script and executes it
32 * after setting QUERY_STRING and other environment variables. If url args
33 * are included in the url or as a post, the args are placed into decoded
34 * environment variables. e.g. /cgi-bin/setup?foo=Hello%20World will set
35 * the $CGI_foo environment variable to "Hello World".
37 * The server can also be invoked as a url arg decoder and html text encoder
39 * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World"
40 * bar=`httpd -e "<Hello World>"` # encode as "<Hello World>"
42 * httpd.conf has the following format:
44 ip:10.10. # Allow any address that begins with 10.10.
45 ip:172.20. # Allow 172.20.x.x
46 ip:127.0.0.1 # Allow local loopback connections
47 /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin
48 /:admin:setup # Require user admin, pwd setup on urls starting with /
51 * To open up the server:
52 * ip:* # Allow any IP address
53 * /:* # no password required for urls starting with / (all)
55 * Processing of the file stops on the first sucessful match. If the file
56 * is not found, the server is assumed to be wide open.
58 *****************************************************************************
60 * Desired enhancements:
66 #include <ctype.h> /* for isspace */
67 #include <stdarg.h> /* for varargs */
68 #include <string.h> /* for strerror */
69 #include <stdlib.h> /* for malloc */
72 #include <unistd.h> /* for close */
74 #include <sys/types.h>
75 #include <sys/socket.h> /* for connect and socket*/
76 #include <netinet/in.h> /* for sockaddr_in */
77 #include <sys/types.h>
82 static const char httpdVersion[] = "busybox httpd/1.13 3-Jan-2003";
85 #ifndef HTTPD_STANDALONE
88 // Note: xfuncs are not used because we want the server to keep running
89 // if something bad happens due to a malformed user request.
90 // As a result, all memory allocation is checked rigorously
93 #define CONFIG_FEATURE_HTTPD_BASIC_AUTH
96 fprintf(stderr,"Usage: httpd [-p <port>] [-c configFile] [-d/-e <string>] [-r realm]\n");
100 /* minimal global vars for busybox */
107 static char *realm = "Web Server Authentication";
108 static char *configFile;
110 static const char* const suffixTable [] = {
111 ".htm.html", "text/html",
112 ".jpg.jpeg", "image/jpeg",
115 ".txt.h.c.cc.cpp", "text/plain",
122 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
123 HTTP_NOT_FOUND = 404,
124 HTTP_INTERNAL_SERVER_ERROR = 500,
125 HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
126 HTTP_BAD_REQUEST = 400, /* malformed syntax */
127 #if 0 /* future use */
129 HTTP_SWITCHING_PROTOCOLS = 101,
132 HTTP_NON_AUTHORITATIVE_INFO = 203,
133 HTTP_NO_CONTENT = 204,
134 HTTP_MULTIPLE_CHOICES = 300,
135 HTTP_MOVED_PERMANENTLY = 301,
136 HTTP_MOVED_TEMPORARILY = 302,
137 HTTP_NOT_MODIFIED = 304,
138 HTTP_PAYMENT_REQUIRED = 402,
139 HTTP_FORBIDDEN = 403,
140 HTTP_BAD_GATEWAY = 502,
141 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
142 HTTP_RESPONSE_SETSIZE=0xffffffff
148 HttpResponseNum type;
153 static const HttpEnumString httpResponseNames[] = {
155 { HTTP_NOT_IMPLEMENTED, "Not Implemented",
156 "The requested method is not recognized by this server." },
157 { HTTP_UNAUTHORIZED, "Unauthorized", "" },
158 { HTTP_NOT_FOUND, "Not Found",
159 "The requested URL was not found on this server." },
160 { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error"
161 "Internal Server Error" },
162 { HTTP_BAD_REQUEST, "Bad Request" ,
163 "Unsupported method.\n" },
165 { HTTP_CREATED, "Created" },
166 { HTTP_ACCEPTED, "Accepted" },
167 { HTTP_NO_CONTENT, "No Content" },
168 { HTTP_MULTIPLE_CHOICES, "Multiple Choices" },
169 { HTTP_MOVED_PERMANENTLY, "Moved Permanently" },
170 { HTTP_MOVED_TEMPORARILY, "Moved Temporarily" },
171 { HTTP_NOT_MODIFIED, "Not Modified" },
172 { HTTP_FORBIDDEN, "Forbidden", "" },
173 { HTTP_BAD_GATEWAY, "Bad Gateway", "" },
174 { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" },
178 /****************************************************************************
180 > $Function: encodeString()
182 * $Description: Given a string, html encode special characters.
183 * This is used for the -e command line option to provide an easy way
184 * for scripts to encode result data without confusing browsers. The
185 * returned string pointer is memory allocated by malloc().
188 * (const char *) string . . The first string to encode.
190 * $Return: (char *) . . . .. . . A pointer to the encoded string.
192 * $Errors: Returns a null string ("") if memory is not available.
194 ****************************************************************************/
195 static char *encodeString(const char *string)
197 /* take the simple route and encode everything */
198 /* could possibly scan once to get length. */
199 int len = strlen(string);
200 char *out = (char*)malloc(len*5 +1);
204 while ((ch = *string++))
206 // very simple check for what to encode
207 if (isalnum(ch)) *p++ = ch;
208 else p += sprintf(p,"&#%d", (unsigned char) ch);
214 /****************************************************************************
216 > $Function: decodeString()
218 * $Description: Given a URL encoded string, convert it to plain ascii.
219 * Since decoding always makes strings smaller, the decode is done in-place.
220 * Thus, callers should strdup() the argument if they do not want the
221 * argument modified. The return is the original pointer, allowing this
222 * function to be easily used as arguments to other functions.
225 * (char *) string . . . The first string to decode.
227 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
231 ****************************************************************************/
232 static char *decodeString(char *string)
234 /* note that decoded string is always shorter than original */
239 if (*ptr == '+') { *string++ = ' '; ptr++; }
240 else if (*ptr != '%') *string++ = *ptr++;
244 sscanf(ptr+1,"%2X",&value);
254 /****************************************************************************
256 > $Function: addEnv()
258 * $Description: Add an enviornment variable setting to the global list.
259 * A NAME=VALUE string is allocated, filled, and added to the list of
260 * environment settings passed to the cgi execution script.
263 * (char *) name . . . The environment variable name.
264 * (char *) value . . The value to which the env variable is set.
268 * $Errors: Silently returns if the env runs out of space to hold the new item
270 ****************************************************************************/
271 static void addEnv(const char *name, const char *value)
274 if (envCount >= ENVSIZE) return;
275 if (!value) value = "";
276 s=(char*)malloc(strlen(name)+strlen(value)+2);
279 sprintf(s,"%s=%s",name, value);
285 /****************************************************************************
287 > $Function: addEnvCgi
289 * $Description: Create environment variables given a URL encoded arg list.
290 * For each variable setting the URL encoded arg list, create a corresponding
291 * environment variable. URL encoded arguments have the form
292 * name1=value1&name2=value2&name3=value3
295 * (char *) pargs . . . . A pointer to the URL encoded arguments.
301 ****************************************************************************/
302 static void addEnvCgi(const char *pargs)
305 if (pargs==0) return;
307 /* args are a list of name=value&name2=value2 sequences */
308 args = strdup(pargs);
309 while (args && *args)
313 char *value=strchr(args,'=');
317 sep=strchr(value,'&');
325 sep = value + strlen(value);
326 args = 0; /* no more */
328 cginame=(char*)malloc(strlen(decodeString(name))+5);
330 sprintf(cginame,"CGI_%s",name);
331 addEnv(cginame,decodeString(value));
336 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
337 static const unsigned char base64ToBin[] = {
338 255 /* ' ' */, 255 /* '!' */, 255 /* '"' */, 255 /* '#' */,
339 1 /* '$' */, 255 /* '%' */, 255 /* '&' */, 255 /* ''' */,
340 255 /* '(' */, 255 /* ')' */, 255 /* '*' */, 62 /* '+' */,
341 255 /* ',' */, 255 /* '-' */, 255 /* '.' */, 63 /* '/' */,
342 52 /* '0' */, 53 /* '1' */, 54 /* '2' */, 55 /* '3' */,
343 56 /* '4' */, 57 /* '5' */, 58 /* '6' */, 59 /* '7' */,
344 60 /* '8' */, 61 /* '9' */, 255 /* ':' */, 255 /* ';' */,
345 255 /* '<' */, 00 /* '=' */, 255 /* '>' */, 255 /* '?' */,
346 255 /* '@' */, 00 /* 'A' */, 01 /* 'B' */, 02 /* 'C' */,
347 03 /* 'D' */, 04 /* 'E' */, 05 /* 'F' */, 06 /* 'G' */,
348 7 /* 'H' */, 8 /* 'I' */, 9 /* 'J' */, 10 /* 'K' */,
349 11 /* 'L' */, 12 /* 'M' */, 13 /* 'N' */, 14 /* 'O' */,
350 15 /* 'P' */, 16 /* 'Q' */, 17 /* 'R' */, 18 /* 'S' */,
351 19 /* 'T' */, 20 /* 'U' */, 21 /* 'V' */, 22 /* 'W' */,
352 23 /* 'X' */, 24 /* 'Y' */, 25 /* 'Z' */, 255 /* '[' */,
353 255 /* '\' */, 255 /* ']' */, 255 /* '^' */, 255 /* '_' */,
354 255 /* '`' */, 26 /* 'a' */, 27 /* 'b' */, 28 /* 'c' */,
355 29 /* 'd' */, 30 /* 'e' */, 31 /* 'f' */, 32 /* 'g' */,
356 33 /* 'h' */, 34 /* 'i' */, 35 /* 'j' */, 36 /* 'k' */,
357 37 /* 'l' */, 38 /* 'm' */, 39 /* 'n' */, 40 /* 'o' */,
358 41 /* 'p' */, 42 /* 'q' */, 43 /* 'r' */, 44 /* 's' */,
359 45 /* 't' */, 46 /* 'u' */, 47 /* 'v' */, 48 /* 'w' */,
360 49 /* 'x' */, 50 /* 'y' */, 51 /* 'z' */, 255 /* '{' */,
361 255 /* '|' */, 255 /* '}' */, 255 /* '~' */, 255 /* '
\7f' */
364 /****************************************************************************
366 > $Function: decodeBase64()
368 > $Description: Decode a base 64 data stream as per rfc1521.
369 * Note that the rfc states that none base64 chars are to be ignored.
370 * Since the decode always results in a shorter size than the input, it is
371 * OK to pass the input arg as an output arg.
374 * (void *) outData. . . Where to place the decoded data.
375 * (size_t) outDataLen . The length of the output data string.
376 * (void *) inData . . . A pointer to a base64 encoded string.
377 * (size_t) inDataLen . The length of the input data string.
379 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
383 ****************************************************************************/
384 static size_t decodeBase64(void *outData, size_t outDataLen,
385 void *inData, size_t inDataLen)
388 unsigned char *in = inData;
389 unsigned char *out = outData;
390 unsigned long ch = 0;
391 while (inDataLen && outDataLen)
393 unsigned char conv = 0;
400 if ((newch < '0') || (newch > 'z')) continue;
401 conv = base64ToBin[newch - 32];
402 if (conv == 255) continue;
405 ch = (ch << 6) | conv;
411 *(out++) = (unsigned char) (ch >> 16);
412 *(out++) = (unsigned char) (ch >> 8);
413 *(out++) = (unsigned char) ch;
420 if ((inDataLen == 0) && (i != 0))
422 /* error - non multiple of 4 chars on input */
428 /* return the actual number of chars in output array */
429 return out-(unsigned char*) outData;
433 /****************************************************************************
435 > $Function: perror_and_exit()
437 > $Description: A helper function to print an error and exit.
440 * (const char *) msg . . . A 'context' message to include.
446 ****************************************************************************/
447 static void perror_exit(const char *msg)
454 /****************************************************************************
456 > $Function: strncmpi()
458 * $Description: compare two strings without regard to case.
461 * (char *) a . . . . . The first string.
462 * (char *) b . . . . . The second string.
463 * (int) n . . . . . . The number of chars to compare.
465 * $Return: (int) . . . . . . 0 if strings equal. 1 if a>b, -1 if b < a.
469 ****************************************************************************/
470 #define __toupper(c) ((('a' <= (c))&&((c) <= 'z')) ? ((c) - 'a' + 'A') : (c))
471 #define __tolower(c) ((('A' <= (c))&&((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
472 static int strncmpi(const char *a, const char *b,int n)
477 while(n-- && ((a1 = *a++) != '\0') && ((b1 = *b++) != '\0'))
479 if(a1 == b1) continue; /* No need to convert */
482 if(a1 != b1) break; /* No match, abort */
486 if(a1 > b1) return 1;
487 if(a1 < b1) return -1;
492 /****************************************************************************
494 > $Function: openServer()
496 * $Description: create a listen server socket on the designated port.
499 * (int) port . . . The port to listen on for connections.
501 * $Return: (int) . . . A connection socket. -1 for errors.
505 ****************************************************************************/
506 static int openServer(int port)
508 struct sockaddr_in lsocket;
511 /* create the socket right now */
512 /* inet_addr() returns a value that is already in network order */
513 memset(&lsocket, 0, sizeof(lsocket));
514 lsocket.sin_family = AF_INET;
515 lsocket.sin_addr.s_addr = INADDR_ANY;
516 lsocket.sin_port = htons(port) ;
517 fd = socket(AF_INET, SOCK_STREAM, 0);
520 /* tell the OS it's OK to reuse a previous address even though */
521 /* it may still be in a close down state. Allows bind to succeed. */
524 setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&one, sizeof(one)) ;
526 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) ;
528 if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0)
531 signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */
535 perror("failure to bind to server port");
543 fprintf(stderr,"httpd: unable to create socket \n");
548 static int sendBuf(int s, char *buf, int len)
550 if (len == -1) len = strlen(buf);
551 return send(s, buf, len, 0);
554 /****************************************************************************
556 > $Function: sendHeaders()
558 * $Description: Create and send HTTP response headers.
559 * The arguments are combined and sent as one write operation. Note that
560 * IE will puke big-time if the headers are not sent in one packet and the
561 * second packet is delayed for any reason. If contentType is null the
562 * content type is assumed to be text/html
565 * (int) s . . . The http socket.
566 * (HttpResponseNum) responseNum . . . The result code to send.
567 * (const char *) contentType . . . . A string indicating the type.
568 * (int) contentLength . . . . . . . . Content length. -1 if unknown.
569 * (time_t) expire . . . . . . . . . . Expiration time (secs since 1970)
571 * $Return: (int) . . . . Always 0
575 ****************************************************************************/
576 static int sendHeaders(int s, HttpResponseNum responseNum ,
577 const char *contentType,
578 int contentLength, time_t expire)
581 const char *responseString = "";
582 const char *infoString = 0;
584 time_t timer = time(0);
587 i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++)
589 if (httpResponseNames[i].type != responseNum) continue;
590 responseString = httpResponseNames[i].name;
591 infoString = httpResponseNames[i].info;
594 if (infoString || !contentType)
596 contentType = "text/html";
599 sprintf(buf, "HTTP/1.0 %d %s\nContent-type: %s\r\n",
600 responseNum, responseString, contentType);
602 /* emit the current date */
603 strftime(timeStr, sizeof(timeStr),
604 "%a, %d %b %Y %H:%M:%S GMT",gmtime(&timer));
605 sprintf(buf+strlen(buf), "Date: %s\r\n", timeStr);
606 sprintf(buf+strlen(buf), "Connection: close\r\n");
609 strftime(timeStr, sizeof(timeStr),
610 "%a, %d %b %Y %H:%M:%S GMT",gmtime(&expire));
611 sprintf(buf+strlen(buf), "Expire: %s\r\n", timeStr);
614 if (responseNum == HTTP_UNAUTHORIZED)
616 sprintf(buf+strlen(buf),
617 "WWW-Authenticate: Basic realm=\"%s\"\r\n", realm);
619 if (contentLength != -1)
621 int len = strlen(buf);
622 sprintf(buf+len,"Content-length: %d\r\n", contentLength);
627 sprintf(buf+strlen(buf),
628 "<HEAD><TITLE>%d %s</TITLE></HEAD>\n"
629 "<BODY><H1>%d %s</H1>\n%s\n</BODY>\n",
630 responseNum, responseString,
631 responseNum, responseString,
635 if (debugHttpd) fprintf(stderr,"Headers:'%s'", buf);
641 /****************************************************************************
643 > $Function: getLine()
645 * $Description: Read from the socket until an end of line char found.
647 * Characters are read one at a time until an eol sequence is found.
650 * (int) s . . . . . The socket fildes.
651 * (char *) buf . . Where to place the read result.
652 * (int) maxBuf . . Maximum number of chars to fit in buf.
654 * $Return: (int) . . . . number of characters read. -1 if error.
656 ****************************************************************************/
657 static int getLine(int s, char *buf, int maxBuf)
660 while (recv(s, buf+count, 1, 0) == 1)
662 if (buf[count] == '\r') continue;
663 if (buf[count] == '\n')
670 if (count) return count;
674 /****************************************************************************
676 > $Function: sendCgi()
678 * $Description: Execute a CGI script and send it's stdout back
680 * Environment variables are set up and the script is invoked with pipes
681 * for stdin/stdout. If a post is being done the script is fed the POST
682 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
685 * (int ) s . . . . . . . . The session socket.
686 * (const char *) url . . . The requested URL (with leading /).
687 * (const char *urlArgs). . Any URL arguments.
688 * (const char *body) . . . POST body contents.
689 * (int bodyLen) . . . . . Length of the post body.
692 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
696 ****************************************************************************/
697 static int sendCgi(int s, const char *url,
698 const char *request, const char *urlArgs,
699 const char *body, int bodyLen)
701 int fromCgi[2]; /* pipe for reading data from CGI */
702 int toCgi[2]; /* pipe for sending data to CGI */
704 char *argp[] = { 0, 0 };
712 if (pipe(fromCgi) != 0)
716 if (pipe(toCgi) != 0)
736 dup2(inFd, 0); // replace stdin with the pipe
737 dup2(outFd, 1); // replace stdout with the pipe
738 if (!debugHttpd) dup2(outFd, 2); // replace stderr with the pipe
750 script = (char*) malloc(strlen(url)+2);
751 if (!script) _exit(242);
752 sprintf(script,".%s",url);
755 addEnv("SCRIPT_NAME",script);
756 addEnv("REQUEST_METHOD",request);
757 addEnv("QUERY_STRING",urlArgs);
758 addEnv("SERVER_SOFTWARE",httpdVersion);
759 if (strncmpi(request,"POST",4)==0) addEnvCgi(body);
760 else addEnvCgi(urlArgs);
763 * Most HTTP servers chdir to the cgi directory.
765 while (*url == '/') url++; // skip leading slash(s)
766 directory = strdup( url );
767 if ( directory == (char*) 0 )
768 script = (char*) (url); /* ignore errors */
771 script = strrchr( directory, '/' );
772 if ( script == (char*) 0 )
777 (void) chdir( directory ); /* ignore errors */
780 // now run the program. If it fails, use _exit() so no destructors
781 // get called and make a mess.
782 execve(script, argp, envp);
785 fprintf(stderr, "exec failed\n");
798 if (body) write(outFd, body, bodyLen);
810 struct timeval timeout;
817 FD_SET(inFd, &readSet);
819 /* Now wait on the set of sockets! */
821 timeout.tv_usec = 10000;
822 nfound = select(inFd+1, &readSet, 0, 0, &timeout);
826 dead_pid = waitpid(pid, &status, WNOHANG);
836 if (WIFEXITED(status))
837 fprintf(stderr,"piped has exited with status=%d\n", WEXITSTATUS(status));
838 if (WIFSIGNALED(status))
839 fprintf(stderr,"piped has exited with signal=%d\n", WTERMSIG(status));
848 // There is something to read
849 count = read(inFd,buf,sizeof(buf)-1);
850 // If a read returns 0 at this point then some type of error has
851 // occurred. Bail now.
852 if (count == 0) break;
857 /* check to see if the user script added headers */
858 if (strcmp(buf,"HTTP")!= 0)
860 write(s,"HTTP/1.0 200 OK\n", 16);
862 if (strstr(buf,"ontent-") == 0)
864 write(s,"Content-type: text/plain\n\n", 26);
871 if (debugHttpd) fprintf(stderr,"cgi read %d bytes\n", count);
880 /****************************************************************************
882 > $Function: sendFile()
884 * $Description: Send a file response to an HTTP request
887 * (int) s . . . . . . . The http session socket.
888 * (const char *) url . . The URL requested.
890 * $Return: (int) . . . . . . Always 0.
892 ****************************************************************************/
893 static int sendFile(int s, const char *url)
895 char *suffix = strrchr(url,'.');
896 const char *content = "application/octet-stream";
902 for (table = (const char **) &suffixTable[0];
903 *table && (strstr(*table, suffix) == 0); table+=2);
904 if (table) content = *(table+1);
907 if (*url == '/') url++;
908 suffix = strchr(url,'?');
909 if (suffix) *suffix = 0;
912 fprintf(stderr,"Sending file '%s'\n", url);
915 f = open(url,O_RDONLY, 0444);
920 sendHeaders(s, HTTP_OK, content, -1, 0 );
921 while ((count = read(f, buf, sizeof(buf))))
923 sendBuf(s, buf, count);
930 fprintf(stderr,"Unable to open '%s'\n", url);
932 sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0);
938 /****************************************************************************
940 > $Function: checkPerm()
942 * $Description: Check the permission file for access.
944 * Both IP addresses as well as url pathnames can be specified. If an IP
945 * address check is desired, the 'path' should be specified as "ip" and the
946 * dotted decimal IP address placed in request.
948 * For url pathnames, place the url (with leading /) in 'path' and any
949 * authentication information in request. e.g. "user:pass"
953 * Keep the algorithm simple.
954 * If config file isn't present, everything is allowed.
955 * Run down /etc/httpd.hosts a line at a time.
956 * Stop if match is found.
957 * Entries are of the form:
958 * ip:10.10 # any address that begins with 10.10
959 * dir:user:pass # dir security for dirs that start with 'dir'
961 * httpd.conf has the following format:
962 * ip:10.10. # Allow any address that begins with 10.10.
963 * ip:172.20. # Allow 172.20.x.x
964 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin
965 * /:foo:bar # Require user foo, pwd bar on urls starting with /
967 * To open up the server:
968 * ip:* # Allow any IP address
969 * /:* # no password required for urls starting with / (all)
972 * (const char *) path . . . . The file path or "ip" for ip addresses.
973 * (const char *) request . . . User information to validate.
975 * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise.
977 ****************************************************************************/
978 static int checkPerm(const char *path, const char *request)
986 /* If httpd.conf not there assume anyone can get in */
987 if (configFile) f = fopen(configFile,"r");
988 if(f == NULL) f = fopen("/etc/httpd.conf","r");
989 if(f == NULL) f = fopen("httpd.conf","r");
993 if (strcmp("ip",path) == 0) ipaddr=1;
997 /* This could stand some work */
998 while ( fgets(buf, 80, f) != NULL)
1000 if(buf[0] == '#') continue;
1001 if(buf[0] == '\0') continue;
1002 for(p = buf + (strlen(buf) - 1); p >= buf; p--)
1004 if(isspace(*p)) *p = 0;
1007 p = strchr(buf,':');
1011 fprintf(stderr,"checkPerm: '%s' ? '%s'\n",buf,path);
1013 if((ipaddr ? strcmp(buf,path) : strncmp(buf, path, strlen(buf))) == 0)
1015 /* match found. Check request */
1016 if ((strcmp("*",p) == 0) ||
1017 (strcmp(p, request) == 0) ||
1018 (ipaddr && (strncmp(p, request, strlen(p)) == 0)))
1024 /* reject on first failure for non ipaddresses */
1033 /****************************************************************************
1035 > $Function: handleIncoming()
1037 * $Description: Handle an incoming http request.
1040 * (s) s . . . . . The http request socket.
1042 * $Return: (int) . . . Always 0.
1044 ****************************************************************************/
1045 static int handleIncoming(int s)
1048 char url[8192]; /* hold args too initially */
1049 char credentials[80];
1060 int count = getLine(s, buf, sizeof(buf));
1062 if (count <= 0) break;
1063 count = sscanf(buf, "%9s %1000s HTTP/%d.%d", request,
1064 url, &major, &minor);
1068 /* Garbled request/URL */
1070 genHttpHeader(&requestInfo,
1071 HTTP_BAD_REQUEST, requestInfo.dataType,
1072 HTTP_LENGTH_UNKNOWN);
1077 /* If no version info, assume 0.9 */
1084 /* extract url args if present */
1085 urlArgs = strchr(url,'?');
1093 if (debugHttpd) fprintf(stderr,"url='%s', args=%s\n", url, urlArgs);
1096 // read until blank line(s)
1098 while ((count = getLine(s, buf, sizeof(buf))) >= 0)
1102 if (major > 0) break;
1104 if (blank == 2) break;
1107 if (debugHttpd) fprintf(stderr,"Header: '%s'\n", buf);
1110 /* try and do our best to parse more lines */
1111 if ((strncmpi(buf, "Content-length:", 15) == 0))
1113 sscanf(buf, "%*s %ld", &length);
1115 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1116 else if (strncmpi(buf, "Authorization:", 14) == 0)
1118 /* We only allow Basic credentials.
1119 * It shows up as "Authorization: Basic <userid:password>" where
1120 * the userid:password is base64 encoded.
1123 while (*ptr == ' ') ptr++;
1124 if (strncmpi(ptr, "Basic", 5) != 0) break;
1126 while (*ptr == ' ') ptr++;
1127 memset(credentials, 0, sizeof(credentials));
1128 decodeBase64(credentials,
1129 sizeof(credentials)-1,
1135 if (!checkPerm(url, credentials))
1137 sendHeaders(s, HTTP_UNAUTHORIZED, 0, -1, 0);
1139 break; /* no more processing */
1143 #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1145 /* we are done if an error occurred */
1146 if (length == -1) break;
1148 if (strcmp(url,"/") == 0) strcpy(url,"/index.html");
1152 body=(char*) malloc(length+1);
1155 length = read(s,body,length);
1156 body[length]=0; // always null terminate for safety
1161 if (strstr(url,"..") || strstr(url, "httpd.conf"))
1163 /* protect from .. path creep */
1164 sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0);
1166 else if (strstr(url,"cgi-bin"))
1168 sendCgi(s, url, request, urlArgs, body, length);
1170 else if (strncmpi(request,"GET",3) == 0)
1176 sendHeaders(s, HTTP_NOT_IMPLEMENTED, 0, -1, 0);
1181 if (debugHttpd) fprintf(stderr,"closing socket\n");
1183 if (body) free(body);
1184 shutdown(s,SHUT_WR);
1185 shutdown(s,SHUT_RD);
1191 /****************************************************************************
1193 > $Function: miniHttpd()
1195 * $Description: The main http server function.
1197 * Given an open socket fildes, listen for new connections and farm out
1198 * the processing as a forked process.
1201 * (int) server. . . The server socket fildes.
1203 * $Return: (int) . . . . Always 0.
1205 ****************************************************************************/
1206 static int miniHttpd(int server)
1208 fd_set readfd, portfd;
1212 FD_SET(server, &portfd);
1214 /* copy the ports we are watching to the readfd set */
1219 /* Now wait INDEFINATELY on the set of sockets! */
1220 nfound = select(server+1, &readfd, 0, 0, 0);
1225 /* select timeout error! */
1231 if (FD_ISSET(server, &readfd))
1234 struct sockaddr_in fromAddr;
1237 socklen_t fromAddrLen = sizeof(fromAddr);
1238 int s = accept(server,
1239 (struct sockaddr *)&fromAddr, &fromAddrLen) ;
1244 addr = ntohl(fromAddr.sin_addr.s_addr);
1245 sprintf(rmt_ip,"%u.%u.%u.%u",
1246 (unsigned char)(addr >> 24),
1247 (unsigned char)(addr >> 16),
1248 (unsigned char)(addr >> 8),
1249 (unsigned char)(addr >> 0));
1253 fprintf(stderr,"httpMini.cpp: connection from IP=%s, port %d\n",
1254 rmt_ip, ntohs(fromAddr.sin_port));
1257 if(checkPerm("ip", rmt_ip) == 0)
1263 /* set the KEEPALIVE option to cull dead connections */
1265 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
1270 /* This is the spawned thread */
1281 int httpd_main(int argc, char *argv[])
1287 /* check if user supplied a port number */
1289 c = getopt( argc, argv, "p:ve:d:"
1290 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1294 if (c == EOF) break;
1300 port = atoi(optarg);
1303 printf("%s",decodeString(optarg));
1306 printf("%s",encodeString(optarg));
1312 configFile = optarg;
1315 fprintf(stderr,"%s\n", httpdVersion);
1321 envp = (char**) malloc((ENVSIZE+1)*sizeof(char*));
1322 if (envp == 0) perror_exit("envp alloc");
1324 server = openServer(port);
1325 if (server < 0) exit(1);
1329 /* remember our current pwd, daemonize, chdir back */
1330 char *dir = (char *) malloc(256);
1331 if (dir == 0) perror_exit("out of memory for getpwd");
1332 if (getcwd(dir, 256) == 0) perror_exit("getcwd failed");
1333 if (daemon(0, 1) < 0) perror_exit("daemon");
1343 #ifdef HTTPD_STANDALONE
1344 int main(int argc, char *argv[])
1346 return httpd_main(argc, argv);