HOWTO: Run Console Applications from IIS6 on Windows Server 2003, Part 2
I finally have enough blog entries about various portions of IIS6 request processing that I can stitch together this meta-blog-entry explaining how it all works together and then apply it towards an issue. You probably want to keep a link to this loaded entry. Anyhow, here goes...
We currently has IIS 6 installed on a Windows 2003 server, and we are attempting to run some executables through the browser. We have "Scripts and Executables" selected for the web application, and we are currently allowing "All Unknown CGI Extensions" and "All Unknown ISAPI Extensions" under Web Service Extensions during testing.
With this setup we are able to run some executables successfully through a browser, like:
However other executables are not working:
We are getting no value for most of these, except "Fsutil quota" which is returning:
The FSUTIL utility requires that you have administrative privileges
I have confirmed that the browser is using an ID that has administrative privileges by returning the userid it is using. This administrative ID does not have any problem running Fsutil quota on the server directly.
What privileges am I missing? Does something else need to be set up, or is it possible the executables not working are not CGI or ISAPI?
Thanks for any help.
Ok, this blog entry is unique in that it is the first time that I am aggregating information from all my prior posts and applying it to resolve a problem. I will NOT explain everything in detail - only the new stuff... for everything else, if I pulled the information from a blog entry, it will be linked in this blog entry somewhere appropriate.
Putting it all together
When it comes to request execution on IIS6, this is how I think about things:
HTTP.SYS parses HTTP requests in kernel mode and sends a digested form to user mode w3wp.exe. Interesting details about this process can be found in this blog entry.
IIS6 in user mode w3wp.exe receives this request and determines the URL namespace (and hence all applicable metadata from configuration that affect the execution of this request - which includes the authentication required, the ScriptMaps applicable for the request, etc)
Then, the authentication process runs to determine the user token used to execute a request.
One of the factors that affect a user token's abilities is the way the user logon was performed (i.e. logon type). In other words, Interactive logon from Remote Desktop/Local Console is different than the Network logon performed by IIS6. This blog entry describes the differences in more detail.
Furthermore, IIS6 can perform different logon types depending on authentication protocol as well as by configuration, though the defaults pretty much work and should not be fiddled with.
So, logon difference alone may affect how FSUTIL.EXE behaves.
In addition to authentication producing a user token, IIS has to determine the correct handler to execute the requested resource (i.e. should the resource be handled as a static file and sent back as-is? or should it go to a CGI/ISAPI to process? or should it produce a courtesy redirect? Etc). This blog entry and this blog entry describes that decision process.
After determining the specific type of handler to execute a request, IIS uses the user token to execute the handler for the request. This blog entry describes how IIS selects the user token to execute the handler.
Additional user code can be run by the handler, which have arbitrary behavior when it comes to user token, privileges, and generated response
The browser client can have automatic behavior which affects the number and sequence of requests made to IIS, as described in this blog entry.
Whew. If you got to here and read through all the associated blog entries, you have just gone through a LOT of details about how IIS6 functions to process requests as well as how it interacts with user identity. Time to put this info to good use and apply it. :-)
The Problem Restated...
Ok, the following details and conclusions all come from the aforementioned blog entries. If you get lost, I suggest you read 'em and read 'em again until you understand it. :-)
The first thing that needs to be clear is HOW you are "running executables through the browser". Since IIS only recognizes static files, CGI/ISAPI, or Scripts (ok, I am simplifying DAV away since it is not involved in this picture), there are two basic ways to run executables:
As a CGI/ISAPI
If you want IIS to directly run the executable as a CGI or ISAPI, then you need to configure "Scripts and Executables" execute permission as well as Web Service Extension for the binary. i.e.
/cgi-bin has "Scripts and Executable" execute permission enabled.
<full-path-to-FSUTIL.EXE>\FSUTIL.EXE is enabled as a Web Service Extension.
You make a request to http://localhost/cgi-bin/FSUTIL.EXE
IIS is going to execute FSUTIL.EXE as a CGI, so it checks it against Web Service Extension. It is allowed.
Thus, IIS will execute FSUTIL.EXE as a CGI EXE using the user token obtained through whatever authentication protocol is negotiated between the browser and server.
The EXE is executed using either CreateProcess() or CreateProcessAsUser() Win32 API call, depending on IIS configuration for CGI execution. If it is CreateProcess(), then FSUTIL.EXE runs with the Process Identity; if it is CreateProcessAsUser(), then FSUTIL.EXE runs with the IIS impersonated identity.
IIS parses the text output from FSUTIL.EXE according to CGI specification and then sends a response back to the client.
If you use the nph- prefix, IIS won't parse the text and just send it back as is. Of course, if you happen to send invalid HTTP response using NPH, the client can complain or behave weirdly...
Via a Scriptmapped CGI/ISAPI
If you want to run executables on IIS from a script (i.e. an ASP, ASP.Net, or PHP page is considered a script resource executed by ASP.DLL, ASPNET_ISAPI.DLL, or PHP-CGI.EXE / PHPISAPI.DLL Script Engine, respectively), then you need to configure "Scripts" execute permission as well as Web Service Extension for the appropriate Script Engine. i.e.
MyScript.asp contains the following content which executes FSUTIL.EXE:
<% set objShell = Server.CreateObject( "WScript.Shell" ) objShell.Run( "FSUTIL.EXE" ) %>
/cgi-bin has "Scripts" execute permission enabled.
%systemroot%\System32\inetsrv\ASP.DLL is enabled as a Web Service Extension.
/cgi-bin has a ScriptMaps property which associates .asp extension to %systemroot%\System32\inetsrv\ASP.DLL as a Script Engine.
You make a request to http://localhost/cgi-bin/MyScript.asp
IIS identifies ASP.DLL as the ISAPI Script Engine to process the /cgi-bin/MyScript.asp resource and checks it against Web Service Extension. Since it is allowed, it executes ASP.DLL using the user token obtained through whatever authentication protocol is negotiated between the browser and server.
Note: even though the ASP page runs FSUTIL.EXE, FSUTIL.EXE does NOT need to be in Web Service Extension because IIS never runs nor knows about FSUTIL.EXE. IIS only knows it is running ASP.DLL so that is what needs to be enabled as a Web Service Extension.
ASP.DLL will keep the impersonated identity from IIS and parse/execute the script code in MyScript.asp using Windows Scripting Host. objShell.Run() translates into a CreateProcess() Win32 API call, and FSUTIL.EXE runs using the Process Identity (this is how CreateProcess is documented to work!)
FSUTIL output is unknown to ASP (and IIS) unless you capture the output of objShell.Run() somehow and then Response.Write() it so that IIS knows about it.
Ok, back to the Original Problem
At this point, a couple differences should jump out at you:
When you ran FSUTIL QUOTA directly, it was executed by a user token with Interactive logon type. When you ran it from IIS, the user token has Network logon type.
Depending on how you ran the executables, FSUTIL.EXE was either called via CreateProcess() or CreateProcessAsUser(). The critical difference between them is that CreateProcess() implicitly uses the Process Identity to create the new FSUTIL.EXE process, while CreateProcessAsUser() uses the impersonated identity obtained by IIS through authentication.
The Application Pool Identity of the Application Pool servicing the URL namespace that executed the request determines the Process Identity
The authentication protocol negotiated between the browser and server determines the impersonated identity, in a manner consistent with this blog entry.
I do not know if FSUTIL.EXE has special code that cares about the above details. But I do know that CMD.EXE does... so YMMV.
What about the Executables which appear to return Nothing
As for the question of whether the executables "not working" are not CGI/ISAPI... After executing a request handler, IIS expects a valid HTTP response to come back so that it can send it back to the requesting client.
CGI is nothing more than an executable which outputs text which conforms to the CGI specification (this blog entry describes an interest interaction between CGI and HTTP). If the output conforms to specification, IIS sends it back as-is; otherwise, IIS returns a 502 response.
Since IIS6 in WS03SP1 is lax about CGI conformance and no longer requires the status: and content-type headers, if the executable happens to output a CRLF before printing data, that data gets interpreted as response entity by IIS and sent back to the client as-is. The following are some illustrative examples with CRLF clearly marked as \r\n.
The syntax of this command is:\r\n \r\n \r\n NET [ ACCOUNTS | COMPUTER | CONFIG | CONTINUE | FILE | GROUP | HELP |\r\n HELPMSG | LOCALGROUP | NAME | PAUSE | PRINT | SEND | SESSION |\r\n SHARE | START | STATISTICS | STOP | TIME | USE | USER | VIEW ]\r\n
\r\n Active Connections\r\n \r\n Proto Local Address Foreign Address State\r\n TCP TEST-MACHINE:80 0.0.0.0:80 ESTABLISHED\r\n
As you can see, NET.EXE fails because its CGI output starts off with what looks like an invalid header named "The syntax of this command is:" (it has spaces in it, and no header value) prior to the double CRLF, so it fails to parse correctly and IIS returns a 502 "Bad Gateway" response. Meanwhile, PING.EXE succeeds because it starts off with a CRLF that tells IIS to use a 200 OK response header ahead of the PING output.
Now, if you prefix the resources with NPH- (i.e. nph-netstat.exe or nph-ping.exe), then IIS will NOT process the CGI output and just send it as-is back to the client. That is what NPH means - Non-Parsed Header. Of course, this avoids the 502 response from IIS, but if the output is not proper HTTP, it will now confuse the client, which can have its own arbitrary behavior...
Basically, these console commands are not designed to produce output that conforms to CGI specification, so the fact that they work when invoked as CGI by IIS is purely random, mostly depending on whether the output begins with something that looks like broken HTTP headers or not.
I congratulate you on getting to the end of this marathon of a blog post. I finally got a juicy topic to explain and a lot of blog entries to logically stitch together. :-)
Hopefully, this clears up common confusions surrounding:
- IIS request execution logic
- User identity used to execute a request
- Executing an EXE via CreateProcess() is different than via CreateProcessAsUser(), regardless if you made IIS directly execute it as a CGI or indirectly through a Script
- Executing an ISAPI uses the impersonated identity by default, but can be the Process Identity, depending on the ISAPI
- How some EXEs magically work as "CGIs" but others fail
- How some programs, like CMD.EXE, simply fail depending on whether invoked via CreateProcess() or CreateProcessAsUser()... and maybe FSUTIL.EXE is in the same category