From 8d85a4a5be88931978fad594b94e762313d37afc Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Wed, 20 Dec 2023 13:45:32 +0000 Subject: httpd: enable support for CGI The upstream code uses fork/exec when running a CGI process. Emulate this by: - Spawning a child httpd process with the special '-I 0' option, along with the options provided on the server command line. This sets up the proper state then calls the cgi_handler() function. - The cgi_handler() function fixes the pipe file descriptors and starts another child process to run the CGI script. These processes are detached from the console on creation. When spawn() functions are run in P_DETACH mode they don't connect to the standard file descriptors. Normally this doesn't matter but the process which runs the CGI scripts needs to inherit the pipe endpoints. The create_detached_process() function handles this. See: https://github.com/rprichard/win32-console-docs/blob/master/README.md Adds about 2.9Kb to the size of the binary. (GitHub issue #266) --- configs/mingw32_defconfig | 4 +- configs/mingw64_defconfig | 4 +- configs/mingw64u_defconfig | 4 +- include/mingw.h | 2 + networking/httpd.c | 117 ++++++++++++++++++++++++++++++++++++++++++--- win32/process.c | 60 ++++++++++++++++++++++- 6 files changed, 176 insertions(+), 15 deletions(-) diff --git a/configs/mingw32_defconfig b/configs/mingw32_defconfig index ee01330d1..0f674d45c 100644 --- a/configs/mingw32_defconfig +++ b/configs/mingw32_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Busybox version: 1.37.0.git -# Sat Dec 9 09:38:58 2023 +# Wed Dec 20 13:23:38 2023 # CONFIG_HAVE_DOT_CONFIG=y # CONFIG_PLATFORM_POSIX is not set @@ -916,7 +916,7 @@ CONFIG_FEATURE_HTTPD_RANGES=y # CONFIG_FEATURE_HTTPD_SETUID is not set CONFIG_FEATURE_HTTPD_BASIC_AUTH=y # CONFIG_FEATURE_HTTPD_AUTH_MD5 is not set -# CONFIG_FEATURE_HTTPD_CGI is not set +CONFIG_FEATURE_HTTPD_CGI=y # CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR is not set # CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV is not set CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y diff --git a/configs/mingw64_defconfig b/configs/mingw64_defconfig index 3fed1c99b..37f95f4c8 100644 --- a/configs/mingw64_defconfig +++ b/configs/mingw64_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Busybox version: 1.37.0.git -# Sat Dec 9 09:38:58 2023 +# Wed Dec 20 13:23:38 2023 # CONFIG_HAVE_DOT_CONFIG=y # CONFIG_PLATFORM_POSIX is not set @@ -916,7 +916,7 @@ CONFIG_FEATURE_HTTPD_RANGES=y # CONFIG_FEATURE_HTTPD_SETUID is not set CONFIG_FEATURE_HTTPD_BASIC_AUTH=y # CONFIG_FEATURE_HTTPD_AUTH_MD5 is not set -# CONFIG_FEATURE_HTTPD_CGI is not set +CONFIG_FEATURE_HTTPD_CGI=y # CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR is not set # CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV is not set CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y diff --git a/configs/mingw64u_defconfig b/configs/mingw64u_defconfig index f3391a206..f26b17ec9 100644 --- a/configs/mingw64u_defconfig +++ b/configs/mingw64u_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Busybox version: 1.37.0.git -# Sat Dec 9 09:38:58 2023 +# Wed Dec 20 13:23:38 2023 # CONFIG_HAVE_DOT_CONFIG=y # CONFIG_PLATFORM_POSIX is not set @@ -916,7 +916,7 @@ CONFIG_FEATURE_HTTPD_RANGES=y # CONFIG_FEATURE_HTTPD_SETUID is not set CONFIG_FEATURE_HTTPD_BASIC_AUTH=y # CONFIG_FEATURE_HTTPD_AUTH_MD5 is not set -# CONFIG_FEATURE_HTTPD_CGI is not set +CONFIG_FEATURE_HTTPD_CGI=y # CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR is not set # CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV is not set CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y diff --git a/include/mingw.h b/include/mingw.h index 46cbe03f8..88da85243 100644 --- a/include/mingw.h +++ b/include/mingw.h @@ -552,12 +552,14 @@ pid_t FAST_FUNC mingw_spawn(char **argv); intptr_t FAST_FUNC mingw_spawn_detach(char **argv); intptr_t FAST_FUNC mingw_spawn_proc(const char **argv); int mingw_execv(const char *cmd, char *const *argv); +int httpd_execv_detach(const char *cmd, char *const *argv); int mingw_execvp(const char *cmd, char *const *argv); int mingw_execve(const char *cmd, char *const *argv, char *const *envp); #define spawn mingw_spawn #define execvp mingw_execvp #define execve mingw_execve #define execv mingw_execv +#define HTTPD_DETACH (8) #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') diff --git a/networking/httpd.c b/networking/httpd.c index 0de56a47a..d049e3842 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -461,6 +461,13 @@ static const struct { struct globals { int verbose; /* must be int (used by getopt32) */ +#if ENABLE_PLATFORM_MINGW32 + smallint foreground; +# if ENABLE_FEATURE_HTTPD_CGI + int server_argc; + char **server_argv; +# endif +#endif smallint flg_deny_all; #if ENABLE_FEATURE_HTTPD_GZIP /* client can handle gzip / we are going to send gzip */ @@ -518,6 +525,11 @@ struct globals { }; #define G (*ptr_to_globals) #define verbose (G.verbose ) +#if ENABLE_PLATFORM_MINGW32 +#define foreground (G.foreground ) +#define server_argc (G.server_argc ) +#define server_argv (G.server_argv ) +#endif #define flg_deny_all (G.flg_deny_all ) #if ENABLE_FEATURE_HTTPD_GZIP # define content_gzip (G.content_gzip ) @@ -1563,6 +1575,52 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post #endif #if ENABLE_FEATURE_HTTPD_CGI +# if ENABLE_PLATFORM_MINGW32 +static void cgi_handler(char **argv) +{ + struct fd_pair fromCgi; /* CGI -> httpd pipe */ + struct fd_pair toCgi; /* httpd -> CGI pipe */ + char *dir, *script; + + xfunc_error_retval = 242; + + if (sscanf(argv[0], "%d:%d:%d:%d", &toCgi.wr, &toCgi.rd, + &fromCgi.wr, &fromCgi.rd) != 4) { + exit(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 */ + + dir = argv[1]; + script = argv[2]; + + if (chdir_or_warn(dir) != 0) { + goto error_execing_cgi; + } + + /* set argv[0] to name without path */ + argv[0] = script; + argv[1] = NULL; + + /* _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 */ + if (foreground) + execv(argv[0], argv); + else + httpd_execv_detach(argv[0], argv); + if (verbose) + bb_perror_msg("can't execute '%s'", argv[0]); + error_execing_cgi: + /* send to stdout */ + iobuf = xmalloc(IOBUF_SIZE); + send_headers_and_exit(HTTP_NOT_FOUND); +} +# endif static void setenv1(const char *name, const char *value) { @@ -1596,6 +1654,10 @@ static void send_cgi_and_exit( struct fd_pair toCgi; /* httpd -> CGI pipe */ char *script, *last_slash; int pid; +#if ENABLE_PLATFORM_MINGW32 + char **argv; + int i; +#endif /* Make a copy. NB: caller guarantees: * url[0] == '/', url[1] != '/' */ @@ -1682,6 +1744,32 @@ static void send_cgi_and_exit( xpiped_pair(fromCgi); xpiped_pair(toCgi); +#if ENABLE_PLATFORM_MINGW32 + /* Find script's dir */ + script = last_slash; + if (script != url) { /* paranoia */ + *script = '\0'; + } + script++; + + argv = xzalloc((server_argc + 9) * sizeof(char *)); + argv[0] = (char *)bb_busybox_exec_path; + argv[1] = (char *)"--busybox"; + argv[2] = (char *)"-httpd" + 1; // skip '-' + argv[3] = (char *)"-I"; + argv[4] = (char *)"0"; + for (i = 0; i < server_argc; ++i) + argv[i + 5] = server_argv[i]; + argv[server_argc + 5] = xasprintf("%d:%d:%d:%d", toCgi.wr, toCgi.rd, + fromCgi.wr, fromCgi.rd); + argv[server_argc + 6] = (char *)url + 1; // script directory + argv[server_argc + 7] = (char *)script; // script name + /* argv[server_argc + 8] = NULL; - xzalloc did it */ + + pid = foreground ? mingw_spawn(argv) : mingw_spawn_detach(argv); + if (pid == -1) + log_and_exit(); +#else pid = vfork(); if (pid < 0) { /* TODO: log perror? */ @@ -1759,6 +1847,7 @@ static void send_cgi_and_exit( /* Restore variables possibly changed by child */ xfunc_error_retval = 0; +#endif /* Pump data */ close(fromCgi.wr); @@ -2797,8 +2886,8 @@ static void mini_httpd_nommu(int server_socket, int argc, char **argv) } #endif #else /* ENABLE_PLATFORM_MINGW32 */ -static void mini_httpd_win32(int fg, int sock, int argc, char **argv) NORETURN; -static void mini_httpd_win32(int fg, int sock, int argc, char **argv) +static void mini_httpd_win32(int sock, int argc, char **argv) NORETURN; +static void mini_httpd_win32(int sock, int argc, char **argv) { char *argv_copy[argc + 5]; @@ -2820,7 +2909,8 @@ static void mini_httpd_win32(int fg, int sock, int argc, char **argv) setsockopt_keepalive(n); argv_copy[4] = itoa(n); - if ((fg ? spawn(argv_copy) : mingw_spawn_detach(argv_copy)) == -1) + if ((foreground ? + spawn(argv_copy) : mingw_spawn_detach(argv_copy)) == -1) bb_perror_msg_and_die("can't execute 'httpd'"); /* parent, or spawn failed */ @@ -2936,7 +3026,7 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) "\0" /* -v counts, -i implies -f */ IF_NOT_PLATFORM_MINGW32("vv:if",) - IF_PLATFORM_MINGW32("vv:If",) + IF_PLATFORM_MINGW32("vv:",) &opt_c_configFile, &url_for_decode, &home_httpd IF_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) IF_FEATURE_HTTPD_BASIC_AUTH(, &g_realm) @@ -2981,7 +3071,8 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) } #endif #else /* ENABLE_PLATFORM_MINGW32 */ - if (!(opt & OPT_FOREGROUND) && argv[0][0] != '-') + foreground = (opt & OPT_FOREGROUND) == OPT_FOREGROUND; + if (!foreground && argv[0][0] != '-') mingw_daemonize(argv); #endif @@ -3032,7 +3123,14 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) #endif parse_conf(DEFAULT_PATH_HTTPD_CONF, FIRST_PARSE); -#if !ENABLE_PLATFORM_MINGW32 +#if ENABLE_PLATFORM_MINGW32 +# if ENABLE_FEATURE_HTTPD_CGI + if ((opt & OPT_INETD) && fd == 0) { + cgi_handler(argv + optind); + return 0; + } +# endif +#else if (!(opt & OPT_INETD)) signal(SIGHUP, sighup_handler); #endif @@ -3044,6 +3142,11 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) xdup2(0, 1); while (--fd > 2) close(fd); +# if ENABLE_FEATURE_HTTPD_CGI + /* Skip 'httpd -I N' and omit any non-option arguments */ + server_argc = optind - 3; + server_argv = argv + 3; +# endif } #endif if (opt & OPT_INETD) @@ -3057,7 +3160,7 @@ int httpd_main(int argc UNUSED_PARAM, char **argv) mini_httpd_nommu(server_socket, argc, argv); /* never returns */ #endif #else /* ENABLE_PLATFORM_MINGW32 */ - mini_httpd_win32(opt & OPT_FOREGROUND, server_socket, argc, argv); + mini_httpd_win32(server_socket, argc, argv); #endif /* return 0; */ } diff --git a/win32/process.c b/win32/process.c index b51f25d5c..35480e08d 100644 --- a/win32/process.c +++ b/win32/process.c @@ -248,6 +248,51 @@ grow_argv(char **argv, int n) return new_argv; } +#if ENABLE_FEATURE_HTTPD_CGI +static int +create_detached_process(const char *prog, char *const *argv) +{ + int argc, i; + char *command = NULL; + STARTUPINFO siStartInfo; + PROCESS_INFORMATION piProcInfo; + int success; + + argc = string_array_len((char **)argv); + for (i = 0; i < argc; i++) + command = xappendword(command, quote_arg(argv[i])); + + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdInput = (HANDLE)_get_osfhandle(STDIN_FILENO); + siStartInfo.hStdOutput = (HANDLE)_get_osfhandle(STDOUT_FILENO); + siStartInfo.dwFlags = STARTF_USESTDHANDLES; + + success = CreateProcess((LPCSTR)prog, + (LPSTR)command, /* command line */ + NULL, /* process security attributes */ + NULL, /* primary thread security attributes */ + TRUE, /* handles are inherited */ + DETACHED_PROCESS, /* creation flags */ + NULL, /* use parent's environment */ + NULL, /* use parent's current directory */ + &siStartInfo, /* STARTUPINFO pointer */ + &piProcInfo); /* receives PROCESS_INFORMATION */ + + free(command); + + if (!success) + return -1; + exit(0); +} + +# define SPAWNVEQ(m, p, a, e) \ + ((m != HTTPD_DETACH) ? spawnveq(m, p, a, e) : \ + create_detached_process(p, a)) +#else +# define SPAWNVEQ(m, p, a, e) spawnveq(m, p, a, e) +#endif + static intptr_t mingw_spawn_interpreter(int mode, const char *prog, char *const *argv, char *const *envp, int level) @@ -259,7 +304,7 @@ mingw_spawn_interpreter(int mode, const char *prog, char *const *argv, char *path = NULL; if (!parse_interpreter(prog, &interp)) - return spawnveq(mode, prog, argv, envp); + return SPAWNVEQ(mode, prog, argv, envp); if (++level > 4) { errno = ELOOP; @@ -275,7 +320,7 @@ mingw_spawn_interpreter(int mode, const char *prog, char *const *argv, if (unix_path(interp.path) && find_applet_by_name(interp.name) >= 0) { /* the fake path indicates the index of the script */ new_argv[0] = path = xasprintf("%d:/%s", nopts+1, interp.name); - ret = mingw_spawn_applet(mode, new_argv, envp); + ret = SPAWNVEQ(mode, bb_busybox_exec_path, new_argv, envp); goto done; } #endif @@ -428,6 +473,17 @@ mingw_execv(const char *cmd, char *const *argv) return mingw_execve(cmd, argv, NULL); } +#if ENABLE_FEATURE_HTTPD_CGI +int httpd_execv_detach(const char *script, char *const *argv) +{ + intptr_t ret = mingw_spawn_interpreter(HTTPD_DETACH, script, + (char *const *)argv, NULL, 0); + if (ret != -1) + exit(0); + return ret; +} +#endif + static inline long long filetime_to_ticks(const FILETIME *ft) { return (((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime)/ -- cgit v1.2.3-55-g6feb