Binding Options

I often talk about Active Directory in somewhat vague terms. We know it is a database distributed to all the domain controllers within an enterprise forest. Presumably, several servers could respond to Active Directory queries. But how do you write code that talks to the appropriate server?

Serverless Binding

In many of the examples presented here, I use an ADsPath that contains DC=coppersoftware,DC=com, which is the distinguished name of my Active Directory domain. The LDAP provider figures out which is the closest domain controller (DC) for that domain to communicate with. Each domain can have multiple DCs, each with a replicated copy of the domain's Active Directory. Ideally, each DC is located in a particular network site that has high-speed connectivity to all the workstations in that site. (See Chapter 2 for information about subnets and sites.)


Notes on Strings in C and C++ Programs

Be aware that there are considerations when you prepare an application for an international market. Windows NT and Windows 2000 support the Unicode character set, which uses two bytes to represent a character. Windows 98 and Windows 95 use either the one-byte ANSI character set for many Western languages or the double byte character set (DBCS), also known as the multi-byte character set (MBCS), for languages that require multiple bytes to represent a single character.

To make code as portable as possible, you should use the Microsoft-specific generic-text mappings defined in Microsoft Visual C++. Generic-text mappings include definitions for various library functions so that they can be mapped at compile time to either the single-byte, double-byte, or wide-character (Unicode) variant of the function. Thus, _tprintf becomes wprint when the program is compiled with _UNICODE defined. This mapping extends to data types as well, so _TCHAR chVarible would declare chVariable to be a char (single-byte character) under ANSI, and a wchar_t (two-byte character) when compiled with _UNICODE defined. The underscore character (_) indicates that the function, macro, or datatype is not part of the Standard ANSI C/C++ language definition. Microsoft Visual C++ prefixes _t to all generic text macros.

To complicate matters, COM strings, including all ADSI and Active Directory strings, must be Unicode strings. That means that even if you compile the program on Windows 98 using the ANSI character set, you need to specify that the strings passed to and returned from COM functions are Unicode. For string literals in C and C++, you do this by inserting the letter L before the string. For example, L"This is a wide string" would tell the compiler to generate a Unicode string regardless of whether _UNICODE is defined.

I've tried to make the sample programs in this book as friendly as possible to the various platforms and character sets, but it's impossible to test under all the possible conditions. Refer to the Visual C++ documentation for more information about developing international software.


The exact method used to find the nearest or most suitable domain controller is complex and out of the scope of this book. ADSI and the LDAP provider use the DsGetDcName function, available in Windows 2000 and previous versions of Windows with the Active Directory Client (DSClient.exe) installed. The DsGetDcName function depends on the correct registration of DNS records in the DNS server for the network. Many difficulties faced by network administrators working with Active Directory are a result of incorrect DNS configuration or operation. If you are experiencing such problems, I recommend the Microsoft Windows 2000 Server Resource Kit (Microsoft Press, 2000). I also recommend articles online at http://support.microsoft.com/support/win2000/dns.asp.

In my network, DC=coppersoftware,DC=com resolves to my domain controller, COPPER1. The following two ADsPaths are functionally identical, retrieving the Active Directory top-level object for the coppersoftware.com domain:

 LDAP://DC=coppersoftware,DC=com
LDAP://COPPER1

You should try to avoid specifying a particular server in your binding strings because doing so circumvents the built-in redundancy of Active Directory. If your application talks only to a specific server and that server is down temporarily, has been replaced, or has even been renamed, your program will fail needlessly, even though there may be another DC handling Active Directory requests.

What should you do if you don't even know the domain name, much less any server names? Active Directory has got that covered.

RootDSE

RootDSE is a special object of Active Directory that contains information about the directory service itself. RootDSE stands for Root DSA-Specific Entry. DSA is the X.500 term for directory system agent. RootDSE was introduced as part of LDAPv3 and is supported in Active Directory. Even though it is named the RootDSE, this object is not part of the directory tree and you cannot navigate to it. Its class and attributes are not part of the directory schema. This setup ensures that clients can access the RootDSE even though they might not be able to access any other part of the directory.

Using LDAP://RootDSE as the ADsPath binding string, you can discover a lot of useful information, such as the following:

  • The name and DNS address of the server responding
  • Versions of LDAP supported, along with any extended features, known as extended controls
  • The naming contexts (directory partitions) that are available on this particular server
  • The level of security and what security protocols are supported

Table 4-3 includes a complete list of RootDSE attributes. There are many, but you won't need to use most of them. The attribute that should interest you most at this point is defaultNamingContext. This attribute contains the distinguished name of the Active Directory for the current domain. You can use its value to form an ADsPath to the top-level domain object in Active Directory. The following Visual Basic function returns the Active Directory distinguished name for the user's default domain.

 Function GetDomainDN() As String
` This function returns the distinguished name of the domain's 
` Active Directory
Dim objADsRootDSE As IADs
   
    ` Get an IADs interface to the RootDSE
    Set objADsRootDSE = GetObject("LDAP://RootDSE")
    
    ` Return the naming context DN
    GetDomainDN = objADsRootDSE.Get("defaultNamingContext")
End Function

You can cut and paste this function into your Visual Basic programs and use it as follows:

 Sub Main()
` Get and display the domain directory name
Dim strDomainDN As String
Dim strADsPath as String
Dim objADsDomain As IADs
    ` Get the DN for users default domain
    strDomainDN = GetDomainDN
    
    ` Build ADsPath to directory
    strADsPath = "LDAP://" & strDomainDN
    Debug.Print "ADsPath to directory: " & strADsPath
    
    ` Access the directory
    Set objADsDomain = GetObject(strADsPath)
    Debug.Print "Name of the directory: " & objADsDomain.Name
End Sub

By always using the RootDSE object to discover information about the Active Directory configuration, you provide a level of flexibility in case the domain or the server name changes. Later, in the section "GUID Binding," I'll describe ways to identify objects even if they have been renamed or moved.

Attribute Description

configurationNamingContext

The DN of the Active Directory forest configuration partition.

currentTime

Current time at the server.

defaultNamingContext

The DN of the directory partition (i.e. DC=aviation,DC =coppersoftware,DC=com).

dnsHostName

The DNS name of the LDAP server.

dsServiceName

The DN of the directory object that contains options for this directory service.

highestCommittedUSN

The highest update sequence number (USN). Used for directory replication.

isGlobalCatalogReady

True if this service is also a global catalog server.

isSynchronized

True or False, depending on whether this server has completed synchronizing with replication partners.

ldapServiceName

The service principal name (SPN). Used for authentication.

namingContexts

A multivalued attribute containing the DN of each directory partition (naming context) that this server contains.

rootDomainNamingContext

The DN of the top-level domain directory partition (i.e. DC=coppersoftware,DC=com) for the forest.

schemaNamingContext

The DN of the schema directory partition.

Attribute

Description

serverName

The DN of the server processing the request.

subschemaSubentry

The DN of the object that contains information about the classes and attributes available in this service. Known as the abstract schema in Active Directory (discussed in Chapter 9).

supportedCapabilities

Multivalued attribute with a list of Microsoft- specific enhancements, uses object identifier (OID).

supportedControl

Multivalued attribute with a list OIDs of supported LDAP extension controls.

supportedLDAPPolicies

Multivalued attribute with a list of strings, each naming an LDAP policy for the directory service. These policies are controlled with the Ntdsutil.exe tool.

supportedLDAPVersion

Multivalued list of supported LDAP versions. Active Directory currently supports LDAP versions 2 and 3.

supportedSASLMechanisms

Multivalued list of security mechanisms supported for Simple Authentication Security Layer (SASL) negotiation. (See the LDAP RFCs.) By default, Generic Security Services API (GSSAPI) is supported.

becomeDomainMaster
becomeInfrastructureMaster
becomePdc
becomeRidMaster
becomeSchemaMaster

These operational attributes indicate that the server should take over the specified master operation role.

Table 4-3 RootDSE attributes.

The initial release of Windows 2000 had a problem with the LDAP module when LDAP://RootDSE was used as an ADsPath binding string. Service packs for Windows 2000 should fix this problem and other issues in the Wldap32.dll module. Refer to Microsoft Knowledge Base articles Q259739 and Q258507 for more information.

Global Catalog

In Chapter 2, I mentioned the global catalog (GC) as a feature that allowed for fast searches across domains. It accomplishes this by cataloging all the objects in the enterprise forest. The global catalog is not a namespace, and it does not have a hierarchy. It is simply a list of all the objects in a forest along with a partial set of attributes for those objects.

A forest represents an entire enterprise, irrespective of DNS names. Microsoft might have a forest configuration with the following trees:

 microsoft.com msn.com hotmail.com 

Each tree might have a number of subdomains, such as:

 research.microsoft.com mspress.microsoft.com 

Remember that in a forest, all the domains in each tree share a common schema and configuration. That means that each domain controller replicates the schema and configuration partitions to its parent domain controller. However, the domain directory partition where objects specific to a domain reside is not replicated to parent and child domains and thus is not available across the entire enterprise.

If the domain partitions are not replicated between domains, how does my editor at Microsoft Press contact someone in Microsoft Research to verify my claims of Internet bottlenecks causing delays in submitting chapters for this book?

The Microsoft Press domain contains information only about the good, understanding, and patient folks employed in that division, like my editor, who is a pillar of patience. [Enough already – Ed.]. Searching the domain's directory partition for a colleague's e-mail address would be easy. However, the e-mail address of someone at Microsoft Research would not be contained in the Microsoft Press domain directory.

One possible solution would be to replicate the domain partitions for the forest to all the domain controllers in that forest. That way, the domain controllers for Microsoft Press would contain all object information for the folks in Microsoft Research. However, doing that is a huge replication issue and would bog down the network with continuous replication traffic and quite possibly exceed the storage of local domain controllers. It also defeats the purpose of having distributed directory partitions in the first place.

A more elegant solution is to simply place the most requested information at a location accessible to all clients. The e-mail address, along with the full name of a person, is defined as being part of the partial attribute set and as such is replicated to all domain controllers that are defined as global catalog servers.

Global Catalog Servers

By default, the first domain controller created in an Active Directory site is the global catalog server for that site. By placing at least one global catalog server in each site, a client can avoid having to communicate over a potentially slow link between sites. Each domain has at least one site, which is named Default-First-Site-Name by default. Network administrators can control which domain controllers in a particular domain are global catalog servers in order to spread query traffic among servers. You can see the structure of the global catalog in Figure 4-3.

Accessing the Global Catalog

Since the global catalog is not a logical namespace, the global catalog server separates queries between its directory partitions and the global catalog by using a special TCP/IP port for the global catalog. To query only the global catalog, a client must make the request to port number 3268 on the global catalog server.

ADSI accommodates the global catalog using GC: instead of LDAP: for the ADsPath binding string. In reality, they both use the LDAP provider, but binding with GC: indicates that port 3268 of the server should be used. Why not use LDAP://servername:3268 instead? Technically that would be the equivalent to GC://servername because a global catalog server responds on TCP/IP port number 3268. It's best not to request a specific global catalog server. Also, ADSI will use TCP/IP port number 3269 when performing an encrypted connection to a global catalog server. By using GC: instead of LDAP:, you can let ADSI pick which ports and servers to contact.

Figure 4-3 Information available on a global catalog server.

The following code snippet from the GlobalCatalog sample on the companion CD shows two methods to bind to the nearest global catalog server.

 ` Method 1 - Enumerate provider
` Bind to the global catalog root
Set objGC = GetObject("GC:")
` Enumerate the global catalog for the forest root object (can only be one)
For Each objADs In objGC
    ` Output ADsPath for global catalog
    Debug.Print objADs.ADsPath
Next
` Method 2 - Use RootDSE (faster)
` Bind to the global catalog server RootDSE object
Set objADs = GetObject("GC://RootDSE")
` Build string to forest root object
Debug.Print "GC://" & objADs.Get("rootDomainNamingContext")

Note that the second method uses the RootDSE object, which will probably be faster than the first method because it avoids the enumeration of the GC provider's container. In the next chapter, I'll show how to use the global catalog to greatly improve search speed.

GUID Binding

Another way to bind to an object in the directory is by specifying the object's globally unique identifier (GUID). Active Directory generates a new GUID for each object that it creates.

A GUID is a 128-bit value that, according to the gurus who invented this stuff, is guaranteed to be unique "in both space and time." I take that to mean that when my great-grandchildren are colonizing Mars, the GUIDs created by their 10-terra-bit-per-second networks will still be different from the ones my Active Directory creates.

A string representing a GUID takes the place of the distinguished name of the object in the ADsPath. Hexadecimal notation is used for each of the 16 bytes that make up a GUID. For example:

 LDAP://<GUID=A8FCE7118CCE6647B69BB62C1B3F0DCA>
LDAP://copper1/<GUID=A8FCE7118CCE6647B69BB62C1B3F0DCA>

By using the object's GUID, as opposed to its name, you can locate an object even if it's been renamed or moved to a different container. If Jane Doe marries Joe Smith and changes her name to Jane Doe-Smith, the RDN and distinguished name would also change:

 CN=Jane Doe,CN=Users,DC=coppersoftware,DC=com
CN=Jane Doe-Smith,CN=Users,DC=coppersoftware,DC=com

The new Mrs. Doe-Smith decides to take a leave of absence, and the user object is moved to a different organizational unit:

 CN=Jane Doe-Smith,OU=Employees On Leave,DC=coppersoftware,DC=com 

Nevertheless, the GUID for this object would remain the same throughout all the changes. This is useful for keeping track of important objects in the directory.

The GUID for every object in Active Directory is stored in the objectGUID attribute of each object. Applications can access the objectGUID attribute using the GUID property of the IADs interface. Listing 4-3 shows code from the GUIDBind sample on the companion CD. It binds to an object using a supplied distinguished name, retrieves the GUID for the object, and then binds to the object again using the LDAP GUID syntax. This sample uses the GetDomainDN function listed earlier in this chapter.

 Sub Main()
Dim objADs As IADs
Dim strDomainDN As String
Dim strADsPath As String
Dim strGUID As String
    
    ` Get the DN for user's default domain
    strDomainDN = GetDomainDN()
    ` Get the DN of the object
    strDomainDN = InputBox("Enter the DN of an object in the directory", _
        "GUID Binding Sample", strDomainDN)
    If strDomainDN <> "" Then
        ` Build ADsPath to object
        strADsPath = "LDAP://" & strDomainDN         ` Display Information
        Debug.Print "Binding using " & strADsPath
    
        ` Bind to the object using the the DN provided
        Set objADs = GetObject(strADsPath)
        ` Display name of the object
        Debug.Print "Name: " & objADs.Name
    
        ` Get the GUID string of the object
        ` Using .GUID formats the GUID as a single string
        strGUID = objADs.Guid
        ` Display the GUID property string
        Debug.Print "GUID: " & strGUID
    
        ` Release interface
        Set objADs = Nothing
        ` Build path to same object using the GUID
        ` Note that <GUID= > syntax is unique to Active Directory 
        ` and the LDAP/GC provider
        strADsPath = "LDAP://<GU>"
        ` Display Information
        Debug.Print "Binding using " & strADsPath
    
        ` Rebind to the same object
        Set objADs = GetObject(strADsPath)
    
        ` Display name of the object
        Debug.Print "Name: " & objADs.Name
    End If
End Sub

Listing 4-3 GUIDBind.bas shows how to bind to an object using its GUID.

Here is example output from the GUIDBind sample in Visual Basic's Immediate window:

 Binding using LDAP://
CN=Charles Oppermann,CN=Users,DC=coppersoftware,DC=com
Name: CN=Charles Oppermann
GUID: 2cc16a7e5d48b64c8c313b709edd2a18
Binding using LDAP://<GUID=2cc16a7e5d48b64c8c313b709edd2a18>
Name: <GUID=2cc16a7e5d48b64c8c313b709edd2a18>

This output shows that the object in the directory representing me (CN=Charles Oppermann) has a GUID value of 2cc16a7e5d48b64c8c313b709edd2a18.

This output also reveals an important limitation of GUID binding. Note that when you bind using the GUID syntax and then ask for the name of the object, instead of CN=Charles Oppermann you get <GUID=2cc16a7e5d48b64c8c313b709edd2a18>.

When binding using the GUID syntax, the ADsPath, Name, and Parent properties of the IADs interface do not return the same information as they do when you bind to an object using a distinguished name string. The CopyHere, Create, Delete, GetObject, and MoveHere methods of IADsContainer are similarly unsupported when the object is bound using the GUID syntax. The reason for this quirky behavior is that the intended use of GUID binding is for high-speed, low-overhead access to the object. ADSI stays out of the way and doesn't spend time utilizing class-specific ADSI interfaces.

In case you're wondering, the objectGUID attribute is an indexed attribute, meaning that Active Directory creates an index for this attribute to improve query performance. Including the objectGUID attribute in this index is in addition to it being part of the partial attribute set, which ensures that the GUID of each object in Active Directory is available at every global catalog server.

Internally a 128-bit GUID is stored in a data structure. To display a GUID as a string, it must be properly converted. ADSI does allow you to retrieve a GUID as a string by providing the GUID property of the IADs interface. Since the GUID property retrieves the objectGUID value, you would expect the following lines to print the same result:

 Debug.Print objADs.Guid
Debug.Print objADs.Get("objectGUID")

The first line uses the GUID property of the IADs interface, whereas the second asks ADSI to retrieve the object's objectGUID attribute. Here is the output:

 2cc16a7e5d48b64c8c313b709edd2a18
????????

The first line prints as expected, but the question marks indicate that Debug.Print was not able to figure out the type of information being returned by the Get method. Debug.Print was not able to print the GUID because the objectGUID attribute is defined as an octet string, which is a convenient way to store binary data in Active Directory. An octet is defined as an 8-bit value (aka a byte). ADSI returns an octet string as a variant array of bytes because it does not know how to interpret the contents of an octet string.

However, in the case of objectGUID, ADSI can assume that the octet string in this case is 16 bytes. The code that implements the GUID property of the IADs interface retrieves the objectGUID data, properly formats it, and passes it back to the application as a binary string (BSTR). A binary string is a Unicode string in which the length of the string prefixes the actual string.

Normally, you don't have to worry about formatting objectGUID into a usable string because the GUID property does the work for you. Nevertheless, if you like to tinker, here is some code that is available on the companion CD that shows how it's done.

 Sub DisplayGUID(objADs As IADs)
Dim strGUID As String
Dim strGUIDattr As String
Dim varGUIDpart As Variant
    Debug.Print "*** DisplayGUID ***"
    
    ` Note that .Get("objectGUID") will return the GUID in an array 
    ` of integers
    For Each varGUIDpart In objADs.Get("objectGUID")
        ` Convert number to hexidecimal string
        If varGUIDpart < 16 Then
            ` If less than 16, then only one digit is used and must 
            ` be padded with a zero
            varGUIDpart = "0" & Hex(varGUIDpart)
        Else
            ` Convert number to hexidecimal string
            varGUIDpart = Hex(varGUIDpart)
        End If
        ` Build VB-style GUID string
        strGUIDattr = strGUIDattr & "&H" & varGUIDpart & " "
        ` Build version similar to .GUID property
        strGUID = strGUID & varGUIDpart
    Next
    
    ` Display string
    Debug.Print "VB string: " & strGUIDattr
    
    ` Display the GUID property string
    Debug.Print "Binding String: " & "<GU>"
End Sub

When you call this routine, it produces output similar to the following:

 *** DisplayGUID ***
VB string: &H2C &HC1 &H6A &H7E &H5D &H48 &HB6 &H4C &H8C &H31 &H3B &H70 
&H9E &HDD &H2A &H18 
Binding String: <GUID=2CC16A7E5D48B64C8C313B709EDD2A18>

The GUID returned by the WinNT provider is represented in a different form from the GUID returned by the LDAP provider for the same object. Contrary to the documentation, the WinNT provider doesn't even return the GUID of an object, but returns the GUID of the IADsXXX interface used to manipulate the object. In short, do not use the WinNT provider to discover the GUID of an object.

Well-Known GUIDs

Being able to find an object that's been renamed or moved by using its GUID is fine, but doing that assumes you know the GUID beforehand. Whenever a new object is created in Active Directory, a GUID is automatically generated and assigned to it. For many of the containers that form the initial structure of the directory, there is a fixed set of GUIDs. These GUIDs were created by Microsoft and are documented as well-known GUIDs.

Let's say that you rename the Users container to Employees. All of your applications do not have to be rewritten to use the new name; they just bind to the container using the GUID constant GUID_USERS_CONTAINER. Table 4-4 lists directory objects and corresponding GUID constants, which are defined in the Ntdsapi.h header file, part of the Microsoft Platform SDK.

Container GUID Constant

Users

GUID_USERS_CONTAINER_W

Computers

GUID_COMPUTRS_CONTAINER_W

System

GUID_SYSTEMS_CONTAINER_W

Domain Controllers

GUID_DOMAIN_CONTROLLERS_CONTAINER_W

Infrastructure

GUID_INFRASTRUCTURE_CONTAINER_W

Deleted Objects

GUID_DELETED_OBJECTS_CONTAINER_W

Lost and Found

GUID_LOSTANDFOUND_CONTAINER_W

Table 4-4 Well-known objects and GUID constants.

You can't directly use the well-known object GUID constants listed in Table 4-4 in Visual Basic and scripting languages because they are not listed in the Active DS Type Library. To use well-known GUIDs in Visual Basic or VBScript, include the following constants in your applications:

 Const strUserContainerGUID = "a9d1ca15768811d1aded00c04fd8d5cd" Const strComputersContainerGUID = "aa312825768811d1aded00c04fd8d5cd" Const strSystemsContainerGUID = "ab1d30f3768811d1aded00c04fd8d5cd" Const strDomainControllersContainerGUID = "a361b2ffffd211d1aa4b00c04fd7d83a" Const strInfrstructureContainerGUID = "2fbac1870ade11d297c400c04fd8d5cd" Const strDeletedObjectContainerGUID = "18e2ea80684f11d2b9aa00c04f79f805" Const strLostAndFoundContainerGUID = "ab8153b7768811d1aded00c04fd8d5cd" 

To use well-known GUIDs, a variant of the GUID binding syntax is used:

 LDAP://<WKGUID=XXX,containerDN> 

XXX is one of the well-known GUIDs, and containerDN is the distinguished name of the parent container. Unlike with regular GUID binding, when binding with well-known GUIDs you must specify a parent container, which is the domain object. For example:

 LDAP://<WKGUID=a9d1ca15768811d1aded00c04fd8d5cd,DC=coppersoftware,DC=com> 

This ADsPath would bind to the Users container in the coppersoftware.com domain regardless of the current name of the container. It's important to note that the same limitations of GUID binding, such as unsupported methods and different property behavior, apply to binding using the well-known GUID syntax. Generally, it's best to bind first with a well-known GUID constant, retrieve the current distinguished name of the object, and then bind again using the distinguished name. In Chapter 10, I present a sample program, CreateComputer, that illustrates this technique.

Authentication

So far I've avoided the issues of security and authentication in order to keep binding simple. Obviously, Active Directory is a secure directory—most companies don't want outsiders to come in and create lists of users or delete names. Usually only certain employees have permissions to create new objects in the directory.

Active Directory keeps a list of access permissions for each object in the directory. These permissions, which determine who can access the object, can be inherited from the parent or set individually on the object. The attributes defined in the object class also carry a list of permissions. For example, a network administrator might grant most users access to the telephoneNumber attribute of a user class object, but only certain groups of users may have access to the homePhone attribute.

When the GetObject or ADsGetObject functions are used in a program, ADSI sends to Active Directory the credentials of the calling program for authentication. Credentials are typically the user name and password for the currently logged in user. When a program is started by a user, it inherits the user's security context. Under Windows 2000, you can change the security context for a program you are about to run by using the RunAs command, as shown in Figure 4-4.

Figure 4-4 Executing an application in the Administrator security context.

You can also change the security context for a program you are about to run by holding down the Shift key, right-clicking the program icon, and then selecting Run As from the shortcut menu. This action displays the Run As Other User dialog box, shown in Figure 4-5, where you can specify the credentials that the selected program should use.

Figure 4-5 The Run As Other User dialog box displayed when Run As is chosen.

ADSI allows you to perform the same impersonation programmatically -by using the ADsOpenObject function or the OpenDSObject method of the IADsOpenDSObject interface. Unlike GetObject and ADsGetObject, ADsOpenObject and OpenDSObject allow you to specify the user name, password, and type of security methods to be used during the session. For ADsOpenObject, the requested interface identifier and indirect interface pointer are the same as with ADsGetObject:

 IADs *pobjADs;
HRESULT hResult;
hResult = ADsOpenObject(
    L"LDAP://CN=John Doe,CN=Users,DC=coppersoftware,DC=com", 
    L"COPPERSOFTWARE\\JDoe",
    L"mypassword",
    ADS_SECURE_AUTHENTICATION,
    IID_IADs,
    (void**) &pobjADs);

There isn't an ADsOpenObject function for Visual Basic and scripting languages that depend on Automation, but ADSI does provides the IADsOpenDSObject interface. The IADsOpenDSObject interface contains a single method, named OpenDS-Object. This method provides a COM-based approach that has the same functionality as ADsOpenObject. There is no practical difference between the two functions; however, if you are working with C or C++, using ADsOpenObject is easier.

 Dim objADsOpenDSObject As IADsOpenDSObject
Dim objADs As IADs
` Get any ADSI object
Set objADsOpenDSObject = GetObject("LDAP:")
` Bind using the domain\username format
Set objADs = objADsOpenDSObject.OpenDSObject( _
    "LDAP://CN=John Doe,CN=Users,DC=coppersoftware,DC=com", _
    "COPPERSOFTWARE\JDoe", _
    "mypassword", _
    ADS_SECURE_AUTHENTICATION)

In Visual Basic, before you can call the OpenDSObject method, you must already have a reference to an ADSI object. But assuming you need alternative authentication, how can you get an object before you are authenticated? This chicken-and-egg problem is solved by connecting to an object that does not require authentication in any situation. The LDAP object is exposed by the LDAP provider and is a container representing the LDAP namespace. This object supports the IADsOpenDSObject interface, and you can call the OpenDSObject method from it.

In two previous code snippets, I passed COPPERSOFTWARE\JDoe as the user name and mypassword as the password to be used. The user name can be specified in a number of formats, such as

user account:

JDoe

user principal name:

JDoe@coppersoftware.com

domain\username:

COPPERSOFTWARE\JDoe

distinguished name:

CN=John Doe,CN=Users,DC=coppersoftware,DC=com

Authentication Options

In the previous code snippets, I specified ADS_SECURE_AUTHENTICATION as the authentication type. This flag instructs the LDAP provider to use the highest level of security that can be negotiated between the client computer and the server. The LDAP client and server will negotiate the level of security they both understand, which is normally Kerberos between two computers running Windows 2000. If Kerberos security cannot be negotiated, the NTLM protocol will be used instead. In addition, you can specify data encryption options such as the Secure Sockets Layer (SSL). When using a distinguished name as the user name, you may need to use the ADS_FAST_BIND authentication type or set the parameter to 0. Other authentication flags that can be used are listed in Table 4-5.

ADS_AUTHENTICATION_ENUM Description

ADS_SECURE_AUTHENTICATION

Performs secure authentication. When connecting to Active Directory, the LDAP provider will use Kerberos if available or NTLM authentication.

ADS_USE_ENCRYPTION

Requests that ADSI use encryption for data exchange over the network.

ADS_USE_SSL

Will attempt to encrypt the communication channel using SSL. Requires that the Certificate Server be installed to support communication with Active Directory.

ADS_READONLY_SERVER

Using the LDAP provider, this flag indicates that a writeable server is not required for serverless binding. This is rarely needed with Active Directory.

ADS_PROMPT_CREDENTIALS

Not used.

ADS_NO_AUTHENTICATION

Explicitly requests no authentication. Equivalent to anonymous access to the Everyone group.

ADS_FAST_BIND

When this flag is set, ADSI will only expose the base interfaces supported by all ADSI objects.

ADS_USE_SIGNING

Performs verification of sent and received data. Set in conjunction with the ADS_SECURE_AUTHENTICATION flag.

ADS_USE_SEALING

Encrypts data using Kerberos. Set in conjunction with the ADS_SECURE_AUTHENTICATION flag.

ADS_USE_DELEGATION

Allows ADSI to delegate the user's security context, which is necessary for moving objects across domains.

ADS_SERVER_BIND

Indicates the ADsPath includes a server name and allows the LDAP provider to skip a few steps when binding.

Table 4-5 Authentication flags used by ADsOpenObject and OpenDSObject.

For more information on Kerberos, NTLM, and encryption, I recommend Microsoft Windows 2000 Security Technical Reference (Microsoft Press, 2000).

Danger, Will Robinson! Danger!

In the popular science-fiction television show "Lost in Space," when the always-curious boy, Will Robinson, was about to do something foolish, the helpful robot would warn him by waving its mechanical arms and saying "Danger, Will Robinson! Danger!" Think of me as that robot, moving my arms up and down at you with these words of caution: Do not code the user name and password of a high-level account, such as the domain Administrator, into your applications.

Passing alternative credentials with ADsOpenObject or OpenDSObject is convenient when the user's default credentials are not sufficient to perform a particular operation. It's tempting to place an administrator's user name and password in your application and use those credentials because administrators have broad access and the operations will rarely fail. However, I strongly recommend that you do not do this. The default security levels in Active Directory are well designed and allow users to manipulate their own objects without the need for greater permissions. By placing the password and user name of an account with broad permissions in your code, the risk of compromising the security of the overall network is greatly increased. Anyone with a binary editor could examine the executable program or view the source code of a scripting file to retrieve the password.

When using ADsOpenObject or OpenDSObject, you can specify that the default credentials be used, just as you do with GetObject and ADsGetObject. By specifying null as the user name and password, ADSI will instead use the security context of the current application, which is generally inherited from the user executing the program. Using ADsOpenObject or OpenDSObject and specifying null for the user name and password gives you the benefit of specifying binding options without potentially compromising security.

Security and ASP

The security risk mentioned above is especially true with Web applications. When a Web application is running under IIS, the security context is defined by the account that IIS is using. By default this account is IUSR_machinename. This account has limited privileges to prevent outside users from accessing network resources. A simple Web page that uses ADSI to allow users to change their own passwords would fail because the IUSR_machinename account does not have sufficient privileges.

Don't be tempted to use OpenDSObject from script code contained within Web pages; anyone who uses the View Source command in their browser will be able to see the credentials supplied. The solution is to execute the Web application in the security context of an authenticated user. In Chapter 11, I'll describe a sample Web application using Active Directory and IIS.

Performance Considerations When Binding

While making a call to GetObject or ADsOpenObject is relatively easy, internally, ADSI, COM, the client machine, and the server are performing a number of steps, from looking up DNS records to accessing the directory schema for the requested object. If your application works with a number of objects in succession, you can do a couple of things to reduce the binding overhead.

Fast Binding

When using ADsOpenObject or OpenDSObject, you can specify ADS_FAST_BIND as the authentication type. This flag is not really an authentication option but a directive to the LDAP provider to skip several steps in order to reduce the binding overhead and thus improve performance. But like most performance enhancements, there is a trade-off. By specifying ADS_FAST_BIND, the LDAP provider will not attempt to access the object's schema information. This means that ADSI does not know the class of the object being retrieved and the created ADSI object will not support class-specific interfaces, such as IADsUser. Only the generic interfaces such as IADs, IADsContainer, IADsPropertyList, and a few others will be available to programs.

In addition, if you use ADS_FAST_BIND, ADSI and the LDAP provider will not verify that the object requested actually exists. This saves another step. But while ADsOpenObject and OpenDSObject will succeed, any subsequent property or method access will fail if the object does not exist. Only use ADS_FAST_BIND if you are certain the object already exists in the directory.

ADS_SERVER_BIND

Windows 2000 Service Pack 1 improves ADSI by including a handy option that speeds access to known directory objects. Using the LDAP provider, if the binding string includes a server name, you can get better performance by using the ADS_SERVER_BIND flag with the ADsOpenObject function or the OpenDSObject method.

The ADS_SERVER_BIND flag is an option that tells ADSI not to perform a series of steps to locate the server. Of course, since server names change, it's not always advisable to hard code them directly into your code. The best method is to use the RootDSE object and the LDAP namespace to discover the server you want to work with at run time and then use that server name along with the ADS_SERVER_BIND flag to achieve optimal performance.

Connection Caching

Another way of improving performance is to avoid repeatedly requesting authentication. The process of negotiating a security protocol and an encryption level is time-consuming. Instead, keep an authenticated connection alive for as long as you need to communicate with the same server. You do this by getting an initial object reference, authenticating your connection to it, and reusing it as many times as you need to. When the object is released, the connection is broken. In C and C++, objects are released when you call the Release method and the reference count for the interface goes to zero. In Visual Basic, objects are released when the object variable is set to Nothing.



MicrosoftR WindowsR 2000 Active DirectoryT Programming
MicrosoftR WindowsR 2000 Active DirectoryT Programming
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 108

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