From: William A. Rowe Jr Date: Mon, 17 Sep 2001 21:07:36 +0000 (+0000) Subject: Remove the Win32 script-processing exception from mod_cgi, and X-Git-Tag: 2.0.26~237 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f4959370858d3d1919740fac27ebc48eca8a6ad1;p=apache Remove the Win32 script-processing exception from mod_cgi, and roll build_command_line/build_argv_list into a unified, overrideable ap_cgi_build_command optional function. Eliminates a ton of Win32 cruft from core.c for registry parsing. Win32 (through the default handler, and newest changes to the apr_proc_create fn) continues to serve .bat/.exe files. This is in preparation for adding modules/arch/win32/mod_win32 for scripts. Please review the mod_cgi.c behavior very carefully. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@91058 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 7180816b91..6606f9933d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ Changes with Apache 2.0.26-dev + *) Remove the Win32 script-processing exception from mod_cgi, and + roll build_command_line/build_argv_list into a unified, overrideable + ap_cgi_build_command optional function. [William Rowe] + *) Rewrite find_start_sequence to use a better search algorithm to find the start tag. [Justin Erenkrantz] diff --git a/include/http_core.h b/include/http_core.h index 75c3efabd6..d9bedaec6f 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -319,20 +319,6 @@ AP_DECLARE(int) ap_satisfies(request_rec *r); */ AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r); -#if defined(WIN32) -/* - * CGI Script stuff for Win32... - */ -typedef enum { eFileTypeUNKNOWN, eFileTypeBIN, eFileTypeEXE16, eFileTypeEXE32, - eFileTypeSCRIPT } file_type_e; -typedef enum { INTERPRETER_SOURCE_UNSET, INTERPRETER_SOURCE_REGISTRY_STRICT, - INTERPRETER_SOURCE_REGISTRY, INTERPRETER_SOURCE_SHEBANG - } interpreter_source_e; -AP_DECLARE(file_type_e) ap_get_win32_interpreter(const request_rec *, - char **interpreter, - char **arguments); -#endif - #ifdef CORE_PRIVATE /* @@ -458,11 +444,6 @@ typedef struct { apr_array_header_t *sec_file; regex_t *r; -#ifdef WIN32 - /* Where to find interpreter to run scripts */ - interpreter_source_e script_interpreter_source; -#endif - const char *mime_type; /* forced with ForceType */ const char *handler; /* forced with SetHandler */ const char *output_filters; /* forced with SetOutputFilters */ diff --git a/modules/generators/mod_cgi.c b/modules/generators/mod_cgi.c index a25778dd77..346f4dabb4 100644 --- a/modules/generators/mod_cgi.c +++ b/modules/generators/mod_cgi.c @@ -74,6 +74,7 @@ #include "apr_thread_proc.h" /* for RLIMIT stuff */ #include "apr_optional.h" #include "apr_buckets.h" +#include "apr_lib.h" #define APR_WANT_STRFUNC #include "apr_want.h" @@ -97,9 +98,26 @@ module AP_MODULE_DECLARE_DATA cgi_module; +/* There has to be a better place to put this - uhm... where exactly? */ +/** + * Reprocess the command and arguments to execute the given CGI script. + * @param cmd Pointer to the command to execute (may be overridden) + * @param argv Pointer to the arguments to pass (may be overridden) + * @param r The current request + * @param p The pool to allocate correct cmd/argv elements within. + * @deffunc apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv, request_rec *r, apr_pool_t *p) + * @tip This callback may be registered by the os-specific module + * to correct the command and arguments for apr_proc_create invocation + * on a given os. mod_cgi will call the function if registered. + */ +APR_DECLARE_OPTIONAL_FN(apr_status_t, ap_cgi_build_command, + (const char **cmd, const char ***argv, + request_rec *r, apr_pool_t *p)); + static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *cgi_pfn_reg_with_ssi; static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value) *cgi_pfn_gtv; static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string) *cgi_pfn_ps; +static APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command; typedef enum {RUN_AS_SSI, RUN_AS_CGI} prog_types; @@ -479,12 +497,21 @@ static apr_status_t run_cgi_child(apr_file_t **script_out, return (rc); } -static apr_status_t build_argv_list(const char ***argv, request_rec *r, - apr_pool_t *p) + +static apr_status_t default_build_command(const char **cmd, const char ***argv, + request_rec *r, apr_pool_t *p) { int numwords, x, idx; char *w; const char *args = r->args; + const char *argv0; + + /* Allow suexec's "/" check to succeed */ + if ((argv0 = strrchr(r->filename, '/')) != NULL) + argv0++; + else + argv0 = r->filename; + *cmd = argv0; if (!args || !args[0] || ap_strchr_c(args, '=')) { numwords = 1; @@ -497,14 +524,14 @@ static apr_status_t build_argv_list(const char ***argv, request_rec *r, } } } - /* Everything is - 1 to account for the first parameter which is the - * program name. We didn't used to have to do this, but APR wants it. + /* Everything is - 1 to account for the first parameter + * which is the program name. */ if (numwords > APACHE_ARG_MAX - 1) { numwords = APACHE_ARG_MAX - 1; /* Truncate args to prevent overrun */ } *argv = apr_palloc(p, (numwords + 2) * sizeof(char *)); - + (*argv)[0] = argv0; for (x = 1, idx = 1; x < numwords; x++) { w = ap_getword_nulls(p, &args, '+'); ap_unescape_url(w); @@ -515,57 +542,6 @@ static apr_status_t build_argv_list(const char ***argv, request_rec *r, return APR_SUCCESS; } -static apr_status_t build_command_line(const char **cmd, request_rec *r, - apr_pool_t *p) -{ -#ifdef WIN32 - char *quoted_filename = NULL; - char *interpreter = NULL; - char *arguments = NULL; - file_type_e fileType; -#endif - const char *argv0; - - /* Allow suexec's "/" check to succeed */ - if ((argv0 = strrchr(r->filename, '/')) != NULL) - argv0++; - else - argv0 = r->filename; - -#ifdef WIN32 - *cmd = NULL; - fileType = ap_get_win32_interpreter(r, &interpreter, &arguments); - - if (fileType == eFileTypeUNKNOWN) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, - "%s is not executable; ensure interpreted scripts have " - "\"#!\" first line", - r->filename); - return APR_EBADF; - } - - /* - * Build the command string to pass to ap_os_create_privileged_process() - */ - quoted_filename = apr_pstrcat(p, "\"", r->filename, "\"", NULL); - if (interpreter && *interpreter) { - if (arguments && *arguments) - *cmd = apr_pstrcat(p, interpreter, " ", quoted_filename, " ", - arguments, NULL); - else - *cmd = apr_pstrcat(p, interpreter, " ", quoted_filename, " ", NULL); - } - else if (arguments && *arguments) { - *cmd = apr_pstrcat(p, quoted_filename, " ", arguments, NULL); - } - else { - *cmd = apr_pstrcat(p, quoted_filename, NULL); - } -#else - *cmd = argv0; -#endif - return APR_SUCCESS; -} static int cgi_handler(request_rec *r) { @@ -596,11 +572,7 @@ static int cgi_handler(request_rec *r) return DECLINED; } - if ((argv0 = strrchr(r->filename, '/')) != NULL) - argv0++; - else - argv0 = r->filename; - + argv0 = apr_filename_of_pathname(r->filename); nph = !(strncmp(argv0, "nph-", 4)); conf = ap_get_module_config(r->server->module_config, &cgi_module); @@ -632,18 +604,12 @@ static int cgi_handler(request_rec *r) ap_add_common_vars(r); /* build the command line */ - if ((rv = build_command_line(&command, r, p)) != APR_SUCCESS) { + if ((rv = cgi_build_command(&command, &argv, r, p)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - "couldn't spawn child process: %s", r->filename); - return HTTP_INTERNAL_SERVER_ERROR; - } - /* build the argument list */ - else if ((rv = build_argv_list(&argv, r, p)) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - "couldn't spawn child process: %s", r->filename); + "don't know how to spawn child process: %s", + r->filename); return HTTP_INTERNAL_SERVER_ERROR; } - argv[0] = apr_pstrdup(p, command); e_info.cmd_type = APR_PROGRAM; e_info.in_pipe = APR_CHILD_BLOCK; @@ -857,13 +823,14 @@ static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb, char *comman apr_file_t *script_out = NULL, *script_in = NULL, *script_err = NULL; apr_bucket_brigade *bcgi; apr_bucket *b; + apr_status_t rv; - if (build_argv_list(&argv, r, r->pool) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, - "couldn't spawn cmd child process: %s", r->filename); + if ((rv = cgi_build_command(&command, &argv, r, r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, + "don't know how to spawn cmd child process: %s", + r->filename); return HTTP_INTERNAL_SERVER_ERROR; } - argv[0] = apr_pstrdup(r->pool, command); e_info.cmd_type = APR_SHELLCMD; e_info.in_pipe = APR_NO_PIPE; @@ -875,9 +842,9 @@ static int include_cmd(include_ctx_t *ctx, apr_bucket_brigade **bb, char *comman e_info.next = f->next; /* run the script in its own process */ - if (run_cgi_child(&script_out, &script_in, &script_err, - command, argv, r, r->pool, &e_info) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + if ((rv = run_cgi_child(&script_out, &script_in, &script_err, + command, argv, r, r->pool, &e_info)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "couldn't spawn child process: %s", r->filename); return HTTP_INTERNAL_SERVER_ERROR; } @@ -978,6 +945,14 @@ static void cgi_post_config(apr_pool_t *p, apr_pool_t *plog, */ cgi_pfn_reg_with_ssi("exec", handle_exec); } + + /* This is the means by which unusual (non-unix) os's may find alternate + * means to run a given command (e.g. shebang/registry parsing on Win32) + */ + cgi_build_command = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command); + if (!cgi_build_command) { + cgi_build_command = default_build_command; + } } static void register_hooks(apr_pool_t *p) diff --git a/server/core.c b/server/core.c index 6639889645..241ad239e5 100644 --- a/server/core.c +++ b/server/core.c @@ -151,9 +151,6 @@ static void *create_core_dir_config(apr_pool_t *a, char *dir) conf->limit_req_body = 0; conf->limit_xml_body = AP_LIMIT_UNSET; conf->sec_file = apr_array_make(a, 2, sizeof(ap_conf_vector_t *)); -#ifdef WIN32 - conf->script_interpreter_source = INTERPRETER_SOURCE_UNSET; -#endif conf->server_signature = srv_sig_unset; @@ -289,12 +286,6 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) conf->satisfy = new->satisfy; } -#ifdef WIN32 - if (new->script_interpreter_source != INTERPRETER_SOURCE_UNSET) { - conf->script_interpreter_source = new->script_interpreter_source; - } -#endif - if (new->server_signature != srv_sig_unset) { conf->server_signature = new->server_signature; } @@ -782,290 +773,6 @@ AP_DECLARE(unsigned long) ap_get_limit_req_body(const request_rec *r) return d->limit_req_body; } -#ifdef WIN32 -static apr_status_t get_win32_registry_default_value(apr_pool_t *p, HKEY hkey, - char* relativepath, - char **value) -{ - HKEY hkeyOpen; - DWORD type; - DWORD size = 0; - DWORD result = RegOpenKeyEx(hkey, relativepath, 0, - KEY_QUERY_VALUE, &hkeyOpen); - - if (result != ERROR_SUCCESS) - return APR_FROM_OS_ERROR(result); - - /* Read to NULL buffer to determine value size */ - result = RegQueryValueEx(hkeyOpen, "", 0, &type, NULL, &size); - - if (result == ERROR_SUCCESS) { - if ((size < 2) || (type != REG_SZ && type != REG_EXPAND_SZ)) { - result = ERROR_INVALID_PARAMETER; - } - else { - *value = apr_palloc(p, size); - /* Read value based on size query above */ - result = RegQueryValueEx(hkeyOpen, "", 0, &type, *value, &size); - } - } - - /* TODO: This might look fine, but we need to provide some warning - * somewhere that some environment variables may -not- be translated, - * seeing as we may have chopped the environment table down somewhat. - */ - if ((result == ERROR_SUCCESS) && (type == REG_EXPAND_SZ)) - { - char *tmp = *value; - size = ExpandEnvironmentStrings(tmp, *value, 0); - if (size) { - *value = apr_palloc(p, size); - size = ExpandEnvironmentStrings(tmp, *value, size); - } - } - - RegCloseKey(hkeyOpen); - return APR_FROM_OS_ERROR(result); -} - -static char* get_interpreter_from_win32_registry(apr_pool_t *p, const char* ext, - char** arguments, int strict) -{ - char execcgi_path[] = "SHELL\\EXECCGI\\COMMAND"; - char execopen_path[] = "SHELL\\OPEN\\COMMAND"; - char typeName[MAX_PATH]; - int cmdOfName = FALSE; - HKEY hkeyName; - HKEY hkeyType; - DWORD type; - int size; - int result; - char *buffer; - char *s; - - if (!ext) - return NULL; - /* - * Future optimization: - * When the registry is successfully searched, store the strings for - * interpreter and arguments in an ext hash to speed up subsequent look-ups - */ - - /* Open the key associated with the script filetype extension */ - result = RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_QUERY_VALUE, - &hkeyType); - - if (result != ERROR_SUCCESS) - return NULL; - - /* Retrieve the name of the script filetype extension */ - size = sizeof(typeName); - result = RegQueryValueEx(hkeyType, "", NULL, &type, typeName, &size); - - if (result == ERROR_SUCCESS && type == REG_SZ && typeName[0]) { - /* Open the key associated with the script filetype extension */ - result = RegOpenKeyEx(HKEY_CLASSES_ROOT, typeName, 0, - KEY_QUERY_VALUE, &hkeyName); - - if (result == ERROR_SUCCESS) - cmdOfName = TRUE; - } - - /* Open the key for the script command path by: - * - * 1) the 'named' filetype key for ExecCGI/Command - * 2) the extension's type key for ExecCGI/Command - * - * and if the strict arg is false, then continue trying: - * - * 3) the 'named' filetype key for Open/Command - * 4) the extension's type key for Open/Command - */ - - if (cmdOfName) { - result = get_win32_registry_default_value(p, hkeyName, - execcgi_path, &buffer); - } - - if (!cmdOfName || (result != ERROR_SUCCESS)) { - result = get_win32_registry_default_value(p, hkeyType, - execcgi_path, &buffer); - } - - if (!strict && cmdOfName && (result != ERROR_SUCCESS)) { - result = get_win32_registry_default_value(p, hkeyName, - execopen_path, &buffer); - } - - if (!strict && (result != ERROR_SUCCESS)) { - result = get_win32_registry_default_value(p, hkeyType, - execopen_path, &buffer); - } - - if (cmdOfName) - RegCloseKey(hkeyName); - - RegCloseKey(hkeyType); - - if (result != ERROR_SUCCESS) - return NULL; - - /* - * The canonical way shell command entries are entered in the Win32 - * registry is as follows: - * shell [options] "%1" [args] - * where - * shell - full path name to interpreter or shell to run. - * E.g., c:\usr\local\ntreskit\perl\bin\perl.exe - * options - optional switches - * E.g., \C - * "%1" - Place holder for file to run the shell against. - * Typically quoted. - * options - additional arguments - * E.g., /silent - * - * If we find a %1 or a quoted %1, lop off the remainder to arguments. - */ - if (buffer && *buffer) { - if ((s = strstr(buffer, "\"%1"))) - { - *s = '\0'; - *arguments = s + 4; - } - else if ((s = strstr(buffer, "%1"))) - { - *s = '\0'; - *arguments = buffer + 2; - } - else - *arguments = strchr(buffer, '\0'); - while (**arguments && isspace(**arguments)) - ++*arguments; - } - - return buffer; -} - -AP_DECLARE (file_type_e) ap_get_win32_interpreter(const request_rec *r, - char** interpreter, - char** arguments) -{ - HANDLE hFile; - DWORD nBytesRead; - BOOLEAN bResult; - char buffer[1024]; - core_dir_config *d; - int i; - file_type_e fileType = eFileTypeUNKNOWN; - char *ext = NULL; - char *exename = NULL; - - d = (core_dir_config *)ap_get_module_config(r->per_dir_config, - &core_module); - - /* Find the file extension */ - exename = strrchr(r->filename, '/'); - if (!exename) { - exename = strrchr(r->filename, '\\'); - } - if (!exename) { - exename = r->filename; - } - else { - exename++; - } - ext = strrchr(exename, '.'); - - if (ext && (!strcasecmp(ext,".bat") || !strcasecmp(ext,".cmd"))) - { - char *comspec = getenv("COMSPEC"); - if (comspec) { - *interpreter = apr_pstrcat(r->pool, "\"", comspec, "\" /c ", NULL); - return eFileTypeSCRIPT; - } - ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r->server, - "Failed to start a '%s' file as a script." APR_EOL_STR - "\tCOMSPEC variable is missing from the environment.", ext); - return eFileTypeUNKNOWN; - } - - /* If the file has an extension and it is not .com and not .exe and - * we've been instructed to search the registry, then do it! - */ - if (ext && strcasecmp(ext,".exe") && strcasecmp(ext,".com") && - (d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY || - d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY_STRICT)) { - /* Check the registry */ - int strict = (d->script_interpreter_source - == INTERPRETER_SOURCE_REGISTRY_STRICT); - *interpreter = get_interpreter_from_win32_registry(r->pool, ext, - arguments, strict); - if (*interpreter) - return eFileTypeSCRIPT; - else if (d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY_STRICT) { - ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r->server, - "ScriptInterpreterSource config directive set to \"registry-strict\"." APR_EOL_STR - "\tInterpreter not found for files of type '%s'.", ext); - return eFileTypeUNKNOWN; - } - else - { - ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r->server, - "ScriptInterpreterSource config directive set to \"registry\"." APR_EOL_STR - "\tInterpreter not found for files of type '%s', " - "trying \"script\" method...", ext); - } - } - - /* Need to peek into the file figure out what it really is... */ - /* This is wrong for Unicode FS ... should move to APR */ - hFile = CreateFile(r->filename, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) { - return eFileTypeUNKNOWN; - } - bResult = ReadFile(hFile, (void*) &buffer, sizeof(buffer) - 1, - &nBytesRead, NULL); - if (!bResult || (nBytesRead == 0)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, GetLastError(), r, - "ReadFile(%s) failed", r->filename); - CloseHandle(hFile); - return eFileTypeUNKNOWN; - } - CloseHandle(hFile); - buffer[nBytesRead] = '\0'; - - /* Script or executable, that is the question... */ - if ((buffer[0] == '#') && (buffer[1] == '!')) { - /* Assuming file is a script since it starts with a shebang */ - fileType = eFileTypeSCRIPT; - for (i = 2; i < sizeof(buffer); i++) { - if ((buffer[i] == '\r') - || (buffer[i] == '\n')) { - break; - } - } - buffer[i] = '\0'; - for (i = 2; buffer[i] == ' ' ; ++i) - ; - *interpreter = apr_pstrdup(r->pool, buffer + i ); - } - else { - /* Not a script, is it an executable? */ - IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer; - if ((nBytesRead >= sizeof(IMAGE_DOS_HEADER)) && (hdr->e_magic == IMAGE_DOS_SIGNATURE)) { - if (hdr->e_lfarlc < 0x40) - fileType = eFileTypeEXE16; - else - fileType = eFileTypeEXE32; - } - else - fileType = eFileTypeUNKNOWN; - } - - return fileType; -} -#endif /***************************************************************** * @@ -1484,7 +1191,9 @@ AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, void *dumm return errmsg; } -/* We use this in and , to ensure that +/* XXX: Bogus - need to do this differently (at least OS2/Netware suffer + * the same problem!!! + * We use this in and , to ensure that * people don't get bitten by wrong-cased regex matches */ @@ -2377,25 +2086,6 @@ AP_DECLARE(size_t) ap_get_limit_xml_body(const request_rec *r) return (size_t)conf->limit_xml_body; } -#ifdef WIN32 -static const char *set_interpreter_source(cmd_parms *cmd, core_dir_config *d, - char *arg) -{ - if (!strcasecmp(arg, "registry")) { - d->script_interpreter_source = INTERPRETER_SOURCE_REGISTRY; - } else if (!strcasecmp(arg, "registry-strict")) { - d->script_interpreter_source = INTERPRETER_SOURCE_REGISTRY_STRICT; - } else if (!strcasecmp(arg, "script")) { - d->script_interpreter_source = INTERPRETER_SOURCE_SHEBANG; - } else { - return apr_pstrcat(cmd->temp_pool, "ScriptInterpreterSource \"", arg, - "\" must be \"registry\", \"registry-strict\" or " - "\"script\"", NULL); - } - return NULL; -} -#endif - #if !defined (RLIMIT_CPU) || !(defined (RLIMIT_DATA) || defined (RLIMIT_VMEM) || defined(RLIMIT_AS)) || !defined (RLIMIT_NPROC) static const char *no_set_limit(cmd_parms *cmd, void *conf_, const char *arg, const char *arg2) @@ -2765,11 +2455,6 @@ AP_INIT_TAKE1("NameVirtualHost", ap_set_name_virtual_host, NULL, RSRC_CONF, AP_INIT_TAKE1("BS2000Account", set_bs2000_account, NULL, RSRC_CONF, "Name of server User's bs2000 logon account name"), #endif -#ifdef WIN32 -AP_INIT_TAKE1("ScriptInterpreterSource", set_interpreter_source, NULL, - OR_FILEINFO, - "Where to find interpreter to run Win32 scripts (Registry or script shebang line)"), -#endif AP_INIT_TAKE1("ServerTokens", set_serv_tokens, NULL, RSRC_CONF, "Determine tokens displayed in the Server: header - Min(imal), OS or Full"), AP_INIT_TAKE1("LimitRequestLine", set_limit_req_line, NULL, RSRC_CONF,