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