2 * httpd implementation for busybox
4 * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
5 * Copyright (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>
7 * simplify patch stolen from libbb without using strdup
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.
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.
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
23 *****************************************************************************
27 * httpd -p 8080 -h $HOME/public_html
28 * or for daemon start from rc script with uid=0:
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"
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.
42 * The server can also be invoked as a url arg decoder and html text encoder
44 * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World"
45 * bar=`httpd -e "<Hello World>"` # encode as "<Hello World>"
47 * httpd.conf has the following format:
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
59 A/D may be as a/d or allow/deny - first char case unsensitive parsed only.
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:
69 If -c don`t setted, used httpd root config, else httpd root config skiped.
73 #include <ctype.h> /* for isspace */
75 #include <stdlib.h> /* for malloc */
77 #include <unistd.h> /* for close */
79 #include <sys/types.h>
80 #include <sys/socket.h> /* for connect and socket*/
81 #include <netinet/in.h> /* for sockaddr_in */
85 #include <fcntl.h> /* for open modes */
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";
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
101 /* Configure options, disabled by default as custom httpd feature */
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
111 /* If set, use this server from internet superserver only */
112 //#define CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
114 /* You can use this server as standalone, require libbb.a for linking */
115 //#define HTTPD_STANDALONE
117 /* Config options, disable this for do very small module */
118 //#define CONFIG_FEATURE_HTTPD_CGI
119 //#define CONFIG_FEATURE_HTTPD_BASIC_AUTH
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
143 /* require from libbb.a for linking */
144 const char *bb_applet_name = "httpd";
146 void bb_show_usage(void)
148 fprintf(stderr, "Usage: %s [-p <port>] [-c configFile] [-d/-e <string>] "
149 "[-r realm] [-u user] [-h homedir]\n", bb_applet_name);
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 */
161 /* CGI environ size */
162 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
163 #define ENVSIZE 50 /* set max 35 CGI_variable */
165 #define ENVSIZE 15 /* minimal requires */
168 #define MAX_POST_SIZE (64*1024) /* 64k. Its Small? May be ;) */
170 #define MAX_MEMORY_BUFF 8192 /* IO buffer */
172 typedef struct HT_ACCESS {
174 struct HT_ACCESS *next;
175 char before_colon[1]; /* really bigger, must last */
180 #ifdef CONFIG_FEATURE_HTTPD_CGI
181 char *envp[ENVSIZE+1];
184 char buf[MAX_MEMORY_BUFF];
186 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
189 const char *configFile;
191 char rmt_ip[16]; /* for set env REMOTE_ADDR */
192 unsigned port; /* server initial port and for
193 set env REMOTE_PORT */
195 const char *found_mime_type;
196 off_t ContentLength; /* -1 - unknown */
199 Htaccess *ip_a_d; /* config allow/deny lines */
200 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
201 Htaccess *auth; /* config user:password lines */
203 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
204 Htaccess *mime_a; /* config mime types */
207 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
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 */
218 static HttpdConfig *config;
220 static const char request_GET[] = "GET"; /* size algorithic optimize */
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",
228 ".txt.h.c.cc.cpp", "text/plain",
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",
241 0, "application/octet-stream" /* default */
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 */
255 HTTP_SWITCHING_PROTOCOLS = 101,
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
273 HttpResponseNum type;
278 static const HttpEnumString httpResponseNames[] = {
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", "" },
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", "" },
305 static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT";
306 static const char Content_length[] = "Content-length:";
310 static void free_config_lines(Htaccess **pprev)
312 Htaccess *prev = *pprev;
315 Htaccess *cur = prev;
324 #define FIRST_PARSE 0
325 #define SUBDIR_PARSE 1
326 #define SIGNALED_PARSE 2
327 #define FIND_FROM_HTTPD_ROOT 3
329 static void parse_conf(const char *path, int flag)
333 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
337 const char *cf = config->configFile;
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)
348 ; /* syntax confuse */
349 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
350 free_config_lines(&config->mime_a);
354 if(flag == SUBDIR_PARSE || cf == NULL) {
355 cf = alloca(strlen(path) + sizeof(httpd_conf) + 2);
357 if(flag == FIRST_PARSE)
358 bb_error_msg_and_die(bb_msg_memory_exhausted);
361 sprintf((char *)cf, "%s/%s", path, httpd_conf);
364 while((f = fopen(cf, "r")) == NULL) {
365 if(flag != FIRST_PARSE) {
366 /* config file not found */
369 if(config->configFile) /* if -c option given */
370 bb_perror_msg_and_die("%s", cf);
371 flag = FIND_FROM_HTTPD_ROOT;
375 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
378 /* This could stand some work */
379 while ( (p0 = fgets(buf, 80, f)) != NULL) {
381 for(p = p0; *p0 != 0 && *p0 != '#'; p0++) {
384 if(*p0 == ':' && c == NULL)
390 /* test for empty or strange line */
391 if (c == NULL || *c == 0)
394 *c = 0; /* Allow all */
400 if(*p0 != 'A' && *p0 != 'D'
401 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
404 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
410 if(*p0 == 'A' && *c == 0) {
411 /* skip default A:* */
415 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
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));
427 sprintf(p0, "/%s%s", cf, buf);
429 /* another call bb_simplify_path */
434 if (*cf == '/') { /* skip duplicate (or initial) slash */
436 } else if (*cf == '.') {
437 if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */
439 } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) {
442 while (*--p != '/'); /* omit previous dir */
451 if ((p == p0) || (*p != '/')) { /* not a trailing slash */
452 ++p; /* so keep last character */
455 sprintf(p0, "%s:%s", p0, c);
458 /* storing current config line */
460 cur = calloc(1, sizeof(Htaccess) + strlen(p0));
462 cf = strcpy(cur->before_colon, p0);
465 cur->after_colon = c;
466 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
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;
476 /* add to bottom current IP config line */
477 Htaccess *prev_IP = config->ip_a_d;
479 if(prev_IP == NULL) {
480 config->ip_a_d = cur;
483 prev_IP = prev_IP->next;
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;
496 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
497 else if(prev == NULL) {
499 config->auth = prev = cur;
501 /* sort path, if current lenght eq or bigger then move up */
502 Htaccess *prev_hti = config->auth;
506 for(hti = prev_hti; hti; hti = hti->next) {
507 if(l >= strlen(hti->before_colon)) {
508 /* insert before hti */
510 if(prev_hti != hti) {
511 prev_hti->next = cur;
520 prev_hti = prev_hti->next;
522 if(!hti) { /* not inserted, add to bottom */
533 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
534 /****************************************************************************
536 > $Function: encodeString()
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().
544 * (const char *) string . . The first string to encode.
546 * $Return: (char *) . . . .. . . A pointer to the encoded string.
548 * $Errors: Returns a null string ("") if memory is not available.
550 ****************************************************************************/
551 static char *encodeString(const char *string)
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);
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);
569 #endif /* CONFIG_FEATURE_HTTPD_ENCODE_URL_STR */
571 /****************************************************************************
573 > $Function: decodeString()
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.
582 * (char *) string . . . The first string to decode.
583 * (int) flag . . . 1 if require decode '+' as ' ' for CGI
585 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
589 ****************************************************************************/
590 static char *decodeString(char *string, int flag_plus_to_space)
592 /* note that decoded string is always shorter than original */
597 if (*ptr == '+' && flag_plus_to_space) { *string++ = ' '; ptr++; }
598 else if (*ptr != '%') *string++ = *ptr++;
601 sscanf(ptr+1, "%2X", &value);
611 #ifdef CONFIG_FEATURE_HTTPD_CGI
612 /****************************************************************************
614 > $Function: addEnv()
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.
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.
627 * $Errors: Silently returns if the env runs out of space to hold the new item
629 ****************************************************************************/
630 static void addEnv(const char *name_before_underline,
631 const char *name_after_underline, const char *value)
635 if (config->envCount >= ENVSIZE)
639 s = malloc(strlen(name_before_underline) + strlen(name_after_underline) +
642 const char *underline = *name_after_underline ? "_" : "";
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;
651 /* set environs SERVER_PORT and REMOTE_PORT */
652 static void addEnvPort(const char *port_name)
656 sprintf(buf, "%u", config->port);
657 addEnv(port_name, "PORT", buf);
659 #endif /* CONFIG_FEATURE_HTTPD_CGI */
661 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
662 /****************************************************************************
664 > $Function: addEnvCgi
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
673 * (char *) pargs . . . . A pointer to the URL encoded arguments.
679 ****************************************************************************/
680 static void addEnvCgi(const char *pargs)
684 if (pargs==0) return;
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, '=');
692 if (!value) /* &XXX without '=' */
695 args = strchr(value, '&');
698 addEnv("CGI", name, decodeString(value, 1));
702 #endif /* CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV */
705 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
706 /****************************************************************************
708 > $Function: decodeBase64()
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.
716 * (char *) Data . . . . A pointer to a base64 encoded string.
717 * Where to place the decoded data.
723 ****************************************************************************/
724 static void decodeBase64(char *Data)
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;
735 if(t >= '0' && t <= '9')
737 else if(t >= 'A' && t <= 'Z')
739 else if(t >= 'a' && t <= 'z')
753 *Data++ = (char) (ch >> 16);
754 *Data++ = (char) (ch >> 8);
764 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
765 /****************************************************************************
767 > $Function: openServer()
769 * $Description: create a listen server socket on the designated port.
771 * $Return: (int) . . . A connection socket. -1 for errors.
775 ****************************************************************************/
776 static int openServer(void)
778 struct sockaddr_in lsocket;
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);
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. */
793 setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)) ;
795 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) ;
797 if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) {
799 signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */
801 bb_perror_msg_and_die("bind");
804 bb_perror_msg_and_die("create socket");
808 #endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
810 /****************************************************************************
812 > $Function: sendHeaders()
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.
820 * (HttpResponseNum) responseNum . . . The result code to send.
822 * $Return: (int) . . . . writing errors
824 ****************************************************************************/
825 static int sendHeaders(HttpResponseNum responseNum)
827 char *buf = config->buf;
828 const char *responseString = "";
829 const char *infoString = 0;
831 time_t timer = time(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;
843 if (responseNum != HTTP_OK) {
844 config->found_mime_type = "text/html"; // error message is HTML
847 /* emit the current date */
848 strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer));
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);
854 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
855 if (responseNum == HTTP_UNAUTHORIZED) {
856 len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n",
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);
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);
875 if (config->debugHttpd) fprintf(stderr, "Headers: '%s'", buf);
877 return bb_full_write(a_c_w, buf, len);
880 /****************************************************************************
882 > $Function: getLine()
884 * $Description: Read from the socket until an end of line char found.
886 * Characters are read one at a time until an eol sequence is found.
889 * (char *) buf . . Where to place the read result.
891 * $Return: (int) . . . . number of characters read. -1 if error.
893 ****************************************************************************/
894 static int getLine(char *buf)
898 while (read(a_c_r, buf + count, 1) == 1) {
899 if (buf[count] == '\r') continue;
900 if (buf[count] == '\n') {
904 if(count < (MAX_MEMORY_BUFF-1)) /* check owerflow */
907 if (count) return count;
911 #ifdef CONFIG_FEATURE_HTTPD_CGI
912 /****************************************************************************
914 > $Function: sendCgi()
916 * $Description: Execute a CGI script and send it's stdout back
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).
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.
930 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
934 ****************************************************************************/
935 static int sendCgi(const char *url,
936 const char *request, const char *urlArgs,
937 const char *body, int bodyLen, const char *cookie)
939 int fromCgi[2]; /* pipe for reading data from CGI */
940 int toCgi[2]; /* pipe for sending data to CGI */
942 static char * argp[] = { 0, 0 };
949 if (pipe(fromCgi) != 0) {
952 if (pipe(toCgi) != 0) {
965 char *purl = strdup( url );
966 char realpath_buff[MAXPATHLEN];
974 dup2(inFd, 0); // replace stdin with the pipe
975 dup2(outFd, 1); // replace stdout with the pipe
977 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
978 if (!config->debugHttpd)
980 dup2(outFd, 2); // replace stderr with the pipe
991 while((script = strchr( script + 1, '/' )) != NULL) {
992 /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
996 if(is_directory(purl + 1, 1, &sb) == 0) {
997 /* not directory, found script.cgi/PATH_INFO */
1001 *script = '/'; /* is directory, find next '/' */
1003 addEnv("PATH", "INFO", script); /* set /PATH_INFO or NULL */
1004 addEnv("PATH", "", getenv("PATH"));
1005 addEnv("REQUEST", "METHOD", request);
1007 char *uri = alloca(strlen(purl) + 2 + strlen(urlArgs));
1009 sprintf(uri, "%s?%s", purl, urlArgs);
1010 addEnv("REQUEST", "URI", uri);
1012 addEnv("REQUEST", "URI", purl);
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");
1026 addEnv("REMOTE_ADDR", "", config->rmt_ip);
1031 sprintf(sbl, "%d", bodyLen);
1032 addEnv("CONTENT_LENGTH", "", sbl);
1035 addEnv("HTTP_COOKIE", "", cookie);
1037 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
1038 if (request != request_GET) {
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, '/');
1052 if(chdir(realpath_buff) == 0) {
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);
1060 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1061 config->accepted_socket = 1; /* send to stdout */
1063 sendHeaders(HTTP_NOT_FOUND);
1070 /* parent process */
1077 if (body) bb_full_write(outFd, body, bodyLen);
1081 struct timeval timeout;
1088 FD_SET(inFd, &readSet);
1090 /* Now wait on the set of sockets! */
1092 timeout.tv_usec = 10000;
1093 nfound = select(inFd + 1, &readSet, 0, 0, &timeout);
1096 if (waitpid(pid, &status, WNOHANG) > 0) {
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));
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;
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);
1123 if (strstr(buf, "ontent-") == 0) {
1124 bb_full_write(s, "Content-type: text/plain\n\n", 26);
1128 bb_full_write(s, buf, count);
1130 if (config->debugHttpd)
1131 fprintf(stderr, "cgi read %d bytes\n", count);
1139 #endif /* CONFIG_FEATURE_HTTPD_CGI */
1141 /****************************************************************************
1143 > $Function: sendFile()
1145 * $Description: Send a file response to an HTTP request
1148 * (const char *) url . . The URL requested.
1149 * (char *) buf . . . . . The stack buffer.
1151 * $Return: (int) . . . . . . Always 0.
1153 ****************************************************************************/
1154 static int sendFile(const char *url, char *buf)
1158 const char * const * table;
1159 const char * try_suffix;
1161 suffix = strrchr(url, '.');
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 == '.')
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
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;
1182 #endif /* CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */
1185 if (config->debugHttpd)
1186 fprintf(stderr, "Sending file '%s' Content-type: %s\n",
1187 url, config->found_mime_type);
1190 f = open(url, O_RDONLY);
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);
1201 if (config->debugHttpd)
1202 bb_perror_msg("Unable to open '%s'", url);
1204 sendHeaders(HTTP_NOT_FOUND);
1210 /****************************************************************************
1212 > $Function: checkPerm()
1214 * $Description: Check the permission file for access.
1216 * If config file isn't present, everything is allowed.
1217 * Entries are of the form you can see example from header source
1220 * (const char *) path . . . . The file path or NULL for ip addresses.
1221 * (const char *) request . . . User information to validate.
1223 * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise.
1225 ****************************************************************************/
1227 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1228 static int checkPerm(const char *path, const char *request)
1234 int ipaddr = path == NULL;
1235 const char *prev = NULL;
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;
1244 if (config->debugHttpd)
1245 fprintf(stderr,"checkPerm: '%s' ? '%s'\n",
1246 (ipaddr ? (*p ? p : "*") : p0), request);
1249 if(strncmp(p, request, strlen(p)) != 0)
1251 return *p0 == 'A'; /* Allow/Deny */
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)
1260 /* unauthorized, but check next /path:user:password */
1266 return prev == NULL;
1269 #else /* ifndef CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1270 static int checkPermIP(const char *request)
1275 /* This could stand some work */
1276 for (cur = config->ip_a_d; cur; cur = cur->next) {
1277 p = cur->after_colon;
1279 if (config->debugHttpd)
1280 fprintf(stderr, "checkPerm: '%s' ? '%s'\n",
1281 (*p ? p : "*"), request);
1283 if(strncmp(p, request, strlen(p)) == 0)
1284 return *cur->before_colon == 'A'; /* Allow/Deny */
1287 /* if uncofigured, return 1 - access from all */
1290 #define checkPerm(null, request) checkPermIP(request)
1291 #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1294 /****************************************************************************
1296 > $Function: handleIncoming()
1298 * $Description: Handle an incoming http request.
1300 ****************************************************************************/
1301 static void handleIncoming(void)
1303 char *buf = config->buf;
1308 #ifdef CONFIG_FEATURE_HTTPD_CGI
1309 const char *prequest = request_GET;
1318 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1319 int credentials = -1; /* if not requred this is Ok */
1325 if (getLine(buf) <= 0)
1328 purl = strpbrk(buf, " \t");
1331 sendHeaders(HTTP_BAD_REQUEST);
1335 #ifdef CONFIG_FEATURE_HTTPD_CGI
1336 if(strcasecmp(buf, prequest) != 0) {
1338 if(strcasecmp(buf, prequest) != 0) {
1339 sendHeaders(HTTP_NOT_IMPLEMENTED);
1344 if(strcasecmp(buf, request_GET) != 0) {
1345 sendHeaders(HTTP_NOT_IMPLEMENTED);
1350 count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank);
1352 decodeString(buf, 0);
1353 if (count < 1 || buf[0] != '/') {
1354 /* Garbled request/URL */
1357 url = alloca(strlen(buf) + 12); /* + sizeof("/index.html\0") */
1359 sendHeaders(HTTP_INTERNAL_SERVER_ERROR);
1363 /* extract url args if present */
1364 urlArgs = strchr(url, '?');
1368 /* algorithm stolen from libbb bb_simplify_path(),
1369 but don`t strdup and reducing trailing slash and protect out root */
1374 if (*test == '/') { /* skip duplicate (or initial) slash */
1376 } else if (*test == '.') {
1377 if (test[1] == '/' || test[1] == 0) { /* skip extra '.' */
1379 } else if ((test[1] == '.') && (test[2] == '/' || test[2] == 0)) {
1382 /* protect out root */
1385 while (*--purl != '/'); /* omit previous dir */
1393 *++purl = 0; /* so keep last character */
1394 test = purl; /* end ptr */
1396 /* If URL is directory, adding '/' */
1397 if(test[-1] != '/') {
1398 if ( is_directory(url + 1, 1, &sb) ) {
1401 purl = test; /* end ptr */
1405 if (config->debugHttpd)
1406 fprintf(stderr, "url='%s', args=%s\n", url, urlArgs);
1410 ip_allowed = checkPerm(NULL, config->rmt_ip);
1411 while(ip_allowed && (test = strchr( test + 1, '/' )) != NULL) {
1412 /* have path1/path2 */
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);
1422 // read until blank line for HTTP version specified, else parse immediate
1423 while (blank >= 0 && (count = getLine(buf)) > 0) {
1426 if (config->debugHttpd) fprintf(stderr, "Header: '%s'\n", buf);
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++)
1437 cookie = strdup(test);
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.
1447 for(test = buf + 14; isspace(*test); test++)
1449 if (strncasecmp(test, "Basic", 5) != 0)
1452 test += 5; /* decodeBase64() skiping space self */
1454 credentials = checkPerm(url, test);
1456 #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1458 } /* while extra header reading */
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 */
1466 sendHeaders(HTTP_FORBIDDEN);
1470 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1471 if (credentials <= 0 && checkPerm(url, ":") == 0) {
1472 sendHeaders(HTTP_UNAUTHORIZED);
1477 test = url + 1; /* skip first '/' */
1479 #ifdef CONFIG_FEATURE_HTTPD_CGI
1480 /* if strange Content-Length */
1481 if (length < 0 || length > MAX_POST_SIZE)
1485 body = malloc(length + 1);
1487 length = bb_full_read(a_c_r, body, length);
1488 if(length < 0) // closed
1490 body[length] = 0; // always null terminate for safety
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);
1499 if (prequest != request_GET)
1500 sendHeaders(HTTP_NOT_IMPLEMENTED);
1502 #endif /* CONFIG_FEATURE_HTTPD_CGI */
1504 strcpy(purl, "index.html");
1505 if ( stat(test, &sb ) == 0 ) {
1506 config->ContentLength = sb.st_size;
1507 config->last_mod = sb.st_mtime;
1509 sendFile(test, buf);
1510 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1511 /* unset if non inetd looped */
1512 config->ContentLength = -1;
1515 #ifdef CONFIG_FEATURE_HTTPD_CGI
1523 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1524 /* from inetd don`t looping: freeing, closing automatic from exit always */
1526 if (config->debugHttpd) fprintf(stderr, "closing socket\n");
1528 # ifdef CONFIG_FEATURE_HTTPD_CGI
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 */
1538 /****************************************************************************
1540 > $Function: miniHttpd()
1542 * $Description: The main http server function.
1544 * Given an open socket fildes, listen for new connections and farm out
1545 * the processing as a forked process.
1548 * (int) server. . . The server socket fildes.
1550 * $Return: (int) . . . . Always 0.
1552 ****************************************************************************/
1553 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1554 static int miniHttpd(int server)
1556 fd_set readfd, portfd;
1559 FD_SET(server, &portfd);
1561 /* copy the ports we are watching to the readfd set */
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)) {
1569 struct sockaddr_in fromAddr;
1572 socklen_t fromAddrLen = sizeof(fromAddr);
1573 int s = accept(server,
1574 (struct sockaddr *)&fromAddr, &fromAddrLen);
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),
1586 config->port = ntohs(fromAddr.sin_port);
1588 if (config->debugHttpd) {
1589 bb_error_msg("connection from IP=%s, port %u\n",
1590 config->rmt_ip, config->port);
1593 /* set the KEEPALIVE option to cull dead connections */
1595 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof (on));
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);
1604 if(!config->debugHttpd)
1617 static int miniHttpd(void)
1619 struct sockaddr_in fromAddrLen;
1620 socklen_t sinlen = sizeof (struct sockaddr_in);
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),
1630 config->port = ntohs(fromAddrLen.sin_port);
1634 #endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
1636 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1637 static void sighup_handler(int sig)
1640 struct sigaction sa;
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);
1651 #ifdef HTTPD_STANDALONE
1652 int main(int argc, char *argv[])
1654 int httpd_main(int argc, char *argv[])
1657 const char *home_httpd = home;
1659 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1663 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1667 config = xcalloc(1, sizeof(*config));
1668 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1669 config->realm = "Web Server Authentication";
1672 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1676 config->ContentLength = -1;
1678 /* check if user supplied a port number */
1680 int c = getopt( argc, argv, "c:d:h:"
1681 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1684 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
1687 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1690 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1694 if (c == EOF) break;
1697 config->configFile = optarg;
1700 home_httpd = optarg;
1702 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1704 config->debugHttpd = 1;
1707 config->port = atoi(optarg);
1708 if(config->port <= 0 || config->port > 0xffff)
1709 bb_error_msg_and_die("invalid %s for -p", optarg);
1713 printf("%s", decodeString(optarg, 1));
1715 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
1717 printf("%s", encodeString(optarg));
1720 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1722 config->realm = optarg;
1725 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1730 uid = strtol(optarg, &e, 0);
1733 uid = my_getpwnam(optarg);
1739 bb_error_msg("%s", httpdVersion);
1744 if(chdir(home_httpd)) {
1745 bb_perror_msg_and_die("can`t chdir to %s", home_httpd);
1747 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1748 server = openServer();
1749 # ifdef CONFIG_FEATURE_HTTPD_SETUID
1750 /* drop privilegies */
1754 # ifdef CONFIG_FEATURE_HTTPD_CGI
1755 addEnvPort("SERVER");
1759 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1762 parse_conf(default_path_httpd_conf, FIRST_PARSE);
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");
1770 return miniHttpd(server);