| /* Copyright 2001-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifdef WIN32 |
| |
| #include "apr_strings.h" |
| #include "apr_portable.h" |
| #include "apr_buckets.h" |
| #include "ap_config.h" |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_core.h" |
| #include "http_protocol.h" |
| #include "http_request.h" |
| #include "http_log.h" |
| #include "util_script.h" |
| #include "mod_core.h" |
| #include "mod_cgi.h" |
| #include "apr_lib.h" |
| #include "ap_regkey.h" |
| |
| extern OSVERSIONINFO osver; /* hiding in mpm_winnt.c */ |
| static int win_nt; |
| |
| /* |
| * 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); |
| |
| module AP_MODULE_DECLARE_DATA win32_module; |
| |
| typedef struct { |
| /* Where to find interpreter to run scripts */ |
| interpreter_source_e script_interpreter_source; |
| } win32_dir_conf; |
| |
| static void *create_win32_dir_config(apr_pool_t *p, char *dir) |
| { |
| win32_dir_conf *conf; |
| conf = (win32_dir_conf*)apr_palloc(p, sizeof(win32_dir_conf)); |
| conf->script_interpreter_source = INTERPRETER_SOURCE_UNSET; |
| return conf; |
| } |
| |
| static void *merge_win32_dir_configs(apr_pool_t *p, void *basev, void *addv) |
| { |
| win32_dir_conf *new; |
| win32_dir_conf *base = (win32_dir_conf *) basev; |
| win32_dir_conf *add = (win32_dir_conf *) addv; |
| |
| new = (win32_dir_conf *) apr_pcalloc(p, sizeof(win32_dir_conf)); |
| new->script_interpreter_source = (add->script_interpreter_source |
| != INTERPRETER_SOURCE_UNSET) |
| ? add->script_interpreter_source |
| : base->script_interpreter_source; |
| return new; |
| } |
| |
| static const char *set_interpreter_source(cmd_parms *cmd, void *dv, |
| char *arg) |
| { |
| win32_dir_conf *d = (win32_dir_conf *)dv; |
| 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; |
| } |
| |
| /* XXX: prep_string should translate the string into unicode, |
| * such that it is compatible with whatever codepage the client |
| * will read characters 80-ff. For the moment, use the unicode |
| * values 0080-00ff. This isn't trivial, since the code page |
| * varies between msdos and Windows applications. |
| * For subsystem 2 [GUI] the default is the system Ansi CP. |
| * For subsystem 3 [CLI] the default is the system OEM CP. |
| */ |
| static void prep_string(const char ** str, apr_pool_t *p) |
| { |
| const char *ch = *str; |
| char *ch2; |
| int widen = 0; |
| |
| if (!ch) { |
| return; |
| } |
| while (*ch) { |
| if (*(ch++) & 0x80) { |
| ++widen; |
| } |
| } |
| if (!widen) { |
| return; |
| } |
| widen += (ch - *str) + 1; |
| ch = *str; |
| *str = ch2 = apr_palloc(p, widen); |
| while (*ch) { |
| if (*ch & 0x80) { |
| /* sign extension won't hurt us here */ |
| *(ch2++) = 0xC0 | ((*ch >> 6) & 0x03); |
| *(ch2++) = 0x80 | (*(ch++) & 0x3f); |
| } |
| else { |
| *(ch2++) = *(ch++); |
| } |
| } |
| *(ch2++) = '\0'; |
| } |
| |
| /* Somewhat more exciting ... figure out where the registry has stashed the |
| * ExecCGI or Open command - it may be nested one level deep (or more???) |
| */ |
| static char* get_interpreter_from_win32_registry(apr_pool_t *p, |
| const char* ext, |
| int strict) |
| { |
| apr_status_t rv; |
| ap_regkey_t *name_key = NULL; |
| ap_regkey_t *type_key; |
| ap_regkey_t *key; |
| char execcgi_path[] = "SHELL\\EXECCGI\\COMMAND"; |
| char execopen_path[] = "SHELL\\OPEN\\COMMAND"; |
| char *type_name; |
| char *buffer; |
| |
| 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 */ |
| rv = ap_regkey_open(&type_key, AP_REGKEY_CLASSES_ROOT, ext, APR_READ, p); |
| |
| if (rv != APR_SUCCESS) { |
| return NULL; |
| } |
| |
| /* Retrieve the name of the script filetype extension */ |
| rv = ap_regkey_value_get(&type_name, type_key, "", p); |
| |
| if (rv == APR_SUCCESS && type_name[0]) { |
| /* Open the key associated with the script filetype extension */ |
| rv = ap_regkey_open(&name_key, AP_REGKEY_CLASSES_ROOT, type_name, |
| APR_READ, p); |
| } |
| |
| /* 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 (name_key) { |
| if ((rv = ap_regkey_open(&key, name_key, execcgi_path, APR_READ, p)) |
| == APR_SUCCESS) { |
| rv = ap_regkey_value_get(&buffer, key, "", p); |
| ap_regkey_close(name_key); |
| } |
| } |
| |
| if (!name_key || (rv != APR_SUCCESS)) { |
| if ((rv = ap_regkey_open(&key, type_key, execcgi_path, APR_READ, p)) |
| == APR_SUCCESS) { |
| rv = ap_regkey_value_get(&buffer, key, "", p); |
| ap_regkey_close(type_key); |
| } |
| } |
| |
| if (!strict && name_key && (rv != APR_SUCCESS)) { |
| if ((rv = ap_regkey_open(&key, name_key, execopen_path, APR_READ, p)) |
| == APR_SUCCESS) { |
| rv = ap_regkey_value_get(&buffer, key, "", p); |
| ap_regkey_close(name_key); |
| } |
| } |
| |
| if (!strict && (rv != APR_SUCCESS)) { |
| if ((rv = ap_regkey_open(&key, type_key, execopen_path, APR_READ, p)) |
| == APR_SUCCESS) { |
| rv = ap_regkey_value_get(&buffer, key, "", p); |
| ap_regkey_close(type_key); |
| } |
| } |
| |
| if (name_key) { |
| ap_regkey_close(name_key); |
| } |
| |
| ap_regkey_close(type_key); |
| |
| if (rv != APR_SUCCESS || !buffer[0]) { |
| return NULL; |
| } |
| |
| return buffer; |
| } |
| |
| |
| static apr_array_header_t *split_argv(apr_pool_t *p, const char *interp, |
| const char *cgiprg, const char *cgiargs) |
| { |
| apr_array_header_t *args = apr_array_make(p, 8, sizeof(char*)); |
| char *d = apr_palloc(p, strlen(interp)+1); |
| const char *ch = interp; |
| const char **arg; |
| int prgtaken = 0; |
| int argtaken = 0; |
| int inquo; |
| int sl; |
| |
| while (*ch) { |
| /* Skip on through Deep Space */ |
| if (apr_isspace(*ch)) { |
| ++ch; continue; |
| } |
| /* One Arg */ |
| if (((*ch == '$') || (*ch == '%')) && (*(ch + 1) == '*')) { |
| const char *cgiarg = cgiargs; |
| argtaken = 1; |
| for (;;) { |
| char *w = ap_getword_nulls(p, &cgiarg, '+'); |
| if (!*w) { |
| break; |
| } |
| ap_unescape_url(w); |
| if (win_nt) { |
| prep_string(&w, p); |
| } |
| arg = (const char**)apr_array_push(args); |
| *arg = ap_escape_shell_cmd(p, w); |
| } |
| ch += 2; |
| continue; |
| } |
| if (((*ch == '$') || (*ch == '%')) && (*(ch + 1) == '1')) { |
| /* Todo: Make short name!!! */ |
| prgtaken = 1; |
| arg = (const char**)apr_array_push(args); |
| if (*ch == '%') { |
| char *repl = apr_pstrdup(p, cgiprg); |
| *arg = repl; |
| while ((repl = strchr(repl, '/'))) { |
| *repl++ = '\\'; |
| } |
| } |
| else { |
| *arg = cgiprg; |
| } |
| ch += 2; |
| continue; |
| } |
| if ((*ch == '\"') && ((*(ch + 1) == '$') |
| || (*(ch + 1) == '%')) && (*(ch + 2) == '1') |
| && (*(ch + 3) == '\"')) { |
| prgtaken = 1; |
| arg = (const char**)apr_array_push(args); |
| if (*(ch + 1) == '%') { |
| char *repl = apr_pstrdup(p, cgiprg); |
| *arg = repl; |
| while ((repl = strchr(repl, '/'))) { |
| *repl++ = '\\'; |
| } |
| } |
| else { |
| *arg = cgiprg; |
| } |
| ch += 4; |
| continue; |
| } |
| arg = (const char**)apr_array_push(args); |
| *arg = d; |
| inquo = 0; |
| while (*ch) { |
| if (apr_isspace(*ch) && !inquo) { |
| ++ch; break; |
| } |
| /* Get 'em backslashes */ |
| for (sl = 0; *ch == '\\'; ++sl) { |
| *d++ = *ch++; |
| } |
| if (sl & 1) { |
| /* last unmatched '\' + '"' sequence is a '"' */ |
| if (*ch == '\"') { |
| *(d - 1) = *ch++; |
| } |
| continue; |
| } |
| if (*ch == '\"') { |
| /* '""' sequence within quotes is a '"' */ |
| if (*++ch == '\"' && inquo) { |
| *d++ = *ch++; continue; |
| } |
| /* Flip quote state */ |
| inquo = !inquo; |
| if (apr_isspace(*ch) && !inquo) { |
| ++ch; break; |
| } |
| /* All other '"'s are Munched */ |
| continue; |
| } |
| /* Anything else is, well, something else */ |
| *d++ = *ch++; |
| } |
| /* Term that arg, already pushed on args */ |
| *d++ = '\0'; |
| } |
| |
| if (!prgtaken) { |
| arg = (const char**)apr_array_push(args); |
| *arg = cgiprg; |
| } |
| |
| if (!argtaken) { |
| const char *cgiarg = cgiargs; |
| for (;;) { |
| char *w = ap_getword_nulls(p, &cgiarg, '+'); |
| if (!*w) { |
| break; |
| } |
| ap_unescape_url(w); |
| if (win_nt) { |
| prep_string(&w, p); |
| } |
| arg = (const char**)apr_array_push(args); |
| *arg = ap_escape_shell_cmd(p, w); |
| } |
| } |
| |
| arg = (const char**)apr_array_push(args); |
| *arg = NULL; |
| |
| return args; |
| } |
| |
| |
| static apr_status_t ap_cgi_build_command(const char **cmd, const char ***argv, |
| request_rec *r, apr_pool_t *p, |
| cgi_exec_info_t *e_info) |
| { |
| const apr_array_header_t *elts_arr = apr_table_elts(r->subprocess_env); |
| const apr_table_entry_t *elts = (apr_table_entry_t *) elts_arr->elts; |
| const char *ext = NULL; |
| const char *interpreter = NULL; |
| win32_dir_conf *d; |
| apr_file_t *fh; |
| const char *args = ""; |
| int i; |
| |
| d = (win32_dir_conf *)ap_get_module_config(r->per_dir_config, |
| &win32_module); |
| |
| if (e_info->cmd_type) { |
| /* We have to consider that the client gets any QUERY_ARGS |
| * without any charset interpretation, use prep_string to |
| * create a string of the literal QUERY_ARGS bytes. |
| */ |
| *cmd = r->filename; |
| if (r->args && r->args[0] && !ap_strchr_c(r->args, '=')) { |
| args = r->args; |
| } |
| } |
| /* Handle the complete file name, we DON'T want to follow suexec, since |
| * an unrooted command is as predictable as shooting craps in Win32. |
| * Notice that unlike most mime extension parsing, we have to use the |
| * win32 parsing here, therefore the final extension is the only one |
| * we will consider. |
| */ |
| ext = strrchr(apr_filepath_name_get(*cmd), '.'); |
| |
| /* 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 so. |
| * Let apr_proc_create do all of the .bat/.cmd dirty work. |
| */ |
| if (ext && (!strcasecmp(ext,".exe") || !strcasecmp(ext,".com") |
| || !strcasecmp(ext,".bat") || !strcasecmp(ext,".cmd"))) { |
| interpreter = ""; |
| } |
| if (!interpreter && ext |
| && (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, |
| strict); |
| if (interpreter && e_info->cmd_type != APR_SHELLCMD) { |
| e_info->cmd_type = APR_PROGRAM_PATH; |
| } |
| else { |
| ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, |
| strict ? "No ExecCGI verb found for files of type '%s'." |
| : "No ExecCGI or Open verb found for files of type '%s'.", |
| ext); |
| } |
| } |
| if (!interpreter) { |
| apr_status_t rv; |
| char buffer[1024]; |
| apr_size_t bytes = sizeof(buffer); |
| apr_size_t i; |
| |
| /* Need to peek into the file figure out what it really is... |
| * ### aught to go back and build a cache for this one of these days. |
| */ |
| if ((rv = apr_file_open(&fh, *cmd, APR_READ | APR_BUFFERED, |
| APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, |
| "Failed to open cgi file %s for testing", *cmd); |
| return rv; |
| } |
| if ((rv = apr_file_read(fh, buffer, &bytes)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, |
| "Failed to read cgi file %s for testing", *cmd); |
| return rv; |
| } |
| apr_file_close(fh); |
| |
| /* Some twisted character [no pun intended] at MS decided that a |
| * zero width joiner as the lead wide character would be ideal for |
| * describing Unicode text files. This was further convoluted to |
| * another MSism that the same character mapped into utf-8, EF BB BF |
| * would signify utf-8 text files. |
| * |
| * Since MS configuration files are all protecting utf-8 encoded |
| * Unicode path, file and resource names, we already have the correct |
| * WinNT encoding. But at least eat the stupid three bytes up front. |
| * |
| * ### A more thorough check would also allow UNICODE text in buf, and |
| * convert it to UTF-8 for invoking unicode scripts. Those are few |
| * and far between, so leave that code an enterprising soul with a need. |
| */ |
| if ((bytes >= 3) && memcmp(buffer, "\xEF\xBB\xBF", 3) == 0) { |
| memmove(buffer, buffer + 3, bytes -= 3); |
| } |
| |
| /* Script or executable, that is the question... */ |
| if ((bytes >= 2) && (buffer[0] == '#') && (buffer[1] == '!')) { |
| /* Assuming file is a script since it starts with a shebang */ |
| for (i = 2; i < bytes; i++) { |
| if ((buffer[i] == '\r') || (buffer[i] == '\n')) { |
| buffer[i] = '\0'; |
| break; |
| } |
| } |
| if (i < bytes) { |
| interpreter = buffer + 2; |
| while (apr_isspace(*interpreter)) { |
| ++interpreter; |
| } |
| if (e_info->cmd_type != APR_SHELLCMD) { |
| e_info->cmd_type = APR_PROGRAM_PATH; |
| } |
| } |
| } |
| else if (bytes >= sizeof(IMAGE_DOS_HEADER)) { |
| /* Not a script, is it an executable? */ |
| IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer; |
| if (hdr->e_magic == IMAGE_DOS_SIGNATURE) { |
| if (hdr->e_lfarlc < 0x40) { |
| /* Ought to invoke this 16 bit exe by a stub, (cmd /c?) */ |
| interpreter = ""; |
| } |
| else { |
| interpreter = ""; |
| } |
| } |
| } |
| } |
| if (!interpreter) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
| "%s is not executable; ensure interpreted scripts have " |
| "\"#!\" first line", *cmd); |
| return APR_EBADF; |
| } |
| |
| *argv = (const char **)(split_argv(p, interpreter, *cmd, |
| args)->elts); |
| *cmd = (*argv)[0]; |
| |
| e_info->detached = 1; |
| |
| /* XXX: Must fix r->subprocess_env to follow utf-8 conventions from |
| * the client's octets so that win32 apr_proc_create is happy. |
| * The -best- way is to determine if the .exe is unicode aware |
| * (using 0x0080-0x00ff) or is linked as a command or windows |
| * application (following the OEM or Ansi code page in effect.) |
| */ |
| for (i = 0; i < elts_arr->nelts; ++i) { |
| if (win_nt && elts[i].key && *elts[i].key |
| && (strncmp(elts[i].key, "HTTP_", 5) == 0 |
| || strncmp(elts[i].key, "SERVER_", 7) == 0 |
| || strncmp(elts[i].key, "REQUEST_", 8) == 0 |
| || strcmp(elts[i].key, "QUERY_STRING") == 0)) { |
| prep_string((const char**) &elts[i].val, r->pool); |
| } |
| } |
| return APR_SUCCESS; |
| } |
| |
| static int win32_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *ptemp) |
| { |
| win_nt = (osver.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS); |
| return OK; |
| } |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| APR_REGISTER_OPTIONAL_FN(ap_cgi_build_command); |
| ap_hook_pre_config(win32_pre_config, NULL, NULL, APR_HOOK_MIDDLE); |
| } |
| |
| static const command_rec win32_cmds[] = { |
| AP_INIT_TAKE1("ScriptInterpreterSource", set_interpreter_source, NULL, |
| OR_FILEINFO, |
| "Where to find interpreter to run Win32 scripts " |
| "(Registry or script shebang line)"), |
| { NULL } |
| }; |
| |
| module AP_MODULE_DECLARE_DATA win32_module = { |
| STANDARD20_MODULE_STUFF, |
| create_win32_dir_config, /* create per-dir config */ |
| merge_win32_dir_configs, /* merge per-dir config */ |
| NULL, /* server config */ |
| NULL, /* merge server config */ |
| win32_cmds, /* command apr_table_t */ |
| register_hooks /* register hooks */ |
| }; |
| |
| #endif /* defined WIN32 */ |