Working with DCOM


DCOM is one of the oldest methods for performing object communication across machine boundaries. When a client application attempts to instantiate a component, Windows looks the entry up in the registry. This entry can point to another machine, in which case, Windows calls on DCOM to make the connection. DCOM is a wire protocol (an RPC)—it affects communication between two machines over the network wire (no matter how long that wire might be). In fact, DCOM is the basis for COM+ communication. You can read more about DCOM at http://msdn.microsoft.com/library/en-us/dndcom/html/msdn_dcomtec.asp. Learn more about how COM+ uses DCOM as a wire protocol at http://www.microsoft.com/msj/0398/dcom.aspx.

The sections that follow won’t teach you DCOM basics. You need to know about DCOM and understand how it works before these sections will prove very helpful. However, these sections will help you understand how DCOM and the .NET Framework interact to a certain extent, especially in the area of security. The sections also provide a little general information because you need it to know how the security features work.

Maintaining Control with COM Attributes

One of the best security guidelines you can follow is to maintain control over the environment. The .NET environment is completely different from the COM environment, which means you have to exercise extreme care if you want to maintain a secure server. COM uses an entirely different set of rules from the ones you’ve used with the .NET Framework. Consequently, you need to address some compatibility and maintainability issues when working with COM in a managed environment.

Note

The use of COM in this section is generic. The attributes described also affect DCOM and COM+ applications.

One of the first problems that you’ll need to consider is how to create a managed version of a COM interface. The best way to do this is to add one or more of four COM interface attributes to the interface description. These attributes tell the compiler to add information to the assembly that it wouldn’t normally provide. The following list tells you about each of the attributes.

[ComImport] This attribute tells the compiler that the interface is based on a COM interface with the same name. The .NET environment will actually import the COM definition for you. To use this attribute, the class in question must derive from Object, which means that many COM interfaces won’t work with this attribute. Make sure you read the documentation for the interface completely. Always use the [ComImport] attribute with the [Guid] attribute—you must specify the GUID of the COM class that the interface will use. Finally, the class must not have any members—the .NET environment creates the required public constructor (without any parameters) automatically. In sum, this is a fast way to create an interface definition, but it’s limited.

[InterfaceType] This attribute describes the type of exposure to provide for the interface when exposed to COM. The acceptable values include dual, IUnknown, and IDispatch. Generally, implementing a dual interface is best because older versions of Visual Basic rely on IDispatch, while older versions of Visual C++ rely on IUnknown. However, you can make the component slightly smaller by implementing one interface or the other if the component has a specific target environment.

[ClassInterface] This attribute tells the compiler that it should expose a particular class as an interface definition to COM callers. You use this attribute as part of a class/interface pair, as shown in Listing 8.3 (see the “Developing a Component with Attributes” section). In general, you expose the interface, but keep the class implementation hidden. This attribute helps keep the class hidden. Of course, you can also use it expose the class directly (as in the [InterfaceType] attribute description).

[Guid] This attribute assigns a globally unique identifier (GUID) to the interface. This must be the same GUID used by the COM unmanaged counterpart. If you aren’t implementing an existing interface, then use the GUIDGen utility to create a new GUID. Using the [Guid] attribute isn’t mandatory, but it should be to ensure that the GUID for your component remains consistent.

start sidebar
Component Management Considerations for GUIDs

Here’s an important housekeeping issue for COM+ components. Using .NET components that lack the [Guid] attribute with COM+ could create a mess that you never dreamed possible in the registry. A globally unique identifier (GUID) provides a method for unmanaged applications to identify your component. The GUID must remain the same if you want to use any of the calls that rely on the GUID to instantiate the component. Therefore, the first reason to use the [Guid] attribute is to ensure that you can document the GUID for unmanaged application calls that require it.

However, the importance of using the [Guid] attribute doesn’t end with simple identification. Another reason to use the [Guid] attribute is to keep your development machine reasonably clean. Every time you register a component, the registration application makes entries in the registry. If you don’t assign a GUID to the component, the Common Language Runtime (CLR) will select a new GUID each time automatically for you. CLR assigns this GUID at random and the GUID won’t be the same from registration to registration.

Theoretically, unregistering the component removes the registry entry, but experience shows otherwise. In some cases, developers have ended up with dozens of entries for the same component in the registry, all of which required manual removal. Trying to find all of the entries for a complex component is time consuming and error prone. These leftover entries can act as breadcrumbs for someone trying to discover information about your system, so cleaning them up isn’t optional.

COM+ also requires the [Guid] attribute for another reason. Imagine that you’re using servers in a cluster and that each server has a copy of your component installed on it. If the component doesn’t use the [Guid] attribute, each server could have a registry entry for the component under a different GUID, which effectively means that each server has a unique version of your component. This little problem makes it impossible for load balancing to work properly because COM will view each component as being different. In sum, even though every server has a copy of the same component, load balancing will see only one copy of the component on one server and won’t work as intended.

end sidebar

Developing a Component with Attributes

Developing a secure component means controlling every aspect of that component, including the GUID and the method of interface exposure. In many ways, the entire issue of security rests on control. You control exposure to ensure that only those with the proper access can use your code. Listing 8.3 shows a typical example of a managed version of the COM interface. When you compile and register this component, it will appear to any client as a COM component. The important thing is that you’ve maintained control over the environment so that the component works as intended without side effects. You’ll find this example in the \Chapter 08\C#\DCOMComp and \Chapter 08\VB\DCOMComp folders of the source code located on the Sybex Web site.

Listing 8.3 Using Attributes in a Managed Component

start example
namespace DCOMComp {     [Guid("EA82646C-2531-42ff-AABF-55028FE0B0B5"),      InterfaceType(ComInterfaceType.InterfaceIsDual)]     public interface IMathFunctions     {         Int32 DoAdd(Int32 Value1, Int32 Value2);         Int32 DoSubtract(Int32 Value1, Int32 Value2);         Int32 DoMultiply(Int32 Value1, Int32 Value2);         Int32 DoDivide(Int32 Value1, Int32 Value2);     }     [Guid("0C4340A2-C362-4287-9A03-8CDD3D1F80F6"),      ClassInterface(ClassInterfaceType.None)]     public class MathFunctions : IMathFunctions     {         public Int32 DoAdd(Int32 Value1, Int32 Value2)         {             return Value1 + Value2;         }         public Int32 DoSubtract(Int32 Value1, Int32 Value2)         {             return Value1 - Value2;         }         public Int32 DoMultiply(Int32 Value1, Int32 Value2)         {             return Value1 * Value2;         }         public Int32 DoDivide(Int32 Value1, Int32 Value2)         {             return Value1 / Value2;         }     } }
end example

To build this component (or any component that will interact with COM), you need to construct the interface first. As you can see from Listing 8.3, the IMathFunctions interface simply lists the public members of the MathFunctions class. After you type the MathFunction declaration, press Tab (Visual Studio will prompt you to do this) and Visual Studio will automatically populate the class with the required public methods.

Notice that the example code uses both the [InterfaceType] and [ClassInterface] attributes. The ComInterfaceType.InterfaceIsDual argument tells the compiler to make the IMathFunctions interface available to both IDispatch and IUnknown users. The ClassInterfaceType.None value tells the compiler to keep the class interface hidden. Not only is this a good practice for making your component easier to use, but it also makes the component more secure because unmanaged code will have a harder time seeing the implementation details of your component.

Some developers discount the benefit of giving the component a strong name, but that’s a mistake. Even if you don’t intend to provide global access to the component by registering it in the Global Assembly Cache (GAC), the strong name tends to ensure that no one modifies your code. See the “Circumventing and Fixing the Standard Check” section of Chapter 6 for a discussion of potential problems with the security that the .NET Framework provides for code. The solutions in Chapter 6, such as using a hash stored in a secret location, work even better with components than they do with applications because components tend to change less often (therefore, the secret hash remains valid longer).

You must register this component with the registry if you intend to use it with unmanaged code. Use the RegAsm DCOMComp.DLL /tlb:DCOMComp.TLB command at the command prompt to perform the registration. To unregister the component, use the RegAsm DCOMComp.DLL /unregister command. The GACUtil -i DCOMComp.DLL command lets you add the assembly to the GAC so that it’s available for global use. Likewise, the GACUtil -u DCOMComp removes the component from the GAC (notice there’s no file extension used with the removal command. Always unregister an old version of a component and remove it from the GAC before you install a new version of the component on a machine.

Creating a Test Application

It’s important to consider the client that the component in Listing 8.3 targets. If you simply wanted to create a component for a .NET application, you’d use the standard procedures to do it and then rely on the remoting services that .NET provides to provide access. However, this section of the book is about using DCOM and allowing unmanaged applications to interact with your managed component in the safest manner possible. Listing 8.4 shows a typical unmanaged code implementation with full security using Visual C++. You’ll find this example in the \Chapter 08\C#\DCOMCompTest folder of the source code located on the Sybex Web site.

Listing 8.4 Safely Calling .NET Components from Unmanaged Code

start example
void CDCOMCompTestDlg::OnBnClickedTest() {    IMathFunctions*   MF;         // Object pointer.    COAUTHIDENTITY    UserInfo;   // User identity information.    COAUTHINFO        Authorize;  // Component authorization.    COSERVERINFO      ServInfo;   // Server information structure.    MULTI_QI          Comps;      // Return container for interface.    long              Result;     // Calculation result.    CString           Output;     // Output from call.    // Initialize the object and COM.    MF = NULL;    CoInitialize(NULL);    // Set the user information.    ZeroMemory (&UserInfo, sizeof (UserInfo));    UserInfo.Domain = (USHORT*)OLESTR("YourDomain");    UserInfo.DomainLength = strlen("YourDomain");    UserInfo.Password = (USHORT*)OLESTR("YourPassword");    UserInfo.PasswordLength = strlen("YourPassword");    UserInfo.User = (USHORT*)OLESTR("YourName");    UserInfo.UserLength = strlen("YourName");    UserInfo.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;    // Set the authorization information.    ZeroMemory (&Authorize, sizeof (Authorize));    Authorize.dwAuthnLevel = RPC_C_AUTHN_LEVEL_CONNECT;    Authorize.dwAuthnSvc = RPC_C_AUTHN_WINNT;    Authorize.dwAuthzSvc = RPC_C_AUTHZ_NONE;    Authorize.dwCapabilities = EOAC_NONE;    Authorize.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;    Authorize.pAuthIdentityData = &UserInfo;    Authorize.pwszServerPrincName = NULL;    // Create the server information structure.    ZeroMemory (&ServInfo, sizeof (ServInfo));    ServInfo.pwszName = L"Winserver";    ServInfo.pAuthInfo = &Authorize;    // Create the interface container.    Comps.pIID = &IID_IMathFunctions;    Comps.pItf = NULL;    Comps.hr = 0;    // Instantiate the object.    CoCreateInstanceEx(CLSID_MathFunctions,                       NULL,                       CLSCTX_REMOTE_SERVER,                       &ServInfo,                       1,                       &Comps);    // Cast the returned interface to a local object.    MF = (IMathFunctions*)Comps.pItf;    // Perform the addition.    Result = MF->DoAdd(1, 2);    // Display the result.    itoa(Result, Output.GetBuffer(10), 10);    Output.ReleaseBuffer(-1);    Output = "1 + 2 = " + Output;    AfxMessageBox(Output);    // Uninitialize COM.    CoUninitialize(); }
end example

Overall, this is a standard call to a component using Visual C++. The important piece of information for this book is the security setup at the beginning of the listing. Many developers don’t set any security on components—they make them available to the Everyone group because they feel that secure access is too much trouble. Even with Visual C++, which is notoriously difficult to use for writing code, you can set up secure access with relatively few steps.

Authorization centers on the three data structures shown in the code. The COAUTHIDENTITY data structure contains the user’s name, password, and domain name. I haven’t used real values in the listing for the obvious reason—my name and password would never work on your system. The COAUTHINFO data structure contains information on how to authorize the user. The example provides standard values that you can use for most situations when working with a managed component. Finally, the COSERVERINFO structure provides the name of the server and packs the rest of the authorization information. In short, security is only mildly difficult and you should use it with every component you create.

If you decide to use this example on a single machine, you’ll need to make two changes to the CoCreateInstanceEx() function call. Change the CLSCTX_REMOTE_SERVER entry to CLSCTX_ALL. This change allows local access. In addition, change the &ServInfo entry to NULL. You don’t need to provide server information for local component access.




.Net Development Security Solutions
.NET Development Security Solutions
ISBN: 0782142664
EAN: 2147483647
Year: 2003
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net