\[Editor's Note: Be aware that as of this writing, Microsoft doesn't officially support pass-through delegation, which is one of the methods discussed in this article.\]
If you're using IIS as your Web server, you have access to excellent virtual directory capabilities. You can use a virtual directory structure to selectively publish the content of certain physical directories without exposing their locations. However, before starting any discussion about IIS virtual directories, you need to be clear about what virtual directories are and how IIS uses them. Then, you can determine the most efficient source for your directories and decide how best to configure access and security.
When you install an IIS Web server, the installation process creates a home directory (\%systemdrive%\inetpub\wwwroot by default) on that system. IIS uses this location to serve any HTTP request that doesn't include a directory name in the URL (e.g., http://webserver/default.asp). All physical directories under the home directory are also available to Web clients. For example, placing default.asp in the subdirectory \%systemdrive%\inetpub\wwwroot\subdir makes the URL http://webserver/subdir/default.asp available to clients. A virtual directory is a mechanism that you can use to make available a specific directory that resides anywhere on the file system or network, rather than only under the IIS server's home directory.
A quick glance at the Microsoft Management Console (MMC) Internet Information Services console reveals that IIS uses different icons to represent virtual directories, physical directories, and Web applications, as Figure 1 shows. In this example, RemoteScripts is a virtual directory, aspnet_client and subdir are physical directories directly under the Default Web Site object (i.e., the home directory), and IISHelp, IISAdmin, and IISSamples are Web applications. (For information about Web applications and their relation to virtual and physical directories, see the Web-exclusive sidebar "Web Applications," http://www.windowswebsolutions.com, InstantDoc ID 25933.) A red Error icon next to a virtual directory, such as the icon beside the Scripts virtual directory in Figure 1, indicates an error condition—most likely that the physical directory to which the virtual directory points no longer exists.
Be aware that a client request that includes ".." (e.g., http://altoid/subdir/../scripts/script.asp) jumps from the specified subdirectory (e.g., \%systemdrive%\inetpub\wwwroot\subdir) to the virtual directory (e.g., the Scripts virtual directory). Although jumping in itself isn't a problem, many viruses (e.g., Nimda) use requests that contain "..". To prevent jumping, you can install the URLScan add-on tool, as the Microsoft article "INFO: Availability of URLScan Version 2.5 Security Tool" (http://support.microsoft.com/default.aspx?scid=kb;en-us;q307608) explains. (For more information about using URLScan, see Randy Franklin Smith, "Deploy URLScan to Protect Your IIS Server," August 2002, InstantDoc ID 25581, and "Protect Your IIS Server with URLScan," July 2002, InstantDoc ID 25230.)
Pick a Source
When configuring a virtual directory, you have a choice of three content sources. These sources are a local directory (i.e., a directory on the Web server), a network share on another computer, or redirection to a URL. Let's take a look at the first two options (I'll discuss the third option in my next article).
Using a local directory. The most common and efficient scenario targets a local directory. The local file system satisfies all requests without going to a remote machine (as using a file share on another computer requires) and without causing an extra HTTP round-trip (as redirecting to a URL requires). When you create a new virtual directory, it points to the local file system by default. To change the local directory that an existing virtual directory targets, open the Internet Information Services snap-in. Open the virtual directory's Properties dialog box and go to the Virtual Directory tab. Select the A directory located on this computer option, then specify the target directory in the Local Path text box, as Figure 2 shows, or click Browse to search for the target directory.
Using the local file system is also the most reliable option. The most common problem arises when someone renames or deletes the target directory without changing the virtual directory's Local Path property setting. (This problem occurs most often when more than one administrator manages IIS.) This error manifests itself as a 404 Object Not Found reply to the server, and an Error icon will appear next to the virtual server object in the Internet Information Services console. To search for such errors, you can open the W3SVC server logs (each Web site has a subdirectory under \%systemroot%\logfiles that contains that site's log files) and search for 404 in the Status Code field. The easiest way to locate a problem is to use Event Viewer (which you can access under Administrative Tools or through the eventvwr.exe command) to examine events in the System log. Look for an error event with a source of W3SVC.
Using a remote share. Using a remote share rather than a local directory lets you serve Web pages from a centralized network computer, even when that system doesn't run IIS. When you specify the A share located on another computer option in the virtual directory's properties, the Virtual Directory tab displays the Network Directory text box and a Connect As button (rather than the Local Path text box and Browse button). In the Network Directory text box, use the Universal Naming Convention (UNC) \\computername\share to specify the remote target system and share.
To establish a connection to the specified target share, you must click Connect As to open the Network Directory Security Credentials dialog box, in which you must supply a valid username and password. You can supply these credentials in one of two ways. When both the Web server and the target machine are members of the same domain or a trusted domain, you can supply domain credentials in the domain\username format; so long as the specified user account has access to the target machine, the connection will succeed. When the machines don't operate within the same domain or a trusted domain, you need to establish a connection in the context of a user with access to the target machine. To do so, you can create two identical accounts on both machines and supply identical passwords for both accounts. For example, I created an account named IISRemote on the target machine (i.e., Leonbrpridotnet) and created another account named IISRemote on the Web server (i.e., Altoid), then specified the same password for both accounts.
If you supply credentials that don't have access to the target machine, the Error icon appears beside the virtual directory in the Internet Information Services console and the System log displays a warning message explaining the cause of the problem (e.g., the account is disabled, the account doesn't exist on the target machine). The sample event in Figure 3 shows that the account I'm using to try to connect (i.e., IISRemote) is disabled. (If the target machine is configured to audit logon and logoff events, a logon failure event also appears in the Security log on that system.)
Keep in mind that all access to the target share occurs according to the user account you specify through the Connect As setting, but the Internet Information Services console lists the contents of the share in the security context of the user who opens the MMC snap-in. Therefore, if you open the snap-in as a local IIS machine user who doesn't have access to the target share, don't be surprised to see an Access Denied error when you try to enumerate files in the virtual directory. Although not being able to see a list of target-directory files is certainly inconvenient, IIS can still serve the files because file access relies on the Connect As account's security context.
An important step in restricting general access to a target resource is to disable anonymous access to the target directory so that access will depend on a user's credentials. Furthermore, when you configure a virtual directory to use a local directory and the local system uses NTFS, you can take advantage of NTFS's granular access-control settings (i.e., access control entries—ACEs—and ACLs) to permit or deny specific user account access to HTML files, Active Server Pages (ASP) pages, and other resources. (For more information about ACEs and ACLs, see Tim Huckaby, "ACL-Based Security Tips for IIS," http://windowswebsolutions.com, InstantDoc ID 22444.) If you've given an authenticated user permission to access a resource, IIS will grant the client request.
As for virtual directories that you configure to access network shares, the process is a bit more complicated. IIS is a process (inetinfo.exe) that runs under the Local System account. IIS has a thread pool that manages incoming connections. For each request, IIS uses a thread from the pool to access files and send results back to the client. Because the Local System account has too many rights on the local system to safely handle client requests, IIS impersonates the user, temporarily changing the thread's identity to access the required resource in the correct security context. To impersonate the user, IIS uses credentials that the Web client supplies. If a client doesn't supply credentials and you haven't disabled anonymous access, the thread impersonates the IUSR_machinename account, which IIS creates during installation and uses for anonymous access. To explore the security context of an IIS thread, you can call a Win32 function—for example, the GetUserNameEx() call—that returns the username of the account under which the thread is running. Because you can't call an API directly from ASP, I've written an ActiveX DLL that calls such a function. You can use the script that Listing 1 shows to create an .asp file that uses this DLL to analyze the security context used to access a virtual directory that resolves to a remote share through UNC. (To use this script as is, however, you also need to install and register the files in the ComGetUserName.zip file at http://windowswebsolutions.com, InstantDoc ID 25930.) This simple script not only reveals a thread's security context but also shows the Windows account that IIS impersonated to access the virtual directory's specific physical path. The script creates an instance of the COM object on the IIS machine. ASP then invokes a method from the COM object and gets the username that the GetUserNameEx() API returns. From the IIS process, ASP directly obtains the PATH_TRANSLATED server variable, which shows the physical path of the .asp file, and the LOGON_USER server variable, which shows the logon credentials of the user who invoked the script.
Figure 4 shows the results for an anonymous request when the sample .asp file getusername.asp resides in the local Scripts virtual directory on the Altoid server. In this example, I haven't disabled anonymous access to the target directory. The test results show that the LOGON_USER variable is empty, which indicates an anonymous request; for a nonanonymous request, this line would show the same name as the IIS thread runs under variable. The IIS thread runs under the local account IUSR_ALTOID.
Figure 5 shows the results for an anonymous request when the .asp file is in the RemoteScripts virtual directory, which maps to a UNC share. The LOGON_USER variable is still empty, but the thread now runs under the IISRemote user account, which is the account that I entered in the virtual directory's Connect As option. Remember, all access to UNC shares occurs in the context of the user you configured under the Connect As option. This rule is true even when you've disabled anonymous access and the user is authenticated.
Figure 6 shows the results for the next test, in which I disabled anonymous access and enabled Basic authentication on the virtual directory that maps to the UNC share. I supplied the credentials (i.e., altoid\LocalAltoid) for a user account that exists only on the IIS machine. Although the LOGON_USER variable now shows the authenticated user (i.e., altoid\LocalAltoid), the IIS thread still runs under the ALTOID\IISRemote account, which exists on both machines, using the same password on both.
You can, however, change this default behavior and pass through client credentials directly to the remote machine. This method employs a security delegation, in which the Web server delegates the client's credentials to a third machine that exposes the UNC share. To enable pass-through delegation, disable anonymous access for the remote share (or for the file on the remote share) and use either Basic or Kerberos authentication. (Both of these schemes support delegation, which is a requirement for pass-through access. However, Basic authentication sends the initial credentials from the client to the server in clear text—something you should take care to avoid, for example, by requiring a Secure Sockets Layer—SSL—connection.) You also need to enable pass-through access in the IIS metabase. To do so, run the following command, which you need to enter on one line:
After you run this command, restart IIS—by running the IISReset command, for example—to make sure that no security tokens are cached. As Figure 7 shows, when I enter domain credentials (i.e., REDMOND\leonbr) to request getusername.asp after enabling pass-through delegation, the IIS thread runs under the same credentials as the LOGON_USER variable.
Preventing pass-through authentication is as simple as enabling it. Run the same command, but change the final parameter to False instead of True.
When you don't enable pass-through delegation, all access occurs in the security context of the configured Connect As user account. However, when you enable pass-through authentication, you can use network-access permissions to control access to remote content in a fashion similar to using NTFS file permissions. This process lets you control user access regardless of file system. In Windows Explorer, open the Properties dialog box for the directory that you want to share. Go to the Sharing tab and click Permissions, then add and delete users to configure network access to the share. (Don't confuse this process with adding users on the Security tab, which controls access through the file system.)
Not everything is perfect with directory redirection. One bug in IIS 5.0 and IIS 4.0 relates to loss of File Change Notifications. As a performance optimization measure, IIS caches static files and ASP pages. When a cached file changes, IIS receives a File Change Notification and deletes the file from the internal cache. If a notification is lost—and this can be the case when you use UNC paths—IIS continues to serve the old version of the file. To remedy this problem, disable caching of static files. To do so, go to the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Inetinfo\parameters registry subkey and set the DisableMemoryCache value (of type REG_DWORD) to 1. Alternatively, you can edit the WWW Service Master properties. Open the WWW Service Master Properties dialog box and go to the Home Directory tab. Click Configuration, then go to the Process Options tab and select the Do not cache ASP files option. The Microsoft article "File Change Notifications Are Lost When Content Is on a UNC Share" (http://support.microsoft.com/default.aspx?scid=kb;en-us;q281253) contains more information about this bug and the workaround. (Note that disabling caching negatively affects IIS performance. Of course, serving files from UNC shares isn't the fastest operation, either. Test thoroughly to determine whether these solutions provide acceptable performance.)
Another problem related to servicing files from a UNC share manifests itself as a Web server hang and occurs when multiple IIS machines use the same UNC share as a common file repository—a situation that can arise in a Web farm. The problem is rooted in the limited number of connections that can be established from client machines to the server that exposes the UNC shares. To remedy the problem, modify the registry on the Web servers and the file server. Under the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstationParameters subkey on the IIS machines, set the MaxCmds value (of type REG_DWORD) to 0x00000800. Under the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters subkey on the file server, set the MaxMpxCT value (of type REG_DWORD) to 0x00000800 and the MaxWorkItems value (of type REG_DWORD) to 0x0000FFFF. The Microsoft article "IIS Runs Out of Work Items and Causes RPC Failures When Connecting to a Remote UNC Path" (http://support.microsoft.com/default.aspx?scid=kb;en-us;q221790) includes more information about resolving this problem. The Microsoft article "ASP Returns Continuous 'Include File Not Found' Errors" (http://support.microsoft.com/default.aspx?scid=kb;en-us;q288270) contains information about a fix for one other problem with opening include files on the UNC directory.
Directories on the local file system or remote network shares are the most common targets for IIS virtual directories; network shares let you leverage IIS's ability to store content on machines that don't even run IIS. In my next article, I'll examine the third option—URL redirection.