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