blob: bceb89ba372f5f23d88d8cb1b1de74e74c4e953a [file] [log] [blame]
Glenn L McGrath58c708a2003-01-05 04:01:56 +00001/*
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
44ip:10.10. # Allow any address that begins with 10.10.
45ip:172.20. # Allow 172.20.x.x
46ip: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
82static 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
94void 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
104int debugHttpd;
105static char **envp;
106static int envCount;
107static char *realm = "Web Server Authentication";
108static char *configFile;
109
110static 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
119typedef 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
146typedef struct
147{
148 HttpResponseNum type;
149 const char *name;
150 const char *info;
151} HttpEnumString;
152
153static 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 ****************************************************************************/
195static 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 ****************************************************************************/
232static 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 ****************************************************************************/
271static 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 ****************************************************************************/
302static 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
337static 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 /* '' */
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 ****************************************************************************/
384static 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 ****************************************************************************/
447static 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))
472static 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 ****************************************************************************/
506static 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
548static 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 ****************************************************************************/
576static 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 ****************************************************************************/
657static 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 ****************************************************************************/
697static 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 ****************************************************************************/
893static 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 ****************************************************************************/
978static 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 ****************************************************************************/
1045static 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 ****************************************************************************/
1206static 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
1281int 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
1344int main(int argc, char *argv[])
1345{
1346 return httpd_main(argc, argv);
1347}
1348
1349#endif