merge fixes with udhcp
[oweals/busybox.git] / networking / httpd.c
1 /*
2  * httpd implementation for busybox
3  *
4  * Copyright (C) 2002 Glenn Engel <glenne@engel.org>
5  *
6  *
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.
11  *
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.
16  *
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
20  *
21  *****************************************************************************
22  *
23  * Typical usage:  
24  *   cd /var/www
25  *   httpd 
26  * This is equivalent to
27  *    cd /var/www
28  *    httpd -p 80 -c /etc/httpd.conf -r "Web Server Authentication"
29  *
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".
36  *
37  * The server can also be invoked as a url arg decoder and html text encoder
38  * as follows:
39  *  foo=`httpd -d $foo`           # decode "Hello%20World" as "Hello World"
40  *  bar=`httpd -e "<Hello World>"`  # encode as "&#60Hello&#32World&#62"
41  *
42  * httpd.conf has the following format:
43  
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 /
49
50  *
51  * To open up the server: 
52  * ip:*              # Allow any IP address
53  * /:*               # no password required for urls starting with / (all)
54  *
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.
57  *
58  *****************************************************************************
59  *
60  * Desired enhancements:
61  *   cache httpd.conf
62  *   support tinylogin
63  *
64  */
65 #include <stdio.h>
66 #include <ctype.h>         /* for isspace           */
67 #include <stdarg.h>        /* for varargs           */
68 #include <string.h>        /* for strerror          */
69 #include <stdlib.h>        /* for malloc            */
70 #include <time.h>
71 #include <errno.h>
72 #include <unistd.h>        /* for close             */
73 #include <signal.h>
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>
78 #include <sys/stat.h>
79 #include <sys/wait.h>
80 #include <fcntl.h>
81
82 static const char httpdVersion[] = "busybox httpd/1.13 3-Jan-2003";
83
84 // #define DEBUG 1 
85 #ifndef HTTPD_STANDALONE
86 #include <config.h>
87 #include <busybox.h>
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
91 #else
92 /* standalone */
93 #define CONFIG_FEATURE_HTTPD_BASIC_AUTH
94 void show_usage()
95 {
96   fprintf(stderr,"Usage: httpd [-p <port>] [-c configFile] [-d/-e <string>] [-r realm]\n");
97 }
98 #endif
99
100 /* minimal global vars for busybox */
101 #ifndef ENVSIZE
102 #define ENVSIZE 50
103 #endif
104 int debugHttpd;
105 static char **envp;
106 static int envCount;
107 static char *realm = "Web Server Authentication";
108 static char *configFile;
109
110 static const char* const suffixTable [] = {
111   ".htm.html", "text/html",
112   ".jpg.jpeg", "image/jpeg",
113   ".gif", "image/gif",
114   ".png", "image/png",
115   ".txt.h.c.cc.cpp", "text/plain",
116   0,0
117   };
118
119 typedef enum
120 {
121   HTTP_OK = 200,
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 */
128   HTTP_CONTINUE = 100,
129   HTTP_SWITCHING_PROTOCOLS = 101,
130   HTTP_CREATED = 201,
131   HTTP_ACCEPTED = 202,
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
143 #endif
144 } HttpResponseNum;
145
146 typedef struct
147 {
148   HttpResponseNum type;
149   const char *name;
150   const char *info;
151 } HttpEnumString;
152
153 static const HttpEnumString httpResponseNames[] = {
154   { HTTP_OK, "OK" },
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" },
164 #if 0
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", "" },
175 #endif
176 };
177
178 /****************************************************************************
179  *
180  > $Function: encodeString()
181  *
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().
186  *
187  * $Parameters:
188  *      (const char *) string . . The first string to encode.
189  *
190  * $Return: (char *) . . . .. . . A pointer to the encoded string.
191  *
192  * $Errors: Returns a null string ("") if memory is not available.
193  *
194  ****************************************************************************/
195 static char *encodeString(const char *string)
196 {
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); 
201   char *p=out;
202   char ch;
203   if (!out) return "";
204   while ((ch = *string++))
205   {
206     // very simple check for what to encode
207     if (isalnum(ch)) *p++ = ch;
208     else p += sprintf(p,"&#%d", (unsigned char) ch);
209   }
210   *p=0;
211   return out;
212 }
213
214 /****************************************************************************
215  *
216  > $Function: decodeString()
217  *
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.
223  *
224  * $Parameters:
225  *      (char *) string . . . The first string to decode.
226  *
227  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
228  *
229  * $Errors: None
230  *
231  ****************************************************************************/
232 static char *decodeString(char *string)
233 {
234   /* note that decoded string is always shorter than original */
235   char *orig = string;
236   char *ptr = string;
237   while (*ptr)
238   {
239     if (*ptr == '+') { *string++ = ' '; ptr++; }
240     else if (*ptr != '%') *string++ = *ptr++;
241     else
242     {
243       unsigned int value;
244       sscanf(ptr+1,"%2X",&value);
245       *string++ = value;
246       ptr += 3;
247     }
248   }
249   *string = '\0';
250   return orig;
251 }
252
253
254 /****************************************************************************
255  *
256  > $Function: addEnv()
257  *
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.
261  *
262  * $Parameters:
263  *      (char *) name . . . The environment variable name.
264  *      (char *) value  . . The value to which the env variable is set.
265  *
266  * $Return: (void)  
267  *
268  * $Errors: Silently returns if the env runs out of space to hold the new item
269  *
270  ****************************************************************************/
271 static void addEnv(const char *name, const char *value)
272 {
273   char *s;
274   if (envCount >= ENVSIZE) return;
275   if (!value) value = "";
276   s=(char*)malloc(strlen(name)+strlen(value)+2);
277   if (s)
278   {
279     sprintf(s,"%s=%s",name, value);
280     envp[envCount++]=s;
281     envp[envCount]=0;
282   }
283 }
284
285 /****************************************************************************
286  *
287  > $Function: addEnvCgi
288  *
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
293  *
294  * $Parameters:
295  *      (char *) pargs . . . . A pointer to the URL encoded arguments.
296  *
297  * $Return: None
298  *
299  * $Errors: None
300  *
301  ****************************************************************************/
302 static void addEnvCgi(const char *pargs)
303 {
304   char *args;
305   if (pargs==0) return;
306   
307   /* args are a list of name=value&name2=value2 sequences */
308   args = strdup(pargs);
309   while (args && *args)
310   {
311     char *sep;
312     char *name=args;
313     char *value=strchr(args,'=');
314     char *cginame;
315     if (!value) break;
316     *value++=0;
317     sep=strchr(value,'&');
318     if (sep)
319     {
320       *sep=0;
321       args=sep+1;
322     }
323     else
324     {
325       sep = value + strlen(value);
326       args = 0; /* no more */
327     }
328     cginame=(char*)malloc(strlen(decodeString(name))+5);
329     if (!cginame) break;
330     sprintf(cginame,"CGI_%s",name);
331     addEnv(cginame,decodeString(value));
332     free(cginame);
333   }
334 }
335
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' */
362 };
363
364 /****************************************************************************
365  *
366  > $Function: decodeBase64()
367  *
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.
372  *
373  * $Parameters:
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.
378  *
379  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
380  *
381  * $Errors: None
382  *
383  ****************************************************************************/
384 static size_t decodeBase64(void *outData, size_t outDataLen,
385                            void *inData, size_t inDataLen)
386 {
387   int i = 0;
388   unsigned char *in = inData;
389   unsigned char *out = outData;
390   unsigned long ch = 0;
391   while (inDataLen && outDataLen)
392   {
393     unsigned char conv = 0;
394     unsigned char newch;
395     
396     while (inDataLen)
397     {
398       inDataLen--;
399       newch = *in++;
400       if ((newch < '0') || (newch > 'z')) continue;
401       conv = base64ToBin[newch - 32];
402       if (conv == 255) continue;
403       break;
404     }
405     ch = (ch << 6) | conv;
406     i++;
407     if (i== 4)
408     {
409       if (outDataLen >= 3)
410       {
411         *(out++) = (unsigned char) (ch >> 16);
412         *(out++) = (unsigned char) (ch >> 8);
413         *(out++) = (unsigned char) ch;
414         outDataLen-=3;
415       }
416       
417       i = 0;
418     }
419     
420     if ((inDataLen == 0) && (i != 0))
421     {
422       /* error - non multiple of 4 chars on input */
423       break;
424     }
425
426   }
427
428   /* return the actual number of chars in output array */
429   return out-(unsigned char*) outData;
430 }
431 #endif
432
433 /****************************************************************************
434  *
435  > $Function: perror_and_exit()
436  *
437  > $Description: A helper function to print an error and exit.
438  *
439  * $Parameters:
440  *      (const char *) msg . . . A 'context' message to include.
441  *
442  * $Return: None
443  *
444  * $Errors: None
445  *
446  ****************************************************************************/
447 static void perror_exit(const char *msg)
448 {
449   perror(msg);
450   exit(1);
451 }
452
453
454 /****************************************************************************
455  *
456  > $Function: strncmpi()
457  *
458  * $Description: compare two strings without regard to case.
459  *
460  * $Parameters:
461  *      (char *) a . . . . . The first string.
462  *      (char *) b . . . . . The second string.
463  *      (int) n  . . . . . . The number of chars to compare.
464  *
465  * $Return: (int) . . . . . . 0 if strings equal. 1 if a>b, -1 if b < a.
466  *
467  * $Errors: None
468  *
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)
473 {
474   char a1,b1;
475   a1 = b1 = 0;
476
477   while(n-- && ((a1 = *a++) != '\0') && ((b1 = *b++) != '\0'))
478   {
479     if(a1 == b1) continue;       /* No need to convert */
480     a1 = __tolower(a1);
481     b1 = __tolower(b1);
482     if(a1 != b1) break;          /* No match, abort */
483   }
484   if (n>=0) 
485   {
486     if(a1 > b1) return 1;
487     if(a1 < b1) return -1;
488   }
489   return 0;
490 }
491
492 /****************************************************************************
493  *
494  > $Function: openServer()
495  *
496  * $Description: create a listen server socket on the designated port.
497  *
498  * $Parameters:
499  *      (int) port . . . The port to listen on for connections.
500  *
501  * $Return: (int)  . . . A connection socket. -1 for errors.
502  *
503  * $Errors: None
504  *
505  ****************************************************************************/
506 static int openServer(int port)
507 {
508   struct sockaddr_in lsocket;
509   int fd;
510   
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);
518   if (fd >= 0)
519   {
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. */
522     int one = 1;
523 #ifdef SO_REUSEPORT
524     setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&one, sizeof(one)) ;
525 #else
526     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) ;
527 #endif
528     if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0)
529     {
530       listen(fd, 9);
531       signal(SIGCHLD, SIG_IGN);   /* prevent zombie (defunct) processes */
532     }
533     else
534     {
535       perror("failure to bind to server port");
536       shutdown(fd,0);
537       close(fd);
538       fd = -1;
539       }
540     }
541     else
542     {
543       fprintf(stderr,"httpd: unable to create socket \n");
544     }
545   return fd;
546 }
547
548 static int sendBuf(int s, char *buf, int len)
549 {
550   if (len == -1) len = strlen(buf);
551   return send(s, buf, len, 0);
552 }
553
554 /****************************************************************************
555  *
556  > $Function: sendHeaders()
557  *
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
563  *
564  * $Parameters:
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)
570  *
571  * $Return: (int)  . . . . Always 0
572  *
573  * $Errors: None
574  *
575  ****************************************************************************/
576 static int sendHeaders(int s, HttpResponseNum responseNum ,
577                        const char *contentType,
578                        int contentLength, time_t expire)
579 {
580   char buf[1200];
581   const char *responseString = "";
582   const char *infoString = 0;
583   unsigned int i;
584   time_t timer = time(0);
585   char timeStr[80];
586   for (i=0;
587        i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++)
588   {
589     if (httpResponseNames[i].type != responseNum) continue;
590     responseString = httpResponseNames[i].name;
591     infoString = httpResponseNames[i].info;
592     break;
593   }
594   if (infoString || !contentType)
595   {
596     contentType = "text/html";
597   }
598   
599   sprintf(buf, "HTTP/1.0 %d %s\nContent-type: %s\r\n",
600           responseNum, responseString, contentType);
601
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");
607   if (expire)
608   {
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);
612   }
613
614   if (responseNum == HTTP_UNAUTHORIZED)
615   {
616     sprintf(buf+strlen(buf),
617             "WWW-Authenticate: Basic realm=\"%s\"\r\n", realm);
618   }
619   if (contentLength != -1)
620   {
621     int len = strlen(buf);
622     sprintf(buf+len,"Content-length: %d\r\n", contentLength);
623   }
624   strcat(buf,"\r\n");
625   if (infoString)
626   {
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,
632             infoString);
633   }
634 #ifdef DEBUG
635   if (debugHttpd) fprintf(stderr,"Headers:'%s'", buf);
636 #endif
637   sendBuf(s, buf,-1);
638   return 0;
639 }
640
641 /****************************************************************************
642  *
643  > $Function: getLine()
644  *
645  * $Description: Read from the socket until an end of line char found.
646  *
647  *   Characters are read one at a time until an eol sequence is found.
648  *
649  * $Parameters:
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.
653  *
654  * $Return: (int) . . . . number of characters read.  -1 if error.
655  *
656  ****************************************************************************/
657 static int getLine(int s, char *buf, int maxBuf)
658 {
659   int  count = 0;
660   while (recv(s, buf+count, 1, 0) == 1)
661   {
662     if (buf[count] == '\r') continue;
663     if (buf[count] == '\n')
664     {
665       buf[count] = 0;
666       return count;
667     }
668     count++;
669   }
670   if (count) return count;
671   else return -1;
672 }
673
674 /****************************************************************************
675  *
676  > $Function: sendCgi()
677  *
678  * $Description: Execute a CGI script and send it's stdout back
679  *
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).
683  *
684  * $Parameters:
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.
690  
691  *
692  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
693  *
694  * $Errors: None
695  *
696  ****************************************************************************/
697 static int sendCgi(int s, const char *url, 
698                    const char *request, const char *urlArgs,
699                    const char *body, int bodyLen)
700 {
701   int fromCgi[2];  /* pipe for reading data from CGI */
702   int toCgi[2];    /* pipe for sending data to CGI */
703   
704   char *argp[] = { 0, 0 };
705   int pid=0;
706   int inFd=inFd;
707   int outFd;
708   int firstLine=1;
709
710   do
711   {
712     if (pipe(fromCgi) != 0)
713     {
714       break;
715     }
716     if (pipe(toCgi) != 0)
717     {
718       break;
719     }
720
721     pid = fork();
722     if (pid < 0)
723     {
724         pid = 0;
725         break;;
726     }
727     
728     if (!pid)   
729     {
730       /* child process */
731       char *script;
732       char *directory;
733       inFd=toCgi[0];
734       outFd=fromCgi[1];
735
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
739       close(toCgi[0]);
740       close(toCgi[1]);
741       close(fromCgi[0]);
742       close(fromCgi[1]);
743
744 #if 0
745       fcntl(0,F_SETFD, 1);
746       fcntl(1,F_SETFD, 1);
747       fcntl(2,F_SETFD, 1);
748 #endif
749       
750       script = (char*) malloc(strlen(url)+2);
751       if (!script) _exit(242);
752       sprintf(script,".%s",url);
753         
754       envCount=0;
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);
761       
762       /*
763        * Most HTTP servers chdir to the cgi directory.
764        */
765       while (*url == '/') url++;  // skip leading slash(s)
766       directory = strdup( url );  
767       if ( directory == (char*) 0 )
768         script = (char*) (url); /* ignore errors */
769       else
770       {
771         script = strrchr( directory, '/' );
772         if ( script == (char*) 0 )
773           script = directory;
774         else
775         {
776           *script++ = '\0';
777           (void) chdir( directory );    /* ignore errors */
778         }
779       }
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);
783       
784 #ifdef DEBUG
785       fprintf(stderr, "exec failed\n");
786 #endif
787       close(2);
788       close(1);
789       close(0);
790       _exit(242);
791     } /* end child */
792
793     /* parent process */
794     inFd=fromCgi[0];
795     outFd=toCgi[1];
796     close(fromCgi[1]);
797     close(toCgi[0]);
798     if (body) write(outFd, body, bodyLen);
799     close(outFd);
800
801   } while (0);
802
803   if (pid)
804   {
805     int status;
806     pid_t dead_pid;
807     
808     while (1)
809     {
810       struct timeval timeout;
811       fd_set readSet;
812       char buf[160];
813       int nfound;
814       int count;
815   
816       FD_ZERO(&readSet);
817       FD_SET(inFd, &readSet);
818   
819       /* Now wait on the set of sockets! */
820       timeout.tv_sec = 0;
821       timeout.tv_usec = 10000;
822       nfound = select(inFd+1, &readSet, 0, 0, &timeout);
823
824       if (nfound <= 0)
825       {
826         dead_pid = waitpid(pid, &status, WNOHANG);
827         if (dead_pid != 0)
828         {
829           close(fromCgi[0]);
830           close(fromCgi[1]);
831           close(toCgi[0]);
832           close(toCgi[1]);
833 #ifdef DEBUG
834           if (debugHttpd)
835           {
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));
840           }
841 #endif  
842           pid = -1;
843           break;
844         }
845       }
846       else
847       {
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;
853         if (count > 0)
854         {
855           if (firstLine)
856           {
857             /* check to see if the user script added headers */
858             if (strcmp(buf,"HTTP")!= 0)
859             {
860               write(s,"HTTP/1.0 200 OK\n", 16);
861             }
862             if (strstr(buf,"ontent-") == 0)
863             {
864               write(s,"Content-type: text/plain\n\n", 26);
865             }
866             
867             firstLine=0;
868           }
869           write(s,buf,count);
870 #ifdef DEBUG
871           if (debugHttpd) fprintf(stderr,"cgi read %d bytes\n", count);
872 #endif
873         }
874       }
875     }
876   }
877   return 0;
878 }
879
880 /****************************************************************************
881  *
882  > $Function: sendFile()
883  *
884  * $Description: Send a file response to an HTTP request
885  *
886  * $Parameters:
887  *      (int) s  . . . . . . . The http session socket.
888  *      (const char *) url . . The URL requested.
889  *
890  * $Return: (int)  . . . . . . Always 0.
891  *
892  ****************************************************************************/
893 static int sendFile(int s, const char *url)
894 {
895   char *suffix = strrchr(url,'.');
896   const char *content = "application/octet-stream";
897   int  f;
898
899   if (suffix)
900   {
901     const char ** table;
902     for (table = (const char **) &suffixTable[0]; 
903          *table && (strstr(*table, suffix) == 0); table+=2);
904     if (table) content = *(table+1);
905   }
906   
907   if (*url == '/') url++;
908   suffix = strchr(url,'?');
909   if (suffix) *suffix = 0;
910
911 #ifdef DEBUG
912     fprintf(stderr,"Sending file '%s'\n", url);
913 #endif
914   
915   f = open(url,O_RDONLY, 0444);
916   if (f >= 0)
917   {
918     char buf[1450];
919     int count;
920     sendHeaders(s, HTTP_OK, content, -1, 0 );
921     while ((count = read(f, buf, sizeof(buf))))
922     {
923       sendBuf(s, buf, count);
924     }
925     close(f);
926   }
927   else
928   {
929 #ifdef DEBUG
930     fprintf(stderr,"Unable to open '%s'\n", url);
931 #endif
932     sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0);
933   }
934   
935   return 0;
936 }
937
938 /****************************************************************************
939  *
940  > $Function: checkPerm()
941  *
942  * $Description: Check the permission file for access.
943  *
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.
947  *
948  *   For url pathnames, place the url (with leading /) in 'path' and any
949  *   authentication information in request.  e.g. "user:pass"
950  *
951  *******
952  *
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'
960  *
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 /
966  *
967  * To open up the server: 
968  *   ip:*              # Allow any IP address
969  *   /:*               # no password required for urls starting with / (all)
970  *
971  * $Parameters:
972  *      (const char *) path  . . . . The file path or "ip" for ip addresses.
973  *      (const char *) request . . . User information to validate.
974  *
975  * $Return: (int)  . . . . . . . . . 1 if request OK, 0 otherwise.
976  *
977  ****************************************************************************/
978 static int checkPerm(const char *path, const char *request)
979 {
980     FILE *f=NULL;
981     int rval;
982     char buf[80];
983     char *p;
984     int ipaddr=0;
985
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");
990     if(f == NULL) {
991         return(1);
992     }
993     if (strcmp("ip",path) == 0) ipaddr=1;
994
995     rval=0;
996
997     /* This could stand some work */
998     while ( fgets(buf, 80, f) != NULL)
999     {
1000         if(buf[0] == '#') continue;
1001         if(buf[0] == '\0') continue;
1002         for(p = buf + (strlen(buf) - 1); p >= buf; p--)
1003         {
1004             if(isspace(*p)) *p = 0;
1005         }
1006         
1007         p = strchr(buf,':');
1008         if (!p) continue;
1009         *p++=0;
1010 #ifdef DEBUG
1011         fprintf(stderr,"checkPerm: '%s' ? '%s'\n",buf,path);
1012 #endif
1013         if((ipaddr ? strcmp(buf,path) : strncmp(buf, path, strlen(buf))) == 0)
1014         {
1015           /* match found.  Check request */
1016           if ((strcmp("*",p) == 0) ||
1017               (strcmp(p, request) == 0) ||
1018               (ipaddr && (strncmp(p, request, strlen(p)) == 0)))
1019           {
1020             rval = 1;
1021             break;
1022           }
1023
1024           /* reject on first failure for non ipaddresses */
1025           if (!ipaddr) break;
1026         }
1027     };
1028     fclose(f);
1029     return(rval);
1030 };
1031
1032
1033 /****************************************************************************
1034  *
1035  > $Function: handleIncoming()
1036  *
1037  * $Description: Handle an incoming http request.
1038  *
1039  * $Parameters:
1040  *      (s) s . . . . . The http request socket.
1041  *
1042  * $Return: (int) . . . Always 0.
1043  *
1044  ****************************************************************************/
1045 static int handleIncoming(int s)
1046 {
1047   char buf[8192];
1048   char url[8192];  /* hold args too initially */
1049   char credentials[80];
1050   char request[20];
1051   long length=0;
1052   int  major;
1053   int  minor;
1054   char *urlArgs;
1055   char *body=0;
1056
1057   credentials[0] = 0;
1058   do
1059   {
1060     int  count = getLine(s, buf, sizeof(buf));
1061     int blank;
1062     if (count <= 0)  break;
1063     count = sscanf(buf, "%9s %1000s HTTP/%d.%d", request,
1064                    url, &major, &minor);
1065     
1066     if (count < 2) 
1067     {
1068       /* Garbled request/URL */
1069 #if 0
1070       genHttpHeader(&requestInfo,
1071                     HTTP_BAD_REQUEST, requestInfo.dataType,
1072                     HTTP_LENGTH_UNKNOWN);
1073 #endif
1074       break;
1075     }
1076     
1077     /* If no version info, assume 0.9 */
1078     if (count != 4)
1079     {
1080       major = 0;
1081       minor = 9;
1082     }
1083
1084     /* extract url args if present */
1085     urlArgs = strchr(url,'?');
1086     if (urlArgs)
1087     {
1088       *urlArgs=0;
1089       urlArgs++;
1090     }
1091     
1092 #ifdef DEBUG
1093     if (debugHttpd) fprintf(stderr,"url='%s', args=%s\n", url, urlArgs);
1094 #endif  
1095
1096     // read until blank line(s)
1097     blank = 0;
1098     while ((count = getLine(s, buf, sizeof(buf))) >= 0)
1099     {
1100       if (count == 0)
1101       {
1102         if (major > 0) break;
1103         blank++;
1104         if (blank == 2) break;
1105       }
1106 #ifdef DEBUG
1107       if (debugHttpd) fprintf(stderr,"Header: '%s'\n", buf);
1108 #endif  
1109
1110       /* try and do our best to parse more lines */
1111       if ((strncmpi(buf, "Content-length:", 15) == 0))
1112       {
1113         sscanf(buf, "%*s %ld", &length);
1114       }    
1115 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1116       else if (strncmpi(buf, "Authorization:", 14) == 0)
1117       {
1118         /* We only allow Basic credentials.
1119          * It shows up as "Authorization: Basic <userid:password>" where
1120          * the userid:password is base64 encoded.
1121          */
1122         char *ptr = buf+14;
1123         while (*ptr == ' ') ptr++;
1124         if (strncmpi(ptr, "Basic", 5) != 0) break;
1125         ptr += 5;
1126         while (*ptr == ' ') ptr++;
1127         memset(credentials, 0, sizeof(credentials));
1128         decodeBase64(credentials,
1129                      sizeof(credentials)-1,
1130                      ptr,
1131                      strlen(ptr) );
1132     
1133       }
1134     }
1135     if (!checkPerm(url, credentials))
1136     {
1137       sendHeaders(s, HTTP_UNAUTHORIZED, 0, -1, 0);
1138       length=-1;
1139       break; /* no more processing */
1140     }
1141 #else
1142     }
1143 #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1144
1145     /* we are done if an error occurred */
1146     if (length == -1) break;
1147
1148     if (strcmp(url,"/") == 0) strcpy(url,"/index.html");
1149
1150     if (length>0)
1151     {
1152       body=(char*) malloc(length+1);
1153       if (body)
1154       {
1155         length = read(s,body,length);
1156         body[length]=0; // always null terminate for safety
1157         urlArgs=body;
1158       }
1159     }
1160     
1161     if (strstr(url,"..") || strstr(url, "httpd.conf")) 
1162     {
1163       /* protect from .. path creep */
1164       sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0);
1165     }
1166     else if (strstr(url,"cgi-bin")) 
1167     { 
1168       sendCgi(s, url, request, urlArgs, body, length);
1169     }
1170     else if (strncmpi(request,"GET",3) == 0)
1171     {
1172       sendFile(s, url);
1173     }
1174     else
1175     {
1176       sendHeaders(s, HTTP_NOT_IMPLEMENTED, 0, -1, 0);
1177     }
1178   } while (0);
1179
1180 #ifdef DEBUG
1181   if (debugHttpd) fprintf(stderr,"closing socket\n");
1182 #endif  
1183   if (body) free(body);
1184   shutdown(s,SHUT_WR);
1185   shutdown(s,SHUT_RD);
1186   close(s);
1187   
1188   return 0;
1189 }
1190
1191 /****************************************************************************
1192  *
1193  > $Function: miniHttpd()
1194  *
1195  * $Description: The main http server function.
1196  *
1197  *   Given an open socket fildes, listen for new connections and farm out
1198  *   the processing as a forked process.
1199  *
1200  * $Parameters:
1201  *      (int) server. . . The server socket fildes.
1202  *
1203  * $Return: (int) . . . . Always 0.
1204  *
1205  ****************************************************************************/
1206 static int miniHttpd(int server)
1207 {
1208   fd_set readfd, portfd;
1209   int nfound;
1210   
1211   FD_ZERO(&portfd);
1212   FD_SET(server, &portfd);
1213   
1214   /* copy the ports we are watching to the readfd set */
1215   while (1) 
1216   {
1217     readfd = portfd ;
1218     
1219     /* Now wait INDEFINATELY on the set of sockets! */
1220     nfound = select(server+1, &readfd, 0, 0, 0);
1221     
1222     switch (nfound)
1223     {
1224     case 0:
1225       /* select timeout error! */
1226       break ;
1227     case -1:
1228       /* select error */
1229       break;
1230     default:
1231       if (FD_ISSET(server, &readfd))
1232       {
1233         char on;
1234         struct sockaddr_in fromAddr;
1235         char rmt_ip[20];
1236         int addr;
1237         socklen_t fromAddrLen = sizeof(fromAddr);
1238         int s = accept(server,
1239                        (struct sockaddr *)&fromAddr, &fromAddrLen) ;
1240         if (s < 0) 
1241         {
1242           continue;
1243         }
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));
1250 #ifdef DEBUG
1251         if (debugHttpd)
1252         {
1253           fprintf(stderr,"httpMini.cpp: connection from IP=%s, port %d\n",
1254                  rmt_ip, ntohs(fromAddr.sin_port));
1255         }
1256 #endif  
1257         if(checkPerm("ip", rmt_ip) == 0)
1258         {
1259           close(s);
1260           continue;
1261         }
1262         
1263         /*  set the KEEPALIVE option to cull dead connections */
1264         on = 1;
1265         setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &on,
1266                    sizeof (on));
1267
1268         if (fork() == 0) 
1269         {
1270           /* This is the spawned thread */
1271           handleIncoming(s);
1272           exit(0);
1273         }
1274         close(s);
1275       }
1276     }
1277   } // while (1)
1278   return 0;
1279 }
1280
1281 int httpd_main(int argc, char *argv[])
1282 {
1283   int server;
1284   int port = 80;
1285   int c;
1286   
1287   /* check if user supplied a port number */
1288   for (;;) {
1289     c = getopt( argc, argv, "p:ve:d:"
1290 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1291     "r:c:"
1292 #endif
1293     );
1294     if (c == EOF) break;
1295     switch (c) {
1296     case 'v':
1297       debugHttpd=1;
1298       break;
1299     case 'p':
1300       port = atoi(optarg);
1301       break;
1302     case 'd':
1303       printf("%s",decodeString(optarg));
1304       return 0;
1305     case 'e':
1306       printf("%s",encodeString(optarg));
1307       return 0;
1308     case 'r':
1309       realm = optarg;
1310       break;
1311     case 'c':
1312       configFile = optarg;
1313       break;
1314     default:
1315       fprintf(stderr,"%s\n", httpdVersion);
1316       show_usage();
1317       exit(1);
1318     }
1319   }
1320
1321   envp = (char**) malloc((ENVSIZE+1)*sizeof(char*));
1322   if (envp == 0) perror_exit("envp alloc");
1323
1324   server = openServer(port);
1325   if (server < 0) exit(1);
1326
1327   if (!debugHttpd)
1328   {
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");
1334     chdir(dir);
1335     free(dir);
1336   }
1337
1338   miniHttpd(server);
1339   
1340   return 0;
1341 }
1342
1343 #ifdef HTTPD_STANDALONE
1344 int main(int argc, char *argv[])
1345
1346   return httpd_main(argc, argv);
1347 }
1348
1349 #endif