Remote Data Services (RDS) is a collection of COM objects (DataFactory, DataControl, and DataSpace) that lets you add HTTP capabilities to ADO. With RDS, you access data sources to obtain data and to set data values over HTTP. For example, you can request a recordset from an HTML page or invoke a business object over a particular Web server.
After reading my article "Remote Data Services in Action," April 2000, a few readers contacted me about some RDS security problems. On the readers' machines, even the simplest RDS demo using the standard DataFactory server didn't run; the demo returned the uninformative error code 0x80070005. Curiously, I was unable to reproduce the same error, even when I used the same (presumably wrong) piece of code on two different machines. At that point, I was certain that the problem lay in an obscure area of the system Registry or in some other place where the Windows system stores configuration data.
For a while, I wasn't able to figure out a reasonable explanation for what appeared to be a supernatural software event. (The Registry glitch was only a supposition.) Then, I installed Windows 2000 Professional on a new, freshly formatted machine. When I finished replacing all the necessary applications, I connected to a page of my intranet where I used RDS. To my surprise, I got the long-awaited 0x80070005 error message and, with it, enough information to start investigating.
RDS Security Issues
Let's start at the end of the story. The guilty party is an innocent-looking Registry entry called HandlerRequired, which RDS uses to enable or disable additional security checks. RDS is part of Microsoft Data Access Components (MDAC), which is the official Microsoft data-access tool chest for any software running under the Microsoft .NET architecture umbrella.
Beginning with MDAC 2.0 (Win2K ships with MDAC 2.5), Microsoft improved the RDS security infrastructure, adding new features to prevent intruders from using Microsoft IIS-based servers to execute malicious operations across the Internet. The keys to this security mechanism consist of COM objects that implement a special COM interface called IDataFactoryHandler. Such objects are often called RDS handlers.
The handler's role is simple: to mediate access to data sources, thereby keeping them from being directly exposed to the Internet. You can write your own handler (I give instructions in the sidebar "Writing Custom Handlers," page 50) or rely on the default handler that Microsoft provides. The handler constitutes an additional layer of code that filters and secures data access. You can enable or disable this extra layer on any machine, but this setting is global and applies to all RDS applications connecting to the specified IIS server.
The Registry is where you set your security preferences, whether you want all the RDS calls to pass through a handler or to pass without a strict control down to the specified data sources. Let's look closely at why Microsoft implemented handlers and examine how they work.
Handlers and DataFactory
RDS lets ADO client applications connect to remote OLE DB data sources that run in separate processes on the same machine or on separate machines connected through HTTP, HTTP over Secure Sockets Layer (HTTPS), or Distributed COM (DCOM). Client-side applications talk to a remote COM object whose ultimate goal is accessing data through ADO and OLE DB. Note, however, that although RDS specializes in data retrieval through ADO, nothing prevents you from accessing a COM object that does everything but issue a SQL-based query.
RDS defines a standard component called DataFactory, which acts as a query executor. Through this component, client applications can work directly with an OLE DB provider on the remote process or machine and send and receive ADO recordsets. The body of the DataFactory component contains no special logic to take care of read and write permissions and user privileges—in a word, security. DataFactory is a simple query executor that serves any client that knows the right user ID (UID) and password for a given database. This situation can lead to direct data exposure across the Internet and open a way for potential intruders to intercept the UID and password and steal or alter data.
To work around DataFactory's potential security flaw, Microsoft introduced RDS handlers in MDAC 2.0. Basically, an RDS handler is the component that the RDS DataFactory object invokes to access data. Because you can write and deploy handlers as part of the RDS application, you can control what goes on during the data-retrieval process and can handle any facet of security yourself. Figure 1, page 50, shows the extended RDS architecture, which includes handlers.
You must properly register a handler both as a COM object and as an RDS handler. (I show you how to register a handler later.) Also, the handler must implement the IDataFactoryHandler COM interface to ensure that the handler can interact properly with the DataFactory object. A COM interface is a list of predefined function signatures. When the caller and the callee agree on a given interface, the former can safely call any of those functions, being sure that the latter will provide executable code for them.
By putting an RDS handler between the client-side code and the DataFactory query executor, you can better control the data-retrieval process and reinforce the otherwise limited security of the data. Handlers are relevant to you only if you use the standard DataFactory object to access data. They simply fill the security holes inherent in DataFactory's design and implementation. These holes let you go directly to the data without the mediation of any data-access component. Handlers are an extra layer of code that add custom logic on top of DataFactory's functionality.
Handlers and Custom Components
Using the DataFactory object is only one of your options for accessing data through RDS across the Web. Even with the combined use of DataFactory and handlers, you can't reach the peaks of security, flexibility, and performance that a handcrafted data-access component can offer. Using custom components instead of DataFactory provides three main features that can be advantageous (or not), depending on your needs. With custom data-access components, you can run only hard-coded queries; carefully check privileges, roles, and permissions; and get more speed because you don't pass through an additional COM object—the handler.
Writing a custom data-access component, however, requires more skills than does simply calling an existing and well-documented component such as DataFactory. Still, custom objects are generally preferable because they don't use handlers, they keep your code more compact, they can run faster, and they give you the greatest flexibility and security. To write good business objects, you need relevant COM and COM+ skills as well as familiarity with Visual Basic (VB) or Visual C++ (VC++). Handlers don't affect any custom data-access component you might want to use instead.
Safe and Unsafe Operations
The RDS security problem that my readers and I ran into originated from a feature of the setup program that installs MDAC 2.1 and later. Figure 2 shows the error message we got. Handler support is determined by the value stored in the HandlerRequired subkey under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Data Factory\HandlerInfo Registry subkey. When the HandlerRequired Registry subkey is set to 1, RDS is working in safe mode; DataFactory will access and return data only through a handler. You can specify a custom handler or rely on the default, whose programmatic identifier (ProgID) is MSDFMAP.Handler. The Handler item in the data-source connection string shows the ProgID of the chosen handler. Omit the Handler item information to use the standard handler. When HandlerRequired is set to 0 or is missing, RDS works in unsafe mode—that is, DataFactory doesn't rely on handlers to get or modify data, and the UID and password for a given database are the only requirements to run any query that the UID has permission to run.
By default, MDAC 2.0 installs with HandlerRequired set to 0 (unsafe mode) to keep from breaking existing RDS 1.5-compliant applications. Beginning with MDAC 2.1, however, Microsoft changed the security policy so that RDS works in safe mode with any new installation of MDAC unless you install MDAC 2.1 or later on top of an existing MDAC 2.0 or earlier installation. For example, I upgraded one of my machines from Windows NT to Win2K. The machine was already running MDAC 2.0 in unsafe mode because of the previous RDS 1.5 version. Usually, installing Win2K on a freshly formatted machine gives you MDAC 2.5 with handlers enabled on all of its standard settings to reinforce security. But because upgrading MDAC doesn't change the Registry settings for security, my RDS applications stopped working properly when I restored them.
You can solve this problem in two ways. The first option is to disable the handler-based security mechanism. Alternatively, you can modify all your RDS client code in all your RDS-based applications to make the code support handlers. If you decide to solve the problem by disabling handlers, you don't need to edit a single line of your client code, but you risk undesired exposure to intruders. Disabling handlers is as easy as running the script in Listing 1. Note that you need to enter these changes on the server machine only. For the change to take effect, you need to reboot the machine.
The Default Handler
To support RDS handlers in your code, first decide which handler you want to drive your data access. As I stated earlier, an RDS handler is a COM object whose only requirement is that you implement the IDataFactoryHandler interface. You decide what logic to use to ensure safe and controlled access. DataFactory calls the interface's two methods to read and write records. Table 1 describes the two methods and shows how to call them.
RDS 2.0 and later versions provide a default handler called MSDFMAP.Handler, which processes only the queries that the administrator has explicitly authorized through an .ini file. This .ini file, which is called msdfmap.ini and is in the Windows directory on the server, designates all the operations that the RDS.DataFactory object can execute on that server. In the .ini file, each authorized operation must have a name and has four sections: connect, sql, userlist, and logs. If the same operation name appears more than once in the .ini file, the handler considers only the first occurrence and ignores all the subsequent occurrences.
The sample msdfmap.ini file in Listing 2, page 52, contains an operation called EmployeeDB. The names of the first three sections in the script are composed of the keyword plus the name of the operation; the logs keyword doesn't need the operation name. If the file you specify in the logs section already exists, RDS appends any new errors to that file. Otherwise, RDS creates a new file to log errors to.
The connect section specifies the access type and the connection string. Note that if you plan to use ODBC to access data, you need to use a system Data Source Name (DSN), not a user DSN. The sql section contains the command text to execute, which can contain as many parameters as you need. Each parameter must have a question-mark placeholder in the text:
Sql="SELECT * from Employees where (lastname = ?" and firstname = ?)
The mapping between the actual and the formal parameters is established by position: The first actual parameter maps to the first occurrence of the question mark, and so forth. You use the userlist keyword to change for certain users the default permissions set in the connect section. In Listing 2, when users execute the EmployeeDB operation, all users except Administrator and Bibi have read-only access to the data. To ascertain the user credentials, RDS relies on a powerful IIS authentication method, which I describe in the sidebar "RDS Clients' User Credentials," page 52.
Coding to Use RDS Handlers
Let's look at how to write VBScript code that obtains a recordset through ADO and RDS with and without handlers. In an unsafe context, the following code would work:
strLastName = frm.empName.value
sql = "select * from Employees where lastname='" & strLastName & "'"
set rds = CreateObject("RDS.DataSpace")
set df = rds.CreateObject("RDSServer.DataFactory", server)
set rst = df.Query("DSN=NW", sql)
But if you run the same code on a machine where RDS is set to work safely, you'll get the error message box that Figure 2 shows.
To work in safe mode, RDS always needs a handler. If you don't specify a handler, RDS uses the default or raises an exception. You can set the default handler's ProgID in the DefaultHandler subkey under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ DataFactory\HandlerInfo Registry subkey.
To use your own handler, register it as the default; then, you don't need to change anything else in your client code. If you want to use the RDS default handler, you must change all client code slightly to make the code comply with the authorized operations listed in the .ini file. To return a recordset with all the employees from the Northwind database, issuing a command such as
or a connection string such as
isn't enough, and raises an error like the one in Figure 2. When you're working with the standard handler, you need to refer to the names of the operations instead of passing the raw command and connection strings. To make your application support the EmployeeDB operation, add the following lines to the msdfmap.ini file:
Sql="SELECT * from Employees where (lastname = ? and firstname = ?)"
Now modify the unsafe block of code from the beginning of this section in this way:
strLastName = frm.empName.value
sql = "EmployeeDB('" & strLastName & "')"
set rds = CreateObject("RDS.DataSpace")
set df = rds.CreateObject("RDSServer.DataFactory", server)
set rst = df.Query("Data Source=EmployeeDB", sql)
The connection string becomes
and the parametric query string becomes
where Davolio is one of the last names in the Northwind database. If you're using more parameters, use comma-delimited arguments:
If you want to use a nondefault handler, or if you want to specify the handler's ProgID anyway, the correct syntax is
If you plan to use the RDS.DataControl component for data binding, the approach is only slightly different from the previous one:
dc.Server = "http://expoware";
dc.Connect = "Data Source=EmployeeDB";
dc.Handler = "MSDFMAP.Handler";
dc.Sql = "EmployeeDB('Davolio')";
You can also use custom .ini files to configure the default MSDFMAP .Handler handler if you want to keep settings separate. To configure the handler in this manner, modify the handler string to
All .ini files for MSDFMAP.Handler must reside in the Windows directory.
More RDS Information
The Microsoft Developer Network (MSDN) contains plenty of valuable information about RDS. The easiest way to select all the most relevant articles is to search the site, using RDS as the keyword.
Here are a few must-read articles I've found especially informative. To help you understand the key role of RDS 2.x and handlers, I recommend two Microsoft technical articles on the January 2000 MSDN CD-ROM: "Remote Data Service in MDAC 2.0" and "Using the Customization Handler Feature in RDS 2.1." If you already know the basics of RDS and are looking for tips and tricks, the following Microsoft articles cover RDS topics such as security, limitations, hierarchical recordsets, and custom business objects:
- "PRB: Firewalls or Proxy Servers Can Cause RDS to Fail" (http:// support.microsoft.com/support/kb/articles/q221/2/40.asp)
- "FIX: RDS 2.0 Client Cannot Submit Changes to RDS 1.5 Server" (http://support.microsoft.com/support/kb/articles/q198/5/31.asp)
- "PRB: Security Implications of RDS 1.5, IIS 3.0 or 4.0, and ODBC" (http://support.microsoft.com/support/kb/articles/q184/3/75.asp)
Note also that you can ask the Microsoft Support Web site to email you these documents. Just write to email@example.com and specify the article code (e.g., Q184375) on the subject line; you'll receive the article within a few hours.