Return-Path: Delivered-To: new-httpd-archive@hyperreal.org Received: (qmail 25898 invoked by uid 6000); 1 Feb 1999 15:54:01 -0000 Received: (qmail 25889 invoked from network); 1 Feb 1999 15:53:59 -0000 Received: from fwns1d.raleigh.ibm.com (HELO fwns1.raleigh.ibm.com) (204.146.167.235) by taz.hyperreal.org with SMTP; 1 Feb 1999 15:53:59 -0000 Received: from rtpmail03.raleigh.ibm.com (rtpmail03.raleigh.ibm.com [9.37.172.47]) by fwns1.raleigh.ibm.com (8.9.0/8.9.0/RTP-FW-1.2) with ESMTP id KAA23000 for ; Mon, 1 Feb 1999 10:53:56 -0500 Received: from raleigh.ibm.com (sashimi.raleigh.ibm.com [9.37.73.10]) by rtpmail03.raleigh.ibm.com (8.8.5/8.8.5/RTP-ral-1.1) with ESMTP id KAA22242 for ; Mon, 1 Feb 1999 10:53:56 -0500 Message-ID: <36B5CF03.5C5BB813@raleigh.ibm.com> Date: Mon, 01 Feb 1999 10:57:56 -0500 From: Bill Stoddard X-Mailer: Mozilla 4.06 [en] (WinNT; U) MIME-Version: 1.0 To: new-httpd@apache.org Subject: [PATCH] Win32 Script Interpreter Source Content-Type: multipart/mixed; boundary="------------45189F2D18726E532B0913FA" Sender: new-httpd-owner@apache.org Precedence: bulk Reply-To: new-httpd@apache.org This is a multi-part message in MIME format. --------------45189F2D18726E532B0913FA Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit This patch introduces a new Win32 specifc config. directive to direct Apache to optionally search the Win32 registry for the interpreter to run scripts with. The default is to peek the shebang (!#) line at the beginning of the script. The directive is: Win32InterpreterSource It takes a single argument, either "registry" or "shebang" (unquoted, of course). ap_call_exec() in the Win32 path has been simplified and cleaned up a bit. If a registry search fails, the code will peek the shebang (and issue an informational message that the registry search failed). I have a couple of ideas on how to make 16-bit CGI's work; all are hacks and are not included with this patch. I'm inclined to say we just don't support them. I'm looking for comments and suggestions. Thanks, Bill Stoddard stoddard@raleigh.ibm.com --------------45189F2D18726E532B0913FA Content-Type: text/plain; charset=us-ascii; name="registry.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="registry.patch" Index: include/http_core.h =================================================================== RCS file: /export/home/cvs/apache-1.3/src/include/http_core.h,v retrieving revision 1.52 diff -u -r1.52 http_core.h --- http_core.h 1999/01/01 19:04:40 1.52 +++ http_core.h 1999/01/30 16:20:17 @@ -152,6 +152,16 @@ API_EXPORT(int) ap_satisfies (request_rec *r); API_EXPORT(const array_header *) ap_requires (request_rec *); +#ifdef WIN32 +/* + * CGI Script stuff for Win32... + */ +typedef enum { FileTypeUNKNOWN, FileTypeBIN, FileTypeEXE, FileTypeSCRIPT } file_type_e; +typedef enum { INTERPRETER_SOURCE_UNSET, INTERPRETER_SOURCE_REGISTRY, + INTERPRETER_SOURCE_SHEBANG } interpreter_source_e; +API_EXPORT(file_type_e) ap_get_win32_interpreter(const request_rec *, char*, char **); +#endif + #ifdef CORE_PRIVATE /* @@ -248,6 +258,11 @@ array_header *sec; regex_t *r; +#ifdef WIN32 + /* Where to find interpreter to run scripts */ + interpreter_source_e script_interpreter_source; +#endif + } core_dir_config; /* Per-server core configuration */ Index: main/http_core.c =================================================================== RCS file: /export/home/cvs/apache-1.3/src/main/http_core.c,v retrieving revision 1.244 diff -u -r1.244 http_core.c --- http_core.c 1999/01/07 20:46:58 1.244 +++ http_core.c 1999/01/30 16:21:07 @@ -145,7 +145,9 @@ conf->limit_req_body = 0; conf->sec = ap_make_array(a, 2, sizeof(void *)); - +#ifdef WIN32 + conf->script_interpreter_source = INTERPRETER_SOURCE_UNSET; +#endif return (void *)conf; } @@ -262,6 +264,13 @@ if (new->satisfy != SATISFY_NOSPEC) { conf->satisfy = new->satisfy; } + +#ifdef WIN32 + if (new->script_interpreter_source != INTERPRETER_SOURCE_UNSET) { + conf->script_interpreter_source = new->script_interpreter_source; + } +#endif + return (void*)conf; } @@ -725,6 +734,176 @@ return d->limit_req_body; } +#ifdef WIN32 +static char* get_interpreter_from_win32_registry(pool *p, const char* ext) +{ + char extension_path[] = "SOFTWARE\\Classes\\"; + char executable_path[] = "\\SHELL\\OPEN\\COMMAND"; + + HKEY hkeyOpen; + DWORD type; + int size; + int result; + char *keyName; + char *buffer; + char *s; + + if (!ext) + return NULL; + /* + * Future optimization: + * When the registry is successfully searched, store the interpreter + * string in a table to make subsequent look-ups faster + */ + + /* Open the key associated with the script extension */ + keyName = ap_pstrcat(p, extension_path, ext, NULL); + + result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE, + &hkeyOpen); + + if (result != ERROR_SUCCESS) + return NULL; + + /* Read to NULL buffer to find value size */ + size = 0; + result = RegQueryValueEx(hkeyOpen, "", NULL, &type, NULL, &size); + + if (result == ERROR_SUCCESS) { + buffer = ap_palloc(p, size); + result = RegQueryValueEx(hkeyOpen, "", NULL, &type, buffer, &size); + } + + RegCloseKey(hkeyOpen); + + if (result != ERROR_SUCCESS) + return NULL; + + /* Open the key associated with the interpreter path */ + keyName = ap_pstrcat(p, extension_path, buffer, executable_path, NULL); + + result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE, + &hkeyOpen); + + if (result != ERROR_SUCCESS) + return NULL; + + /* Read to NULL buffer to find value size */ + size = 0; + result = RegQueryValueEx(hkeyOpen, "", 0, &type, NULL, &size); + + if (result == ERROR_SUCCESS) { + buffer = ap_palloc(p, size); + result = RegQueryValueEx(hkeyOpen, "", 0, &type, buffer, &size); + } + + RegCloseKey(hkeyOpen); + + if (result != ERROR_SUCCESS) + return NULL; + + /* + * The canonical way shell command entries are entered in the Win32 + * registry is as follows: + * shell [options] "%1" + * 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. + * + * If we find a %1 or a quoted %1, lop it off. + */ + if (buffer && *buffer) { + if ((s = strstr(buffer, "\"%1"))) + *s = '\0'; + else if ((s = strstr(buffer, "%1"))) + *s = '\0'; + } + + return buffer; +} + +API_EXPORT (file_type_e) ap_get_win32_interpreter(const request_rec *r, + char* ext, + char** interpreter ) +{ + HANDLE hFile; + DWORD nBytesRead; + BOOLEAN bResult; + char buffer[1024]; + core_dir_config *d; + file_type_e fileType = FileTypeUNKNOWN; + int i; + + d = (core_dir_config *)ap_get_module_config(r->per_dir_config, + &core_module); + + if (d->script_interpreter_source == INTERPRETER_SOURCE_REGISTRY) { + /* + * Check the registry + */ + *interpreter = get_interpreter_from_win32_registry(r->pool, ext); + if (*interpreter) + return FileTypeSCRIPT; + else { + ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, + r->server, + "Win32InterpreterSource config directive set to \"registry\".\n\t" + "Registry was searched but interpreter not found. Trying the shebang line."); + } + } + + /* + * Look for a #! line in the script + */ + hFile = CreateFile(r->filename, GENERIC_READ, + FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { + return FileTypeUNKNOWN; + } + + bResult = ReadFile(hFile, (void*) &buffer, sizeof(buffer), + &nBytesRead, NULL); + if (!bResult || (nBytesRead == 0)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "ReadFile(%s) failed", r->filename); + CloseHandle(hFile); + return (FileTypeUNKNOWN); + } + CloseHandle(hFile); + + buffer[nBytesRead] = '\0'; + + if ((buffer[0] == '#') && (buffer[1] == '!')) { + fileType = FileTypeSCRIPT; + 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 = ap_pstrdup(r->pool, buffer + i ); + } + else { + /* Check to see if it's a executable */ + IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)interpreter; + if (hdr->e_magic == IMAGE_DOS_SIGNATURE && hdr->e_cblp < 512) { + fileType = FileTypeEXE; + } + } + + return fileType; +} +#endif + /***************************************************************** * * Commands... this module handles almost all of the NCSA httpd.conf @@ -2452,6 +2631,19 @@ return NULL; } +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, "shebang")) { + d->script_interpreter_source = INTERPRETER_SOURCE_SHEBANG; + } else { + d->script_interpreter_source = INTERPRETER_SOURCE_SHEBANG; + } + return NULL; +} + /* Note --- ErrorDocument will now work from .htaccess files. * The AllowOverride of Fileinfo allows webmasters to turn it off */ @@ -2668,6 +2860,8 @@ (void*)XtOffsetOf(core_dir_config, limit_req_body), OR_ALL, TAKE1, "Limit (in bytes) on maximum size of request message body" }, +{ "Win32InterpreterSource", set_interpreter_source, NULL, OR_FILEINFO, TAKE1, + "Where to find interpreter to run Win32 scripts (Registry or script shebang line)" }, { NULL }, }; Index: main/util_script.c =================================================================== RCS file: /export/home/cvs/apache-1.3/src/main/util_script.c,v retrieving revision 1.137 diff -u -r1.137 util_script.c --- util_script.c 1999/01/01 19:04:54 1.137 +++ util_script.c 1999/01/30 16:21:40 @@ -820,31 +820,31 @@ } #elif defined(WIN32) { - /* Adapted from Alec Kloss' work for OS/2 */ - int is_script = 0; - int is_binary = 0; - char interpreter[2048]; /* hope it's enough for the interpreter path */ - FILE *program; - int i, sz; - char *dot; - char *exename; + /* Adapted from Alec Kloss' work for OS/2 */ + char *interpreter = NULL; + char *arguments = NULL; + char *ext = NULL; + char *exename = NULL; + char *s = NULL; char *quoted_filename; - int is_exe = 0; - STARTUPINFO si; - PROCESS_INFORMATION pi; char *pCommand; char *pEnvBlock, *pNext; + + int i; int iEnvBlockLen; + + file_type_e fileType; - memset(&si, 0, sizeof(si)); - memset(&pi, 0, sizeof(pi)); + STARTUPINFO si; + PROCESS_INFORMATION pi; - interpreter[0] = 0; - pid = -1; + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); - quoted_filename = ap_pstrcat(r->pool, "\"", r->filename, "\"", NULL); + pid = -1; if (!shellcmd) { + /* Find the file name */ exename = strrchr(r->filename, '/'); if (!exename) { exename = strrchr(r->filename, '\\'); @@ -855,66 +855,88 @@ else { exename++; } - dot = strrchr(exename, '.'); - if (dot) { - if (!strcasecmp(dot, ".BAT") - || !strcasecmp(dot, ".CMD") - || !strcasecmp(dot, ".EXE") - || !strcasecmp(dot, ".COM")) { - is_exe = 1; - } + + ext = strrchr(exename, '.'); + if ((ext) && (!strcasecmp(ext,".bat") || + !strcasecmp(ext,".cmd"))) { + fileType = FileTypeEXE; + } + else if ((ext) && (!strcasecmp(ext,".exe") || + !strcasecmp(ext,".com"))) { + /* 16 bit or 32 bit? */ + fileType = FileTypeEXE; + } + else { + /* Maybe a script or maybe a binary.. */ + fileType = ap_get_win32_interpreter(r, ext, &interpreter); } - if (!is_exe) { - program = fopen(r->filename, "rb"); - if (!program) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, r, - "fopen(%s) failed", r->filename); - return (pid); - } - sz = fread(interpreter, 1, sizeof(interpreter) - 1, program); - if (sz < 0) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, r, - "fread of %s failed", r->filename); - fclose(program); - return (pid); - } - interpreter[sz] = 0; - fclose(program); - if (!strncmp(interpreter, "#!", 2)) { - is_script = 1; - for (i = 2; i < sizeof(interpreter); i++) { - if ((interpreter[i] == '\r') - || (interpreter[i] == '\n')) { - break; - } - } - interpreter[i] = 0; - for (i = 2; interpreter[i] == ' '; ++i) - ; - memmove(interpreter+2,interpreter+i,strlen(interpreter+i)+1); - } - else { - /* Check to see if it's a executable */ - IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)interpreter; - if (hdr->e_magic == IMAGE_DOS_SIGNATURE && hdr->e_cblp < 512) { - is_binary = 1; + if (fileType == FileTypeUNKNOWN) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, + "%s is not executable; ensure interpreted scripts have " + "\"#!\" first line", + r->filename); + return (pid); + } + + /* + * Look at the arguments... + */ + arguments = ""; + if ((r->args) && (r->args[0]) && !strchr(r->args, '=')) { + /* If we are in this leg, there are some other arguments + * that we must include in the execution of the CGI. + * Because CreateProcess is the way it is, we have to + * create a command line like format for the execution + * of the CGI. This means we need to create on long + * string with the executable and arguments. + * + * The arguments string comes in the request structure, + * and each argument is separated by a '+'. We'll replace + * these pluses with spaces. + */ + + int iStringSize = 0; + int x; + + /* + * Duplicate the request structure string so we don't change it. + */ + arguments = ap_pstrdup(r->pool, r->args); + + /* + * Change the '+' to ' ' + */ + for (x=0; arguments[x]; x++) { + if ('+' == arguments[x]) { + arguments[x] = ' '; } } + + /* + * We need to unescape any characters that are + * in the arguments list. + */ + ap_unescape_url(arguments); + arguments = ap_escape_shell_cmd(r->pool, arguments); } - /* Bail out if we haven't figured out what kind of - * file this is by now.. + + /* + * We have the interpreter (if there is one) and we have + * the arguments (if there are any). + * Build the command string to pass to CreateProcess. */ - if (!is_exe && !is_script && !is_binary) { - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, - "%s is not executable; ensure interpreted scripts have " - "\"#!\" first line", - r->filename); - return (pid); + quoted_filename = ap_pstrcat(r->pool, "\"", r->filename, "\"", NULL); + if (interpreter && *interpreter) { + pCommand = ap_pstrcat(r->pool, interpreter, " ", + quoted_filename, " ", arguments, NULL); } - } + else { + pCommand = ap_pstrcat(r->pool, quoted_filename, " ", arguments, NULL); + } - if (shellcmd) { + } else { + char *shell_cmd = "CMD.EXE /C "; OSVERSIONINFO osver; osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); @@ -929,96 +951,17 @@ } pCommand = ap_pstrcat(r->pool, shell_cmd, argv0, NULL); } - else if ((!r->args) || (!r->args[0]) || strchr(r->args, '=')) { - if (is_exe || is_binary) { - /* - * When the CGI is a straight binary executable, - * we can run it as is - */ - pCommand = quoted_filename; - } - else if (is_script) { - /* When an interpreter is needed, we need to create - * a command line that has the interpreter name - * followed by the CGI script name. - */ - pCommand = ap_pstrcat(r->pool, interpreter + 2, " ", - quoted_filename, NULL); - } - else { - /* If not an executable or script, just execute it - * from a command prompt. - */ - pCommand = ap_pstrcat(r->pool, SHELL_PATH, " /C ", - quoted_filename, NULL); - } - } - else { - - /* If we are in this leg, there are some other arguments - * that we must include in the execution of the CGI. - * Because CreateProcess is the way it is, we have to - * create a command line like format for the execution - * of the CGI. This means we need to create on long - * string with the executable and arguments. - * - * The arguments string comes in the request structure, - * and each argument is separated by a '+'. We'll replace - * these pluses with spaces. - */ - char *arguments=NULL; - int iStringSize = 0; - int x; - - /* - * Duplicate the request structure string so we don't change it. - */ - arguments = ap_pstrdup(r->pool, r->args); - - /* - * Change the '+' to ' ' - */ - for (x=0; arguments[x]; x++) { - if ('+' == arguments[x]) { - arguments[x] = ' '; - } - } - - /* - * We need to unescape any characters that are - * in the arguments list. - */ - ap_unescape_url(arguments); - arguments = ap_escape_shell_cmd(r->pool, arguments); - - /* - * The argument list should now be good to use, - * so now build the command line. - */ - if (is_exe || is_binary) { - pCommand = ap_pstrcat(r->pool, quoted_filename, " ", - arguments, NULL); - } - else if (is_script) { - pCommand = ap_pstrcat(r->pool, interpreter + 2, " ", - quoted_filename, " ", arguments, NULL); - } - else { - pCommand = ap_pstrcat(r->pool, SHELL_PATH, " /C ", - quoted_filename, " ", arguments, NULL); - } - } - - /* - * Make child process use hPipeOutputWrite as standard out, - * and make sure it does not show on screen. - */ - si.cb = sizeof(si); - si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; - si.wShowWindow = SW_HIDE; - si.hStdInput = pinfo->hPipeInputRead; - si.hStdOutput = pinfo->hPipeOutputWrite; - si.hStdError = pinfo->hPipeErrorWrite; + + /* + * Make child process use hPipeOutputWrite as standard out, + * and make sure it does not show on screen. + */ + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdInput = pinfo->hPipeInputRead; + si.hStdOutput = pinfo->hPipeOutputWrite; + si.hStdError = pinfo->hPipeErrorWrite; /* * Win32's CreateProcess call requires that the environment @@ -1052,50 +995,10 @@ */ CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - } else { - if (is_script) { - /* since we are doing magic to find what we are executing - * if running a script, log what we think we should have - * executed - */ - ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, r, - "could not run script interpreter: %s", pCommand); - } - } -#if 0 - if ((!r->args) || (!r->args[0]) || strchr(r->args, '=')) { - if (is_exe || is_binary) { - pid = spawnle(_P_NOWAIT, r->filename, r->filename, NULL, env); - } - else if (is_script) { - pid = spawnle(_P_NOWAIT, interpreter + 2, interpreter + 2, - r->filename, NULL, env); - } - else { - pid = spawnle(_P_NOWAIT, SHELL_PATH, SHELL_PATH, "/C", - r->filename, NULL, env); - } - } - else { - if (is_exe || is_binary) { - pid = spawnve(_P_NOWAIT, r->filename, - create_argv(r->pool, NULL, NULL, NULL, argv0, - r->args), env); - } - else if (is_script) { - pid = spawnve(_P_NOWAIT, interpreter + 2, - create_argv(r->pool, interpreter + 2, NULL, NULL, - r->filename, r->args), env); - } - else { - pid = spawnve(_P_NOWAIT, SHELL_PATH, - create_argv_cmd(r->pool, argv0, r->args, - r->filename), env); - } - } -#endif - return (pid); + } + return (pid); } + #else if (ap_suexec_enabled && ((r->server->server_uid != ap_user_id) --------------45189F2D18726E532B0913FA Content-Type: text/plain; charset=us-ascii; name="license" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="license" Users of the Apache webserver are hereby granted a non-exclusive, irrevocable, world-wide, royalty-free, non-transferable license to use, execute, prepare derivative works of, and distribute (internally and externally, and including derivative works) the code accompanying this license as part of, and integrated into the Apache webserver. This code is provided "AS IS" WITHOUT WARRANTY OF ANY KIND EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTY OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND ANY WARRANTY OF NON-INFRINGEMENT. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THIS CODE REMAINS WITH USERS OF THE APACHE WEBSERVER. The owner of this code represents and warrants that it is legally entitled to grant the above license. --------------45189F2D18726E532B0913FA--