source: MondoRescue/branches/stable/mindi-busybox/networking/httpd.c

Last change on this file was 1842, checked in by Bruno Cornec, 16 years ago
  • Fix lack of /etc/raidtab at restore rime in the right place
  • Adds support for nls_utf8 and pata_serverworks Patches from Dirk Husung (husung_at_tu-harburg.de)
  • Add efibootmgr to deplist.txt in order to be able to modify EFI menu at restore time on ia64
  • Usage of the new testver param of pb 0.8.12 to deliver 2.2.5 packages in a test dir.
  • Update pbcl files for upcoming delivery of 2.2.5
  • Fix an issue on parted output (K instead of KB) in parted2fdisk at restore time
  • Adds support for alb/aft types of bonding on Intel cards (Mark Pinkerton <Mark.Pinkerton_at_emageon.com>)
  • Escape variables in a perl script like mindi-bkphw
  • Better Proliant support at restore time now.
  • Fix a label bug where the variable label should also be re-initialized at each loop
  • start-nfs should NOT do exit => kernel panix as init exits
  • some other fixes for init on denymods
  • Adds boot options denymods (for udev) and forcemods (if not udev)
  • kqemu is denied. Probably should also be done for kvm stuff
  • Fix again the bug on modules link at root of the initrd. Hopefully for good.
  • Wait more around the USB CD emulated by iLO as it takes up to 5 seconds to come up
  • Attempt to fix udev support for RHEL 5.1
  • Better support for automatic iLO Virtual Media with udev
  • Some VMWare support improvements (however, it seems that VMWare support won't be possible anytime soon)
  • during init at restore time, copy all static evices availble for udev, as some distro implementation are no

t yet supported correctly for udev. Not very elegant, but should solve our current issues

  • Fedora 8 now supported as a build target for pb
  • svn.log removed.
  • dependencies reviewd for rhel_3
  • Attempt to fix the lack of modules loaded at restore time - especially fs device drivers
  • removes bkphw dir before potential creation
  • render mount command more vebose at restore time
  • Should fix #217
  • Increase BOOT_SIZE and EXTRA_SIZE to support features such as HW recovery
  • Handles udev.files files which could be symlinks
  • Improve udev support for distro with compressed modules (mdv e.g.)
  • Fix modules.dep copy
  • /sbin/pam_console_apply is needed by mdv udev conf
  • Adds support for nohw boot option to avoid re-setuping the HW conf
  • Improved support for Proliant on cpqacuxe
  • RstHW should be called after driver initialization
  • For debian/ubuntu mindi dpends on mindi-busybox
  • Update mindi-busybox pbcl for correct debian changelog generation, and up2date infos
  • Should fix #215
  • Fix #214 (Thanks to xdelaruelle)
  • Fix again svn.log not used anymore with new pb version
  • Adaptation of build process to upcoming 0.8.12 version of pb
  • Remove MONOTONIC clock to continue to support 2.4 kernels (RHEL 3 e.g. or ESX)
  • Attempt to solve ia64 BMC access through the serial port
  • parted2fdisk binary no more generated
  • do not require perl modules (strict.pm) at restore time
  • try to avoid modprobe messages at restore time
  • on ia64 now use the perl script parted2fdisk at retore time also
  • Also modprobe modules for udev after decompressing the additional ones
  • replace gzip -v9 by gzip -c9. Fix a bug at least on ia64
  • For all modules supported, create symlinks under the mountpoint and extract dev files as some are not automat ically created at the moment
  • Support /vmfs/volumes for ESX
  • Finally do not use vdf for ESX. Only creates issues.
  • Avoids continuing hw support if no product name found
  • Improves ia64 support for bootable image build
  • Fix a potential problem with ramdisk_blocksize param
  • MAKEDEV should also be included in deplist.txt
  • More fixes for udev support for Debian
  • Do not mount /boot if it's already mounted - avoids ESX hang
  • Fix NICs renumbering on Debian at least
  • Udev startup improvement to support iLO + NFS so modprobing all modules seems required as of now
  • Update to version 1.7.3 of busybox for pb
  • Load USB earlier in order to support KBD such as with iLO
  • Do busybox ldd first in order to create a potential /lib64 link correctly and then use it later in the image
  • Use variable DF in mindi to be able to support the ESX vdf
  • Fix mindi for Debian x86_64 where /lib64 is a link
  • Fix issue at restore time for ext2fs params not reused (Fix from Klaus Ade Johnstad <klaus_at_skolelinux.no>)
  • Do not copy udev files if they do not exist In udev case do not insert modules at all (udev should do it alone) May avoid the issue with rhel4.5 kernel ? To be tested
  • Update dependencies for rpm base build as well
  • And also perl is a debian/ubuntu dep
  • Better debian/ubuntu dependecies requirements (mtools)
  • Fix modes on mindi-bkphw (Thanks Phil Walker phil.walker_at_hp.com)
  • Complete rev [1771] for upper case RESTORE cli with syslinux as well
  • Update mindi-busybox to 1.7.3
  • Use RESTORE consistently across mondo to restore without interaction (report from Takeshi Shoji t.shoji_at_tripodw.jp)

(merge -r1769:1841 $SVN_M/branches/2.2.5)

File size: 57.2 KB
RevLine 
[821]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 *
[1770]23 * When a url starts by "/cgi-bin/" it is assumed to be a cgi script. The
[821]24 * server changes directory to the location of the script and executes it
25 * after setting QUERY_STRING and other environment variables.
26 *
[1770]27 * Doc:
28 * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
29 *
[821]30 * The server can also be invoked as a url arg decoder and html text encoder
31 * as follows:
32 * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World"
33 * bar=`httpd -e "<Hello World>"` # encode as "&#60Hello&#32World&#62"
34 * Note that url encoding for arguments is not the same as html encoding for
35 * presentation. -d decodes a url-encoded argument while -e encodes in html
36 * for page display.
37 *
38 * httpd.conf has the following format:
39 *
40 * A:172.20. # Allow address from 172.20.0.0/16
41 * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127
42 * A:10.0.0.0/255.255.255.128 # Allow any address that previous set
43 * A:127.0.0.1 # Allow local loopback connections
44 * D:* # Deny from other IP connections
[1770]45 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
[821]46 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/
47 * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/
48 * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
49 * .au:audio/basic # additional mime type for audio.au files
50 * *.php:/path/php # running cgi.php scripts through an interpreter
51 *
52 * A/D may be as a/d or allow/deny - first char case insensitive
53 * Deny IP rules take precedence over allow rules.
54 *
55 *
56 * The Deny/Allow IP logic:
57 *
58 * - Default is to allow all. No addresses are denied unless
59 * denied with a D: rule.
60 * - Order of Deny/Allow rules is significant
61 * - Deny rules take precedence over allow rules.
62 * - If a deny all rule (D:*) is used it acts as a catch-all for unmatched
63 * addresses.
64 * - Specification of Allow all (A:*) is a no-op
65 *
66 * Example:
67 * 1. Allow only specified addresses
68 * A:172.20 # Allow any address that begins with 172.20.
69 * A:10.10. # Allow any address that begins with 10.10.
70 * A:127.0.0.1 # Allow local loopback connections
71 * D:* # Deny from other IP connections
72 *
73 * 2. Only deny specified addresses
74 * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255
75 * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255
76 * A:* # (optional line added for clarity)
77 *
78 * If a sub directory contains a config file it is parsed and merged with
79 * any existing settings as if it was appended to the original configuration.
80 *
81 * subdir paths are relative to the containing subdir and thus cannot
82 * affect the parent rules.
83 *
84 * Note that since the sub dir is parsed in the forked thread servicing the
85 * subdir http request, any merge is discarded when the process exits. As a
86 * result, the subdir settings only have a lifetime of a single request.
87 *
[1770]88 * Custom error pages can contain an absolute path or be relative to
89 * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
90 * page can only be defined in the root configuration file and are not taken
91 * into account in local (directories) config files.
[821]92 *
93 * If -c is not set, an attempt will be made to open the default
94 * root configuration file. If -c is set and the file is not found, the
95 * server exits with an error.
96 *
[1770]97 */
[821]98
[1770]99#include "libbb.h"
100#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
101#include <sys/sendfile.h>
102#endif
[821]103
[1770]104//#define DEBUG 1
105#define DEBUG 0
[821]106
[1770]107/* amount of buffering in a pipe */
108#ifndef PIPE_BUF
109# define PIPE_BUF 4096
[821]110#endif
111
[1770]112#define IOBUF_SIZE 8192 /* IO buffer */
[821]113
[1770]114#define HEADER_READ_TIMEOUT 60
[821]115
[1770]116static const char default_path_httpd_conf[] ALIGN1 = "/etc";
117static const char httpd_conf[] ALIGN1 = "httpd.conf";
118static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n";
[821]119
[1770]120typedef struct has_next_ptr {
121 struct has_next_ptr *next;
122} has_next_ptr;
[821]123
[1770]124/* Must have "next" as a first member */
125typedef struct Htaccess {
126 struct Htaccess *next;
[821]127 char *after_colon;
[1770]128 char before_colon[1]; /* really bigger, must be last */
[821]129} Htaccess;
130
[1770]131/* Must have "next" as a first member */
132typedef struct Htaccess_IP {
133 struct Htaccess_IP *next;
134 unsigned ip;
135 unsigned mask;
[821]136 int allow_deny;
137} Htaccess_IP;
138
[1770]139enum {
140 HTTP_OK = 200,
141 HTTP_MOVED_TEMPORARILY = 302,
142 HTTP_BAD_REQUEST = 400, /* malformed syntax */
143 HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
144 HTTP_NOT_FOUND = 404,
145 HTTP_FORBIDDEN = 403,
146 HTTP_REQUEST_TIMEOUT = 408,
147 HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
148 HTTP_INTERNAL_SERVER_ERROR = 500,
149 HTTP_CONTINUE = 100,
150#if 0 /* future use */
151 HTTP_SWITCHING_PROTOCOLS = 101,
152 HTTP_CREATED = 201,
153 HTTP_ACCEPTED = 202,
154 HTTP_NON_AUTHORITATIVE_INFO = 203,
155 HTTP_NO_CONTENT = 204,
156 HTTP_MULTIPLE_CHOICES = 300,
157 HTTP_MOVED_PERMANENTLY = 301,
158 HTTP_NOT_MODIFIED = 304,
159 HTTP_PAYMENT_REQUIRED = 402,
160 HTTP_BAD_GATEWAY = 502,
161 HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
162 HTTP_RESPONSE_SETSIZE = 0xffffffff
163#endif
164};
[821]165
[1770]166static const uint16_t http_response_type[] ALIGN2 = {
167 HTTP_OK,
168 HTTP_MOVED_TEMPORARILY,
169 HTTP_REQUEST_TIMEOUT,
170 HTTP_NOT_IMPLEMENTED,
171#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
172 HTTP_UNAUTHORIZED,
173#endif
174 HTTP_NOT_FOUND,
175 HTTP_BAD_REQUEST,
176 HTTP_FORBIDDEN,
177 HTTP_INTERNAL_SERVER_ERROR,
178#if 0 /* not implemented */
179 HTTP_CREATED,
180 HTTP_ACCEPTED,
181 HTTP_NO_CONTENT,
182 HTTP_MULTIPLE_CHOICES,
183 HTTP_MOVED_PERMANENTLY,
184 HTTP_NOT_MODIFIED,
185 HTTP_BAD_GATEWAY,
186 HTTP_SERVICE_UNAVAILABLE,
187#endif
188};
[821]189
[1770]190static const struct {
191 const char *name;
192 const char *info;
193} http_response[ARRAY_SIZE(http_response_type)] = {
194 { "OK", NULL },
195 { "Found", "Directories must end with a slash" }, /* ?? */
196 { "Request Timeout", "No request appeared within 60 seconds" },
197 { "Not Implemented", "The requested method is not recognized" },
198#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
199 { "Unauthorized", "" },
200#endif
201 { "Not Found", "The requested URL was not found" },
202 { "Bad Request", "Unsupported method" },
203 { "Forbidden", "" },
204 { "Internal Server Error", "Internal Server Error" },
205#if 0 /* not implemented */
206 { "Created" },
207 { "Accepted" },
208 { "No Content" },
209 { "Multiple Choices" },
210 { "Moved Permanently" },
211 { "Not Modified" },
212 { "Bad Gateway", "" },
213 { "Service Unavailable", "" },
214#endif
215};
[821]216
[1770]217struct globals {
218 int verbose; /* must be int (used by getopt32) */
219 smallint flg_deny_all;
[821]220
[1770]221 unsigned rmt_ip; /* used for IP-based allow/deny rules */
222 time_t last_mod;
223 off_t ContentLength; /* -1 - unknown */
224 char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */
225 const char *bind_addr_or_port;
[821]226
[1770]227 const char *g_query;
228 const char *configFile;
229 const char *home_httpd;
230
[821]231 const char *found_mime_type;
232 const char *found_moved_temporarily;
[1770]233 Htaccess_IP *ip_a_d; /* config allow/deny lines */
[821]234
[1770]235 USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
236 USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
237 USE_FEATURE_HTTPD_CGI(char *referer;)
238 USE_FEATURE_HTTPD_CGI(char *user_agent;)
[821]239
[1770]240#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
241 Htaccess *g_auth; /* config user:password lines */
[821]242#endif
[1770]243#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
244 Htaccess *mime_a; /* config mime types */
[821]245#endif
[1770]246#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
247 Htaccess *script_i; /* config script interpreters */
[821]248#endif
[1770]249 char *iobuf; /* [IOBUF_SIZE] */
250#define hdr_buf bb_common_bufsiz1
251 char *hdr_ptr;
252 int hdr_cnt;
253#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
254 const char *http_error_page[ARRAY_SIZE(http_response_type)];
[821]255#endif
[1770]256};
257#define G (*ptr_to_globals)
258#define verbose (G.verbose )
259#define flg_deny_all (G.flg_deny_all )
260#define rmt_ip (G.rmt_ip )
261#define bind_addr_or_port (G.bind_addr_or_port)
262#define g_query (G.g_query )
263#define configFile (G.configFile )
264#define home_httpd (G.home_httpd )
265#define found_mime_type (G.found_mime_type )
266#define found_moved_temporarily (G.found_moved_temporarily)
267#define ContentLength (G.ContentLength )
268#define last_mod (G.last_mod )
269#define ip_a_d (G.ip_a_d )
270#define g_realm (G.g_realm )
271#define remoteuser (G.remoteuser )
272#define referer (G.referer )
273#define user_agent (G.user_agent )
274#define rmt_ip_str (G.rmt_ip_str )
275#define g_auth (G.g_auth )
276#define mime_a (G.mime_a )
277#define script_i (G.script_i )
278#define iobuf (G.iobuf )
279#define hdr_ptr (G.hdr_ptr )
280#define hdr_cnt (G.hdr_cnt )
281#define http_error_page (G.http_error_page )
282#define INIT_G() do { \
283 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
284 USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
285 bind_addr_or_port = "80"; \
286 ContentLength = -1; \
287} while (0)
[821]288
289
[1770]290#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
[821]291
[1770]292/* Prototypes */
293static void send_file_and_exit(const char *url, int headers) ATTRIBUTE_NORETURN;
[821]294
[1770]295static void free_llist(has_next_ptr **pptr)
[821]296{
[1770]297 has_next_ptr *cur = *pptr;
298 while (cur) {
299 has_next_ptr *t = cur;
300 cur = cur->next;
301 free(t);
302 }
303 *pptr = NULL;
304}
[821]305
[1770]306#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
307 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
308 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
309static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
[821]310{
[1770]311 free_llist((has_next_ptr**)pptr);
312}
[821]313#endif
314
[1770]315static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
316{
317 free_llist((has_next_ptr**)pptr);
318}
[821]319
[1770]320/* Returns presumed mask width in bits or < 0 on error.
321 * Updates strp, stores IP at provided pointer */
322static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
323{
324 const char *p = *strp;
325 int auto_mask = 8;
326 unsigned ip = 0;
327 int j;
[821]328
[1770]329 if (*p == '/')
330 return -auto_mask;
[821]331
[1770]332 for (j = 0; j < 4; j++) {
333 unsigned octet;
[821]334
[1770]335 if ((*p < '0' || *p > '9') && *p != '/' && *p)
336 return -auto_mask;
337 octet = 0;
338 while (*p >= '0' && *p <= '9') {
339 octet *= 10;
340 octet += *p - '0';
341 if (octet > 255)
342 return -auto_mask;
343 p++;
344 }
345 if (*p == '.')
346 p++;
347 if (*p != '/' && *p)
348 auto_mask += 8;
349 ip = (ip << 8) | octet;
[821]350 }
[1770]351 if (*p) {
352 if (*p != endc)
353 return -auto_mask;
354 p++;
355 if (*p == '\0')
356 return -auto_mask;
357 }
358 *ipp = ip;
359 *strp = p;
360 return auto_mask;
[821]361}
362
[1770]363/* Returns 0 on success. Stores IP and mask at provided pointers */
364static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
[821]365{
[1770]366 int i;
367 unsigned mask;
368 char *p;
[821]369
[1770]370 i = scan_ip(&str, ipp, '/');
371 if (i < 0)
372 return i;
[821]373
[1770]374 if (*str) {
375 /* there is /xxx after dotted-IP address */
376 i = bb_strtou(str, &p, 10);
377 if (*p == '.') {
378 /* 'xxx' itself is dotted-IP mask, parse it */
379 /* (return 0 (success) only if it has N.N.N.N form) */
380 return scan_ip(&str, maskp, '\0') - 32;
381 }
382 if (*p)
[821]383 return -1;
384 }
385
[1770]386 if (i > 32)
387 return -1;
[821]388
[1770]389 if (sizeof(unsigned) == 4 && i == 32) {
390 /* mask >>= 32 below may not work */
391 mask = 0;
392 } else {
393 mask = 0xffffffff;
394 mask >>= i;
395 }
396 /* i == 0 -> *maskp = 0x00000000
397 * i == 1 -> *maskp = 0x80000000
398 * i == 4 -> *maskp = 0xf0000000
399 * i == 31 -> *maskp = 0xfffffffe
400 * i == 32 -> *maskp = 0xffffffff */
401 *maskp = (uint32_t)(~mask);
402 return 0;
[821]403}
404
[1770]405/*
406 * Parse configuration file into in-memory linked list.
[821]407 *
408 * The first non-white character is examined to determine if the config line
409 * is one of the following:
410 * .ext:mime/type # new mime type not compiled into httpd
411 * [adAD]:from # ip address allow/deny, * for wildcard
412 * /path:user:pass # username/password
[1770]413 * Ennn:error.html # error page for status nnn
[821]414 *
415 * Any previous IP rules are discarded.
416 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
417 * are also discarded. That is, previous settings are retained if flag is
418 * SUBDIR_PARSE.
[1770]419 * Error pages are only parsed on the main config file.
[821]420 *
[1770]421 * path Path where to look for httpd.conf (without filename).
422 * flag Type of the parse request.
423 */
424/* flag */
425#define FIRST_PARSE 0
426#define SUBDIR_PARSE 1
427#define SIGNALED_PARSE 2
428#define FIND_FROM_HTTPD_ROOT 3
[821]429static void parse_conf(const char *path, int flag)
430{
[1770]431 FILE *f;
432#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
433 Htaccess *prev;
[821]434#endif
[1770]435#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
436 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
437 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
438 Htaccess *cur;
439#endif
440 const char *cf = configFile;
441 char buf[160];
442 char *p0 = NULL;
443 char *c, *p;
444 Htaccess_IP *pip;
[821]445
[1770]446 /* discard old rules */
447 free_Htaccess_IP_list(&ip_a_d);
448 flg_deny_all = 0;
449#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
450 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
451 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
452 /* retain previous auth and mime config only for subdir parse */
453 if (flag != SUBDIR_PARSE) {
454#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
455 free_Htaccess_list(&g_auth);
[821]456#endif
[1770]457#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
458 free_Htaccess_list(&mime_a);
[821]459#endif
[1770]460#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
461 free_Htaccess_list(&script_i);
[821]462#endif
[1770]463 }
[821]464#endif
465
[1770]466 if (flag == SUBDIR_PARSE || cf == NULL) {
467 cf = alloca(strlen(path) + sizeof(httpd_conf) + 2);
468 sprintf((char *)cf, "%s/%s", path, httpd_conf);
[821]469 }
470
[1770]471 while ((f = fopen(cf, "r")) == NULL) {
472 if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) {
473 /* config file not found, no changes to config */
474 return;
475 }
476 if (configFile && flag == FIRST_PARSE) /* if -c option given */
477 bb_perror_msg_and_die("%s", cf);
478 flag = FIND_FROM_HTTPD_ROOT;
479 cf = httpd_conf;
[821]480 }
481
[1770]482#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
483 prev = g_auth;
[821]484#endif
[1770]485 /* This could stand some work */
486 while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) {
487 c = NULL;
488 for (p = p0; *p0 != '\0' && *p0 != '#'; p0++) {
489 if (!isspace(*p0)) {
490 *p++ = *p0;
491 if (*p0 == ':' && c == NULL)
492 c = p;
493 }
[821]494 }
[1770]495 *p = '\0';
[821]496
[1770]497 /* test for empty or strange line */
498 if (c == NULL || *c == '\0')
499 continue;
500 p0 = buf;
501 if (*p0 == 'd')
[821]502 *p0 = 'D';
[1770]503 if (*c == '*') {
504 if (*p0 == 'D') {
505 /* memorize deny all */
506 flg_deny_all = 1;
[821]507 }
[1770]508 /* skip default other "word:*" config lines */
509 continue;
[821]510 }
511
[1770]512 if (*p0 == 'a')
513 *p0 = 'A';
514 if (*p0 == 'A' || *p0 == 'D') {
515 /* storing current config IP line */
516 pip = xzalloc(sizeof(Htaccess_IP));
517 if (pip) {
518 if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) {
519 /* syntax IP{/mask} error detected, protect all */
520 *p0 = 'D';
521 pip->mask = 0;
522 }
523 pip->allow_deny = *p0;
524 if (*p0 == 'D') {
525 /* Deny:from_IP move top */
526 pip->next = ip_a_d;
527 ip_a_d = pip;
528 } else {
529 /* add to bottom A:form_IP config line */
530 Htaccess_IP *prev_IP = ip_a_d;
[821]531
[1770]532 if (prev_IP == NULL) {
533 ip_a_d = pip;
534 } else {
535 while (prev_IP->next)
536 prev_IP = prev_IP->next;
537 prev_IP->next = pip;
538 }
[821]539 }
[1770]540 }
541 continue;
542 }
543
544#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
545 if (flag == FIRST_PARSE && *p0 == 'E') {
546 int i;
547 /* error status code */
548 int status = atoi(++p0);
549 /* c already points at the character following ':' in parse loop */
550 /* c = strchr(p0, ':'); c++; */
551 if (status < HTTP_CONTINUE) {
552 bb_error_msg("config error '%s' in '%s'", buf, cf);
[821]553 continue;
554 }
555
[1770]556 /* then error page; find matching status */
557 for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
558 if (http_response_type[i] == status) {
559 http_error_page[i] = concat_path_file((*c == '/') ? NULL : home_httpd, c);
560 break;
561 }
562 }
563 continue;
564 }
[821]565#endif
566
[1770]567#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
568 if (*p0 == '/') {
569 /* make full path from httpd root / current_path / config_line_path */
570 cf = (flag == SUBDIR_PARSE ? path : "");
571 p0 = xmalloc(strlen(cf) + (c - buf) + 2 + strlen(c));
572 c[-1] = '\0';
573 sprintf(p0, "/%s%s", cf, buf);
574
575 /* another call bb_simplify_path */
576 cf = p = p0;
577
578 do {
579 if (*p == '/') {
580 if (*cf == '/') { /* skip duplicate (or initial) slash */
581 continue;
582 } else if (*cf == '.') {
583 if (cf[1] == '/' || cf[1] == '\0') { /* remove extra '.' */
584 continue;
585 } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == '\0')) {
586 ++cf;
587 if (p > p0) {
588 while (*--p != '/') /* omit previous dir */;
589 }
590 continue;
591 }
592 }
593 }
594 *++p = *cf;
595 } while (*++cf);
596
597 if ((p == p0) || (*p != '/')) { /* not a trailing slash */
598 ++p; /* so keep last character */
599 }
600 *p = '\0';
601 sprintf(p0 + strlen(p0), ":%s", c);
602 }
[821]603#endif
[1770]604
605#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
606 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
607 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
608 /* storing current config line */
609 cur = xzalloc(sizeof(Htaccess) + strlen(p0));
610 if (cur) {
611 cf = strcpy(cur->before_colon, p0);
612 c = strchr(cf, ':');
613 *c++ = 0;
614 cur->after_colon = c;
615#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
616 if (*cf == '.') {
617 /* config .mime line move top for overwrite previous */
618 cur->next = mime_a;
619 mime_a = cur;
620 continue;
621 }
[821]622#endif
[1770]623#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
624 if (*cf == '*' && cf[1] == '.') {
625 /* config script interpreter line move top for overwrite previous */
626 cur->next = script_i;
627 script_i = cur;
628 continue;
629 }
630#endif
631#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
632 free(p0);
633 if (prev == NULL) {
634 /* first line */
635 g_auth = prev = cur;
636 } else {
637 /* sort path, if current lenght eq or bigger then move up */
638 Htaccess *prev_hti = g_auth;
639 size_t l = strlen(cf);
640 Htaccess *hti;
[821]641
[1770]642 for (hti = prev_hti; hti; hti = hti->next) {
643 if (l >= strlen(hti->before_colon)) {
644 /* insert before hti */
645 cur->next = hti;
646 if (prev_hti != hti) {
647 prev_hti->next = cur;
648 } else {
649 /* insert as top */
650 g_auth = cur;
651 }
652 break;
653 }
654 if (prev_hti != hti)
655 prev_hti = prev_hti->next;
656 }
657 if (!hti) { /* not inserted, add to bottom */
658 prev->next = cur;
659 prev = cur;
660 }
[821]661 }
[1770]662#endif
[821]663 }
664#endif
[1770]665 }
666 fclose(f);
[821]667}
668
[1770]669#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
670/*
671 * Given a string, html-encode special characters.
672 * This is used for the -e command line option to provide an easy way
673 * for scripts to encode result data without confusing browsers. The
674 * returned string pointer is memory allocated by malloc().
[821]675 *
[1770]676 * Returns a pointer to the encoded string (malloced).
677 */
[821]678static char *encodeString(const char *string)
679{
[1770]680 /* take the simple route and encode everything */
681 /* could possibly scan once to get length. */
682 int len = strlen(string);
683 char *out = xmalloc(len * 6 + 1);
684 char *p = out;
685 char ch;
[821]686
[1770]687 while ((ch = *string++)) {
688 /* very simple check for what to encode */
689 if (isalnum(ch))
690 *p++ = ch;
691 else
692 p += sprintf(p, "&#%d;", (unsigned char) ch);
693 }
694 *p = '\0';
695 return out;
[821]696}
[1770]697#endif /* FEATURE_HTTPD_ENCODE_URL_STR */
[821]698
[1770]699/*
700 * Given a URL encoded string, convert it to plain ascii.
701 * Since decoding always makes strings smaller, the decode is done in-place.
702 * Thus, callers should strdup() the argument if they do not want the
703 * argument modified. The return is the original pointer, allowing this
704 * function to be easily used as arguments to other functions.
[821]705 *
[1770]706 * string The first string to decode.
707 * option_d 1 if called for httpd -d
[821]708 *
[1770]709 * Returns a pointer to the decoded string (same as input).
710 */
711static unsigned hex_to_bin(unsigned char c)
[821]712{
[1770]713 unsigned v;
[821]714
[1770]715 v = c - '0';
716 if (v <= 9)
717 return v;
718 /* c | 0x20: letters to lower case, non-letters
719 * to (potentially different) non-letters */
720 v = (unsigned)(c | 0x20) - 'a';
721 if (v <= 5)
722 return v + 10;
723 return ~0;
[821]724}
[1770]725/* For testing:
726void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); }
727int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f');
728t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; }
729*/
730static char *decodeString(char *orig, int option_d)
[821]731{
[1770]732 /* note that decoded string is always shorter than original */
733 char *string = orig;
734 char *ptr = string;
735 char c;
[821]736
[1770]737 while ((c = *ptr++) != '\0') {
738 unsigned v;
[821]739
[1770]740 if (option_d && c == '+') {
741 *string++ = ' ';
742 continue;
743 }
744 if (c != '%') {
745 *string++ = c;
746 continue;
747 }
748 v = hex_to_bin(ptr[0]);
749 if (v > 15) {
750 bad_hex:
751 if (!option_d)
752 return NULL;
753 *string++ = '%';
754 continue;
755 }
756 v = (v * 16) | hex_to_bin(ptr[1]);
757 if (v > 255)
758 goto bad_hex;
759 if (!option_d && (v == '/' || v == '\0')) {
760 /* caller takes it as indication of invalid
761 * (dangerous wrt exploits) chars */
762 return orig + 1;
763 }
764 *string++ = v;
765 ptr += 2;
766 }
767 *string = '\0';
768 return orig;
[821]769}
770
[1770]771#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
772/*
773 * Decode a base64 data stream as per rfc1521.
774 * Note that the rfc states that non base64 chars are to be ignored.
775 * Since the decode always results in a shorter size than the input,
776 * it is OK to pass the input arg as an output arg.
777 * Parameter: a pointer to a base64 encoded string.
778 * Decoded data is stored in-place.
779 */
[821]780static void decodeBase64(char *Data)
781{
[1770]782 const unsigned char *in = (const unsigned char *)Data;
783 /* The decoded size will be at most 3/4 the size of the encoded */
784 unsigned ch = 0;
785 int i = 0;
[821]786
[1770]787 while (*in) {
788 int t = *in++;
[821]789
[1770]790 if (t >= '0' && t <= '9')
791 t = t - '0' + 52;
792 else if (t >= 'A' && t <= 'Z')
793 t = t - 'A';
794 else if (t >= 'a' && t <= 'z')
795 t = t - 'a' + 26;
796 else if (t == '+')
797 t = 62;
798 else if (t == '/')
799 t = 63;
800 else if (t == '=')
801 t = 0;
802 else
803 continue;
[821]804
[1770]805 ch = (ch << 6) | t;
806 i++;
807 if (i == 4) {
808 *Data++ = (char) (ch >> 16);
809 *Data++ = (char) (ch >> 8);
810 *Data++ = (char) ch;
811 i = 0;
812 }
813 }
814 *Data = '\0';
[821]815}
816#endif
817
[1770]818/*
819 * Create a listen server socket on the designated port.
820 */
[821]821static int openServer(void)
822{
[1770]823 int n = bb_strtou(bind_addr_or_port, NULL, 10);
824 if (!errno && n && n <= 0xffff)
825 n = create_and_bind_stream_or_die(NULL, n);
826 else
827 n = create_and_bind_stream_or_die(bind_addr_or_port, 80);
828 xlisten(n, 9);
829 return n;
830}
[821]831
[1770]832/*
833 * Log the connection closure and exit.
834 */
835static void log_and_exit(void) ATTRIBUTE_NORETURN;
836static void log_and_exit(void)
837{
838 /* Paranoia. IE said to be buggy. It may send some extra data
839 * or be confused by us just exiting without SHUT_WR. Oh well. */
840 shutdown(1, SHUT_WR);
841 ndelay_on(0);
842 while (read(0, iobuf, IOBUF_SIZE) > 0)
843 continue;
844
845 if (verbose > 2)
846 bb_error_msg("closed");
847 _exit(xfunc_error_retval);
[821]848}
849
[1770]850/*
851 * Create and send HTTP response headers.
852 * The arguments are combined and sent as one write operation. Note that
853 * IE will puke big-time if the headers are not sent in one packet and the
854 * second packet is delayed for any reason.
855 * responseNum - the result code to send.
856 */
857static void send_headers(int responseNum)
[821]858{
[1770]859 static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
[821]860
[1770]861 const char *responseString = "";
862 const char *infoString = NULL;
863 const char *mime_type;
864#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
865 const char *error_page = 0;
866#endif
867 unsigned i;
868 time_t timer = time(0);
869 char tmp_str[80];
870 int len;
871
872 for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
873 if (http_response_type[i] == responseNum) {
874 responseString = http_response[i].name;
875 infoString = http_response[i].info;
876#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
877 error_page = http_error_page[i];
878#endif
[821]879 break;
880 }
[1770]881 }
882 /* error message is HTML */
883 mime_type = responseNum == HTTP_OK ?
884 found_mime_type : "text/html";
[821]885
[1770]886 if (verbose)
887 bb_error_msg("response:%u", responseNum);
[821]888
[1770]889 /* emit the current date */
890 strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer));
891 len = sprintf(iobuf,
892 "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
893 "Date: %s\r\nConnection: close\r\n",
894 responseNum, responseString, mime_type, tmp_str);
895
896#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
897 if (responseNum == HTTP_UNAUTHORIZED) {
898 len += sprintf(iobuf + len,
899 "WWW-Authenticate: Basic realm=\"%s\"\r\n",
900 g_realm);
901 }
[821]902#endif
[1770]903 if (responseNum == HTTP_MOVED_TEMPORARILY) {
904 len += sprintf(iobuf + len, "Location: %s/%s%s\r\n",
905 found_moved_temporarily,
906 (g_query ? "?" : ""),
907 (g_query ? g_query : ""));
908 }
[821]909
[1770]910#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
911 if (error_page && !access(error_page, R_OK)) {
912 strcat(iobuf, "\r\n");
913 len += 2;
914
915 if (DEBUG)
916 fprintf(stderr, "headers: '%s'\n", iobuf);
917 full_write(1, iobuf, len);
918 if (DEBUG)
919 fprintf(stderr, "writing error page: '%s'\n", error_page);
920 return send_file_and_exit(error_page, FALSE);
921 }
[821]922#endif
[1770]923
924 if (ContentLength != -1) { /* file */
925 strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
926 len += sprintf(iobuf + len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n",
927 tmp_str, "Content-length:", ContentLength);
928 }
929 iobuf[len++] = '\r';
930 iobuf[len++] = '\n';
931 if (infoString) {
932 len += sprintf(iobuf + len,
933 "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n"
934 "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
935 responseNum, responseString,
936 responseNum, responseString, infoString);
937 }
938 if (DEBUG)
939 fprintf(stderr, "headers: '%s'\n", iobuf);
940 if (full_write(1, iobuf, len) != len) {
941 if (verbose > 1)
942 bb_perror_msg("error");
943 log_and_exit();
944 }
[821]945}
946
[1770]947static void send_headers_and_exit(int responseNum) ATTRIBUTE_NORETURN;
948static void send_headers_and_exit(int responseNum)
[821]949{
[1770]950 send_headers(responseNum);
951 log_and_exit();
952}
[821]953
[1770]954/*
955 * Read from the socket until '\n' or EOF. '\r' chars are removed.
956 * '\n' is replaced with NUL.
957 * Return number of characters read or 0 if nothing is read
958 * ('\r' and '\n' are not counted).
959 * Data is returned in iobuf.
960 */
961static int get_line(void)
962{
963 int count = 0;
964 char c;
965
966 while (1) {
967 if (hdr_cnt <= 0) {
968 hdr_cnt = safe_read(0, hdr_buf, sizeof(hdr_buf));
969 if (hdr_cnt <= 0)
970 break;
971 hdr_ptr = hdr_buf;
972 }
973 iobuf[count] = c = *hdr_ptr++;
974 hdr_cnt--;
975
976 if (c == '\r')
977 continue;
978 if (c == '\n') {
979 iobuf[count] = '\0';
980 return count;
981 }
982 if (count < (IOBUF_SIZE - 1)) /* check overflow */
983 count++;
984 }
985 return count;
[821]986}
987
[1770]988#if ENABLE_FEATURE_HTTPD_CGI
989static void setenv1(const char *name, const char *value)
990{
991 setenv(name, value ? value : "", 1);
992}
[821]993
[1770]994/*
995 * Spawn CGI script, forward CGI's stdin/out <=> network
[821]996 *
[1770]997 * Environment variables are set up and the script is invoked with pipes
998 * for stdin/stdout. If a post is being done the script is fed the POST
999 * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
[821]1000 *
[1770]1001 * Parameters:
1002 * const char *url The requested URL (with leading /).
1003 * int bodyLen Length of the post body.
1004 * const char *cookie For set HTTP_COOKIE.
1005 * const char *content_type For set CONTENT_TYPE.
1006 */
1007static void send_cgi_and_exit(
1008 const char *url,
1009 const char *request,
1010 int bodyLen,
1011 const char *cookie,
1012 const char *content_type) ATTRIBUTE_NORETURN;
1013static void send_cgi_and_exit(
1014 const char *url,
1015 const char *request,
1016 int bodyLen,
1017 const char *cookie,
1018 const char *content_type)
[821]1019{
[1770]1020 struct { int rd; int wr; } fromCgi; /* CGI -> httpd pipe */
1021 struct { int rd; int wr; } toCgi; /* httpd -> CGI pipe */
1022 char *fullpath;
1023 char *script;
1024 char *purl;
1025 int buf_count;
1026 int status;
1027 int pid = 0;
[821]1028
[1770]1029 /*
1030 * We are mucking with environment _first_ and then vfork/exec,
1031 * this allows us to use vfork safely. Parent don't care about
1032 * these environment changes anyway.
1033 */
[821]1034
[1770]1035 /*
1036 * Find PATH_INFO.
1037 */
1038 purl = xstrdup(url);
1039 script = purl;
1040 while ((script = strchr(script + 1, '/')) != NULL) {
1041 /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */
1042 struct stat sb;
[821]1043
[1770]1044 *script = '\0';
1045 if (!is_directory(purl + 1, 1, &sb)) {
1046 /* not directory, found script.cgi/PATH_INFO */
1047 *script = '/';
1048 break;
1049 }
1050 *script = '/'; /* is directory, find next '/' */
1051 }
1052 setenv1("PATH_INFO", script); /* set /PATH_INFO or "" */
1053 setenv1("REQUEST_METHOD", request);
1054 if (g_query) {
1055 putenv(xasprintf("%s=%s?%s", "REQUEST_URI", purl, g_query));
1056 } else {
1057 setenv1("REQUEST_URI", purl);
1058 }
1059 if (script != NULL)
1060 *script = '\0'; /* cut off /PATH_INFO */
[821]1061
[1770]1062 /* SCRIPT_FILENAME required by PHP in CGI mode */
1063 fullpath = concat_path_file(home_httpd, purl);
1064 setenv1("SCRIPT_FILENAME", fullpath);
1065 /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
1066 setenv1("SCRIPT_NAME", purl);
1067 /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
1068 * QUERY_STRING: The information which follows the ? in the URL
1069 * which referenced this script. This is the query information.
1070 * It should not be decoded in any fashion. This variable
1071 * should always be set when there is query information,
1072 * regardless of command line decoding. */
1073 /* (Older versions of bbox seem to do some decoding) */
1074 setenv1("QUERY_STRING", g_query);
1075 putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
1076 putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
1077 putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
1078 /* Having _separate_ variables for IP and port defeats
1079 * the purpose of having socket abstraction. Which "port"
1080 * are you using on Unix domain socket?
1081 * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
1082 * Oh well... */
1083 {
1084 char *p = rmt_ip_str ? rmt_ip_str : (char*)"";
1085 char *cp = strrchr(p, ':');
1086 if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
1087 cp = NULL;
1088 if (cp) *cp = '\0'; /* delete :PORT */
1089 setenv1("REMOTE_ADDR", p);
1090 if (cp) {
1091 *cp = ':';
1092#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
1093 setenv1("REMOTE_PORT", cp + 1);
1094#endif
1095 }
1096 }
1097 setenv1("HTTP_USER_AGENT", user_agent);
1098 if (bodyLen)
1099 putenv(xasprintf("CONTENT_LENGTH=%d", bodyLen));
1100 if (cookie)
1101 setenv1("HTTP_COOKIE", cookie);
1102 if (content_type)
1103 setenv1("CONTENT_TYPE", content_type);
1104#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1105 if (remoteuser) {
1106 setenv1("REMOTE_USER", remoteuser);
1107 putenv((char*)"AUTH_TYPE=Basic");
1108 }
1109#endif
1110 if (referer)
1111 setenv1("HTTP_REFERER", referer);
[821]1112
[1770]1113 xpipe(&fromCgi.rd);
1114 xpipe(&toCgi.rd);
[821]1115
[1770]1116 pid = vfork();
1117 if (pid < 0) {
1118 /* TODO: log perror? */
1119 log_and_exit();
1120 }
[821]1121
[1770]1122 if (!pid) {
1123 /* Child process */
1124 xfunc_error_retval = 242;
[821]1125
[1770]1126 xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */
1127 xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */
1128 close(fromCgi.rd);
1129 close(toCgi.wr);
1130 /* User seeing stderr output can be a security problem.
1131 * If CGI really wants that, it can always do dup itself. */
1132 /* dup2(1, 2); */
[821]1133
[1770]1134 /* script must have absolute path */
1135 script = strrchr(fullpath, '/');
1136 if (!script)
1137 goto error_execing_cgi;
[821]1138 *script = '\0';
[1770]1139 /* chdiring to script's dir */
1140 if (chdir(fullpath) == 0) {
1141 char *argv[2];
1142#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1143 char *interpr = NULL;
1144 char *suffix = strrchr(purl, '.');
[821]1145
[1770]1146 if (suffix) {
1147 Htaccess *cur;
1148 for (cur = script_i; cur; cur = cur->next) {
1149 if (strcmp(cur->before_colon + 1, suffix) == 0) {
1150 interpr = cur->after_colon;
1151 break;
1152 }
[821]1153 }
[1770]1154 }
[821]1155#endif
[1770]1156 *script = '/';
1157 /* set argv[0] to name without path */
1158 argv[0] = (char*)bb_basename(purl);
1159 argv[1] = NULL;
1160#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1161 if (interpr)
1162 execv(interpr, argv);
1163 else
[821]1164#endif
[1770]1165 execv(fullpath, argv);
[821]1166 }
[1770]1167 error_execing_cgi:
1168 /* send to stdout
1169 * (we are CGI here, our stdout is pumped to the net) */
1170 send_headers_and_exit(HTTP_NOT_FOUND);
1171 } /* end child */
[821]1172
[1770]1173 /* Parent process */
[821]1174
[1770]1175 /* First, restore variables possibly changed by child */
1176 xfunc_error_retval = 0;
[821]1177
[1770]1178 /* Prepare for pumping data.
1179 * iobuf is used for CGI -> network data,
1180 * hdr_buf is for network -> CGI data (POSTDATA) */
1181 buf_count = 0;
1182 close(fromCgi.wr);
1183 close(toCgi.rd);
[821]1184
[1770]1185 /* If CGI dies, we still want to correctly finish reading its output
1186 * and send it to the peer. So please no SIGPIPEs! */
1187 signal(SIGPIPE, SIG_IGN);
[821]1188
[1842]1189 /* Accound for POSTDATA already in hdr_buf */
1190 bodyLen -= hdr_cnt;
1191
[1770]1192 /* This loop still looks messy. What is an exit criteria?
1193 * "CGI's output closed"? Or "CGI has exited"?
1194 * What to do if CGI has closed both input and output, but
1195 * didn't exit? etc... */
[821]1196
[1770]1197 /* NB: breaking out of this loop jumps to log_and_exit() */
1198 while (1) {
1199 fd_set readSet;
1200 fd_set writeSet;
1201 int nfound;
1202 int count;
1203
1204 FD_ZERO(&readSet);
1205 FD_ZERO(&writeSet);
1206 FD_SET(fromCgi.rd, &readSet);
1207 if (bodyLen > 0 || hdr_cnt > 0) {
1208 FD_SET(toCgi.wr, &writeSet);
1209 nfound = toCgi.wr > fromCgi.rd ? toCgi.wr : fromCgi.rd;
1210 if (hdr_cnt <= 0)
1211 FD_SET(0, &readSet);
1212 /* Now wait on the set of sockets! */
1213 nfound = select(nfound + 1, &readSet, &writeSet, NULL, NULL);
[821]1214 } else {
[1770]1215 if (!bodyLen) {
1216 close(toCgi.wr); /* no more POST data to CGI */
1217 bodyLen = -1;
1218 }
1219 nfound = select(fromCgi.rd + 1, &readSet, NULL, NULL, NULL);
[821]1220 }
[1770]1221
1222 if (nfound <= 0) {
1223 if (waitpid(pid, &status, WNOHANG) <= 0) {
1224 /* Weird. CGI didn't exit and no fd's
1225 * are ready, yet select returned?! */
1226 continue;
1227 }
1228 close(fromCgi.rd);
1229 if (DEBUG && WIFEXITED(status))
1230 bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
1231 if (DEBUG && WIFSIGNALED(status))
1232 bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
1233 break;
[821]1234 }
1235
[1770]1236 if (hdr_cnt > 0 && FD_ISSET(toCgi.wr, &writeSet)) {
1237 /* Have data from peer and can write to CGI */
1238 count = safe_write(toCgi.wr, hdr_ptr, hdr_cnt);
1239 /* Doesn't happen, we dont use nonblocking IO here
1240 *if (count < 0 && errno == EAGAIN) {
1241 * ...
1242 *} else */
1243 if (count > 0) {
1244 hdr_ptr += count;
1245 hdr_cnt -= count;
1246 } else {
1247 hdr_cnt = bodyLen = 0; /* EOF/broken pipe to CGI */
1248 }
1249 } else if (bodyLen > 0 && hdr_cnt == 0
1250 && FD_ISSET(0, &readSet)
1251 ) {
1252 /* We expect data, prev data portion is eaten by CGI
1253 * and there *is* data to read from the peer
1254 * (POSTDATA?) */
1255 count = bodyLen > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : bodyLen;
1256 count = safe_read(0, hdr_buf, count);
1257 if (count > 0) {
1258 hdr_cnt = count;
1259 hdr_ptr = hdr_buf;
1260 bodyLen -= count;
1261 } else {
1262 bodyLen = 0; /* closed */
1263 }
1264 }
1265
1266#define PIPESIZE PIPE_BUF
1267#if PIPESIZE >= IOBUF_SIZE
1268# error "PIPESIZE >= IOBUF_SIZE"
[821]1269#endif
[1770]1270 if (FD_ISSET(fromCgi.rd, &readSet)) {
1271 /* There is something to read from CGI */
1272 char *rbuf = iobuf;
[821]1273
[1770]1274 /* Are we still buffering CGI output? */
1275 if (buf_count >= 0) {
1276 /* HTTP_200[] has single "\r\n" at the end.
1277 * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
1278 * CGI scripts MUST send their own header terminated by
1279 * empty line, then data. That's why we have only one
1280 * <cr><lf> pair here. We will output "200 OK" line
1281 * if needed, but CGI still has to provide blank line
1282 * between header and body */
[821]1283
[1770]1284 /* Must use safe_read, not full_read, because
1285 * CGI may output a few first bytes and then wait
1286 * for POSTDATA without closing stdout.
1287 * With full_read we may wait here forever. */
1288 count = safe_read(fromCgi.rd, rbuf + buf_count, PIPESIZE - 8);
1289 if (count <= 0) {
1290 /* eof (or error) and there was no "HTTP",
1291 * so write it, then write received data */
1292 if (buf_count) {
1293 full_write(1, HTTP_200, sizeof(HTTP_200)-1);
1294 full_write(1, rbuf, buf_count);
1295 }
1296 break; /* CGI stdout is closed, exiting */
1297 }
1298 buf_count += count;
1299 count = 0;
1300 /* "Status" header format is: "Status: 302 Redirected\r\n" */
1301 if (buf_count >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
1302 /* send "HTTP/1.0 " */
1303 if (full_write(1, HTTP_200, 9) != 9)
1304 break;
1305 rbuf += 8; /* skip "Status: " */
1306 count = buf_count - 8;
1307 buf_count = -1; /* buffering off */
1308 } else if (buf_count >= 4) {
1309 /* Did CGI add "HTTP"? */
1310 if (memcmp(rbuf, HTTP_200, 4) != 0) {
1311 /* there is no "HTTP", do it ourself */
1312 if (full_write(1, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
1313 break;
1314 }
1315 /* Commented out:
1316 if (!strstr(rbuf, "ontent-")) {
1317 full_write(s, "Content-type: text/plain\r\n\r\n", 28);
1318 }
1319 * Counter-example of valid CGI without Content-type:
1320 * echo -en "HTTP/1.0 302 Found\r\n"
1321 * echo -en "Location: http://www.busybox.net\r\n"
1322 * echo -en "\r\n"
1323 */
1324 count = buf_count;
1325 buf_count = -1; /* buffering off */
1326 }
1327 } else {
1328 count = safe_read(fromCgi.rd, rbuf, PIPESIZE);
1329 if (count <= 0)
1330 break; /* eof (or error) */
1331 }
1332 if (full_write(1, rbuf, count) != count)
1333 break;
1334 if (DEBUG)
1335 fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
1336 } /* if (FD_ISSET(fromCgi.rd)) */
1337 } /* while (1) */
1338 log_and_exit();
[821]1339}
[1770]1340#endif /* FEATURE_HTTPD_CGI */
[821]1341
[1770]1342/*
1343 * Send a file response to a HTTP request, and exit
1344 *
1345 * Parameters:
1346 * const char *url The requested URL (with leading /).
1347 * headers Don't send headers before if FALSE.
1348 */
1349static void send_file_and_exit(const char *url, int headers)
[821]1350{
[1770]1351 static const char *const suffixTable[] = {
1352 /* Warning: shorter equivalent suffix in one line must be first */
1353 ".htm.html", "text/html",
1354 ".jpg.jpeg", "image/jpeg",
1355 ".gif", "image/gif",
1356 ".png", "image/png",
1357 ".txt.h.c.cc.cpp", "text/plain",
1358 ".css", "text/css",
1359 ".wav", "audio/wav",
1360 ".avi", "video/x-msvideo",
1361 ".qt.mov", "video/quicktime",
1362 ".mpe.mpeg", "video/mpeg",
1363 ".mid.midi", "audio/midi",
1364 ".mp3", "audio/mpeg",
1365#if 0 /* unpopular */
1366 ".au", "audio/basic",
1367 ".pac", "application/x-ns-proxy-autoconfig",
1368 ".vrml.wrl", "model/vrml",
1369#endif
1370 NULL
1371 };
[821]1372
[1770]1373 char *suffix;
1374 int f;
1375 const char *const *table;
1376 const char *try_suffix;
1377 ssize_t count;
1378#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1379 off_t offset = 0;
1380#endif
[821]1381
[1770]1382 suffix = strrchr(url, '.');
1383
1384 /* If not found, set default as "application/octet-stream"; */
1385 found_mime_type = "application/octet-stream";
1386 if (suffix) {
1387#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1388 Htaccess *cur;
1389#endif
1390 for (table = suffixTable; *table; table += 2) {
1391 try_suffix = strstr(table[0], suffix);
1392 if (try_suffix) {
1393 try_suffix += strlen(suffix);
1394 if (*try_suffix == '\0' || *try_suffix == '.') {
1395 found_mime_type = table[1];
1396 break;
1397 }
1398 }
1399 }
1400#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
1401 for (cur = mime_a; cur; cur = cur->next) {
1402 if (strcmp(cur->before_colon, suffix) == 0) {
1403 found_mime_type = cur->after_colon;
1404 break;
1405 }
1406 }
1407#endif
[821]1408 }
1409
[1770]1410 if (DEBUG)
1411 bb_error_msg("sending file '%s' content-type: %s",
1412 url, found_mime_type);
1413
1414 f = open(url, O_RDONLY);
1415 if (f < 0) {
1416 if (DEBUG)
1417 bb_perror_msg("cannot open '%s'", url);
1418 if (headers)
1419 send_headers_and_exit(HTTP_NOT_FOUND);
[821]1420 }
1421
[1770]1422 if (headers)
1423 send_headers(HTTP_OK);
[821]1424
[1770]1425 /* If you want to know about EPIPE below
1426 * (happens if you abort downloads from local httpd): */
1427 signal(SIGPIPE, SIG_IGN);
[821]1428
[1770]1429#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1430 do {
1431 /* byte count (3rd arg) is rounded down to 64k */
1432 count = sendfile(1, f, &offset, MAXINT(ssize_t) - 0xffff);
1433 if (count < 0) {
1434 if (offset == 0)
1435 goto fallback;
1436 goto fin;
1437 }
1438 } while (count > 0);
1439 log_and_exit();
1440
1441 fallback:
1442#endif
1443 while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) {
1444 ssize_t n = count;
1445 count = full_write(1, iobuf, count);
1446 if (count != n)
[821]1447 break;
1448 }
[1770]1449#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
1450 fin:
[821]1451#endif
[1770]1452 if (count < 0 && verbose > 1)
1453 bb_perror_msg("error");
1454 log_and_exit();
[821]1455}
1456
1457static int checkPermIP(void)
1458{
[1770]1459 Htaccess_IP *cur;
[821]1460
[1770]1461 /* This could stand some work */
1462 for (cur = ip_a_d; cur; cur = cur->next) {
[821]1463#if DEBUG
[1770]1464 fprintf(stderr,
1465 "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
1466 rmt_ip_str,
1467 (unsigned char)(cur->ip >> 24),
1468 (unsigned char)(cur->ip >> 16),
1469 (unsigned char)(cur->ip >> 8),
1470 (unsigned char)(cur->ip),
1471 (unsigned char)(cur->mask >> 24),
1472 (unsigned char)(cur->mask >> 16),
1473 (unsigned char)(cur->mask >> 8),
1474 (unsigned char)(cur->mask)
1475 );
[821]1476#endif
[1770]1477 if ((rmt_ip & cur->mask) == cur->ip)
1478 return cur->allow_deny == 'A'; /* Allow/Deny */
1479 }
[821]1480
[1770]1481 /* if unconfigured, return 1 - access from all */
1482 return !flg_deny_all;
[821]1483}
1484
[1770]1485#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1486/*
1487 * Check the permission file for access password protected.
[821]1488 *
[1770]1489 * If config file isn't present, everything is allowed.
1490 * Entries are of the form you can see example from header source
[821]1491 *
[1770]1492 * path The file path.
1493 * request User information to validate.
[821]1494 *
[1770]1495 * Returns 1 if request is OK.
1496 */
[821]1497static int checkPerm(const char *path, const char *request)
1498{
[1770]1499 Htaccess *cur;
1500 const char *p;
1501 const char *p0;
[821]1502
[1770]1503 const char *prev = NULL;
[821]1504
[1770]1505 /* This could stand some work */
1506 for (cur = g_auth; cur; cur = cur->next) {
1507 size_t l;
[821]1508
[1770]1509 p0 = cur->before_colon;
1510 if (prev != NULL && strcmp(prev, p0) != 0)
1511 continue; /* find next identical */
1512 p = cur->after_colon;
1513 if (DEBUG)
1514 fprintf(stderr, "checkPerm: '%s' ? '%s'\n", p0, request);
1515
1516 l = strlen(p0);
1517 if (strncmp(p0, path, l) == 0
1518 && (l == 1 || path[l] == '/' || path[l] == '\0')
1519 ) {
1520 char *u;
1521 /* path match found. Check request */
1522 /* for check next /path:user:password */
1523 prev = p0;
1524 u = strchr(request, ':');
1525 if (u == NULL) {
1526 /* bad request, ':' required */
1527 break;
[821]1528 }
1529
[1770]1530 if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
1531 char *cipher;
1532 char *pp;
[821]1533
[1770]1534 if (strncmp(p, request, u - request) != 0) {
1535 /* user doesn't match */
1536 continue;
1537 }
1538 pp = strchr(p, ':');
1539 if (pp && pp[1] == '$' && pp[2] == '1'
1540 && pp[3] == '$' && pp[4]
1541 ) {
1542 pp++;
1543 cipher = pw_encrypt(u+1, pp);
1544 if (strcmp(cipher, pp) == 0)
1545 goto set_remoteuser_var; /* Ok */
1546 /* unauthorized */
1547 continue;
1548 }
[821]1549 }
[1770]1550
1551 if (strcmp(p, request) == 0) {
1552 set_remoteuser_var:
1553 remoteuser = strdup(request);
1554 if (remoteuser)
1555 remoteuser[u - request] = '\0';
1556 return 1; /* Ok */
[821]1557 }
[1770]1558 /* unauthorized */
[821]1559 }
[1770]1560 } /* for */
[821]1561
[1770]1562 return prev == NULL;
[821]1563}
[1770]1564#endif /* FEATURE_HTTPD_BASIC_AUTH */
[821]1565
[1770]1566/*
1567 * Handle timeouts
1568 */
1569static void exit_on_signal(int sig) ATTRIBUTE_NORETURN;
1570static void exit_on_signal(int sig)
[821]1571{
[1770]1572 send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
[821]1573}
1574
[1770]1575/*
1576 * Handle an incoming http request and exit.
1577 */
1578static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) ATTRIBUTE_NORETURN;
1579static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
[821]1580{
[1770]1581 static const char request_GET[] ALIGN1 = "GET";
1582
1583 struct stat sb;
1584 char *urlcopy;
1585 char *urlp;
1586 char *tptr;
1587 int http_major_version;
1588 int ip_allowed;
1589#if ENABLE_FEATURE_HTTPD_CGI
1590 const char *prequest;
1591 unsigned long length = 0;
1592 char *cookie = 0;
1593 char *content_type = 0;
[821]1594#endif
[1770]1595 struct sigaction sa;
1596#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1597 int credentials = -1; /* if not required this is Ok */
1598#endif
[821]1599
[1770]1600 /* Allocation of iobuf is postponed until now
1601 * (IOW, server process doesn't need to waste 8k) */
1602 iobuf = xmalloc(IOBUF_SIZE);
1603
1604 rmt_ip = 0;
1605 if (fromAddr->sa.sa_family == AF_INET) {
1606 rmt_ip = ntohl(fromAddr->sin.sin_addr.s_addr);
1607 }
1608#if ENABLE_FEATURE_IPV6
1609 if (fromAddr->sa.sa_family == AF_INET6
1610 && fromAddr->sin6.sin6_addr.s6_addr32[0] == 0
1611 && fromAddr->sin6.sin6_addr.s6_addr32[1] == 0
1612 && ntohl(fromAddr->sin6.sin6_addr.s6_addr32[2]) == 0xffff)
1613 rmt_ip = ntohl(fromAddr->sin6.sin6_addr.s6_addr32[3]);
[821]1614#endif
[1770]1615 if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
1616 rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->sa);
1617 }
1618 if (verbose) {
1619 /* this trick makes -v logging much simpler */
1620 applet_name = rmt_ip_str;
1621 if (verbose > 2)
1622 bb_error_msg("connected");
1623 }
[821]1624
[1770]1625 /* Install timeout handler */
1626 memset(&sa, 0, sizeof(sa));
1627 sa.sa_handler = exit_on_signal;
1628 /* sigemptyset(&sa.sa_mask); - memset should be enough */
1629 /*sa.sa_flags = 0; - no SA_RESTART */
1630 sigaction(SIGALRM, &sa, NULL);
1631 alarm(HEADER_READ_TIMEOUT);
[821]1632
[1770]1633 if (!get_line()) /* EOF or error or empty line */
1634 send_headers_and_exit(HTTP_BAD_REQUEST);
[821]1635
[1770]1636 /* Determine type of request (GET/POST) */
1637 urlp = strpbrk(iobuf, " \t");
1638 if (urlp == NULL)
1639 send_headers_and_exit(HTTP_BAD_REQUEST);
1640 *urlp++ = '\0';
1641#if ENABLE_FEATURE_HTTPD_CGI
1642 prequest = request_GET;
1643 if (strcasecmp(iobuf, prequest) != 0) {
1644 prequest = "POST";
1645 if (strcasecmp(iobuf, prequest) != 0)
1646 send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
[821]1647 }
1648#else
[1770]1649 if (strcasecmp(iobuf, request_GET) != 0)
1650 send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
[821]1651#endif
[1770]1652 urlp = skip_whitespace(urlp);
1653 if (urlp[0] != '/')
1654 send_headers_and_exit(HTTP_BAD_REQUEST);
[821]1655
[1770]1656 /* Find end of URL and parse HTTP version, if any */
1657 http_major_version = -1;
1658 tptr = strchrnul(urlp, ' ');
1659 /* Is it " HTTP/"? */
1660 if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0)
1661 http_major_version = (tptr[6] - '0');
1662 *tptr = '\0';
[821]1663
[1770]1664 /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
1665 urlcopy = alloca((tptr - urlp) + sizeof("/index.html"));
1666 /*if (urlcopy == NULL)
1667 * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
1668 strcpy(urlcopy, urlp);
1669 /* NB: urlcopy ptr is never changed after this */
[821]1670
[1770]1671 /* Extract url args if present */
1672 tptr = strchr(urlcopy, '?');
1673 g_query = NULL;
1674 if (tptr) {
1675 *tptr++ = '\0';
1676 g_query = tptr;
[821]1677 }
1678
[1770]1679 /* Decode URL escape sequences */
1680 tptr = decodeString(urlcopy, 0);
1681 if (tptr == NULL)
1682 send_headers_and_exit(HTTP_BAD_REQUEST);
1683 if (tptr == urlcopy + 1) {
1684 /* '/' or NUL is encoded */
1685 send_headers_and_exit(HTTP_NOT_FOUND);
1686 }
[821]1687
[1770]1688 /* Canonicalize path */
1689 /* Algorithm stolen from libbb bb_simplify_path(),
1690 * but don't strdup and reducing trailing slash and protect out root */
1691 urlp = tptr = urlcopy;
1692 do {
1693 if (*urlp == '/') {
1694 /* skip duplicate (or initial) slash */
1695 if (*tptr == '/') {
1696 continue;
1697 }
1698 if (*tptr == '.') {
1699 /* skip extra '.' */
1700 if (tptr[1] == '/' || !tptr[1]) {
1701 continue;
1702 }
1703 /* '..': be careful */
1704 if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) {
1705 ++tptr;
1706 if (urlp == urlcopy) /* protect root */
1707 send_headers_and_exit(HTTP_BAD_REQUEST);
1708 while (*--urlp != '/') /* omit previous dir */;
1709 continue;
1710 }
1711 }
1712 }
1713 *++urlp = *tptr;
1714 } while (*++tptr);
1715 *++urlp = '\0'; /* so keep last character */
1716 tptr = urlp; /* end ptr */
[821]1717
[1770]1718 /* If URL is a directory, add '/' */
1719 if (tptr[-1] != '/') {
1720 if (is_directory(urlcopy + 1, 1, &sb)) {
1721 found_moved_temporarily = urlcopy;
1722 }
[821]1723 }
1724
[1770]1725 /* Log it */
1726 if (verbose > 1)
1727 bb_error_msg("url:%s", urlcopy);
[821]1728
[1770]1729 tptr = urlcopy;
1730 ip_allowed = checkPermIP();
1731 while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) {
1732 /* have path1/path2 */
1733 *tptr = '\0';
1734 if (is_directory(urlcopy + 1, 1, &sb)) {
1735 /* may be having subdir config */
1736 parse_conf(urlcopy + 1, SUBDIR_PARSE);
1737 ip_allowed = checkPermIP();
1738 }
1739 *tptr = '/';
[821]1740 }
[1770]1741 if (http_major_version >= 0) {
1742 /* Request was with "... HTTP/nXXX", and n >= 0 */
[821]1743
[1770]1744 /* Read until blank line for HTTP version specified, else parse immediate */
1745 while (1) {
1746 alarm(HEADER_READ_TIMEOUT);
1747 if (!get_line())
1748 break; /* EOF or error or empty line */
1749 if (DEBUG)
1750 bb_error_msg("header: '%s'", iobuf);
[821]1751
[1770]1752#if ENABLE_FEATURE_HTTPD_CGI
1753 /* try and do our best to parse more lines */
1754 if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
1755 /* extra read only for POST */
1756 if (prequest != request_GET) {
1757 tptr = iobuf + sizeof("Content-length:") - 1;
1758 if (!tptr[0])
1759 send_headers_and_exit(HTTP_BAD_REQUEST);
1760 errno = 0;
1761 /* not using strtoul: it ignores leading minus! */
1762 length = strtol(tptr, &tptr, 10);
1763 /* length is "ulong", but we need to pass it to int later */
1764 /* so we check for negative or too large values in one go: */
1765 /* (long -> ulong conv caused negatives to be seen as > INT_MAX) */
1766 if (tptr[0] || errno || length > INT_MAX)
1767 send_headers_and_exit(HTTP_BAD_REQUEST);
1768 }
1769 } else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
1770 cookie = strdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
1771 } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
1772 content_type = strdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
1773 } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
1774 referer = strdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
1775 } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
1776 user_agent = strdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
1777 }
1778#endif
1779#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1780 if (STRNCASECMP(iobuf, "Authorization:") == 0) {
1781 /* We only allow Basic credentials.
1782 * It shows up as "Authorization: Basic <userid:password>" where
1783 * the userid:password is base64 encoded.
1784 */
1785 tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
1786 if (STRNCASECMP(tptr, "Basic") != 0)
1787 continue;
1788 tptr += sizeof("Basic")-1;
1789 /* decodeBase64() skips whitespace itself */
1790 decodeBase64(tptr);
1791 credentials = checkPerm(urlcopy, tptr);
1792 }
1793#endif /* FEATURE_HTTPD_BASIC_AUTH */
1794 } /* while extra header reading */
[821]1795 }
1796
[1770]1797 /* We read headers, disable peer timeout */
1798 alarm(0);
[821]1799
[1770]1800 if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || ip_allowed == 0) {
[821]1801 /* protect listing [/path]/httpd_conf or IP deny */
[1770]1802 send_headers_and_exit(HTTP_FORBIDDEN);
1803 }
[821]1804
[1770]1805#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
1806 if (credentials <= 0 && checkPerm(urlcopy, ":") == 0) {
1807 send_headers_and_exit(HTTP_UNAUTHORIZED);
1808 }
[821]1809#endif
1810
[1770]1811 if (found_moved_temporarily) {
1812 send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
1813 }
[821]1814
[1770]1815 tptr = urlcopy + 1; /* skip first '/' */
[821]1816
[1770]1817#if ENABLE_FEATURE_HTTPD_CGI
1818 if (strncmp(tptr, "cgi-bin/", 8) == 0) {
1819 if (tptr[8] == '\0') {
1820 /* protect listing "cgi-bin/" */
1821 send_headers_and_exit(HTTP_FORBIDDEN);
[821]1822 }
[1770]1823 send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
1824 }
1825#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
1826 {
1827 char *suffix = strrchr(tptr, '.');
1828 if (suffix) {
1829 Htaccess *cur;
1830 for (cur = script_i; cur; cur = cur->next) {
1831 if (strcmp(cur->before_colon + 1, suffix) == 0) {
1832 send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
1833 }
1834 }
1835 }
1836 }
[821]1837#endif
[1770]1838 if (prequest != request_GET) {
1839 send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
1840 }
1841#endif /* FEATURE_HTTPD_CGI */
[821]1842
[1770]1843 if (urlp[-1] == '/')
1844 strcpy(urlp, "index.html");
1845 if (stat(tptr, &sb) == 0) {
1846 /* It's a dir URL and there is index.html */
1847 ContentLength = sb.st_size;
1848 last_mod = sb.st_mtime;
[821]1849 }
[1770]1850#if ENABLE_FEATURE_HTTPD_CGI
1851 else if (urlp[-1] == '/') {
1852 /* It's a dir URL and there is no index.html
1853 * Try cgi-bin/index.cgi */
1854 if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
1855 urlp[0] = '\0';
1856 g_query = urlcopy;
1857 send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type);
1858 }
1859 }
[821]1860#endif
[1770]1861 /* else {
1862 * fall through to send_file, it errors out if open fails
1863 * }
1864 */
[821]1865
[1770]1866 send_file_and_exit(tptr, TRUE);
1867}
[821]1868
[1770]1869/*
1870 * The main http server function.
1871 * Given a socket, listen for new connections and farm out
1872 * the processing as a [v]forked process.
1873 * Never returns.
1874 */
1875#if BB_MMU
1876static void mini_httpd(int server_socket) ATTRIBUTE_NORETURN;
1877static void mini_httpd(int server_socket)
1878{
1879 /* NB: it's best to not use xfuncs in this loop before fork().
1880 * Otherwise server may die on transient errors (temporary
1881 * out-of-memory condition, etc), which is Bad(tm).
1882 * Try to do any dangerous calls after fork.
1883 */
1884 while (1) {
1885 int n;
1886 len_and_sockaddr fromAddr;
1887
1888 /* Wait for connections... */
1889 fromAddr.len = LSA_SIZEOF_SA;
1890 n = accept(server_socket, &fromAddr.sa, &fromAddr.len);
[821]1891
[1770]1892 if (n < 0)
1893 continue;
1894 /* set the KEEPALIVE option to cull dead connections */
1895 setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1896
1897 if (fork() == 0) {
1898 /* child */
1899#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1900 /* Do not reload config on HUP */
1901 signal(SIGHUP, SIG_IGN);
[821]1902#endif
[1770]1903 close(server_socket);
1904 xmove_fd(n, 0);
1905 xdup2(0, 1);
[821]1906
[1770]1907 handle_incoming_and_exit(&fromAddr);
1908 }
1909 /* parent, or fork failed */
1910 close(n);
1911 } /* while (1) */
1912 /* never reached */
[821]1913}
[1770]1914#else
1915static void mini_httpd_nommu(int server_socket, int argc, char **argv) ATTRIBUTE_NORETURN;
1916static void mini_httpd_nommu(int server_socket, int argc, char **argv)
[821]1917{
[1770]1918 char *argv_copy[argc + 2];
[821]1919
[1770]1920 argv_copy[0] = argv[0];
1921 argv_copy[1] = (char*)"-i";
1922 memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0]));
[821]1923
[1770]1924 /* NB: it's best to not use xfuncs in this loop before vfork().
1925 * Otherwise server may die on transient errors (temporary
1926 * out-of-memory condition, etc), which is Bad(tm).
1927 * Try to do any dangerous calls after fork.
1928 */
1929 while (1) {
1930 int n;
1931 len_and_sockaddr fromAddr;
1932
1933 /* Wait for connections... */
1934 fromAddr.len = LSA_SIZEOF_SA;
1935 n = accept(server_socket, &fromAddr.sa, &fromAddr.len);
[821]1936
[1770]1937 if (n < 0)
1938 continue;
1939 /* set the KEEPALIVE option to cull dead connections */
1940 setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
[821]1941
[1770]1942 if (vfork() == 0) {
1943 /* child */
1944#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
1945 /* Do not reload config on HUP */
1946 signal(SIGHUP, SIG_IGN);
[821]1947#endif
[1770]1948 close(server_socket);
1949 xmove_fd(n, 0);
1950 xdup2(0, 1);
[821]1951
[1770]1952 /* Run a copy of ourself in inetd mode */
1953 re_exec(argv_copy);
1954 }
1955 /* parent, or vfork failed */
1956 close(n);
1957 } /* while (1) */
1958 /* never reached */
1959}
[821]1960#endif
1961
[1770]1962/*
1963 * Process a HTTP connection on stdin/out.
1964 * Never returns.
1965 */
1966static void mini_httpd_inetd(void) ATTRIBUTE_NORETURN;
1967static void mini_httpd_inetd(void)
[821]1968{
[1770]1969 len_and_sockaddr fromAddr;
[821]1970
[1770]1971 fromAddr.len = LSA_SIZEOF_SA;
1972 getpeername(0, &fromAddr.sa, &fromAddr.len);
1973 handle_incoming_and_exit(&fromAddr);
[821]1974}
1975
[1770]1976#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
[821]1977static void sighup_handler(int sig)
1978{
1979 struct sigaction sa;
1980
[1770]1981 parse_conf(default_path_httpd_conf, sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE);
1982
1983 memset(&sa, 0, sizeof(sa));
[821]1984 sa.sa_handler = sighup_handler;
[1770]1985 /*sigemptyset(&sa.sa_mask); - memset should be enough */
[821]1986 sa.sa_flags = SA_RESTART;
1987 sigaction(SIGHUP, &sa, NULL);
1988}
1989#endif
1990
[1770]1991enum {
[821]1992 c_opt_config_file = 0,
1993 d_opt_decode_url,
1994 h_opt_home_httpd,
1995 USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
[1770]1996 USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,)
1997 USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,)
1998 USE_FEATURE_HTTPD_SETUID( u_opt_setuid ,)
1999 p_opt_port ,
2000 p_opt_inetd ,
2001 p_opt_foreground,
2002 p_opt_verbose ,
2003 OPT_CONFIG_FILE = 1 << c_opt_config_file,
2004 OPT_DECODE_URL = 1 << d_opt_decode_url,
2005 OPT_HOME_HTTPD = 1 << h_opt_home_httpd,
2006 OPT_ENCODE_URL = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
2007 OPT_REALM = USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0,
2008 OPT_MD5 = USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0,
2009 OPT_SETUID = USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0,
2010 OPT_PORT = 1 << p_opt_port,
2011 OPT_INETD = 1 << p_opt_inetd,
2012 OPT_FOREGROUND = 1 << p_opt_foreground,
2013 OPT_VERBOSE = 1 << p_opt_verbose,
[821]2014};
2015
2016
[1770]2017int httpd_main(int argc, char **argv);
2018int httpd_main(int argc, char **argv)
[821]2019{
[1770]2020 int server_socket = server_socket; /* for gcc */
2021 unsigned opt;
2022 char *url_for_decode;
2023 USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
2024 USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
2025 USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
2026 USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;)
[821]2027
[1770]2028 INIT_G();
[821]2029
[1770]2030#if ENABLE_LOCALE_SUPPORT
2031 /* Undo busybox.c: we want to speak English in http (dates etc) */
2032 setlocale(LC_TIME, "C");
[821]2033#endif
2034
[1770]2035 home_httpd = xrealloc_getcwd_or_warn(NULL);
2036 /* -v counts, -i implies -f */
2037 opt_complementary = "vv:if";
2038 /* We do not "absolutize" path given by -h (home) opt.
2039 * If user gives relative path in -h, $SCRIPT_FILENAME can end up
2040 * relative too. */
2041 opt = getopt32(argv, "c:d:h:"
2042 USE_FEATURE_HTTPD_ENCODE_URL_STR("e:")
2043 USE_FEATURE_HTTPD_BASIC_AUTH("r:")
2044 USE_FEATURE_HTTPD_AUTH_MD5("m:")
2045 USE_FEATURE_HTTPD_SETUID("u:")
2046 "p:ifv",
2047 &configFile, &url_for_decode, &home_httpd
[821]2048 USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
[1770]2049 USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
[821]2050 USE_FEATURE_HTTPD_AUTH_MD5(, &pass)
[1770]2051 USE_FEATURE_HTTPD_SETUID(, &s_ugid)
2052 , &bind_addr_or_port
2053 , &verbose
2054 );
2055 if (opt & OPT_DECODE_URL) {
2056 fputs(decodeString(url_for_decode, 1), stdout);
2057 return 0;
2058 }
2059#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
2060 if (opt & OPT_ENCODE_URL) {
2061 fputs(encodeString(url_for_encode), stdout);
2062 return 0;
2063 }
[821]2064#endif
[1770]2065#if ENABLE_FEATURE_HTTPD_AUTH_MD5
2066 if (opt & OPT_MD5) {
2067 puts(pw_encrypt(pass, "$1$"));
2068 return 0;
2069 }
[821]2070#endif
[1770]2071#if ENABLE_FEATURE_HTTPD_SETUID
2072 if (opt & OPT_SETUID) {
2073 if (!get_uidgid(&ugid, s_ugid, 1))
2074 bb_error_msg_and_die("unrecognized user[:group] "
2075 "name '%s'", s_ugid);
2076 }
2077#endif
[821]2078
[1770]2079#if !BB_MMU
2080 if (!(opt & OPT_FOREGROUND)) {
2081 bb_daemonize_or_rexec(0, argv); /* don't change current directory */
[821]2082 }
2083#endif
2084
[1770]2085 xchdir(home_httpd);
2086 if (!(opt & OPT_INETD)) {
2087 signal(SIGCHLD, SIG_IGN);
2088 server_socket = openServer();
2089#if ENABLE_FEATURE_HTTPD_SETUID
2090 /* drop privileges */
2091 if (opt & OPT_SETUID) {
2092 if (ugid.gid != (gid_t)-1) {
2093 if (setgroups(1, &ugid.gid) == -1)
2094 bb_perror_msg_and_die("setgroups");
2095 xsetgid(ugid.gid);
2096 }
2097 xsetuid(ugid.uid);
2098 }
[821]2099#endif
[1770]2100 }
[821]2101
[1770]2102#if ENABLE_FEATURE_HTTPD_CGI
2103 {
2104 char *p = getenv("PATH");
2105 /* env strings themself are not freed, no need to strdup(p): */
2106 clearenv();
2107 if (p)
2108 putenv(p - 5);
2109// if (!(opt & OPT_INETD))
2110// setenv_long("SERVER_PORT", ???);
[821]2111 }
2112#endif
2113
[1770]2114#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
2115 if (!(opt & OPT_INETD))
2116 sighup_handler(0);
2117 else /* do not install HUP handler in inetd mode */
[821]2118#endif
[1770]2119 parse_conf(default_path_httpd_conf, FIRST_PARSE);
[821]2120
[1770]2121 xfunc_error_retval = 0;
2122 if (opt & OPT_INETD)
2123 mini_httpd_inetd();
2124#if BB_MMU
2125 if (!(opt & OPT_FOREGROUND))
2126 bb_daemonize(0); /* don't change current directory */
2127 mini_httpd(server_socket); /* never returns */
[821]2128#else
[1770]2129 mini_httpd_nommu(server_socket, argc, argv); /* never returns */
[821]2130#endif
[1770]2131 /* return 0; */
[821]2132}
Note: See TracBrowser for help on using the repository browser.