+#endif
+
+#if ENABLE_FEATURE_HTTPD_CGI
+
+static void setenv1(const char *name, const char *value)
+{
+ setenv(name, value ? value : "", 1);
+}
+
+/*
+ * Spawn CGI script, forward CGI's stdin/out <=> network
+ *
+ * Environment variables are set up and the script is invoked with pipes
+ * for stdin/stdout. If a POST is being done the script is fed the POST
+ * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
+ *
+ * Parameters:
+ * const char *url The requested URL (with leading /).
+ * const char *orig_uri The original URI before rewriting (if any)
+ * int post_len Length of the POST body.
+ * const char *cookie For set HTTP_COOKIE.
+ * const char *content_type For set CONTENT_TYPE.
+ */
+static void send_cgi_and_exit(
+ const char *url,
+ const char *orig_uri,
+ const char *request,
+ int post_len,
+ const char *cookie,
+ const char *content_type) NORETURN;
+static void send_cgi_and_exit(
+ const char *url,
+ const char *orig_uri,
+ const char *request,
+ int post_len,
+ const char *cookie,
+ const char *content_type)
+{
+ struct fd_pair fromCgi; /* CGI -> httpd pipe */
+ struct fd_pair toCgi; /* httpd -> CGI pipe */
+ char *script, *last_slash;
+ int pid;
+
+ /* Make a copy. NB: caller guarantees:
+ * url[0] == '/', url[1] != '/' */
+ url = xstrdup(url);
+
+ /*
+ * We are mucking with environment _first_ and then vfork/exec,
+ * this allows us to use vfork safely. Parent doesn't care about
+ * these environment changes anyway.
+ */
+
+ /* Check for [dirs/]script.cgi/PATH_INFO */
+ last_slash = script = (char*)url;
+ while ((script = strchr(script + 1, '/')) != NULL) {
+ int dir;
+ *script = '\0';
+ dir = is_directory(url + 1, /*followlinks:*/ 1);
+ *script = '/';
+ if (!dir) {
+ /* not directory, found script.cgi/PATH_INFO */
+ break;
+ }
+ /* is directory, find next '/' */
+ last_slash = script;
+ }
+ setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */
+ setenv1("REQUEST_METHOD", request);
+ if (g_query) {
+ putenv(xasprintf("%s=%s?%s", "REQUEST_URI", orig_uri, g_query));
+ } else {
+ setenv1("REQUEST_URI", orig_uri);
+ }
+ if (script != NULL)
+ *script = '\0'; /* cut off /PATH_INFO */
+
+ /* SCRIPT_FILENAME is required by PHP in CGI mode */
+ if (home_httpd[0] == '/') {
+ char *fullpath = concat_path_file(home_httpd, url);
+ setenv1("SCRIPT_FILENAME", fullpath);
+ }
+ /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
+ setenv1("SCRIPT_NAME", url);
+ /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
+ * QUERY_STRING: The information which follows the ? in the URL
+ * which referenced this script. This is the query information.
+ * It should not be decoded in any fashion. This variable
+ * should always be set when there is query information,
+ * regardless of command line decoding. */
+ /* (Older versions of bbox seem to do some decoding) */
+ setenv1("QUERY_STRING", g_query);
+ putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
+ putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
+ putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
+ /* Having _separate_ variables for IP and port defeats
+ * the purpose of having socket abstraction. Which "port"
+ * are you using on Unix domain socket?
+ * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
+ * Oh well... */
+ {
+ char *p = rmt_ip_str ? rmt_ip_str : (char*)"";
+ char *cp = strrchr(p, ':');
+ if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
+ cp = NULL;
+ if (cp) *cp = '\0'; /* delete :PORT */
+ setenv1("REMOTE_ADDR", p);
+ if (cp) {
+ *cp = ':';
+#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
+ setenv1("REMOTE_PORT", cp + 1);
+#endif
+ }
+ }
+ setenv1("HTTP_USER_AGENT", user_agent);
+ if (http_accept)
+ setenv1("HTTP_ACCEPT", http_accept);
+ if (http_accept_language)
+ setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language);
+ if (post_len)
+ putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
+ if (cookie)
+ setenv1("HTTP_COOKIE", cookie);
+ if (content_type)
+ setenv1("CONTENT_TYPE", content_type);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (remoteuser) {
+ setenv1("REMOTE_USER", remoteuser);
+ putenv((char*)"AUTH_TYPE=Basic");
+ }
+#endif
+ if (referer)
+ setenv1("HTTP_REFERER", referer);
+ setenv1("HTTP_HOST", host); /* set to "" if NULL */
+ /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
+ * just run "env SERVER_NAME=xyz httpd ..." instead */
+
+ xpiped_pair(fromCgi);
+ xpiped_pair(toCgi);
+
+ pid = vfork();
+ if (pid < 0) {
+ /* TODO: log perror? */
+ log_and_exit();
+ }
+
+ if (pid == 0) {
+ /* Child process */
+ char *argv[3];
+
+ xfunc_error_retval = 242;
+
+ /* NB: close _first_, then move fds! */
+ close(toCgi.wr);
+ close(fromCgi.rd);
+ xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */
+ xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */
+ /* User seeing stderr output can be a security problem.
+ * If CGI really wants that, it can always do dup itself. */
+ /* dup2(1, 2); */
+
+ /* Chdiring to script's dir */
+ script = last_slash;
+ if (script != url) { /* paranoia */
+ *script = '\0';
+ if (chdir(url + 1) != 0) {
+ bb_perror_msg("can't change directory to '%s'", url + 1);
+ goto error_execing_cgi;
+ }
+ // not needed: *script = '/';
+ }
+ script++;
+
+ /* set argv[0] to name without path */
+ argv[0] = script;
+ argv[1] = NULL;
+
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ {
+ char *suffix = strrchr(script, '.');
+
+ if (suffix) {
+ Htaccess *cur;
+ for (cur = script_i; cur; cur = cur->next) {
+ if (strcmp(cur->before_colon + 1, suffix) == 0) {
+ /* found interpreter name */
+ argv[0] = cur->after_colon;
+ argv[1] = script;
+ argv[2] = NULL;
+ break;
+ }
+ }
+ }
+ }
+#endif
+ /* restore default signal dispositions for CGI process */
+ bb_signals(0
+ | (1 << SIGCHLD)
+ | (1 << SIGPIPE)
+ | (1 << SIGHUP)
+ , SIG_DFL);
+
+ /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
+ * without any dir components and will only match a file
+ * in the current directory */
+ execv(argv[0], argv);
+ if (verbose)
+ bb_perror_msg("can't execute '%s'", argv[0]);
+ error_execing_cgi:
+ /* send to stdout
+ * (we are CGI here, our stdout is pumped to the net) */
+ send_headers_and_exit(HTTP_NOT_FOUND);
+ } /* end child */
+
+ /* Parent process */
+
+ /* Restore variables possibly changed by child */
+ xfunc_error_retval = 0;
+
+ /* Pump data */
+ close(fromCgi.wr);
+ close(toCgi.rd);
+ cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
+}
+