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