fb37224cf882fa2006af0fa1e25c85481d71f612
[oweals/busybox.git] / networking / httpd.c
1 /*
2  * httpd implementation for busybox
3  *
4  * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
5  * Copyright (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>
6  *
7  * simplify patch stolen from libbb without using strdup
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  *
23  *****************************************************************************
24  *
25  * Typical usage:
26  *   for non root user
27  * httpd -p 8080 -h $HOME/public_html
28  *   or for daemon start from rc script with uid=0:
29  * httpd -u www
30  * This is equivalent if www user have uid=80 to
31  * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication"
32  *
33  *
34  * When a url contains "cgi-bin" it is assumed to be a cgi script.  The
35  * server changes directory to the location of the script and executes it
36  * after setting QUERY_STRING and other environment variables.  If url args
37  * are included in the url or as a post, the args are placed into decoded
38  * environment variables.  e.g. /cgi-bin/setup?foo=Hello%20World  will set
39  * the $CGI_foo environment variable to "Hello World" while
40  * CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV enabled.
41  *
42  * The server can also be invoked as a url arg decoder and html text encoder
43  * as follows:
44  *  foo=`httpd -d $foo`             # decode "Hello%20World" as "Hello World"
45  *  bar=`httpd -e "<Hello World>"`  # encode as "%3CHello%20World%3E"
46  *
47  * httpd.conf has the following format:
48
49 A:172.20.         # Allow any address that begins with 172.20
50 A:10.10.          # Allow any address that begins with 10.10.
51 A:10.10           # Allow any address that previous set and 10.100-109.X.X
52 A:127.0.0.1       # Allow local loopback connections
53 D:*               # Deny from other IP connections
54 /cgi-bin:foo:bar  # Require user foo, pwd bar on urls starting with /cgi-bin/
55 /adm:admin:setup  # Require user admin, pwd setup on urls starting with /adm/
56 /adm:toor:PaSsWd  # or user toor, pwd PaSsWd on urls starting with /adm/
57 .au:audio/basic   # additional mime type for audio.au files
58
59 A shortes path and D:from[^*] automaticaly sorting to top.
60 All longest path can`t reset user:password if shorted protect setted.
61
62 A/D may be as a/d or allow/deny - first char case unsensitive parsed only.
63
64 Each subdir can have config file.
65 For protect as user:pass current subdir and subpathes set from subdir config:
66 /:user:pass
67 if not, other subpathes for give effect must have path from httpd root
68 /current_subdir_path_from_httpd_root/subpath:user:pass
69
70 The Deny/Allow IP logic:
71
72         1. Allow all:
73 The config don`t set D: lines
74
75         2. Allow from setted only:
76 see the begin format example
77
78         3. Set deny, allow from other:
79 D:1.2.3.        # deny from 1.2.3.0 - 1.2.3.255
80 D:2.3.4.        # deny from 2.3.4.0 - 2.3.4.255
81 A:*             # allow from other, this line not strongly require
82
83   A global and subdirs config merging logic:
84 allow rules reducing, deny lines strongled.
85   The algorithm combinations:
86
87         4. If current config have
88 A:from
89 D:*
90   subdir config A: lines skiping, D:from - moving top
91
92         5. If current config have
93 D:from
94 A:*
95   result config:
96 D:from current
97 D:from subdir
98 A:from subdir
99  and seting D:* if subdir config have this
100
101  If -c don`t setted, used httpd root config, else httpd root config skiped.
102  Exited with fault if can`t open start config.
103  For set wide open server, use -c /dev/null ;=)
104  */
105
106 #include <stdio.h>
107 #include <ctype.h>         /* for isspace           */
108 #include <string.h>
109 #include <stdlib.h>        /* for malloc            */
110 #include <time.h>
111 #include <unistd.h>        /* for close             */
112 #include <signal.h>
113 #include <sys/types.h>
114 #include <sys/socket.h>    /* for connect and socket*/
115 #include <netinet/in.h>    /* for sockaddr_in       */
116 #include <sys/time.h>
117 #include <sys/stat.h>
118 #include <sys/wait.h>
119 #include <fcntl.h>         /* for open modes        */
120 #include "busybox.h"
121
122
123 static const char httpdVersion[] = "busybox httpd/1.20 31-Jan-2003";
124 static const char default_patch_httpd_conf[] = "/etc";
125 static const char httpd_conf[] = "httpd.conf";
126 static const char home[] = "/www";
127
128 // Note: xfuncs are not used because we want the server to keep running
129 //       if something bad happens due to a malformed user request.
130 //       As a result, all memory allocation after daemonize
131 //       is checked rigorously
132
133 //#define DEBUG 1
134
135 /* Configure options, disabled by default as nonstandart httpd feature */
136 //#define CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
137 //#define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
138 //#define CONFIG_FEATURE_HTTPD_DECODE_URL_STR
139
140 /* disabled as not necessary feature */
141 //#define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
142 //#define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
143 //#define CONFIG_FEATURE_HTTPD_SETUID
144 //#define CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
145
146 /* If seted this you can use this server from internet superserver only */
147 //#define CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
148
149 /* You can use this server as standalone, require libbb.a for linking */
150 //#define HTTPD_STANDALONE
151
152 /* Config options, disable this for do very small module */
153 //#define CONFIG_FEATURE_HTTPD_CGI
154 //#define CONFIG_FEATURE_HTTPD_BASIC_AUTH
155
156 #ifdef HTTPD_STANDALONE
157 /* standalone, enable all features */
158 #undef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
159 /* unset config option for remove warning as redefined */
160 #undef CONFIG_FEATURE_HTTPD_BASIC_AUTH
161 #undef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
162 #undef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
163 #undef CONFIG_FEATURE_HTTPD_DECODE_URL_STR
164 #undef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
165 #undef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
166 #undef CONFIG_FEATURE_HTTPD_CGI
167 #undef CONFIG_FEATURE_HTTPD_SETUID
168 #undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
169 /* enable all features now */
170 #define CONFIG_FEATURE_HTTPD_BASIC_AUTH
171 #define CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
172 #define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
173 #define CONFIG_FEATURE_HTTPD_DECODE_URL_STR
174 #define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
175 #define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
176 #define CONFIG_FEATURE_HTTPD_CGI
177 #define CONFIG_FEATURE_HTTPD_SETUID
178 #define CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
179
180 /* require from libbb.a for linking */
181 const char *applet_name = "httpd";
182
183 void show_usage(void)
184 {
185   fprintf(stderr, "Usage: %s [-p <port>] [-c configFile] [-d/-e <string>] "
186                   "[-r realm] [-u user]\n", applet_name);
187   exit(1);
188 }
189 #endif
190
191 #ifdef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
192 #undef CONFIG_FEATURE_HTTPD_SETUID  /* use inetd user.group config settings */
193 #undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP    /* so is not daemon */
194 /* inetd set stderr to accepted socket and we can`t true see debug messages */
195 #undef DEBUG
196 #endif
197
198 /* CGI environ size */
199 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
200 #define ENVSIZE 50    /* set max 35 CGI_variable */
201 #else
202 #define ENVSIZE 15    /* minimal requires */
203 #endif
204
205 #define MAX_POST_SIZE (64*1024) /* 64k. Its Small? May be ;) */
206
207 #define MAX_MEMORY_BUFF 8192    /* IO buffer */
208
209 typedef struct HT_ACCESS {
210         char *after_colon;
211         struct HT_ACCESS *next;
212         char before_colon[1];         /* really bigger, must last */
213 } Htaccess;
214
215 typedef struct
216 {
217 #ifdef CONFIG_FEATURE_HTTPD_CGI
218   char *envp[ENVSIZE+1];
219   int envCount;
220 #endif
221   char buf[MAX_MEMORY_BUFF];
222
223 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
224   const char *realm;
225 #endif
226   const char *configFile;
227
228   char rmt_ip[16];         /* for set env REMOTE_ADDR */
229   unsigned port;           /* server initial port and for
230                               set env REMOTE_PORT */
231
232   const char *found_mime_type;
233   off_t ContentLength;          /* -1 - unknown */
234   time_t last_mod;
235 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
236   int accepted_socket;
237 #define a_c_r config->accepted_socket
238 #define a_c_w config->accepted_socket
239   int debugHttpd;          /* if seted, don`t stay daemon */
240 #else
241 #define a_c_r 0
242 #define a_c_w 1
243 #endif
244   Htaccess *Httpd_conf_parsed;
245 } HttpdConfig;
246
247 static HttpdConfig *config;
248
249 static const char request_GET[] = "GET";    /* size algorithic optimize */
250
251 static const char* const suffixTable [] = {
252 /* Warning: shorted equalent suffix in one line must be first */
253   ".htm.html", "text/html",
254   ".jpg.jpeg", "image/jpeg",
255   ".gif", "image/gif",
256   ".png", "image/png",
257   ".txt.h.c.cc.cpp", "text/plain",
258   ".css", "text/css",
259   ".wav", "audio/wav",
260   ".avi", "video/x-msvideo",
261   ".qt.mov", "video/quicktime",
262   ".mpe.mpeg", "video/mpeg",
263   ".mid.midi", "audio/midi",
264   ".mp3", "audio/mpeg",
265 #if 0                        /* unpopular */
266   ".au", "audio/basic",
267   ".pac", "application/x-ns-proxy-autoconfig",
268   ".vrml.wrl", "model/vrml",
269 #endif
270   0, "application/octet-stream" /* default */
271   };
272
273 typedef enum
274 {
275   HTTP_OK = 200,
276   HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
277   HTTP_NOT_FOUND = 404,
278   HTTP_NOT_IMPLEMENTED = 501,   /* used for unrecognized requests */
279   HTTP_BAD_REQUEST = 400,       /* malformed syntax */
280   HTTP_FORBIDDEN = 403,
281   HTTP_INTERNAL_SERVER_ERROR = 500,
282 #if 0 /* future use */
283   HTTP_CONTINUE = 100,
284   HTTP_SWITCHING_PROTOCOLS = 101,
285   HTTP_CREATED = 201,
286   HTTP_ACCEPTED = 202,
287   HTTP_NON_AUTHORITATIVE_INFO = 203,
288   HTTP_NO_CONTENT = 204,
289   HTTP_MULTIPLE_CHOICES = 300,
290   HTTP_MOVED_PERMANENTLY = 301,
291   HTTP_MOVED_TEMPORARILY = 302,
292   HTTP_NOT_MODIFIED = 304,
293   HTTP_PAYMENT_REQUIRED = 402,
294   HTTP_BAD_GATEWAY = 502,
295   HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
296   HTTP_RESPONSE_SETSIZE=0xffffffff
297 #endif
298 } HttpResponseNum;
299
300 typedef struct
301 {
302   HttpResponseNum type;
303   const char *name;
304   const char *info;
305 } HttpEnumString;
306
307 static const HttpEnumString httpResponseNames[] = {
308   { HTTP_OK, "OK" },
309   { HTTP_NOT_IMPLEMENTED, "Not Implemented",
310     "The requested method is not recognized by this server." },
311   { HTTP_UNAUTHORIZED, "Unauthorized", "" },
312   { HTTP_NOT_FOUND, "Not Found",
313     "The requested URL was not found on this server." },
314   { HTTP_BAD_REQUEST, "Bad Request" ,
315     "Unsupported method." },
316   { HTTP_FORBIDDEN, "Forbidden", "" },
317   { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error"
318     "Internal Server Error" },
319 #if 0
320   { HTTP_CREATED, "Created" },
321   { HTTP_ACCEPTED, "Accepted" },
322   { HTTP_NO_CONTENT, "No Content" },
323   { HTTP_MULTIPLE_CHOICES, "Multiple Choices" },
324   { HTTP_MOVED_PERMANENTLY, "Moved Permanently" },
325   { HTTP_MOVED_TEMPORARILY, "Moved Temporarily" },
326   { HTTP_NOT_MODIFIED, "Not Modified" },
327   { HTTP_BAD_GATEWAY, "Bad Gateway", "" },
328   { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" },
329 #endif
330 };
331
332
333 static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT";
334 static const char Content_length[] = "Content-length:";
335
336
337 /*
338  * sotring to:
339  * .ext:mime/type
340  * /path:user:pass
341  * /path/subdir:user:pass
342  * D:from
343  * A:from
344  * D:*
345  */
346 static int conf_sort(const void *p1, const void *p2)
347 {
348     const Htaccess *cl1 = *(const Htaccess **)p1;
349     const Htaccess *cl2 = *(const Htaccess **)p2;
350     char c1 = cl1->before_colon[0];
351     char c2 = cl2->before_colon[0];
352     int test;
353
354 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
355     /* .ext line up before other lines for simlify algorithm */
356     test = c2 == '.';
357     if(c1 == '.')
358         return -(!test);
359     if(test)
360         return test;
361 #endif
362
363 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
364     test = c1 == '/';
365     /* /path line up before A/D lines for simlify algorithm */
366     if(test) {
367         if(c2 != '/')
368             return -test;
369         /* a shortes path with user:pass must be first */
370         return strlen(cl1->before_colon) - strlen(cl2->before_colon);
371     } else if(c2 == '/')
372             return !test;
373 #endif
374
375     /* D:from must move top */
376     test = c2 == 'D' && cl2->after_colon[0] != 0;
377     if(c1 == 'D' && cl1->after_colon[0] != 0) {
378         return -(!test);
379     }
380     if(test)
381         return test;
382
383     /* next lines -  A:from */
384     test = c2 == 'A' && cl2->after_colon[0] != 0;
385     if(c1 == 'A' && cl1->after_colon[0] != 0) {
386         return -(!test);
387     }
388     if(test)
389         return test;
390
391     /* end lines - D:* */
392     test = c2 == 'D' && cl2->after_colon[0] == 0;
393     if(c1 == 'D' && cl1->after_colon[0] == 0) {
394         return -(!test);
395     }
396 #ifdef DEBUG
397     if(!test)
398         error_msg_and_die("sort: can`t found compares!");
399 #endif
400     return test;
401 }
402
403
404 /* flag */
405 #define FIRST_PARSE    0
406 #define SUBDIR_PARSE   1
407 #define SIGNALED_PARSE 2
408
409 static void parse_conf(const char *path, int flag)
410 {
411 #define bc cur->before_colon[0]
412 #define ac cur->after_colon[0]
413     FILE *f;
414     Htaccess *prev;
415     Htaccess *cur;
416     const char *cf = config->configFile;
417     char buf[80];
418     char *p0 = NULL;
419     int deny_all = 0;   /* default A:* */
420     int n = 0;          /* count config lines */
421
422     if(flag == SUBDIR_PARSE || cf == NULL) {
423         cf = p0 = alloca(strlen(path) + sizeof(httpd_conf) + 2);
424         if(p0 == NULL) {
425             if(flag == FIRST_PARSE)
426                 error_msg_and_die(memory_exhausted);
427             return;
428         }
429         sprintf(p0, "%s/%s", path, httpd_conf);
430     }
431
432     while((f = fopen(cf, "r")) == NULL) {
433         if(flag != FIRST_PARSE)
434             return;                 /* subdir config not found */
435         if(p0 == NULL)              /* if -c option gived */
436                 perror_msg_and_die("%s", cf);
437         p0 = NULL;
438         cf = httpd_conf;            /* set -c ./httpd_conf */
439     }
440
441     prev = config->Httpd_conf_parsed;
442     if(flag != SUBDIR_PARSE) {
443         /* free previous setuped */
444         while( prev ) {
445                 cur = prev;
446                 prev = cur->next;
447                 free(cur);
448         }
449         config->Httpd_conf_parsed = prev; /* eq NULL */
450     } else {
451         /* parse previous IP logic for merge */
452         for(cur = prev; cur; cur = cur->next) {
453             if(bc == 'D' && ac == 0)
454                 deny_all++;
455             n++;
456             /* find last for set prev->next of merging */
457             if(cur != prev)
458                 prev = prev->next;
459         }
460     }
461
462         /* This could stand some work */
463     while ( (p0 = fgets(buf, 80, f)) != NULL) {
464         char *p;
465         char *colon;
466
467         for(p = colon = p0; *p; p++) {
468                 if(*p == '#') {
469                         *p = 0;
470                         break;
471                 }
472                 if(isspace(*p)) {
473                         if(p != p0) {
474                                 *p = 0;
475                                 break;
476                         }
477                         p0++;
478                 }
479                 else if(*p == ':' && colon <= p0)
480                         colon = p;
481         }
482
483         /* test for empty or strange line */
484         if (colon <= p0 || colon[1] == 0)
485             continue;
486         if(colon[1] == '*')
487             colon[1] = 0;   /* Allow all */
488         if(*p0 == 'a')
489             *p0 = 'A';
490         if(*p0 == 'd')
491             *p0 = 'D';
492         if(*p0 != 'A' && *p0 != 'D'
493 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
494                                     && *p0 != '/'
495 #endif
496 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
497                                         && *p0 != '.'
498 #endif
499                                                        )
500                continue;
501 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
502         if(*p0 == '/' && colon[1] == 0) {
503             /* skip /path:* */
504             continue;
505         }
506 #endif
507
508         if(*p0 == 'A' || *p0 == 'D') {
509             if(colon[1] == 0) {
510                 if(*p0 == 'A' || deny_all != 0)
511                     continue;   /* skip default A:* or double D:* */
512             }
513             if(deny_all != 0 && *p0 == 'A')
514                 continue;   // if previous setted rule D:* skip all subdir A:
515         }
516
517         /* storing current config line */
518         cur = calloc(1, sizeof(Htaccess) + (p-p0));
519         if(cur) {
520             if(*(colon-1) == '/' && (colon-1) != p0)
521                 colon[-1] = 0;      // remove last / from /path/
522             cur->after_colon = strcpy(cur->before_colon, p0);
523             cur->after_colon += (colon-p0);
524             *cur->after_colon++ = 0;
525
526             if(prev == NULL) {
527                 /* first line */
528                 config->Httpd_conf_parsed = prev = cur;
529             } else {
530                 prev->next = cur;
531                 prev = cur;
532             }
533             n++;
534         }
535    }
536    fclose(f);
537
538    if(n > 1) {
539         /* sorting conf lines */
540         Htaccess ** pcur;     /* array for qsort */
541
542         prev = config->Httpd_conf_parsed;
543         pcur = alloca((n + 1) * sizeof(Htaccess *));
544         if(pcur == NULL) {
545             if(flag == FIRST_PARSE)
546                 error_msg_and_die(memory_exhausted);
547             return;
548         }
549         n = 0;
550         for(cur = prev; cur; cur = cur->next)
551                 pcur[n++] = cur;
552         pcur[n] = NULL;
553
554         qsort(pcur, n, sizeof(Htaccess *), conf_sort);
555
556         /* storing sorted config */
557         config->Httpd_conf_parsed = *pcur;
558         for(cur = *pcur; cur; cur = cur->next) {
559 #ifdef DEBUG
560             error_msg("%s: %s:%s", cf, cur->before_colon, cur->after_colon);
561 #endif
562             cur->next = *++pcur;
563         }
564     }
565 }
566
567 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
568 /****************************************************************************
569  *
570  > $Function: encodeString()
571  *
572  * $Description: Given a string, html encode special characters.
573  *   This is used for the -e command line option to provide an easy way
574  *   for scripts to encode result data without confusing browsers.  The
575  *   returned string pointer is memory allocated by malloc().
576  *
577  * $Parameters:
578  *      (const char *) string . . The first string to encode.
579  *
580  * $Return: (char *) . . . .. . . A pointer to the encoded string.
581  *
582  * $Errors: Returns a null string ("") if memory is not available.
583  *
584  ****************************************************************************/
585 static char *encodeString(const char *string)
586 {
587   /* take the simple route and encode everything */
588   /* could possibly scan once to get length.     */
589   int len = strlen(string);
590   char *out = malloc(len*5 +1);
591   char *p=out;
592   char ch;
593
594   if (!out) return "";
595   while ((ch = *string++)) {
596     // very simple check for what to encode
597     if (isalnum(ch)) *p++ = ch;
598     else p += sprintf(p, "&#%d", (unsigned char) ch);
599   }
600   *p=0;
601   return out;
602 }
603 #endif          /* CONFIG_FEATURE_HTTPD_ENCODE_URL_STR */
604
605 #ifdef CONFIG_FEATURE_HTTPD_DECODE_URL_STR
606 /****************************************************************************
607  *
608  > $Function: decodeString()
609  *
610  * $Description: Given a URL encoded string, convert it to plain ascii.
611  *   Since decoding always makes strings smaller, the decode is done in-place.
612  *   Thus, callers should strdup() the argument if they do not want the
613  *   argument modified.  The return is the original pointer, allowing this
614  *   function to be easily used as arguments to other functions.
615  *
616  * $Parameters:
617  *      (char *) string . . . The first string to decode.
618  *
619  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
620  *
621  * $Errors: None
622  *
623  ****************************************************************************/
624 static char *decodeString(char *string)
625 {
626   /* note that decoded string is always shorter than original */
627   char *orig = string;
628   char *ptr = string;
629   while (*ptr)
630   {
631     if (*ptr == '+')    { *string++ = ' '; ptr++; }
632     else if (*ptr != '%') *string++ = *ptr++;
633     else  {
634       unsigned int value;
635       sscanf(ptr+1, "%2X", &value);
636       *string++ = value;
637       ptr += 3;
638     }
639   }
640   *string = '\0';
641   return orig;
642 }
643 #endif          /* CONFIG_FEATURE_HTTPD_DECODE_URL_STR */
644
645
646 #ifdef CONFIG_FEATURE_HTTPD_CGI
647 /****************************************************************************
648  *
649  > $Function: addEnv()
650  *
651  * $Description: Add an enviornment variable setting to the global list.
652  *    A NAME=VALUE string is allocated, filled, and added to the list of
653  *    environment settings passed to the cgi execution script.
654  *
655  * $Parameters:
656  *  (char *) name_before_underline - The first part environment variable name.
657  *  (char *) name_after_underline  - The second part environment variable name.
658  *  (char *) value  . . The value to which the env variable is set.
659  *
660  * $Return: (void)
661  *
662  * $Errors: Silently returns if the env runs out of space to hold the new item
663  *
664  ****************************************************************************/
665 static void addEnv(const char *name_before_underline,
666                         const char *name_after_underline, const char *value)
667 {
668   char *s;
669
670   if (config->envCount >= ENVSIZE)
671         return;
672   if (!value)
673         value = "";
674   s = malloc(strlen(name_before_underline) + strlen(name_after_underline) +
675                         strlen(value) + 3);
676   if (s) {
677     const char *underline = *name_after_underline ? "_" : "";
678
679     sprintf(s,"%s%s%s=%s", name_before_underline, underline,
680                                         name_after_underline, value);
681     config->envp[config->envCount++] = s;
682     config->envp[config->envCount] = 0;
683   }
684 }
685
686 /* set environs SERVER_PORT and REMOTE_PORT */
687 static void addEnvPort(const char *port_name)
688 {
689       char buf[16];
690
691       sprintf(buf, "%u", config->port);
692       addEnv(port_name, "PORT", buf);
693 }
694 #endif          /* CONFIG_FEATURE_HTTPD_CGI */
695
696 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
697 /****************************************************************************
698  *
699  > $Function: addEnvCgi
700  *
701  * $Description: Create environment variables given a URL encoded arg list.
702  *   For each variable setting the URL encoded arg list, create a corresponding
703  *   environment variable.  URL encoded arguments have the form
704  *      name1=value1&name2=value2&name3=&ignores
705  *       from this example, name3 set empty value, tail without '=' skiping
706  *
707  * $Parameters:
708  *      (char *) pargs . . . . A pointer to the URL encoded arguments.
709  *
710  * $Return: None
711  *
712  * $Errors: None
713  *
714  ****************************************************************************/
715 static void addEnvCgi(const char *pargs)
716 {
717   char *args;
718   char *memargs;
719   if (pargs==0) return;
720
721   /* args are a list of name=value&name2=value2 sequences */
722   memargs = args = strdup(pargs);
723   while (args && *args) {
724     const char *name = args;
725     char *value = strchr(args, '=');
726
727     if (!value)         /* &XXX without '=' */
728         break;
729     *value++ = 0;
730     args = strchr(value, '&');
731     if (args)
732         *args++ = 0;
733     addEnv("CGI", name, decodeString(value));
734   }
735   free(memargs);
736 }
737 #endif /* CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV */
738
739
740 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
741 /****************************************************************************
742  *
743  > $Function: decodeBase64()
744  *
745  > $Description: Decode a base 64 data stream as per rfc1521.
746  *    Note that the rfc states that none base64 chars are to be ignored.
747  *    Since the decode always results in a shorter size than the input, it is
748  *    OK to pass the input arg as an output arg.
749  *
750  * $Parameter:
751  *      (char *) Data . . . . A pointer to a base64 encoded string.
752  *                            Where to place the decoded data.
753  *
754  * $Return: void
755  *
756  * $Errors: None
757  *
758  ****************************************************************************/
759 static void decodeBase64(char *Data)
760 {
761   int i = 0;
762   static const char base64ToBin[] =
763     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
764
765   const unsigned char *in = Data;
766   // The decoded size will be at most 3/4 the size of the encoded
767   unsigned long ch = 0;
768
769   while (*in) {
770     unsigned char conv = 0;
771
772     while (*in) {
773       const char *p64;
774
775       p64 = strchr(base64ToBin, *in++);
776       if(p64 == NULL)
777         continue;
778       conv = (p64 - base64ToBin);
779       break;
780     }
781     ch = (ch << 6) | conv;
782     i++;
783     if (i== 4) {
784       *Data++ = (char) (ch >> 16);
785       *Data++ = (char) (ch >> 8);
786       *Data++ = (char) ch;
787       i = 0;
788     }
789   }
790   *Data = 0;
791 }
792 #endif
793
794
795 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
796 /****************************************************************************
797  *
798  > $Function: openServer()
799  *
800  * $Description: create a listen server socket on the designated port.
801  *
802  * $Return: (int)  . . . A connection socket. -1 for errors.
803  *
804  * $Errors: None
805  *
806  ****************************************************************************/
807 static int openServer(void)
808 {
809   struct sockaddr_in lsocket;
810   int fd;
811
812   /* create the socket right now */
813   /* inet_addr() returns a value that is already in network order */
814   memset(&lsocket, 0, sizeof(lsocket));
815   lsocket.sin_family = AF_INET;
816   lsocket.sin_addr.s_addr = INADDR_ANY;
817   lsocket.sin_port = htons(config->port) ;
818   fd = socket(AF_INET, SOCK_STREAM, 0);
819   if (fd >= 0) {
820     /* tell the OS it's OK to reuse a previous address even though */
821     /* it may still be in a close down state.  Allows bind to succeed. */
822     int on = 1;
823 #ifdef SO_REUSEPORT
824     setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)) ;
825 #else
826     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) ;
827 #endif
828     if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) {
829       listen(fd, 9);
830       signal(SIGCHLD, SIG_IGN);   /* prevent zombie (defunct) processes */
831     } else {
832         perror_msg_and_die("bind");
833     }
834   } else {
835         perror_msg_and_die("create socket");
836   }
837   return fd;
838 }
839 #endif  /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
840
841 /****************************************************************************
842  *
843  > $Function: sendHeaders()
844  *
845  * $Description: Create and send HTTP response headers.
846  *   The arguments are combined and sent as one write operation.  Note that
847  *   IE will puke big-time if the headers are not sent in one packet and the
848  *   second packet is delayed for any reason.
849  *
850  * $Parameter:
851  *      (HttpResponseNum) responseNum . . . The result code to send.
852  *
853  * $Return: (int)  . . . . writing errors
854  *
855  ****************************************************************************/
856 static int sendHeaders(HttpResponseNum responseNum)
857 {
858   char *buf = config->buf;
859   const char *responseString = "";
860   const char *infoString = 0;
861   unsigned int i;
862   time_t timer = time(0);
863   char timeStr[80];
864   int len;
865
866   for (i = 0;
867         i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++) {
868                 if (httpResponseNames[i].type == responseNum) {
869                         responseString = httpResponseNames[i].name;
870                         infoString = httpResponseNames[i].info;
871                         break;
872                 }
873   }
874   if (responseNum != HTTP_OK) {
875         config->found_mime_type = "text/html";  // error message is HTML
876   }
877
878   /* emit the current date */
879   strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer));
880   len = sprintf(buf,
881         "HTTP/1.0 %d %s\nContent-type: %s\r\n"
882         "Date: %s\r\nConnection: close\r\n",
883           responseNum, responseString, config->found_mime_type, timeStr);
884
885 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
886   if (responseNum == HTTP_UNAUTHORIZED) {
887     len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n",
888                                                             config->realm);
889   }
890 #endif
891   if (config->ContentLength != -1) {    /* file */
892     strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod));
893     len += sprintf(buf+len, "Last-Modified: %s\r\n%s %ld\r\n",
894                               timeStr, Content_length, config->ContentLength);
895   }
896   strcat(buf, "\r\n");
897   len += 2;
898   if (infoString) {
899     len += sprintf(buf+len,
900             "<HEAD><TITLE>%d %s</TITLE></HEAD>\n"
901             "<BODY><H1>%d %s</H1>\n%s\n</BODY>\n",
902             responseNum, responseString,
903             responseNum, responseString, infoString);
904   }
905 #ifdef DEBUG
906   if (config->debugHttpd) fprintf(stderr, "Headers: '%s'", buf);
907 #endif
908   return full_write(a_c_w, buf, len);
909 }
910
911 /****************************************************************************
912  *
913  > $Function: getLine()
914  *
915  * $Description: Read from the socket until an end of line char found.
916  *
917  *   Characters are read one at a time until an eol sequence is found.
918  *
919  * $Parameters:
920  *      (char *) buf  . . Where to place the read result.
921  *
922  * $Return: (int) . . . . number of characters read.  -1 if error.
923  *
924  ****************************************************************************/
925 static int getLine(char *buf)
926 {
927   int  count = 0;
928
929   while (read(a_c_r, buf + count, 1) == 1) {
930     if (buf[count] == '\r') continue;
931     if (buf[count] == '\n') {
932       buf[count] = 0;
933       return count;
934     }
935     if(count < (MAX_MEMORY_BUFF-1))      /* check owerflow */
936         count++;
937   }
938   if (count) return count;
939   else return -1;
940 }
941
942 #ifdef CONFIG_FEATURE_HTTPD_CGI
943 /****************************************************************************
944  *
945  > $Function: sendCgi()
946  *
947  * $Description: Execute a CGI script and send it's stdout back
948  *
949  *   Environment variables are set up and the script is invoked with pipes
950  *   for stdin/stdout.  If a post is being done the script is fed the POST
951  *   data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
952  *
953  * $Parameters:
954  *      (const char *) url . . . The requested URL (with leading /).
955  *      (const char *urlArgs). . Any URL arguments.
956  *      (const char *body) . . . POST body contents.
957  *      (int bodyLen)  . . . . . Length of the post body.
958  *      (const char *cookie) . . For set HTTP_COOKIE.
959
960  *
961  * $Return: (char *)  . . . . A pointer to the decoded string (same as input).
962  *
963  * $Errors: None
964  *
965  ****************************************************************************/
966 static int sendCgi(const char *url,
967                    const char *request, const char *urlArgs,
968                    const char *body, int bodyLen, const char *cookie)
969 {
970   int fromCgi[2];  /* pipe for reading data from CGI */
971   int toCgi[2];    /* pipe for sending data to CGI */
972
973   static char * argp[] = { 0, 0 };
974   int pid = 0;
975   int inFd;
976   int outFd;
977   int firstLine = 1;
978
979   do {
980     if (pipe(fromCgi) != 0) {
981       break;
982     }
983     if (pipe(toCgi) != 0) {
984       break;
985     }
986
987     pid = fork();
988     if (pid < 0) {
989         pid = 0;
990         break;
991     }
992
993     if (!pid) {
994       /* child process */
995       char *script;
996       char *purl = strdup( url );
997       char realpath_buff[MAXPATHLEN];
998
999       if(purl == NULL)
1000         _exit(242);
1001
1002       inFd  = toCgi[0];
1003       outFd = fromCgi[1];
1004
1005       dup2(inFd, 0);  // replace stdin with the pipe
1006       dup2(outFd, 1);  // replace stdout with the pipe
1007
1008 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1009       if (!config->debugHttpd)
1010 #endif
1011         dup2(outFd, 2);  // replace stderr with the pipe
1012
1013       close(toCgi[0]);
1014       close(toCgi[1]);
1015       close(fromCgi[0]);
1016       close(fromCgi[1]);
1017
1018       /*
1019        * Find PATH_INFO.
1020        */
1021       script = purl;
1022       while((script = strchr( script + 1, '/' )) != NULL) {
1023         /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
1024         struct stat sb;
1025
1026         *script = '\0';
1027         if(is_directory(purl + 1, 1, &sb) == 0) {
1028                 /* not directory, found script.cgi/PATH_INFO */
1029                 *script = '/';
1030                 break;
1031         }
1032         *script = '/';          /* is directory, find next '/' */
1033       }
1034       addEnv("PATH", "INFO", script);   /* set /PATH_INFO or NULL */
1035       addEnv("PATH",           "",         getenv("PATH"));
1036       addEnv("REQUEST",        "METHOD",   request);
1037       if(urlArgs) {
1038         char *uri = alloca(strlen(purl) + 2 + strlen(urlArgs));
1039         if(uri)
1040             sprintf(uri, "%s?%s", purl, urlArgs);
1041         addEnv("REQUEST",        "URI",   uri);
1042       } else {
1043         addEnv("REQUEST",        "URI",   purl);
1044       }
1045       if(script != NULL)
1046         *script = '\0';         /* reduce /PATH_INFO */
1047       /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
1048       addEnv("SCRIPT_NAME",    "",         purl);
1049       addEnv("QUERY_STRING",   "",         urlArgs);
1050       addEnv("SERVER",         "SOFTWARE", httpdVersion);
1051       addEnv("SERVER",         "PROTOCOL", "HTTP/1.0");
1052       addEnv("GATEWAY_INTERFACE", "",      "CGI/1.1");
1053 #ifdef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
1054       addEnv("REMOTE",         "ADDR",     config->rmt_ip);
1055       addEnvPort("REMOTE");
1056 #else
1057       addEnv("REMOTE_ADDR",     "",        config->rmt_ip);
1058 #endif
1059       if(bodyLen) {
1060         char sbl[32];
1061
1062         sprintf(sbl, "%d", bodyLen);
1063         addEnv("CONTENT_LENGTH", "", sbl);
1064       }
1065       if(cookie)
1066         addEnv("HTTP_COOKIE", "", cookie);
1067
1068 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
1069       if (request != request_GET) {
1070         addEnvCgi(body);
1071       } else {
1072         addEnvCgi(urlArgs);
1073       }
1074 #endif
1075
1076         /* set execve argp[0] without path */
1077       argp[0] = strrchr( purl, '/' ) + 1;
1078         /* but script argp[0] must have absolute path and chdiring to this */
1079       if(realpath(purl + 1, realpath_buff) != NULL) {
1080             script = strrchr(realpath_buff, '/');
1081             if(script) {
1082                 *script = '\0';
1083                 if(chdir(realpath_buff) == 0) {
1084                     *script = '/';
1085       // now run the program.  If it fails, use _exit() so no destructors
1086       // get called and make a mess.
1087                     execve(realpath_buff, argp, config->envp);
1088                 }
1089             }
1090       }
1091 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1092       config->accepted_socket = 1;      /* send to stdout */
1093 #endif
1094       sendHeaders(HTTP_NOT_FOUND);
1095       _exit(242);
1096     } /* end child */
1097
1098   } while (0);
1099
1100   if (pid) {
1101     /* parent process */
1102     int status;
1103
1104     inFd  = fromCgi[0];
1105     outFd = toCgi[1];
1106     close(fromCgi[1]);
1107     close(toCgi[0]);
1108     if (body) full_write(outFd, body, bodyLen);
1109     close(outFd);
1110
1111     while (1) {
1112       struct timeval timeout;
1113       fd_set readSet;
1114       char buf[160];
1115       int nfound;
1116       int count;
1117
1118       FD_ZERO(&readSet);
1119       FD_SET(inFd, &readSet);
1120
1121       /* Now wait on the set of sockets! */
1122       timeout.tv_sec = 0;
1123       timeout.tv_usec = 10000;
1124       nfound = select(inFd + 1, &readSet, 0, 0, &timeout);
1125
1126       if (nfound <= 0) {
1127         if (waitpid(pid, &status, WNOHANG) > 0) {
1128           close(inFd);
1129 #ifdef DEBUG
1130           if (config->debugHttpd) {
1131             if (WIFEXITED(status))
1132               error_msg("piped has exited with status=%d", WEXITSTATUS(status));
1133             if (WIFSIGNALED(status))
1134               error_msg("piped has exited with signal=%d", WTERMSIG(status));
1135           }
1136 #endif
1137           pid = -1;
1138           break;
1139         }
1140       } else {
1141         int s = a_c_w;
1142
1143         // There is something to read
1144         count = full_read(inFd, buf, sizeof(buf)-1);
1145         // If a read returns 0 at this point then some type of error has
1146         // occurred.  Bail now.
1147         if (count == 0) break;
1148         if (count > 0) {
1149           if (firstLine) {
1150             /* check to see if the user script added headers */
1151             if (strncmp(buf, "HTTP/1.0 200 OK\n", 4) != 0) {
1152               full_write(s, "HTTP/1.0 200 OK\n", 16);
1153             }
1154             if (strstr(buf, "ontent-") == 0) {
1155               full_write(s, "Content-type: text/plain\n\n", 26);
1156             }
1157             firstLine=0;
1158           }
1159           full_write(s, buf, count);
1160 #ifdef DEBUG
1161           if (config->debugHttpd)
1162                 fprintf(stderr, "cgi read %d bytes\n", count);
1163 #endif
1164         }
1165       }
1166     }
1167   }
1168   return 0;
1169 }
1170 #endif          /* CONFIG_FEATURE_HTTPD_CGI */
1171
1172 /****************************************************************************
1173  *
1174  > $Function: sendFile()
1175  *
1176  * $Description: Send a file response to an HTTP request
1177  *
1178  * $Parameter:
1179  *      (const char *) url . . The URL requested.
1180  *      (char *) buf . . . . . The stack buffer.
1181  *
1182  * $Return: (int)  . . . . . . Always 0.
1183  *
1184  ****************************************************************************/
1185 static int sendFile(const char *url, char *buf)
1186 {
1187   char * suffix;
1188   int  f;
1189   const char * const * table;
1190   const char * try_suffix;
1191
1192   suffix = strrchr(url, '.');
1193
1194   for (table = suffixTable; *table; table += 2)
1195         if(suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) {
1196                 try_suffix += strlen(suffix);
1197                 if(*try_suffix == 0 || *try_suffix == '.')
1198                         break;
1199         }
1200   /* also, if not found, set default as "application/octet-stream";  */
1201   config->found_mime_type = *(table+1);
1202 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1203   if (suffix) {
1204     Htaccess * cur;
1205
1206     for (cur = config->Httpd_conf_parsed; cur; cur = cur->next) {
1207         if(strcmp(cur->before_colon, suffix) == 0) {
1208                 config->found_mime_type = cur->after_colon;
1209                 break;
1210         }
1211     }
1212   }
1213 #endif  /* CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */
1214
1215 #ifdef DEBUG
1216     if (config->debugHttpd)
1217         fprintf(stderr, "Sending file '%s' Content-type: %s\n",
1218                                         url, config->found_mime_type);
1219 #endif
1220
1221   f = open(url, O_RDONLY);
1222   if (f >= 0) {
1223         int count;
1224
1225         sendHeaders(HTTP_OK);
1226         while ((count = full_read(f, buf, MAX_MEMORY_BUFF)) > 0) {
1227                 full_write(a_c_w, buf, count);
1228         }
1229         close(f);
1230   } else {
1231 #ifdef DEBUG
1232         if (config->debugHttpd)
1233                 perror_msg("Unable to open '%s'", url);
1234 #endif
1235         sendHeaders(HTTP_NOT_FOUND);
1236   }
1237
1238   return 0;
1239 }
1240
1241 /****************************************************************************
1242  *
1243  > $Function: checkPerm()
1244  *
1245  * $Description: Check the permission file for access.
1246  *
1247  *   If config file isn't present, everything is allowed.
1248  *   Entries are of the form you can see example from header source
1249  *
1250  * $Parameters:
1251  *      (const char *) path  . . . . The file path or NULL for ip addresses.
1252  *      (const char *) request . . . User information to validate.
1253  *
1254  * $Return: (int)  . . . . . . . . . 1 if request OK, 0 otherwise.
1255  *
1256  ****************************************************************************/
1257
1258
1259 static int checkPerm(const char *path, const char *request)
1260 {
1261     Htaccess * cur;
1262     const char *p;
1263
1264 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1265     int ipaddr = path == NULL;
1266     const char *prev = NULL;
1267 #else
1268 #   define ipaddr 1
1269 #endif
1270
1271     /* This could stand some work */
1272     for (cur = config->Httpd_conf_parsed; cur; cur = cur->next) {
1273         const char *p0 = cur->before_colon;
1274
1275         if(*p0 == 'A' || *p0 == 'D') {
1276                 if(!ipaddr)
1277                         continue;
1278         } else {
1279                 if(ipaddr)
1280                         continue;
1281 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1282                 if(*p0 == '.')
1283                         continue;
1284 #endif
1285 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1286                 if(prev != NULL && strcmp(prev, p0) != 0)
1287                         continue;       /* find next identical */
1288 #endif
1289         }
1290
1291         p = cur->after_colon;
1292 #ifdef DEBUG
1293         if (config->debugHttpd)
1294                 fprintf(stderr,"checkPerm: '%s' ? '%s'\n",
1295                                 (ipaddr ? p : p0), request);
1296 #endif
1297         if(ipaddr) {
1298                 if(strncmp(p, request, strlen(p)) != 0)
1299                         continue;
1300                 return *p0 == 'A';   /* Allow/Deny */
1301
1302         }
1303 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1304           else {
1305                 int l = strlen(p0);
1306
1307                 if(strncmp(p0, path, l) == 0 &&
1308                                 (l == 1 || path[l] == '/' || path[l] == 0)) {
1309                         /* path match found.  Check request */
1310                         if (strcmp(p, request) == 0)
1311                                 return 1;   /* Ok */
1312                         /* unauthorized, but check next /path:user:password */
1313                         prev = p0;
1314                 }
1315           }
1316 #endif
1317     }   /* for */
1318
1319 #ifndef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1320         /* if uncofigured, return 1 - access from all */
1321     return 1;
1322 #else
1323     return prev == NULL;
1324 #endif
1325 }
1326
1327
1328 /****************************************************************************
1329  *
1330  > $Function: handleIncoming()
1331  *
1332  * $Description: Handle an incoming http request.
1333  *
1334  ****************************************************************************/
1335 static void handleIncoming(void)
1336 {
1337   char *buf = config->buf;
1338   char *url;
1339   char *purl;
1340   int  blank = -1;
1341   char *urlArgs;
1342 #ifdef CONFIG_FEATURE_HTTPD_CGI
1343   const char *prequest = request_GET;
1344   char *body = 0;
1345   long length=0;
1346   char *cookie = 0;
1347 #endif
1348   char *test;
1349   struct stat sb;
1350
1351 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1352   int credentials = -1;  /* if not requred this is Ok */
1353 #endif
1354
1355   do {
1356     int  count;
1357
1358     if (getLine(buf) <= 0)
1359         break;  /* closed */
1360
1361     purl = strpbrk(buf, " \t");
1362     if(purl == NULL) {
1363 BAD_REQUEST:
1364       sendHeaders(HTTP_BAD_REQUEST);
1365       break;
1366     }
1367     *purl = 0;
1368 #ifdef CONFIG_FEATURE_HTTPD_CGI
1369     if(strcasecmp(buf, prequest) != 0) {
1370         prequest = "POST";
1371         if(strcasecmp(buf, prequest) != 0) {
1372             sendHeaders(HTTP_NOT_IMPLEMENTED);
1373             break;
1374         }
1375     }
1376 #else
1377     if(strcasecmp(buf, request_GET) != 0) {
1378         sendHeaders(HTTP_NOT_IMPLEMENTED);
1379         break;
1380     }
1381 #endif
1382     *purl = ' ';
1383     count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank);
1384
1385     if (count < 1 || buf[0] != '/') {
1386       /* Garbled request/URL */
1387       goto BAD_REQUEST;
1388     }
1389     url = alloca(strlen(buf) + 12);      /* + sizeof("/index.html\0") */
1390     if(url == NULL) {
1391         sendHeaders(HTTP_INTERNAL_SERVER_ERROR);
1392         break;
1393     }
1394     strcpy(url, buf);
1395     /* extract url args if present */
1396     urlArgs = strchr(url, '?');
1397     if (urlArgs) {
1398       *urlArgs++ = 0;   /* next code can set '/' to this pointer,
1399                            but CGI script can`t be a directory */
1400     }
1401
1402     /* algorithm stolen from libbb simplify_path(),
1403        but don`t strdup and reducing trailing slash */
1404     purl = test = url;
1405
1406     do {
1407         if (*purl == '/') {
1408             if (*test == '/') {        /* skip duplicate (or initial) slash */
1409                 continue;
1410             } else if (*test == '.') {
1411                 if (test[1] == '/' || test[1] == 0) { /* skip extra '.' */
1412                     continue;
1413                 } else if ((test[1] == '.') && (test[2] == '/' || test[2] == 0)) {
1414                     ++test;
1415                     if (purl == url) {
1416                         /* protect out root */
1417                         goto BAD_REQUEST;
1418                     }
1419                     while (*--purl != '/');    /* omit previous dir */
1420                     continue;
1421                 }
1422             }
1423         }
1424         *++purl = *test;
1425     } while (*++test);
1426
1427     *++purl = 0;        /* so keep last character */
1428     test = purl;        /* end ptr */
1429
1430     /* If URL is directory, adding '/' */
1431     if(test[-1] != '/') {
1432             if ( is_directory(url + 1, 1, &sb) ) {
1433                     *test++ = '/';
1434                     *test = 0;
1435                     purl = test;    /* end ptr */
1436             }
1437     }
1438 #ifdef DEBUG
1439     if (config->debugHttpd)
1440         fprintf(stderr, "url='%s', args=%s\n", url, urlArgs);
1441 #endif
1442
1443     test = url;
1444     while((test = strchr( test + 1, '/' )) != NULL) {
1445         /* have path1/path2 */
1446         *test = '\0';
1447         if( is_directory(url + 1, 1, &sb) ) {
1448                 /* may be having subdir config */
1449                 parse_conf(url + 1, SUBDIR_PARSE);
1450         }
1451         *test = '/';
1452     }
1453
1454     // read until blank line for HTTP version specified, else parse immediate
1455     while (blank >= 0 && (count = getLine(buf)) > 0) {
1456
1457 #ifdef DEBUG
1458       if (config->debugHttpd) fprintf(stderr, "Header: '%s'\n", buf);
1459 #endif
1460
1461 #ifdef CONFIG_FEATURE_HTTPD_CGI
1462       /* try and do our best to parse more lines */
1463       if ((strncasecmp(buf, Content_length, 15) == 0)) {
1464         if(prequest != request_GET)
1465                 length = strtol(buf + 15, 0, 0); // extra read only for POST
1466       } else if ((strncasecmp(buf, "Cookie:", 7) == 0)) {
1467                 for(test = buf + 7; isspace(*test); test++)
1468                         ;
1469                 cookie = strdup(test);
1470       }
1471 #endif
1472
1473 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1474       if (strncasecmp(buf, "Authorization:", 14) == 0) {
1475         /* We only allow Basic credentials.
1476          * It shows up as "Authorization: Basic <userid:password>" where
1477          * the userid:password is base64 encoded.
1478          */
1479         for(test = buf + 14; isspace(*test); test++)
1480                 ;
1481         if (strncasecmp(test, "Basic", 5) != 0)
1482                 continue;
1483
1484         test += 5;  /* decodeBase64() skiping space self */
1485         decodeBase64(test);
1486         credentials = checkPerm(url, test);
1487       }
1488 #endif          /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1489
1490     }   /* while extra header reading */
1491
1492
1493     if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 ||
1494                         checkPerm(NULL, config->rmt_ip) == 0) {
1495                 /* protect listing [/path]/httpd_conf or IP deny */
1496 #ifdef CONFIG_FEATURE_HTTPD_CGI
1497 FORBIDDEN:      /* protect listing /cgi-bin */
1498 #endif
1499                 sendHeaders(HTTP_FORBIDDEN);
1500                 break;
1501     }
1502
1503 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1504     if (credentials <= 0 && checkPerm(url, ":") == 0) {
1505       sendHeaders(HTTP_UNAUTHORIZED);
1506       break;
1507     }
1508 #endif
1509
1510     test = url + 1;      /* skip first '/' */
1511
1512 #ifdef CONFIG_FEATURE_HTTPD_CGI
1513     /* if strange Content-Length */
1514     if (length < 0 || length > MAX_POST_SIZE)
1515         break;
1516
1517     if (length > 0) {
1518       body = malloc(length + 1);
1519       if (body) {
1520         length = full_read(a_c_r, body, length);
1521         if(length < 0)          // closed
1522                 length = 0;
1523         body[length] = 0;       // always null terminate for safety
1524       }
1525     }
1526
1527     if (strncmp(test, "cgi-bin", 7) == 0) {
1528                 if(test[7] == 0 || (test[7] == '/' && test[8] == 0))
1529                         goto FORBIDDEN;     // protect listing cgi-bin
1530                 sendCgi(url, prequest, urlArgs, body, length, cookie);
1531     } else {
1532         if (prequest != request_GET)
1533                 sendHeaders(HTTP_NOT_IMPLEMENTED);
1534         else {
1535 #endif  /* CONFIG_FEATURE_HTTPD_CGI */
1536                 if(purl[-1] == '/')
1537                         strcpy(purl, "index.html");
1538                 if ( stat(test, &sb ) == 0 ) {
1539                         config->ContentLength = sb.st_size;
1540                         config->last_mod = sb.st_mtime;
1541                 }
1542                 sendFile(test, buf);
1543 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1544                 /* unset if non inetd looped */
1545                 config->ContentLength = -1;
1546 #endif
1547
1548 #ifdef CONFIG_FEATURE_HTTPD_CGI
1549         }
1550     }
1551 #endif
1552
1553   } while (0);
1554
1555
1556 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1557 /* from inetd don`t looping: freeing, closing automatic from exit always */
1558 # ifdef DEBUG
1559   if (config->debugHttpd) fprintf(stderr, "closing socket\n");
1560 # endif
1561 # ifdef CONFIG_FEATURE_HTTPD_CGI
1562   free(body);
1563   free(cookie);
1564 # endif
1565   shutdown(a_c_w, SHUT_WR);
1566   shutdown(a_c_r, SHUT_RD);
1567   close(config->accepted_socket);
1568 #endif  /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
1569 }
1570
1571 /****************************************************************************
1572  *
1573  > $Function: miniHttpd()
1574  *
1575  * $Description: The main http server function.
1576  *
1577  *   Given an open socket fildes, listen for new connections and farm out
1578  *   the processing as a forked process.
1579  *
1580  * $Parameters:
1581  *      (int) server. . . The server socket fildes.
1582  *
1583  * $Return: (int) . . . . Always 0.
1584  *
1585  ****************************************************************************/
1586 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1587 static int miniHttpd(int server)
1588 {
1589   fd_set readfd, portfd;
1590   int nfound;
1591
1592   FD_ZERO(&portfd);
1593   FD_SET(server, &portfd);
1594
1595   /* copy the ports we are watching to the readfd set */
1596   while (1) {
1597     readfd = portfd;
1598
1599     /* Now wait INDEFINATELY on the set of sockets! */
1600     nfound = select(server + 1, &readfd, 0, 0, 0);
1601
1602     switch (nfound) {
1603     case 0:
1604       /* select timeout error! */
1605       break ;
1606     case -1:
1607       /* select error */
1608       break;
1609     default:
1610       if (FD_ISSET(server, &readfd)) {
1611         int on;
1612         struct sockaddr_in fromAddr;
1613
1614         unsigned int addr;
1615         socklen_t fromAddrLen = sizeof(fromAddr);
1616         int s = accept(server,
1617                        (struct sockaddr *)&fromAddr, &fromAddrLen);
1618
1619         if (s < 0) {
1620           continue;
1621         }
1622         config->accepted_socket = s;
1623         addr = ntohl(fromAddr.sin_addr.s_addr);
1624         sprintf(config->rmt_ip, "%u.%u.%u.%u",
1625                 (unsigned char)(addr >> 24),
1626                 (unsigned char)(addr >> 16),
1627                 (unsigned char)(addr >> 8),
1628                                 addr & 0xff);
1629         config->port = ntohs(fromAddr.sin_port);
1630 #ifdef DEBUG
1631         if (config->debugHttpd) {
1632                 error_msg("connection from IP=%s, port %u\n",
1633                                         config->rmt_ip, config->port);
1634         }
1635 #endif
1636         /*  set the KEEPALIVE option to cull dead connections */
1637         on = 1;
1638         setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof (on));
1639
1640         if (config->debugHttpd || fork() == 0) {
1641           /* This is the spawned thread */
1642           handleIncoming();
1643           if(!config->debugHttpd)
1644                 exit(0);
1645         }
1646         close(s);
1647       }
1648     }
1649   } // while (1)
1650   return 0;
1651 }
1652
1653 #else
1654     /* from inetd */
1655
1656 static int miniHttpd(void)
1657 {
1658   struct sockaddr_in fromAddrLen;
1659   socklen_t sinlen = sizeof (struct sockaddr_in);
1660   unsigned int addr;
1661
1662   getpeername (0, (struct sockaddr *)&fromAddrLen, &sinlen);
1663   addr = ntohl(fromAddrLen.sin_addr.s_addr);
1664   sprintf(config->rmt_ip, "%u.%u.%u.%u",
1665                 (unsigned char)(addr >> 24),
1666                 (unsigned char)(addr >> 16),
1667                 (unsigned char)(addr >> 8),
1668                                 addr & 0xff);
1669   config->port = ntohs(fromAddrLen.sin_port);
1670   handleIncoming();
1671   return 0;
1672 }
1673 #endif  /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
1674
1675 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1676 static void sighup_handler(int sig)
1677 {
1678         /* set and reset */
1679         struct sigaction sa;
1680
1681         sa.sa_handler = sighup_handler;
1682         sigemptyset(&sa.sa_mask);
1683         sa.sa_flags = SA_RESTART;
1684         sigaction(SIGHUP, &sa, NULL);
1685         parse_conf(default_patch_httpd_conf,
1686                     sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE);
1687 }
1688 #endif
1689
1690 #ifdef HTTPD_STANDALONE
1691 int main(int argc, char *argv[])
1692 #else
1693 int httpd_main(int argc, char *argv[])
1694 #endif
1695 {
1696   const char *home_httpd = home;
1697
1698 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1699   int server;
1700 #endif
1701
1702 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1703   long uid = -1;
1704 #endif
1705
1706   config = xcalloc(1, sizeof(*config));
1707 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1708   config->realm = "Web Server Authentication";
1709 #endif
1710
1711 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1712   config->port = 80;
1713 #endif
1714
1715   config->ContentLength = -1;
1716
1717   /* check if user supplied a port number */
1718   for (;;) {
1719     int c = getopt( argc, argv, "c:h:"
1720 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1721                                 "p:v"
1722 #endif
1723 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
1724                 "e:"
1725 #endif
1726 #ifdef CONFIG_FEATURE_HTTPD_DECODE_URL_STR
1727                 "d:"
1728 #endif
1729 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1730                 "r:"
1731 #endif
1732 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1733                 "u:"
1734 #endif
1735     );
1736     if (c == EOF) break;
1737     switch (c) {
1738     case 'c':
1739       config->configFile = optarg;
1740       break;
1741     case 'h':
1742       home_httpd = optarg;
1743       break;
1744 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1745     case 'v':
1746       config->debugHttpd = 1;
1747       break;
1748     case 'p':
1749       config->port = atoi(optarg);
1750       if(config->port <= 0 || config->port > 0xffff)
1751         error_msg_and_die("invalid %s for -p", optarg);
1752       break;
1753 #endif
1754 #ifdef CONFIG_FEATURE_HTTPD_DECODE_URL_STR
1755     case 'd':
1756       printf("%s", decodeString(optarg));
1757       return 0;
1758 #endif
1759 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
1760     case 'e':
1761       printf("%s", encodeString(optarg));
1762       return 0;
1763 #endif
1764 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1765     case 'r':
1766       config->realm = optarg;
1767       break;
1768 #endif
1769 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1770     case 'u':
1771       {
1772         char *e;
1773
1774         uid = strtol(optarg, &e, 0);
1775         if(*e != '\0') {
1776                 /* not integer */
1777                 uid = my_getpwnam(optarg);
1778         }
1779       }
1780       break;
1781 #endif
1782     default:
1783       error_msg("%s", httpdVersion);
1784       show_usage();
1785     }
1786   }
1787
1788   if(chdir(home_httpd)) {
1789     perror_msg_and_die("can`t chdir to %s", home_httpd);
1790   }
1791 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1792   server = openServer();
1793 # ifdef CONFIG_FEATURE_HTTPD_SETUID
1794   /* drop privilegies */
1795   if(uid > 0)
1796         setuid(uid);
1797 # endif
1798 # ifdef CONFIG_FEATURE_HTTPD_CGI
1799   addEnvPort("SERVER");
1800 # endif
1801 #endif
1802
1803 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1804   sighup_handler(0);
1805 #else
1806   parse_conf(default_patch_httpd_conf, FIRST_PARSE);
1807 #endif
1808
1809 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1810   if (!config->debugHttpd) {
1811     if (daemon(1, 0) < 0)     /* don`t change curent directory */
1812         perror_msg_and_die("daemon");
1813   }
1814   return miniHttpd(server);
1815 #else
1816   return miniHttpd();
1817 #endif
1818 }