aboutsummaryrefslogtreecommitdiff
path: root/networking/httpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'networking/httpd.c')
-rw-r--r--networking/httpd.c264
1 files changed, 200 insertions, 64 deletions
diff --git a/networking/httpd.c b/networking/httpd.c
index 920702141..af1f61d2d 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -43,6 +43,11 @@
43 * A:127.0.0.1 # Allow local loopback connections 43 * A:127.0.0.1 # Allow local loopback connections
44 * D:* # Deny from other IP connections 44 * D:* # Deny from other IP connections
45 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page 45 * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
46 *
47 * P:/url:[http://]hostname[:port]/new/path
48 * # When /urlXXXXXX is requested, reverse proxy
49 * # it to http://hostname[:port]/new/pathXXXXXX
50 *
46 * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ 51 * /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/ 52 * /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/ 53 * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
@@ -139,6 +144,14 @@ typedef struct Htaccess_IP {
139 int allow_deny; 144 int allow_deny;
140} Htaccess_IP; 145} Htaccess_IP;
141 146
147/* Must have "next" as a first member */
148typedef struct Htaccess_Proxy {
149 struct Htaccess_Proxy *next;
150 char *url_from;
151 char *host_port;
152 char *url_to;
153} Htaccess_Proxy;
154
142enum { 155enum {
143 HTTP_OK = 200, 156 HTTP_OK = 200,
144 HTTP_PARTIAL_CONTENT = 206, 157 HTTP_PARTIAL_CONTENT = 206,
@@ -270,6 +283,9 @@ struct globals {
270#if ENABLE_FEATURE_HTTPD_ERROR_PAGES 283#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
271 const char *http_error_page[ARRAY_SIZE(http_response_type)]; 284 const char *http_error_page[ARRAY_SIZE(http_response_type)];
272#endif 285#endif
286#if ENABLE_FEATURE_HTTPD_PROXY
287 Htaccess_Proxy *proxy;
288#endif
273}; 289};
274#define G (*ptr_to_globals) 290#define G (*ptr_to_globals)
275#define verbose (G.verbose ) 291#define verbose (G.verbose )
@@ -301,6 +317,7 @@ struct globals {
301#define hdr_ptr (G.hdr_ptr ) 317#define hdr_ptr (G.hdr_ptr )
302#define hdr_cnt (G.hdr_cnt ) 318#define hdr_cnt (G.hdr_cnt )
303#define http_error_page (G.http_error_page ) 319#define http_error_page (G.http_error_page )
320#define proxy (G.proxy )
304#define INIT_G() do { \ 321#define INIT_G() do { \
305 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \ 322 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
306 USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \ 323 USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
@@ -441,6 +458,7 @@ static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
441 * [adAD]:from # ip address allow/deny, * for wildcard 458 * [adAD]:from # ip address allow/deny, * for wildcard
442 * /path:user:pass # username/password 459 * /path:user:pass # username/password
443 * Ennn:error.html # error page for status nnn 460 * Ennn:error.html # error page for status nnn
461 * P:/url:[http://]hostname[:port]/new/path # reverse proxy
444 * 462 *
445 * Any previous IP rules are discarded. 463 * Any previous IP rules are discarded.
446 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules 464 * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
@@ -469,7 +487,7 @@ static void parse_conf(const char *path, int flag)
469#endif 487#endif
470 const char *cf = configFile; 488 const char *cf = configFile;
471 char buf[160]; 489 char buf[160];
472 char *p0 = NULL; 490 char *p0;
473 char *c, *p; 491 char *c, *p;
474 Htaccess_IP *pip; 492 Htaccess_IP *pip;
475 493
@@ -594,6 +612,42 @@ static void parse_conf(const char *path, int flag)
594 } 612 }
595#endif 613#endif
596 614
615#if ENABLE_FEATURE_HTTPD_PROXY
616 if (flag == FIRST_PARSE && *p0 == 'P') {
617 /* P:/url:[http://]hostname[:port]/new/path */
618 char *url_from, *host_port, *url_to;
619 Htaccess_Proxy *proxy_entry;
620
621 url_from = c;
622 host_port = strchr(c, ':');
623 if (host_port == NULL) {
624 bb_error_msg("config error '%s' in '%s'", buf, cf);
625 continue;
626 }
627 *host_port++ = '\0';
628 if (strncmp(host_port, "http://", 7) == 0)
629 c += 7;
630 if (*host_port == '\0') {
631 bb_error_msg("config error '%s' in '%s'", buf, cf);
632 continue;
633 }
634 url_to = strchr(host_port, '/');
635 if (url_to == NULL) {
636 bb_error_msg("config error '%s' in '%s'", buf, cf);
637 continue;
638 }
639 *url_to = '\0';
640 proxy_entry = xzalloc(sizeof(Htaccess_Proxy));
641 proxy_entry->url_from = xstrdup(url_from);
642 proxy_entry->host_port = xstrdup(host_port);
643 *url_to = '/';
644 proxy_entry->url_to = xstrdup(url_to);
645 proxy_entry->next = proxy;
646 proxy = proxy_entry;
647 continue;
648 }
649#endif
650
597#if ENABLE_FEATURE_HTTPD_BASIC_AUTH 651#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
598 if (*p0 == '/') { 652 if (*p0 == '/') {
599 /* make full path from httpd root / current_path / config_line_path */ 653 /* make full path from httpd root / current_path / config_line_path */
@@ -639,62 +693,63 @@ static void parse_conf(const char *path, int flag)
639 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR 693 || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
640 /* storing current config line */ 694 /* storing current config line */
641 cur = xzalloc(sizeof(Htaccess) + strlen(p0)); 695 cur = xzalloc(sizeof(Htaccess) + strlen(p0));
642 if (cur) { 696 cf = strcpy(cur->before_colon, p0);
643 cf = strcpy(cur->before_colon, p0); 697#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
644 c = strchr(cf, ':'); 698 if (*p0 == '/')
645 *c++ = 0; 699 free(p0);
646 cur->after_colon = c; 700#endif
701 c = strchr(cf, ':');
702 *c++ = '\0';
703 cur->after_colon = c;
647#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES 704#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
648 if (*cf == '.') { 705 if (*cf == '.') {
649 /* config .mime line move top for overwrite previous */ 706 /* config .mime line move top for overwrite previous */
650 cur->next = mime_a; 707 cur->next = mime_a;
651 mime_a = cur; 708 mime_a = cur;
652 continue; 709 continue;
653 } 710 }
654#endif 711#endif
655#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR 712#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
656 if (*cf == '*' && cf[1] == '.') { 713 if (*cf == '*' && cf[1] == '.') {
657 /* config script interpreter line move top for overwrite previous */ 714 /* config script interpreter line move top for overwrite previous */
658 cur->next = script_i; 715 cur->next = script_i;
659 script_i = cur; 716 script_i = cur;
660 continue; 717 continue;
661 } 718 }
662#endif 719#endif
663#if ENABLE_FEATURE_HTTPD_BASIC_AUTH 720#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
664 free(p0); 721 if (prev == NULL) {
665 if (prev == NULL) { 722 /* first line */
666 /* first line */ 723 g_auth = prev = cur;
667 g_auth = prev = cur; 724 } else {
668 } else { 725 /* sort path, if current length eq or bigger then move up */
669 /* sort path, if current lenght eq or bigger then move up */ 726 Htaccess *prev_hti = g_auth;
670 Htaccess *prev_hti = g_auth; 727 size_t l = strlen(cf);
671 size_t l = strlen(cf); 728 Htaccess *hti;
672 Htaccess *hti; 729
673 730 for (hti = prev_hti; hti; hti = hti->next) {
674 for (hti = prev_hti; hti; hti = hti->next) { 731 if (l >= strlen(hti->before_colon)) {
675 if (l >= strlen(hti->before_colon)) { 732 /* insert before hti */
676 /* insert before hti */ 733 cur->next = hti;
677 cur->next = hti; 734 if (prev_hti != hti) {
678 if (prev_hti != hti) { 735 prev_hti->next = cur;
679 prev_hti->next = cur; 736 } else {
680 } else { 737 /* insert as top */
681 /* insert as top */ 738 g_auth = cur;
682 g_auth = cur;
683 }
684 break;
685 } 739 }
686 if (prev_hti != hti) 740 break;
687 prev_hti = prev_hti->next;
688 }
689 if (!hti) { /* not inserted, add to bottom */
690 prev->next = cur;
691 prev = cur;
692 } 741 }
742 if (prev_hti != hti)
743 prev_hti = prev_hti->next;
744 }
745 if (!hti) { /* not inserted, add to bottom */
746 prev->next = cur;
747 prev = cur;
693 } 748 }
694#endif
695 } 749 }
696#endif 750#endif /* BASIC_AUTH */
697 } 751#endif /* BASIC_AUTH || MIME_TYPES || SCRIPT_INTERPR */
752 } /* while (fgets) */
698 fclose(f); 753 fclose(f);
699} 754}
700 755
@@ -852,7 +907,7 @@ static void decodeBase64(char *Data)
852 */ 907 */
853static int openServer(void) 908static int openServer(void)
854{ 909{
855 int n = bb_strtou(bind_addr_or_port, NULL, 10); 910 unsigned n = bb_strtou(bind_addr_or_port, NULL, 10);
856 if (!errno && n && n <= 0xffff) 911 if (!errno && n && n <= 0xffff)
857 n = create_and_bind_stream_or_die(NULL, n); 912 n = create_and_bind_stream_or_die(NULL, n);
858 else 913 else
@@ -1033,7 +1088,7 @@ static int get_line(void)
1033 return count; 1088 return count;
1034} 1089}
1035 1090
1036#if ENABLE_FEATURE_HTTPD_CGI 1091#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
1037 1092
1038/* gcc 4.2.1 fares better with NOINLINE */ 1093/* gcc 4.2.1 fares better with NOINLINE */
1039static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) ATTRIBUTE_NORETURN; 1094static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) ATTRIBUTE_NORETURN;
@@ -1207,6 +1262,9 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post
1207 } /* while (1) */ 1262 } /* while (1) */
1208 log_and_exit(); 1263 log_and_exit();
1209} 1264}
1265#endif
1266
1267#if ENABLE_FEATURE_HTTPD_CGI
1210 1268
1211static void setenv1(const char *name, const char *value) 1269static void setenv1(const char *name, const char *value)
1212{ 1270{
@@ -1655,6 +1713,18 @@ static int checkPerm(const char *path, const char *request)
1655} 1713}
1656#endif /* FEATURE_HTTPD_BASIC_AUTH */ 1714#endif /* FEATURE_HTTPD_BASIC_AUTH */
1657 1715
1716#if ENABLE_FEATURE_HTTPD_PROXY
1717static Htaccess_Proxy *find_proxy_entry(const char *url)
1718{
1719 Htaccess_Proxy *p;
1720 for (p = proxy; p; p = p->next) {
1721 if (strncmp(url, p->url_from, strlen(p->url_from)) == 0)
1722 return p;
1723 }
1724 return NULL;
1725}
1726#endif
1727
1658/* 1728/*
1659 * Handle timeouts 1729 * Handle timeouts
1660 */ 1730 */
@@ -1676,13 +1746,22 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1676 char *urlcopy; 1746 char *urlcopy;
1677 char *urlp; 1747 char *urlp;
1678 char *tptr; 1748 char *tptr;
1679 int http_major_version;
1680 int ip_allowed; 1749 int ip_allowed;
1681#if ENABLE_FEATURE_HTTPD_CGI 1750#if ENABLE_FEATURE_HTTPD_CGI
1682 const char *prequest; 1751 const char *prequest;
1752 char *cookie = NULL;
1753 char *content_type = NULL;
1754 unsigned long length = 0;
1755#elif ENABLE_FEATURE_HTTPD_PROXY
1756#define prequest request_GET
1683 unsigned long length = 0; 1757 unsigned long length = 0;
1684 char *cookie = 0; 1758#endif
1685 char *content_type = 0; 1759 char http_major_version;
1760#if ENABLE_FEATURE_HTTPD_PROXY
1761 char http_minor_version;
1762 char *headers = headers;
1763 char *headers_ptr = headers_ptr;
1764 Htaccess_Proxy *proxy_entry;
1686#endif 1765#endif
1687 struct sigaction sa; 1766 struct sigaction sa;
1688#if ENABLE_FEATURE_HTTPD_BASIC_AUTH 1767#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -1746,11 +1825,14 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1746 send_headers_and_exit(HTTP_BAD_REQUEST); 1825 send_headers_and_exit(HTTP_BAD_REQUEST);
1747 1826
1748 /* Find end of URL and parse HTTP version, if any */ 1827 /* Find end of URL and parse HTTP version, if any */
1749 http_major_version = -1; 1828 http_major_version = '0';
1829 USE_FEATURE_HTTPD_PROXY(http_minor_version = '0';)
1750 tptr = strchrnul(urlp, ' '); 1830 tptr = strchrnul(urlp, ' ');
1751 /* Is it " HTTP/"? */ 1831 /* Is it " HTTP/"? */
1752 if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) 1832 if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) {
1753 http_major_version = (tptr[6] - '0'); 1833 http_major_version = tptr[6];
1834 USE_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];)
1835 }
1754 *tptr = '\0'; 1836 *tptr = '\0';
1755 1837
1756 /* Copy URL from after "GET "/"POST " to stack-allocated char[] */ 1838 /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
@@ -1761,8 +1843,8 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1761 /* NB: urlcopy ptr is never changed after this */ 1843 /* NB: urlcopy ptr is never changed after this */
1762 1844
1763 /* Extract url args if present */ 1845 /* Extract url args if present */
1764 tptr = strchr(urlcopy, '?');
1765 g_query = NULL; 1846 g_query = NULL;
1847 tptr = strchr(urlcopy, '?');
1766 if (tptr) { 1848 if (tptr) {
1767 *tptr++ = '\0'; 1849 *tptr++ = '\0';
1768 g_query = tptr; 1850 g_query = tptr;
@@ -1830,7 +1912,14 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1830 } 1912 }
1831 *tptr = '/'; 1913 *tptr = '/';
1832 } 1914 }
1833 if (http_major_version >= 0) { 1915
1916#if ENABLE_FEATURE_HTTPD_PROXY
1917 proxy_entry = find_proxy_entry(urlcopy);
1918 if (proxy_entry)
1919 headers = headers_ptr = xmalloc(IOBUF_SIZE);
1920#endif
1921
1922 if (http_major_version >= '0') {
1834 /* Request was with "... HTTP/nXXX", and n >= 0 */ 1923 /* Request was with "... HTTP/nXXX", and n >= 0 */
1835 1924
1836 /* Read until blank line for HTTP version specified, else parse immediate */ 1925 /* Read until blank line for HTTP version specified, else parse immediate */
@@ -1841,8 +1930,23 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1841 if (DEBUG) 1930 if (DEBUG)
1842 bb_error_msg("header: '%s'", iobuf); 1931 bb_error_msg("header: '%s'", iobuf);
1843 1932
1844#if ENABLE_FEATURE_HTTPD_CGI 1933#if ENABLE_FEATURE_HTTPD_PROXY
1845 /* try and do our best to parse more lines */ 1934 /* We need 2 more bytes for yet another "\r\n" -
1935 * see fdprintf(proxy_fd...) further below */
1936 if (proxy_entry && headers_ptr - headers < IOBUF_SIZE - 2) {
1937 int len = strlen(iobuf);
1938 if (len > IOBUF_SIZE - (headers_ptr - headers) - 4)
1939 len = IOBUF_SIZE - (headers_ptr - headers) - 4;
1940 memcpy(headers_ptr, iobuf, len);
1941 headers_ptr += len;
1942 headers_ptr[0] = '\r';
1943 headers_ptr[1] = '\n';
1944 headers_ptr += 2;
1945 }
1946#endif
1947
1948#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
1949 /* Try and do our best to parse more lines */
1846 if ((STRNCASECMP(iobuf, "Content-length:") == 0)) { 1950 if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
1847 /* extra read only for POST */ 1951 /* extra read only for POST */
1848 if (prequest != request_GET) { 1952 if (prequest != request_GET) {
@@ -1858,7 +1962,10 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1858 if (tptr[0] || errno || length > INT_MAX) 1962 if (tptr[0] || errno || length > INT_MAX)
1859 send_headers_and_exit(HTTP_BAD_REQUEST); 1963 send_headers_and_exit(HTTP_BAD_REQUEST);
1860 } 1964 }
1861 } else if (STRNCASECMP(iobuf, "Cookie:") == 0) { 1965 }
1966#endif
1967#if ENABLE_FEATURE_HTTPD_CGI
1968 else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
1862 cookie = strdup(skip_whitespace(iobuf + sizeof("Cookie:")-1)); 1969 cookie = strdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
1863 } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) { 1970 } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
1864 content_type = strdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1)); 1971 content_type = strdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
@@ -1885,7 +1992,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1885#endif /* FEATURE_HTTPD_BASIC_AUTH */ 1992#endif /* FEATURE_HTTPD_BASIC_AUTH */
1886#if ENABLE_FEATURE_HTTPD_RANGES 1993#if ENABLE_FEATURE_HTTPD_RANGES
1887 if (STRNCASECMP(iobuf, "Range:") == 0) { 1994 if (STRNCASECMP(iobuf, "Range:") == 0) {
1888 // We know only bytes=NNN-[MMM] 1995 /* We know only bytes=NNN-[MMM] */
1889 char *s = skip_whitespace(iobuf + sizeof("Range:")-1); 1996 char *s = skip_whitespace(iobuf + sizeof("Range:")-1);
1890 if (strncmp(s, "bytes=", 6) == 0) { 1997 if (strncmp(s, "bytes=", 6) == 0) {
1891 s += sizeof("bytes=")-1; 1998 s += sizeof("bytes=")-1;
@@ -1903,7 +2010,7 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1903 } /* while extra header reading */ 2010 } /* while extra header reading */
1904 } 2011 }
1905 2012
1906 /* We read headers, disable peer timeout */ 2013 /* We are done reading headers, disable peer timeout */
1907 alarm(0); 2014 alarm(0);
1908 2015
1909 if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || ip_allowed == 0) { 2016 if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || ip_allowed == 0) {
@@ -1921,6 +2028,35 @@ static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
1921 send_headers_and_exit(HTTP_MOVED_TEMPORARILY); 2028 send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
1922 } 2029 }
1923 2030
2031#if ENABLE_FEATURE_HTTPD_PROXY
2032 if (proxy_entry != NULL) {
2033 int proxy_fd;
2034 len_and_sockaddr *lsa;
2035
2036 proxy_fd = socket(AF_INET, SOCK_STREAM, 0);
2037 if (proxy_fd < 0)
2038 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2039 lsa = host2sockaddr(proxy_entry->host_port, 80);
2040 if (lsa == NULL)
2041 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2042 if (connect(proxy_fd, &lsa->sa, lsa->len) < 0)
2043 send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
2044 fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n",
2045 prequest, /* GET or POST */
2046 proxy_entry->url_to, /* url part 1 */
2047 urlcopy + strlen(proxy_entry->url_from), /* url part 2 */
2048 (g_query ? "?" : ""), /* "?" (maybe) */
2049 (g_query ? g_query : ""), /* query string (maybe) */
2050 http_major_version, http_minor_version);
2051 headers_ptr[0] = '\r';
2052 headers_ptr[1] = '\n';
2053 headers_ptr += 2;
2054 write(proxy_fd, headers, headers_ptr - headers);
2055 /* cgi_io_loop_and_exit needs to have two disctinct fds */
2056 cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length);
2057 }
2058#endif
2059
1924 tptr = urlcopy + 1; /* skip first '/' */ 2060 tptr = urlcopy + 1; /* skip first '/' */
1925 2061
1926#if ENABLE_FEATURE_HTTPD_CGI 2062#if ENABLE_FEATURE_HTTPD_CGI
@@ -2211,7 +2347,7 @@ int httpd_main(int argc, char **argv)
2211 /* User can do it himself: 'env - PATH="$PATH" httpd' 2347 /* User can do it himself: 'env - PATH="$PATH" httpd'
2212 * We don't do it because we don't want to screw users 2348 * We don't do it because we don't want to screw users
2213 * which want to do 2349 * which want to do
2214 * 'env - VAR1=val1 VAR2=val2 https' 2350 * 'env - VAR1=val1 VAR2=val2 httpd'
2215 * and have VAR1 and VAR2 values visible in their CGIs. 2351 * and have VAR1 and VAR2 values visible in their CGIs.
2216 * Besides, it is also smaller. */ 2352 * Besides, it is also smaller. */
2217 { 2353 {