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;
323 static void add_config_line(Htaccess **pprev, Htaccess *cur)
330 for(prev = *pprev; prev->next; prev = prev->next)
337 #define FIRST_PARSE 0
338 #define SUBDIR_PARSE 1
339 #define SIGNALED_PARSE 2
340 #define FIND_FROM_HTTPD_ROOT 3
342 static void parse_conf(const char *path, int flag)
346 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
350 const char *cf = config->configFile;
355 /* free previous setuped */
356 free_config_lines(&config->ip_a_d);
357 if(flag != SUBDIR_PARSE) {
358 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
359 free_config_lines(&config->auth)
361 ; /* syntax confuse */
362 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
363 free_config_lines(&config->mime_a);
367 if(flag == SUBDIR_PARSE || cf == NULL) {
368 cf = alloca(strlen(path) + sizeof(httpd_conf) + 2);
370 if(flag == FIRST_PARSE)
371 bb_error_msg_and_die(bb_msg_memory_exhausted);
374 sprintf((char *)cf, "%s/%s", path, httpd_conf);
377 while((f = fopen(cf, "r")) == NULL) {
378 if(flag != FIRST_PARSE) {
379 /* config file not found */
382 if(config->configFile) /* if -c option given */
383 bb_perror_msg_and_die("%s", cf);
384 flag = FIND_FROM_HTTPD_ROOT;
388 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
391 /* This could stand some work */
392 while ( (p0 = fgets(buf, 80, f)) != NULL) {
394 for(p = p0; *p0 != 0 && *p0 != '#'; p0++) {
397 if(*p0 == ':' && c == NULL)
403 /* test for empty or strange line */
404 if (c == NULL || *c == 0)
407 *c = 0; /* Allow all */
413 if(*p0 != 'A' && *p0 != 'D'
414 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
417 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
423 if(*p0 == 'A' && *c == 0) {
424 /* skip default A:* */
428 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
434 /* make full path from httpd root / curent_path / config_line_path */
435 cf = flag == SUBDIR_PARSE ? path : "";
436 p0 = malloc(strlen(cf) + (c - buf) + 2 + strlen(c));
440 sprintf(p0, "/%s%s", cf, buf);
442 /* another call bb_simplify_path */
447 if (*cf == '/') { /* skip duplicate (or initial) slash */
449 } else if (*cf == '.') {
450 if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */
452 } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) {
455 while (*--p != '/'); /* omit previous dir */
464 if ((p == p0) || (*p != '/')) { /* not a trailing slash */
465 ++p; /* so keep last character */
468 sprintf(p0, "%s:%s", p0, c);
471 /* storing current config line */
473 cur = calloc(1, sizeof(Htaccess) + strlen(p0));
475 cf = strcpy(cur->before_colon, p0);
478 cur->after_colon = c;
479 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
483 if(*cf == 'A' || *cf == 'D')
484 add_config_line(&config->ip_a_d, cur);
485 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
487 add_config_line(&config->mime_a, cur);
490 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
491 else if(prev == NULL) {
493 config->auth = prev = cur;
495 /* sort path, if current lenght eq or bigger then move up */
496 Htaccess *prev_hti = config->auth;
500 for(hti = prev_hti; hti; hti = hti->next) {
501 if(l >= strlen(hti->before_colon)) {
502 /* insert before hti */
504 if(prev_hti != hti) {
505 prev_hti->next = cur;
514 prev_hti = prev_hti->next;
516 if(!hti) { /* not inserted, add to bottom */
527 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
528 /****************************************************************************
530 > $Function: encodeString()
532 * $Description: Given a string, html encode special characters.
533 * This is used for the -e command line option to provide an easy way
534 * for scripts to encode result data without confusing browsers. The
535 * returned string pointer is memory allocated by malloc().
538 * (const char *) string . . The first string to encode.
540 * $Return: (char *) . . . .. . . A pointer to the encoded string.
542 * $Errors: Returns a null string ("") if memory is not available.
544 ****************************************************************************/
545 static char *encodeString(const char *string)
547 /* take the simple route and encode everything */
548 /* could possibly scan once to get length. */
549 int len = strlen(string);
550 char *out = malloc(len*5 +1);
555 while ((ch = *string++)) {
556 // very simple check for what to encode
557 if (isalnum(ch)) *p++ = ch;
558 else p += sprintf(p, "&#%d", (unsigned char) ch);
563 #endif /* CONFIG_FEATURE_HTTPD_ENCODE_URL_STR */
565 /****************************************************************************
567 > $Function: decodeString()
569 * $Description: Given a URL encoded string, convert it to plain ascii.
570 * Since decoding always makes strings smaller, the decode is done in-place.
571 * Thus, callers should strdup() the argument if they do not want the
572 * argument modified. The return is the original pointer, allowing this
573 * function to be easily used as arguments to other functions.
576 * (char *) string . . . The first string to decode.
577 * (int) flag . . . 1 if require decode '+' as ' ' for CGI
579 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
583 ****************************************************************************/
584 static char *decodeString(char *string, int flag_plus_to_space)
586 /* note that decoded string is always shorter than original */
591 if (*ptr == '+' && flag_plus_to_space) { *string++ = ' '; ptr++; }
592 else if (*ptr != '%') *string++ = *ptr++;
595 sscanf(ptr+1, "%2X", &value);
605 #ifdef CONFIG_FEATURE_HTTPD_CGI
606 /****************************************************************************
608 > $Function: addEnv()
610 * $Description: Add an enviornment variable setting to the global list.
611 * A NAME=VALUE string is allocated, filled, and added to the list of
612 * environment settings passed to the cgi execution script.
615 * (char *) name_before_underline - The first part environment variable name.
616 * (char *) name_after_underline - The second part environment variable name.
617 * (char *) value . . The value to which the env variable is set.
621 * $Errors: Silently returns if the env runs out of space to hold the new item
623 ****************************************************************************/
624 static void addEnv(const char *name_before_underline,
625 const char *name_after_underline, const char *value)
629 if (config->envCount >= ENVSIZE)
633 s = malloc(strlen(name_before_underline) + strlen(name_after_underline) +
636 const char *underline = *name_after_underline ? "_" : "";
638 sprintf(s,"%s%s%s=%s", name_before_underline, underline,
639 name_after_underline, value);
640 config->envp[config->envCount++] = s;
641 config->envp[config->envCount] = 0;
645 /* set environs SERVER_PORT and REMOTE_PORT */
646 static void addEnvPort(const char *port_name)
650 sprintf(buf, "%u", config->port);
651 addEnv(port_name, "PORT", buf);
653 #endif /* CONFIG_FEATURE_HTTPD_CGI */
655 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
656 /****************************************************************************
658 > $Function: addEnvCgi
660 * $Description: Create environment variables given a URL encoded arg list.
661 * For each variable setting the URL encoded arg list, create a corresponding
662 * environment variable. URL encoded arguments have the form
663 * name1=value1&name2=value2&name3=&ignores
664 * from this example, name3 set empty value, tail without '=' skiping
667 * (char *) pargs . . . . A pointer to the URL encoded arguments.
673 ****************************************************************************/
674 static void addEnvCgi(const char *pargs)
678 if (pargs==0) return;
680 /* args are a list of name=value&name2=value2 sequences */
681 memargs = args = strdup(pargs);
682 while (args && *args) {
683 const char *name = args;
684 char *value = strchr(args, '=');
686 if (!value) /* &XXX without '=' */
689 args = strchr(value, '&');
692 addEnv("CGI", name, decodeString(value, 1));
696 #endif /* CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV */
699 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
700 /****************************************************************************
702 > $Function: decodeBase64()
704 > $Description: Decode a base 64 data stream as per rfc1521.
705 * Note that the rfc states that none base64 chars are to be ignored.
706 * Since the decode always results in a shorter size than the input, it is
707 * OK to pass the input arg as an output arg.
710 * (char *) Data . . . . A pointer to a base64 encoded string.
711 * Where to place the decoded data.
717 ****************************************************************************/
718 static void decodeBase64(char *Data)
721 const unsigned char *in = Data;
722 // The decoded size will be at most 3/4 the size of the encoded
723 unsigned long ch = 0;
754 *Data++ = (char) (ch >> 16);
755 *Data++ = (char) (ch >> 8);
765 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
766 /****************************************************************************
768 > $Function: openServer()
770 * $Description: create a listen server socket on the designated port.
772 * $Return: (int) . . . A connection socket. -1 for errors.
776 ****************************************************************************/
777 static int openServer(void)
779 struct sockaddr_in lsocket;
782 /* create the socket right now */
783 /* inet_addr() returns a value that is already in network order */
784 memset(&lsocket, 0, sizeof(lsocket));
785 lsocket.sin_family = AF_INET;
786 lsocket.sin_addr.s_addr = INADDR_ANY;
787 lsocket.sin_port = htons(config->port) ;
788 fd = socket(AF_INET, SOCK_STREAM, 0);
790 /* tell the OS it's OK to reuse a previous address even though */
791 /* it may still be in a close down state. Allows bind to succeed. */
794 setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)) ;
796 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) ;
798 if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) {
800 signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */
802 bb_perror_msg_and_die("bind");
805 bb_perror_msg_and_die("create socket");
809 #endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
811 /****************************************************************************
813 > $Function: sendHeaders()
815 * $Description: Create and send HTTP response headers.
816 * The arguments are combined and sent as one write operation. Note that
817 * IE will puke big-time if the headers are not sent in one packet and the
818 * second packet is delayed for any reason.
821 * (HttpResponseNum) responseNum . . . The result code to send.
823 * $Return: (int) . . . . writing errors
825 ****************************************************************************/
826 static int sendHeaders(HttpResponseNum responseNum)
828 char *buf = config->buf;
829 const char *responseString = "";
830 const char *infoString = 0;
832 time_t timer = time(0);
837 i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++) {
838 if (httpResponseNames[i].type == responseNum) {
839 responseString = httpResponseNames[i].name;
840 infoString = httpResponseNames[i].info;
844 if (responseNum != HTTP_OK) {
845 config->found_mime_type = "text/html"; // error message is HTML
848 /* emit the current date */
849 strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer));
851 "HTTP/1.0 %d %s\nContent-type: %s\r\n"
852 "Date: %s\r\nConnection: close\r\n",
853 responseNum, responseString, config->found_mime_type, timeStr);
855 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
856 if (responseNum == HTTP_UNAUTHORIZED) {
857 len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n",
861 if (config->ContentLength != -1) { /* file */
862 strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod));
863 len += sprintf(buf+len, "Last-Modified: %s\r\n%s %ld\r\n",
864 timeStr, Content_length, config->ContentLength);
869 len += sprintf(buf+len,
870 "<HEAD><TITLE>%d %s</TITLE></HEAD>\n"
871 "<BODY><H1>%d %s</H1>\n%s\n</BODY>\n",
872 responseNum, responseString,
873 responseNum, responseString, infoString);
876 if (config->debugHttpd) fprintf(stderr, "Headers: '%s'", buf);
878 return bb_full_write(a_c_w, buf, len);
881 /****************************************************************************
883 > $Function: getLine()
885 * $Description: Read from the socket until an end of line char found.
887 * Characters are read one at a time until an eol sequence is found.
890 * (char *) buf . . Where to place the read result.
892 * $Return: (int) . . . . number of characters read. -1 if error.
894 ****************************************************************************/
895 static int getLine(char *buf)
899 while (read(a_c_r, buf + count, 1) == 1) {
900 if (buf[count] == '\r') continue;
901 if (buf[count] == '\n') {
905 if(count < (MAX_MEMORY_BUFF-1)) /* check owerflow */
908 if (count) return count;
912 #ifdef CONFIG_FEATURE_HTTPD_CGI
913 /****************************************************************************
915 > $Function: sendCgi()
917 * $Description: Execute a CGI script and send it's stdout back
919 * Environment variables are set up and the script is invoked with pipes
920 * for stdin/stdout. If a post is being done the script is fed the POST
921 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
924 * (const char *) url . . . The requested URL (with leading /).
925 * (const char *urlArgs). . Any URL arguments.
926 * (const char *body) . . . POST body contents.
927 * (int bodyLen) . . . . . Length of the post body.
928 * (const char *cookie) . . For set HTTP_COOKIE.
931 * $Return: (char *) . . . . A pointer to the decoded string (same as input).
935 ****************************************************************************/
936 static int sendCgi(const char *url,
937 const char *request, const char *urlArgs,
938 const char *body, int bodyLen, const char *cookie)
940 int fromCgi[2]; /* pipe for reading data from CGI */
941 int toCgi[2]; /* pipe for sending data to CGI */
943 static char * argp[] = { 0, 0 };
950 if (pipe(fromCgi) != 0) {
953 if (pipe(toCgi) != 0) {
966 char *purl = strdup( url );
967 char realpath_buff[MAXPATHLEN];
975 dup2(inFd, 0); // replace stdin with the pipe
976 dup2(outFd, 1); // replace stdout with the pipe
978 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
979 if (!config->debugHttpd)
981 dup2(outFd, 2); // replace stderr with the pipe
992 while((script = strchr( script + 1, '/' )) != NULL) {
993 /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
997 if(is_directory(purl + 1, 1, &sb) == 0) {
998 /* not directory, found script.cgi/PATH_INFO */
1002 *script = '/'; /* is directory, find next '/' */
1004 addEnv("PATH", "INFO", script); /* set /PATH_INFO or NULL */
1005 addEnv("PATH", "", getenv("PATH"));
1006 addEnv("REQUEST", "METHOD", request);
1008 char *uri = alloca(strlen(purl) + 2 + strlen(urlArgs));
1010 sprintf(uri, "%s?%s", purl, urlArgs);
1011 addEnv("REQUEST", "URI", uri);
1013 addEnv("REQUEST", "URI", purl);
1016 *script = '\0'; /* reduce /PATH_INFO */
1017 /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
1018 addEnv("SCRIPT_NAME", "", purl);
1019 addEnv("QUERY_STRING", "", urlArgs);
1020 addEnv("SERVER", "SOFTWARE", httpdVersion);
1021 addEnv("SERVER", "PROTOCOL", "HTTP/1.0");
1022 addEnv("GATEWAY_INTERFACE", "", "CGI/1.1");
1023 #ifdef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
1024 addEnv("REMOTE", "ADDR", config->rmt_ip);
1025 addEnvPort("REMOTE");
1027 addEnv("REMOTE_ADDR", "", config->rmt_ip);
1032 sprintf(sbl, "%d", bodyLen);
1033 addEnv("CONTENT_LENGTH", "", sbl);
1036 addEnv("HTTP_COOKIE", "", cookie);
1038 #ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV
1039 if (request != request_GET) {
1046 /* set execve argp[0] without path */
1047 argp[0] = strrchr( purl, '/' ) + 1;
1048 /* but script argp[0] must have absolute path and chdiring to this */
1049 if(realpath(purl + 1, realpath_buff) != NULL) {
1050 script = strrchr(realpath_buff, '/');
1053 if(chdir(realpath_buff) == 0) {
1055 // now run the program. If it fails, use _exit() so no destructors
1056 // get called and make a mess.
1057 execve(realpath_buff, argp, config->envp);
1061 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1062 config->accepted_socket = 1; /* send to stdout */
1064 sendHeaders(HTTP_NOT_FOUND);
1071 /* parent process */
1078 if (body) bb_full_write(outFd, body, bodyLen);
1082 struct timeval timeout;
1089 FD_SET(inFd, &readSet);
1091 /* Now wait on the set of sockets! */
1093 timeout.tv_usec = 10000;
1094 nfound = select(inFd + 1, &readSet, 0, 0, &timeout);
1097 if (waitpid(pid, &status, WNOHANG) > 0) {
1100 if (config->debugHttpd) {
1101 if (WIFEXITED(status))
1102 bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status));
1103 if (WIFSIGNALED(status))
1104 bb_error_msg("piped has exited with signal=%d", WTERMSIG(status));
1113 // There is something to read
1114 count = bb_full_read(inFd, buf, sizeof(buf)-1);
1115 // If a read returns 0 at this point then some type of error has
1116 // occurred. Bail now.
1117 if (count == 0) break;
1120 /* check to see if the user script added headers */
1121 if (strncmp(buf, "HTTP/1.0 200 OK\n", 4) != 0) {
1122 bb_full_write(s, "HTTP/1.0 200 OK\n", 16);
1124 if (strstr(buf, "ontent-") == 0) {
1125 bb_full_write(s, "Content-type: text/plain\n\n", 26);
1129 bb_full_write(s, buf, count);
1131 if (config->debugHttpd)
1132 fprintf(stderr, "cgi read %d bytes\n", count);
1140 #endif /* CONFIG_FEATURE_HTTPD_CGI */
1142 /****************************************************************************
1144 > $Function: sendFile()
1146 * $Description: Send a file response to an HTTP request
1149 * (const char *) url . . The URL requested.
1150 * (char *) buf . . . . . The stack buffer.
1152 * $Return: (int) . . . . . . Always 0.
1154 ****************************************************************************/
1155 static int sendFile(const char *url, char *buf)
1159 const char * const * table;
1160 const char * try_suffix;
1162 suffix = strrchr(url, '.');
1164 for (table = suffixTable; *table; table += 2)
1165 if(suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) {
1166 try_suffix += strlen(suffix);
1167 if(*try_suffix == 0 || *try_suffix == '.')
1170 /* also, if not found, set default as "application/octet-stream"; */
1171 config->found_mime_type = *(table+1);
1172 #ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1176 for (cur = config->mime_a; cur; cur = cur->next) {
1177 if(strcmp(cur->before_colon, suffix) == 0) {
1178 config->found_mime_type = cur->after_colon;
1183 #endif /* CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */
1186 if (config->debugHttpd)
1187 fprintf(stderr, "Sending file '%s' Content-type: %s\n",
1188 url, config->found_mime_type);
1191 f = open(url, O_RDONLY);
1195 sendHeaders(HTTP_OK);
1196 while ((count = bb_full_read(f, buf, MAX_MEMORY_BUFF)) > 0) {
1197 bb_full_write(a_c_w, buf, count);
1202 if (config->debugHttpd)
1203 bb_perror_msg("Unable to open '%s'", url);
1205 sendHeaders(HTTP_NOT_FOUND);
1211 /****************************************************************************
1213 > $Function: checkPerm()
1215 * $Description: Check the permission file for access.
1217 * If config file isn't present, everything is allowed.
1218 * Entries are of the form you can see example from header source
1221 * (const char *) path . . . . The file path or NULL for ip addresses.
1222 * (const char *) request . . . User information to validate.
1224 * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise.
1226 ****************************************************************************/
1228 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1229 static int checkPerm(const char *path, const char *request)
1235 int ipaddr = path == NULL;
1236 const char *prev = NULL;
1238 /* This could stand some work */
1239 for (cur = ipaddr ? config->ip_a_d : config->auth; cur; cur = cur->next) {
1240 p0 = cur->before_colon;
1241 if(prev != NULL && strcmp(prev, p0) != 0)
1242 continue; /* find next identical */
1243 p = cur->after_colon;
1245 if (config->debugHttpd)
1246 fprintf(stderr,"checkPerm: '%s' ? '%s'\n",
1247 (ipaddr ? (*p ? p : "*") : p0), request);
1250 if(strncmp(p, request, strlen(p)) != 0)
1252 return *p0 == 'A'; /* Allow/Deny */
1256 if(strncmp(p0, path, l) == 0 &&
1257 (l == 1 || path[l] == '/' || path[l] == 0)) {
1258 /* path match found. Check request */
1259 if (strcmp(p, request) == 0)
1261 /* unauthorized, but check next /path:user:password */
1267 return prev == NULL;
1270 #else /* ifndef CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1271 static int checkPermIP(const char *request)
1276 /* This could stand some work */
1277 for (cur = config->ip_a_d; cur; cur = cur->next) {
1278 p = cur->after_colon;
1280 if (config->debugHttpd)
1281 fprintf(stderr, "checkPerm: '%s' ? '%s'\n",
1282 (*p ? p : "*"), request);
1284 if(strncmp(p, request, strlen(p)) == 0)
1285 return *cur->before_colon == 'A'; /* Allow/Deny */
1288 /* if uncofigured, return 1 - access from all */
1291 #define checkPerm(null, request) checkPermIP(request)
1292 #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1295 /****************************************************************************
1297 > $Function: handleIncoming()
1299 * $Description: Handle an incoming http request.
1301 ****************************************************************************/
1302 static void handleIncoming(void)
1304 char *buf = config->buf;
1309 #ifdef CONFIG_FEATURE_HTTPD_CGI
1310 const char *prequest = request_GET;
1319 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1320 int credentials = -1; /* if not requred this is Ok */
1326 if (getLine(buf) <= 0)
1329 purl = strpbrk(buf, " \t");
1332 sendHeaders(HTTP_BAD_REQUEST);
1336 #ifdef CONFIG_FEATURE_HTTPD_CGI
1337 if(strcasecmp(buf, prequest) != 0) {
1339 if(strcasecmp(buf, prequest) != 0) {
1340 sendHeaders(HTTP_NOT_IMPLEMENTED);
1345 if(strcasecmp(buf, request_GET) != 0) {
1346 sendHeaders(HTTP_NOT_IMPLEMENTED);
1351 count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank);
1353 decodeString(buf, 0);
1354 if (count < 1 || buf[0] != '/') {
1355 /* Garbled request/URL */
1358 url = alloca(strlen(buf) + 12); /* + sizeof("/index.html\0") */
1360 sendHeaders(HTTP_INTERNAL_SERVER_ERROR);
1364 /* extract url args if present */
1365 urlArgs = strchr(url, '?');
1369 /* algorithm stolen from libbb bb_simplify_path(),
1370 but don`t strdup and reducing trailing slash and protect out root */
1375 if (*test == '/') { /* skip duplicate (or initial) slash */
1377 } else if (*test == '.') {
1378 if (test[1] == '/' || test[1] == 0) { /* skip extra '.' */
1380 } else if ((test[1] == '.') && (test[2] == '/' || test[2] == 0)) {
1383 /* protect out root */
1386 while (*--purl != '/'); /* omit previous dir */
1394 *++purl = 0; /* so keep last character */
1395 test = purl; /* end ptr */
1397 /* If URL is directory, adding '/' */
1398 if(test[-1] != '/') {
1399 if ( is_directory(url + 1, 1, &sb) ) {
1402 purl = test; /* end ptr */
1406 if (config->debugHttpd)
1407 fprintf(stderr, "url='%s', args=%s\n", url, urlArgs);
1411 ip_allowed = checkPerm(NULL, config->rmt_ip);
1412 while(ip_allowed && (test = strchr( test + 1, '/' )) != NULL) {
1413 /* have path1/path2 */
1415 if( is_directory(url + 1, 1, &sb) ) {
1416 /* may be having subdir config */
1417 parse_conf(url + 1, SUBDIR_PARSE);
1418 ip_allowed = checkPerm(NULL, config->rmt_ip);
1423 // read until blank line for HTTP version specified, else parse immediate
1424 while (blank >= 0 && (count = getLine(buf)) > 0) {
1427 if (config->debugHttpd) fprintf(stderr, "Header: '%s'\n", buf);
1430 #ifdef CONFIG_FEATURE_HTTPD_CGI
1431 /* try and do our best to parse more lines */
1432 if ((strncasecmp(buf, Content_length, 15) == 0)) {
1433 if(prequest != request_GET)
1434 length = strtol(buf + 15, 0, 0); // extra read only for POST
1435 } else if ((strncasecmp(buf, "Cookie:", 7) == 0)) {
1436 for(test = buf + 7; isspace(*test); test++)
1438 cookie = strdup(test);
1442 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1443 if (strncasecmp(buf, "Authorization:", 14) == 0) {
1444 /* We only allow Basic credentials.
1445 * It shows up as "Authorization: Basic <userid:password>" where
1446 * the userid:password is base64 encoded.
1448 for(test = buf + 14; isspace(*test); test++)
1450 if (strncasecmp(test, "Basic", 5) != 0)
1453 test += 5; /* decodeBase64() skiping space self */
1455 credentials = checkPerm(url, test);
1457 #endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */
1459 } /* while extra header reading */
1462 if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) {
1463 /* protect listing [/path]/httpd_conf or IP deny */
1464 #ifdef CONFIG_FEATURE_HTTPD_CGI
1465 FORBIDDEN: /* protect listing /cgi-bin */
1467 sendHeaders(HTTP_FORBIDDEN);
1471 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1472 if (credentials <= 0 && checkPerm(url, ":") == 0) {
1473 sendHeaders(HTTP_UNAUTHORIZED);
1478 test = url + 1; /* skip first '/' */
1480 #ifdef CONFIG_FEATURE_HTTPD_CGI
1481 /* if strange Content-Length */
1482 if (length < 0 || length > MAX_POST_SIZE)
1486 body = malloc(length + 1);
1488 length = bb_full_read(a_c_r, body, length);
1489 if(length < 0) // closed
1491 body[length] = 0; // always null terminate for safety
1495 if (strncmp(test, "cgi-bin", 7) == 0) {
1496 if(test[7] == '/' && test[8] == 0)
1497 goto FORBIDDEN; // protect listing cgi-bin/
1498 sendCgi(url, prequest, urlArgs, body, length, cookie);
1500 if (prequest != request_GET)
1501 sendHeaders(HTTP_NOT_IMPLEMENTED);
1503 #endif /* CONFIG_FEATURE_HTTPD_CGI */
1505 strcpy(purl, "index.html");
1506 if ( stat(test, &sb ) == 0 ) {
1507 config->ContentLength = sb.st_size;
1508 config->last_mod = sb.st_mtime;
1510 sendFile(test, buf);
1511 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1512 /* unset if non inetd looped */
1513 config->ContentLength = -1;
1516 #ifdef CONFIG_FEATURE_HTTPD_CGI
1524 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1525 /* from inetd don`t looping: freeing, closing automatic from exit always */
1527 if (config->debugHttpd) fprintf(stderr, "closing socket\n");
1529 # ifdef CONFIG_FEATURE_HTTPD_CGI
1533 shutdown(a_c_w, SHUT_WR);
1534 shutdown(a_c_r, SHUT_RD);
1535 close(config->accepted_socket);
1536 #endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
1539 /****************************************************************************
1541 > $Function: miniHttpd()
1543 * $Description: The main http server function.
1545 * Given an open socket fildes, listen for new connections and farm out
1546 * the processing as a forked process.
1549 * (int) server. . . The server socket fildes.
1551 * $Return: (int) . . . . Always 0.
1553 ****************************************************************************/
1554 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1555 static int miniHttpd(int server)
1557 fd_set readfd, portfd;
1560 FD_SET(server, &portfd);
1562 /* copy the ports we are watching to the readfd set */
1566 /* Now wait INDEFINATELY on the set of sockets! */
1567 if (select(server + 1, &readfd, 0, 0, 0) > 0) {
1568 if (FD_ISSET(server, &readfd)) {
1570 struct sockaddr_in fromAddr;
1573 socklen_t fromAddrLen = sizeof(fromAddr);
1574 int s = accept(server,
1575 (struct sockaddr *)&fromAddr, &fromAddrLen);
1580 config->accepted_socket = s;
1581 addr = ntohl(fromAddr.sin_addr.s_addr);
1582 sprintf(config->rmt_ip, "%u.%u.%u.%u",
1583 (unsigned char)(addr >> 24),
1584 (unsigned char)(addr >> 16),
1585 (unsigned char)(addr >> 8),
1587 config->port = ntohs(fromAddr.sin_port);
1589 if (config->debugHttpd) {
1590 bb_error_msg("connection from IP=%s, port %u\n",
1591 config->rmt_ip, config->port);
1594 /* set the KEEPALIVE option to cull dead connections */
1596 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof (on));
1598 if (config->debugHttpd || fork() == 0) {
1599 /* This is the spawned thread */
1600 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1601 /* protect reload config, may be confuse checking */
1602 signal(SIGHUP, SIG_IGN);
1605 if(!config->debugHttpd)
1618 static int miniHttpd(void)
1620 struct sockaddr_in fromAddrLen;
1621 socklen_t sinlen = sizeof (struct sockaddr_in);
1624 getpeername (0, (struct sockaddr *)&fromAddrLen, &sinlen);
1625 addr = ntohl(fromAddrLen.sin_addr.s_addr);
1626 sprintf(config->rmt_ip, "%u.%u.%u.%u",
1627 (unsigned char)(addr >> 24),
1628 (unsigned char)(addr >> 16),
1629 (unsigned char)(addr >> 8),
1631 config->port = ntohs(fromAddrLen.sin_port);
1635 #endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */
1637 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1638 static void sighup_handler(int sig)
1641 struct sigaction sa;
1643 sa.sa_handler = sighup_handler;
1644 sigemptyset(&sa.sa_mask);
1645 sa.sa_flags = SA_RESTART;
1646 sigaction(SIGHUP, &sa, NULL);
1647 parse_conf(default_path_httpd_conf,
1648 sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE);
1652 #ifdef HTTPD_STANDALONE
1653 int main(int argc, char *argv[])
1655 int httpd_main(int argc, char *argv[])
1658 const char *home_httpd = home;
1660 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1664 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1668 config = xcalloc(1, sizeof(*config));
1669 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1670 config->realm = "Web Server Authentication";
1673 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1677 config->ContentLength = -1;
1679 /* check if user supplied a port number */
1681 int c = getopt( argc, argv, "c:d:h:"
1682 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1685 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
1688 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1691 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1695 if (c == EOF) break;
1698 config->configFile = optarg;
1701 home_httpd = optarg;
1703 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1705 config->debugHttpd = 1;
1708 config->port = atoi(optarg);
1709 if(config->port <= 0 || config->port > 0xffff)
1710 bb_error_msg_and_die("invalid %s for -p", optarg);
1714 printf("%s", decodeString(optarg, 1));
1716 #ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR
1718 printf("%s", encodeString(optarg));
1721 #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH
1723 config->realm = optarg;
1726 #ifdef CONFIG_FEATURE_HTTPD_SETUID
1731 uid = strtol(optarg, &e, 0);
1734 uid = my_getpwnam(optarg);
1740 bb_error_msg("%s", httpdVersion);
1745 if(chdir(home_httpd)) {
1746 bb_perror_msg_and_die("can`t chdir to %s", home_httpd);
1748 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1749 server = openServer();
1750 # ifdef CONFIG_FEATURE_HTTPD_SETUID
1751 /* drop privilegies */
1755 # ifdef CONFIG_FEATURE_HTTPD_CGI
1756 addEnvPort("SERVER");
1760 #ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1763 parse_conf(default_path_httpd_conf, FIRST_PARSE);
1766 #ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY
1767 if (!config->debugHttpd) {
1768 if (daemon(1, 0) < 0) /* don`t change curent directory */
1769 bb_perror_msg_and_die("daemon");
1771 return miniHttpd(server);