You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by W G Stoddard <wg...@us.ibm.com> on 1998/06/13 19:08:56 UTC

[PATCH] NT CGI - Fetch interpreter from registry and other a

This patch uses a CGI script's file extension to search the registry for the
interpreter, rather than shebang peeking the file. This should allow unix
scripts to be run on NT without modifing the #! line in the script. Cleaned up
the Win32 section of ap_call_exec a bit as well. Included this patch both
inline and as a mime attachment.  Let me know if you experience problems with
the inline patch. My mail program has been know to mangle text on occasion.
Probably a config thing I've not quite mastered yet....

Bill Stoddard
wgstodda@us.ibm.com


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.  THE ENTIRE RISK ARISING OUT OF THE USE
OR PERFORMANCE OF THIS CODE REMAINS WITH USERS OF THE APACHE WEBSERVER.

I represent and warrant that I am legally entitled to grant the above license.



*** util_script.c.original Tue Jun 09 16:23:00 1998
--- util_script.c Thu Jun 11 19:04:09 1998
***************
*** 579,586 ****
  }
  #endif


! API_EXPORT(int) ap_call_exec(request_rec *r, child_info *pinfo, char *argv0,
char **env, int shellcmd)
  {
      int pid = 0;
  #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
--- 579,649 ----
  }
  #endif

+ #ifdef WIN32
+ static char* get_interpreter(pool *p, char *dot_ext) {

!     char extension_path[] = "SOFTWARE\\Classes\\";
!     char executable_path[] = "\\SHELL\\OPEN\\COMMAND";
!
!     HKEY hkeyOpen;
!     DWORD type;
!     int size;
!     int result;
!     char *keyName;
!     char *buffer;
!
!     /* Open the key associated with the script extension */
!     keyName = ap_pstrcat(p, extension_path, dot_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;
!
!     return buffer;
! }
! #endif
!
! API_EXPORT(int) ap_call_exec(request_rec *r, child_info *pinfo, char *argv0,
!                              char **env, int shellcmd)
  {
      int pid = 0;
  #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
***************
*** 700,884 ****
      }
  #elif defined(WIN32)
      {
!  /* Adapted from work by Alec Kloss, to allow exec'ing of scripts
under OS/2 */
!  int is_script = 0;
!  int is_binary = 0;
!  char interpreter[2048]; /* hope this is large enough for the interpreter
path */
!  FILE *program;
!  int i, sz;
!  char *dot;
!  char *exename;
!  int is_exe = 0;
!  STARTUPINFO si;
!  PROCESS_INFORMATION pi;
          char *pCommand;
          char *pEnvBlock, *pNext;
          int iEnvBlockLen;

!  memset(&si, 0, sizeof(si));
!  memset(&pi, 0, sizeof(pi));

!  interpreter[0] = 0;
!
!  exename = strrchr(r->filename, '/');
!  if (!exename)
!      exename = strrchr(r->filename, '\\');
!  if (!exename)
!      exename = r->filename;
!  else
!      exename++;
!  dot = strrchr(exename, '.');
!  if (dot) {
!      if (!strcasecmp(dot, ".BAT") ||
!   !strcasecmp(dot, ".CMD") ||
!   !strcasecmp(dot, ".EXE") ||
!   !strcasecmp(dot, ".COM"))
!   is_exe = 1;
!  }

!  if (!is_exe) {
!      program = fopen(r->filename, "rb");
!      if (!program) {
!   ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
!        "fopen(%s) failed", r->filename);
!   return (pid);
!      }
!      sz = fread(interpreter, 1, sizeof(interpreter) - 1, program);
!      if (sz < 0) {
!   ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
!        "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;
!      }
!  }

!  /*
!   * 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;
!
!  pid = -1;
!  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 = r->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, " ",
!           r->filename, NULL);
!      }
!      else {
!          /* If not an executable or script, just execute it
                   * from a command prompt.
                   */
!          pCommand = ap_pstrcat(r->pool, SHELL_PATH, " /C ",
!           r->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, r->filename, " ",
-           arguments, NULL);
-      }
-      else if (is_script) {
-          pCommand = ap_pstrcat(r->pool, interpreter + 2, " ",
-           r->filename, " ", arguments, NULL);
-      }
-      else {
-          pCommand = ap_pstrcat(r->pool, SHELL_PATH, " /C ",
-           r->filename, " ", arguments, NULL);
-      }
-  }
-
          /*
           * Win32's CreateProcess call requires that the environment
           * be passed in an environment block, a null terminated block of
           * null terminated strings.
           */
!
          i = 0;
          iEnvBlockLen = 1;
          while (env[i]) {
              iEnvBlockLen += strlen(env[i]) + 1;
              i++;
          }
!
          pEnvBlock = (char *)ap_pcalloc(r->pool,iEnvBlockLen);
!
          i = 0;
          pNext = pEnvBlock;
          while (env[i]) {
--- 763,976 ----
      }
  #elif defined(WIN32)
      {
!         /* Adapted from work by Alec Kloss, to allow exec'ing of scripts
under OS/2 */
!         int is_script = 0;
!         int is_binary = 0;
!         int is_exe = 0;
!         char buffer[2048]; /* hope this is large enough for the interpreter
path */
!         char *interpreter = NULL;
!         FILE *program;
!         int i, sz;
!         char *dot;
!         char *exename;
!         char *arguments="";
!
!         STARTUPINFO si;
!         PROCESS_INFORMATION pi;
          char *pCommand;
          char *pEnvBlock, *pNext;
          int iEnvBlockLen;

!         memset(&si, 0, sizeof(si));
!         memset(&pi, 0, sizeof(pi));

!         /*
!          * 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;
!
!         pid = -1;

!         if (!shellcmd) {
!             exename = strrchr(r->filename, '/');
!             if (!exename)
!                 exename = strrchr(r->filename, '\\');
!             if (!exename)
!                 exename = r->filename;
!             else
!                 exename++;
!
!             dot = strrchr(exename, '.');
!
!             if (dot) {
!                 if (!strcasecmp(dot, ".BAT") ||
!                     !strcasecmp(dot, ".CMD") ||
!                     !strcasecmp(dot, ".EXE") ||
!                     !strcasecmp(dot, ".COM")) {
!                     is_exe = 1;
!                 }
!             }
!
!             if (!is_exe) {
!
!                 if (dot) {
!                     /* Check the Win32 registry for the interpreter */
!                     interpreter = get_interpreter(r->pool, dot);
!                 }
!
!                 if (interpreter) {
!                     is_script = 1;
!                 }
!                 else {
!                     /* Peek into the file and find the interpreter */
!                     program = fopen(r->filename, "rb");
!                     if (!program) {
!                         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
!                                      "fopen(%s) failed", r->filename);
!                         return (pid);
!                     }
!                     sz = fread(buffer, 1, sizeof(buffer) - 1, program);
!                     if (sz < 0) {
!                         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
!                                      "fread of %s failed", r->filename);
!                         fclose(program);
!                         return (pid);
!                     }
!                     buffer[sz] = 0;
!                     fclose(program);
!                     if (!strncmp(buffer, "#!", 2)) {
!                         is_script = 1;
!                         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 = buffer + i;
!                     }
!                     else {
                          /* Check to see if it's a executable */
!                         IMAGE_DOS_HEADER *hdr = (IMAGE_DOS_HEADER*)buffer;
!                         if (hdr->e_magic == IMAGE_DOS_SIGNATURE &&
hdr->e_cblp < 512)
!                             is_binary = 1;
!                     }
!                 }
!             }
!
!             /* Bail out if we haven't figured out what kind of
!              * file this is by now..
!              */
!             if (!is_exe && !is_script && !is_binary) {
!                 ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "%s is not
executable",
!                              r->filename);
!                 return (pid);
!             }
!
!             /* Build the arguments list */
!             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);
!
!             }
!
!             /* Build the command string */
!             if (is_exe || is_binary) {
!                 /* When the CGI is a straight binary executable,
!                  * we can run it as is
!                  */
!                 pCommand = ap_pstrcat(r->pool, r->filename, " ",
!                                       arguments, NULL);
!             }
!             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.  Handle interpreter %1 and interpreter "%1"
correctly.
!                  */
!                 char *s;
!                 if (s = strstr(interpreter, "%1")) {
!                     *s = '\0';
!                     s+=2;
!                 }
!                 if (s && *s) {
!                     pCommand = ap_pstrcat(r->pool, interpreter, r->filename,
s,
!                                           " ", arguments, NULL);
!                 }
!                 else {
!                     pCommand = ap_pstrcat(r->pool, interpreter, " ",
!                                           r->filename, " ", arguments, NULL);
!                 }
!             }
!             else {
!                 /* If not an executable or script, just execute it
                   * from a command prompt.
                   */
!                 pCommand = ap_pstrcat(r->pool, SHELL_PATH, " /C ",
!                                       r->filename, " ", arguments, NULL);
!
!             }
!         }
!         else {
!             /* shellcmd */
!             pCommand = ap_pstrcat(r->pool, SHELL_PATH, " /C ", argv0, NULL);
!         }

          /*
           * Win32's CreateProcess call requires that the environment
           * be passed in an environment block, a null terminated block of
           * null terminated strings.
           */
!
          i = 0;
          iEnvBlockLen = 1;
          while (env[i]) {
              iEnvBlockLen += strlen(env[i]) + 1;
              i++;
          }
!
          pEnvBlock = (char *)ap_pcalloc(r->pool,iEnvBlockLen);
!
          i = 0;
          pNext = pEnvBlock;
          while (env[i]) {




Re: [PATCH] NT CGI - Fetch interpreter from registry and other a

Posted by Rodent of Unusual Size <Ke...@Golux.Com>.
Marc Slemko wrote:
> 
> I suggest it be configurable.  Performance sucks having to open the file
> just to read the interpreter, so it should be disablable and the
> preference between the two should be settable.

Indeed; the peek should be a last resort unless the user has explicitly
requested it.  And whatever configuration directive is created
for this should be (IMHO) OR_FILEINFO, like AddType.

#ken	P-)}

Ken Coar                    <http://Web.Golux.Com/coar/>
Apache Group member         <http://www.apache.org/>
"Apache Server for Dummies" <http://Web.Golux.Com/coar/ASFD/>

Re: [PATCH] NT CGI - Fetch interpreter from registry and other a

Posted by Marc Slemko <ma...@worldgate.com>.
On Sat, 13 Jun 1998, Gregory A Lundberg wrote:

> On Sat, 13 Jun 1998, Rodent of Unusual Size wrote:
> 
> > I can see checking the shebang line if the extension isn't
> > listed in the registry.  Other than that, I'm inclined
> > to think that maybe the registry should dominate.  On the
> > other hand, I can foresee confusion if someone has multiple
> > versions of Perl installed and frobbing the shebang line
> > doesn't do anything.  Hmm.
> 
> Don't think like a Unix guru .. think like a Windows-weenie .. go by the
> registry and forget the whole shebang.

Except that lots of people like not having hidden voodo behind the scenes,
having things magically change from server to server, etc.

I suggest it be configurable.  Performance sucks having to open the file
just to read the interpreter, so it should be disablable and the
preference between the two should be settable.


Re: [PATCH] NT CGI - Fetch interpreter from registry and other a

Posted by Gregory A Lundberg <lu...@vr.net>.
On Sat, 13 Jun 1998, Rodent of Unusual Size wrote:

> I can see checking the shebang line if the extension isn't
> listed in the registry.  Other than that, I'm inclined
> to think that maybe the registry should dominate.  On the
> other hand, I can foresee confusion if someone has multiple
> versions of Perl installed and frobbing the shebang line
> doesn't do anything.  Hmm.

Don't think like a Unix guru .. think like a Windows-weenie .. go by the
registry and forget the whole shebang.

----

Gregory A Lundberg		Senior Partner, VRnet Company
1441 Elmdale Drive              lundberg@vr.net
Kettering, OH 45409-1615 USA    1-800-809-2195


Re: [PATCH] NT CGI - Fetch interpreter from registry and other a

Posted by Rodent of Unusual Size <Ke...@Golux.Com>.
Ben Laurie wrote:
> 
> Also, I think that #! should be optionally obeyed. I don't know if you
> cater for that already, though.

I can see checking the shebang line if the extension isn't
listed in the registry.  Other than that, I'm inclined
to think that maybe the registry should dominate.  On the
other hand, I can foresee confusion if someone has multiple
versions of Perl installed and frobbing the shebang line
doesn't do anything.  Hmm.

#ken	P-)}

Ken Coar                    <http://Web.Golux.Com/coar/>
Apache Group member         <http://www.apache.org/>
"Apache Server for Dummies" <http://Web.Golux.Com/coar/ASFD/>

Re: [PATCH] NT CGI - Fetch interpreter from registry and other a

Posted by Ben Laurie <be...@algroup.co.uk>.
W G Stoddard wrote:
> 
> This patch uses a CGI script's file extension to search the registry for the
> interpreter, rather than shebang peeking the file. This should allow unix
> scripts to be run on NT without modifing the #! line in the script. Cleaned up
> the Win32 section of ap_call_exec a bit as well. Included this patch both
> inline and as a mime attachment.  Let me know if you experience problems with
> the inline patch. My mail program has been know to mangle text on occasion.
> Probably a config thing I've not quite mastered yet....

90% of the patch seems to be gratuitous reformatting, which makes it
rather hard to see what it really does.

Also, I think that #! should be optionally obeyed. I don't know if you
cater for that already, though.

Cheers,

Ben.

-- 
Ben Laurie            |Phone: +44 (181) 735 0686| Apache Group member
Freelance Consultant  |Fax:   +44 (181) 735 0689|http://www.apache.org/
and Technical Director|Email: ben@algroup.co.uk |
A.L. Digital Ltd,     |Apache-SSL author     http://www.apache-ssl.org/
London, England.      |"Apache: TDG" http://www.ora.com/catalog/apache/

WE'RE RECRUITING! http://www.aldigital.co.uk/recruit/