Active Directory as a Directory Service

54 downloads 210211 Views 686KB Size Report
Search base (or base object): It defines the basic location, i.e. the forest node or leaf, where .... Domain partition whose “name” attribute begins with “al”. The Option ... the DC with the Schema Master Role is not available, then it must be seized.
Active Directory as a Directory Service1

A Structural Analysis of the Active Directory Architecture with Basics of ADSI Programming in C# and C++

Authors: S. Pinardi, E. Colombo, T.A. Aruanno, R. Bisiani

1

The authors thank Microsoft for the support material provided to write this monograph

1

Active Directory as a Directory Service2 A Structural Analysis of the Active Directory Architecture with Basics of ADSI Programming in C# and C++ S. Pinardi, E. Colombo, T.A. Aruanno, R. Bisiani

Abstract The goal of this paper is to present and analyze the architectural structure of the .NET3 and Windows 2000 Active Directory (AD) and to show how it supports the programming and administration model of an Active Directory. Active Directory is a directory service that collects objects and attributes in a tree structure and manages them through the LDAP protocol. We will show how AD is useful for the creation of a directory, for data representation and management and for supporting distributed programming. We will also include practical administration examples that were carried out with common tools and short programming examples written in C# (.NET) and in C++. These brief but useful examples are helpful for both the analysis and the modification of the Active Directory’s content and can be used as a basis for a university-level course.

Objectives Analysis of a directory service, programming support, support for a programmingsystem or distributed-system course, support for a programming lab.

Method Presentation of the conceptual model, analysis carried out with readily available tools and with ADSI4 programming examples in C# and C++.

Concept Requirements Object programming, basic knowledge of operating systems, basic knowledge of Microsoft domains, basics of network knowledge, in particular TCP/IP and DNS, basic knowledge of the COM/DCOM model (to understand examples in C++).

Operational Requirements C# or C++ programming knowledge, basic knowledge of Microsoft operating systems (deployment) and of Microsoft domains management (administration).

Note For an explanation of the functional and operational structure of Microsoft domains, necessary for correctly understanding parts of this paper, please see the material listed in the bibliography. 2

Some of the product or company names appearing in this paper are trade-marks or registered trademarks of their owners. 3 This paper refers to version RC1 (build 3663). 4 ADSI is a set of COM programming interfaces that make it easy to build applications that register with, access, and manage multiple directory services with a single set of well-defined interfaces.

2

1. Introduction The architectural kernel of the Windows .NET/2000 operating system is very similar to the one in Windows NT 3.51, which was designed by D.N. Cutler at Microsoft at the beginning of the Nineties. No big changes were made to the kernel, the main ones being the introduction of some new features, like the cache manager, and some concept modifications, like job objects, which do not change the architectural model’s substance to a great extent. In addition, Windows XP/.NET significantly improved the process startup (activation) method by reducing startup times5. On the other hand, differences between NT 4.0/3.51 and Windows .NET/2000 are quite significant as far as the distributed components, whose purpose is to define the domain and the internetworking infrastructures, are involved. Diffusion of networks, due to the spread of Internet largely changed culturally and operatively the production model of modern companies; this led Windows .NET/2000 designers to emphasize and strengthen Windows NT’s distributed characteristics even further. Along these lines, the most important change introduced by Microsoft from Windows NT to Windows 2000 was a directory service, i.e. Active Directory, the topic of this paper.

2. Active Directory as a Directory Service A directory service delivers data following a specific request submitted according to an agreed-upon syntax. Data in a directory service are stored in a database according to a specific organizational logic, e.g. locally or geographically distributed nodes in a tree, tables or simple files. Each organization conforms to characteristic models and provides specific advantages. A directory service could be best described as a program – an agent – whose purpose is to insert, store and retrieve information (kept inside a database), while offering the user methods to manage it. To this purpose, a directory service must provide an effective data access model that is able to keep its database consistent. Of course a database can be stored in a central location (some web databases are like that), or it can be distributed among several locations that are connected by cooperating agents (the DNS service is a typical example of such database and distributed service). In the latter case, data will have to be distributed and copied according to a logic that will maintain their consistency and will then have to be reassembled in compliance with the user’s expectations. X.500 (CCITT 1988) is a well known directory-service implementation with a specific data management protocol called DAP (Directory Access Protocol), which includes its own transfer protocol. DAP has been redefined in 1995 (RCF 1777) at the University of Michigan by using TCP/IP for transferring data. This new, more flexible and concise protocol has been called LDAP (Lightweight Directory Access Protocol). Another well-known directory service implementation is Active Directory, whose design was inspired by the X.500 model and was based on LDAPv2 and LDAPv3 (RFC2251, etc.). Active Directory’s data management logic and data representation 5

For further information, see the specific literature listed in the bibliography [10].

3

logic share some characteristics, but they basically follow orthogonal and separate definitions. Although Microsoft based its design on the LDAP model as far as data management is concerned, they used an object metaphor to represent them, following the newest trend for database definition.

3. Directory Services: an In-depth View Active Directory (AD) is a directory service that is also used as a security database (password database). Thanks to Active Directory, the Domain Controller (DC) provides one single authentication point (Single Sign On, SSO) thus allowing the user’s information to be centralized and guaranteeing that users have a consistent representation in the domain, independently of where the authentication takes place. AD also allows storage of different information that can be shared among users or among the domain’s distributed applications, so that it can be retrieved and used. Active Directory is therefore an important and complex component of the domain. Active Directory defines the domain itself (what), its authentication boundary (where) and its security boundary (who can access it). This is a functional or “high-level” definition of this service. An architectural or “low-level” definition of Active Directory would instead describe it as a partitioned, distributed, replicated, secure and object-oriented directory service. In the following chapters we will have a closer look at these characteristics by analyzing the architectural details first in order to have an insight into the single functions and then by applying the acquired concepts to programming.

3.1 A Concise Definition In order to provide an overview of the single functions involved, we will first give a concise definition of Active Directory (AD), then go into the details of the single concepts.

3.1.1 Active Directory is a Directory Service Active Directory is a directory service, i.e. a service that provides specific information, located in a database, upon well-formed queries. The LDAP protocol allows interaction with the AD in order to create, modify and obtain information. 3.1.2 Partitioned Active Directory An AD directory service is a collection of partitions, otherwise called Naming Contexts; each partition corresponds to a portion of the directory service. AD elements (objects) can be stored within each partition. The AD objects themselves are pieces of information or contain information as attributes.

3.1.3 Object-oriented Active Directory Active Directory provides an “object-oriented” view of its contents; the elements that make up an AD are objects consisting of attributes. Objects and attributes are abstraction instances, in other words they are instances of predefined classes and are contained within a special partition of the directory called Schema. As it will be obvious later on, the Schema itself is an object, and the data it contains are themselves objects. The Schema uses objects to define its own abstractions. 4

3.1.4 Distributed Active Directory Partitions are not bound to a specific physical location, since they can be placed on different Domain Controllers (DC), but they are connected to each other according to a reference logic called knowledge that is used to reconstruct the directory structure itself.

3.1.5 Replicated Active Directory Some data, some objects within a forest are global (they refer to the whole forest), others are local (they refer to the domain). Global data are replicated (shadowed) on all forest DCs to provide better performance; due to fault tolerance and load balancing, local data can be placed on more than one DC. In addition, a service called Global Catalog collects and provides some of the data of the whole forest for more effective data retrieval. Several kinds of data must then be replicated in the forest when going from one DC to another and this operation must be secure, consistent and effective.

3.1.6 Active Directory is Secure Active Directory requires authentication before allowing access to its database and supports authorization for reading and writing data. Discretionary Access Control Lists (similar to those used on the NTFS file systems) are associated with each AD object to guarantee controlled access and better management. This topic will not be discussed in depth, because it is strictly related to administrative issues that are not relevant to this paper.

3.1.7 Active Directory Represents the Domain Despite the definition “directory service”, AD’s main objective is to store all the information associated with a domain, including the password database. This means that AD is a directory service that can serve several purposes, including domain representation.

3.1.8 Active Directory, LDAP Server and DSA Domain Controllers are LDAP servers. The election protocol for Active Directory’s data request, modification and input is LDAP, as defined in RFC 1777 (LDAPv2), RFC 2251, 2252, 2253, 2254, 2256, etc. (LDAPv3). LDAPv2 was used by Microsoft for the Exchange 5.x directory service; LDAPv3 is used for AD and for Exchange 2000 (which is integrated with AD). Other protocols are used to interact with the directory service: the DSA (Directory System Agent), AD’s operating core, has different application interfaces. Now we’ll proceed with a deeper analysis of each of the previously mentioned characteristics.

3.2 Active Directory is a Directory Service AD’s information can be retrieved through some access points (services), called providers, that use different interaction protocols or correspond to different LDAP protocol implementations.

5

Provider LDAP: GC:

Description Compatible with Lightweight Directory Access Protocol Compatible with LDAP, connection request is specified with a Global Catalog server WinNT: Compatible with Windows NT/2000/XP/.NET systems NDS: Compatible with Novell NetWare Directory Service NWCOMPAT: Compatible with Novell NetWare 3.x Table 1 A list of commonly used providers.

In this paper we will only focus on the LDAP protocol that represents the chosen protocol for .NET/2000 domains. We will then examine the providers that use it, i.e. GC: and LDAP: (some concepts related to the WinNT: provider will be briefly mentioned in the programming section).

3.2.1 Tree Structure and Attributes Both AD and X.500 organize their data in a tree structure whose nodes are identified by names. Directory services like X.500 are based on the idea that data contained in the nodes are collections of attributes and that an attribute-based search is possible. In this way, the user can obtain specific, attribute-defined views upon the data, instead of obtaining a single piece of information, as it happens with name services (e.g. DNS). The difference between a name service and a directory service is comparable to the difference between the phone’s white pages and the yellow pages. Information you look up in the white pages (the customer’s name) does not belong to a category but is rather information that you want to transform into more functional low-level data (the phone number). Yellow pages serve a different purpose: subscribers are listed by category (dentists, restaurants, hardware stores, etc.) that can be grouped according to a characteristic they share; if you are looking for a specific information, you usually turn to this kind of book and start a search by category refining it until you get the specific data you are looking for (names, addresses, phone numbers, etc.). This is similar to what happens in directory services: if you enter information along with one or more attributes describing their characteristics into the database, your search will be carried out according to categories. Consequently, different views of the same database can be generated. It is therefore important for the data in the directory to be represented correctly and for the protocol that allows interaction with Active Directory to accept an attribute-based query by the user.

6

Fig.1 A schematic view of how an attribute-based query will show a “tree” database.

3.2.2 LDAP Request LDAP allows creation, deletion and move of an object, listing of a container’s content, and so on. Later, a few programming examples will show how some of the LDAP functions can be used through ADSI (see Section 4.2.3). The focus here will be on the retrieval of information related to objects contained in an AD by utilizing a specific syntax that allows searching for directory objects and selecting them on the basis of their attributes. A well-formed LDAP query is an application message of SearchRequest (3) type made up of a set of parameters that lead to the following search criteria: •



• •

Search base (or base object): It defines the basic location, i.e. the forest node or leaf, where the search must start; the location is defined by using an LDAP Distinguished Name (see Section 3.3). Search base is used to indicate the directory section where the search should take place. Scope: It specifies how deep the search should go. There are three kinds of scopes: base, one level and subtree. Base scope restrict the search to the search base; if, for instance, the search base is a container object, its attributes will be listed, but not its contents. One level scope takes the search just beyond the base object, excluding the base object itself. With subtree scope, the search will extend over the whole subdirectory (including the base object). Filter: It’s used to single out the required elements in the specified tree section. Selection (or attribute description list): It determines which attributes of the selected objects are important

7

Directory Search base

Base One level Subtree

= node Fig. 2 Possible filter scopes.

For a comparison with the SQL (Structured Query Language) expression SELECT FROM WHERE

selection has functions that are comparable to , search base and scope to
and filter to . In short, the former expression could be re-written as follows: SELECT FROM WHERE

The purpose of a comparison with a simplified SQL query is to understand the role of appropriate expressions in LDAP query syntaxes6. Anyway, the directory’s operating core is not SQL, but LDAP (and at a low-level ISAM). See an example of a programmatic search in AD in Section 4.2.3. Other important parameters can be defined by a Search Request, e.g.: • • •

Size limit: INTEGER (0 .. max); a user can define a maximum number of elements to be returned from a search; 0 (zero) means no restriction. Time limit: INTEGER (0 .. max); a user can define the maximum time, in seconds, for an answer to a search; 0 (zero) means no restriction. Types only: BOOLEAN; this establishes if the user wants the attributes’ name (TRUE) or also the relative values (FALSE).

Syntax of a Filter This is the syntax of a filter: ([]()[()...])

6

In the appendix see the example of a search that was carried out with SQL-like syntax.

8

where = ()

• • •

is the attribute of an object is one of the values listed in the following table is a value compatible with the attribute type

Operator & | ! = >= Get(L"defaultNamingContext",&var); //IADs object is no more needed pRootDSE->Release(); if (FAILED(hr)) { wprintf(L"IADs::Get error: %x\n", hr); CoUninitialize(); return ; } //print on standard output the local Domain DN wprintf(L"Default Naming Context: %s\n", var.bstrVal); size = (int)wcslen(L"LDAP://") + wcslen(var.bstrVal) + 1; sDomainPath = (WCHAR*) malloc(size*sizeof(WCHAR)); //build the binding string to the local Domain NC swprintf(sDomainPath,L"LDAP://%s\0",var.bstrVal); VariantClear(&var); //print an ADsPath of the local Domain Partition wprintf(L"Default Naming Context ADsPath: %s\n",sDomainPath);

42

//free COM library CoUninitialize(); return ; } END CODE01.CPP

CODE01.CS using System; using System.DirectoryServices; //.NET classes for ADSI

class RootDSEApp { static void Main(string[] args) { //bind to rootDSE object DirectoryEntry rootDSE = new DirectoryEntry("LDAP://rootdse"); //get local Domain NC Distinguished Name string domainDN = (string)rootDSE.Properties["defaultNamingContext"][0]; //print on standard output the local Domain DN Console.WriteLine("Default Naming Context: {0}",domainDN); //build the binding string to the local Domain NC string domainADsPath = "LDAP://" + domainDN; //print ADsPath of the local Domain Partition Console.WriteLine("Default Naming Context ADsPath: {0}",domainADsPath); //Release DirectoryEntry object resources rootDSE.Dispose(); } } END CODE01.CS

4.2.2 Container Listing Let’s see now how we can connect to a directory node and list the elements it contains. The first example is in C++, the second one is in C#. CODE02.CPP #include //CoInitialize and CoUninitialize declaration #include //ADSIs #include void wmain() { HRESULT hr, hre = NULL; //put in sContainerDN the DN of the container to be enumerated

43

WCHAR *sContainerDN = L"DC=isqre,DC=net"; WCHAR* sADsPath; IADsContainer *pCont; IADs *pAD; IEnumVARIANT* pEnum; VARIANT var; ULONG elts; BSTR bstr; //initialize COM library hr = CoInitialize(NULL); if (FAILED(hr)) { wprintf(L"CoInitialize() error: %x\n", hr); return; } int size = wcslen(sContainerDN) + 8; // 8 equals "LDAP://" + '\0' sADsPath = (WCHAR*)malloc(size*sizeof(WCHAR)); //build binding string to the container swprintf(sADsPath,L"LDAP://%s",sContainerDN); //bind to the container, using an IADsContainer interface hr = ADsGetObject(sADsPath,IID_IADsContainer,(void**)&pCont); if( FAILED(hr)) { wprintf(L"Error: %x\n",hr); CoUninitialize(); return; } //Get an enumerator on the container hr = ADsBuildEnumerator(pCont,&pEnum); if( FAILED(hr)) { wprintf(L"Error: %x\n",hr); CoUninitialize(); return; } VariantInit(&var); //begin enumeration hr = pEnum->Next(1, &var, &elts); while(SUCCEEDED(hr)) { //getting next contained object if(!elts) break; hre = V_DISPATCH(&var)->QueryInterface(IID_IADs, (void **) &pAD); if (SUCCEEDED(hre)) { //for each object, print RDN and ADsPath hre = pAD->get_Name(&bstr); wprintf(L"Obj Name: %s\n",bstr); SysFreeString(bstr); hre = pAD->get_ADsPath(&bstr); wprintf(L"\tADs Path: %s\n",bstr); SysFreeString(bstr); pAD->Release(); }

44

VariantClear(&var); elts = 0L; //get next element in the container hr = pEnum->Next(1, &var, &elts); } hr = ADsFreeEnumerator(pEnum); //free COM library CoUninitialize(); } END CODE02.CPP

In C#, for listing a container, the foreach statement can be used with the objects classes that implement the IEnumerable interface; the whole code becomes more concise. CODE02.CS using System; using System.DirectoryServices; //.NET classes for ADSI class EnumerateContainer { static void Main(string[] args) { //put in containerDN the DN of the container to be enumerated string containerDN = "DC=isqre,DC=net"; //bind to the container DirectoryEntry cont = new DirectoryEntry("LDAP://" + containerDN); //enumerate the containment foreach (DirectoryEntry dirEntry in cont.Children) { //for each object, print RDN and ADsPath Console.WriteLine("Obj Name: {0}",dirEntry.Name); Console.WriteLine("\tADsPath: {0}",dirEntry.Path); } cont.Dispose(); } } END CODE02.CS

4.2.3 Searching for Objects in Active Directory As was already mentioned, a key feature for directory services is an effective support to search for the information they contain. In the following examples we will show how LDAP queries can be used in programming to search for objects in AD (see Section 3.2.2.). In this instance as well, the C# code will be more concise than the C++ one. In the ANR (Ambiguous Name Resolution) section following the examples, we will briefly explain the characteristics of the “ANR” attribute specified in the following examples as first parameter of the LDAP filter. CODE03.CPP

45

#include //CoInitialize and CoUninitialize declaration #include //ADSIs #include void wmain() { HRESULT hr; IADs *pADs; IDirectorySearch *pDSearch; WCHAR *sADsPath; VARIANT var; //LDAP filter WCHAR sFilter[] = L"(&(ANR=a)(objectCategory=user))"; //attribute description list WCHAR* arAttributes[] = { L"distinguishedName", L"sAMAccountName", L"memberOf" }; ADS_SEARCH_HANDLE hSearch; ADS_SEARCH_COLUMN hSClmn; int len; //initialize COM library hr = CoInitialize(NULL); if (FAILED(hr)) { wprintf(L"CoInitialize() error: %x\n", hr); return; } //bind to rootDSE and build ADsPath to local Domain NC hr = ADsGetObject(L"LDAP://rootDSE",IID_IADs,(void**)&pADs); if (FAILED(hr)) { wprintf(L"ADsGetObject() error: %x\n", hr); CoUninitialize(); return; } VariantInit(&var); hr = pADs->Get(L"defaultNamingContext",&var); pADs->Release(); if (FAILED(hr)) { wprintf(L"IADs::Get() error: %x\n", hr); CoUninitialize(); return; } len = (int) wcslen(var.bstrVal) + 8; sADsPath = (WCHAR*)malloc(len * sizeof(WCHAR)); swprintf(sADsPath,L"LDAP://%s",var.bstrVal); VariantClear(&var); /* bind to the local Domain NC, the search base, using an IDirectorySearch interface */ hr = ADsGetObject(sADsPath,IID_IDirectorySearch,(void**)&pDSearch); if (FAILED(hr))

46

{ wprintf(L"ADsGetObject() error: %x\n", hr); CoUninitialize(); return; } //search preference values are defined in ADS_SEARCHPREF_ENUM ADS_SEARCHPREF_INFO arSearchPref[2]; //set search scope arSearchPref[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE; arSearchPref[0].vValue.dwType = ADSTYPE_INTEGER; arSearchPref[0].vValue.Integer = ADS_SCOPE_SUBTREE; //set referral chasing option arSearchPref[0].dwSearchPref = ADS_SEARCHPREF_CHASE_REFERRALS; arSearchPref[0].vValue.dwType = ADSTYPE_INTEGER; arSearchPref[0].vValue.Integer = ADS_CHASE_REFERRALS_ALWAYS; //commit search preferences hr = pDSearch->SetSearchPreference(arSearchPref, sizeof(arSearchPref)/sizeof(ADS_SEARCHPREF_INFO)); //start search hr = pDSearch->ExecuteSearch(sFilter,arAttributes, sizeof(arAttributes)/sizeof(WCHAR*),&hSearch); if (SUCCEEDED(hr)) { //begin processing the result hr = pDSearch->GetFirstRow(hSearch); while(hr != S_ADS_NOMORE_ROWS) { //choose columns and print values hr = pDSearch>GetColumn(hSearch,arAttributes[0],&hSClmn); if (SUCCEEDED(hr)) { //print distinguishedName wprintf(L"--> %s\n",hSClmn.pADsValues>CaseIgnoreString); pDSearch->FreeColumn(&hSClmn); } hr = pDSearch>GetColumn(hSearch,arAttributes[1],&hSClmn); if (SUCCEEDED(hr)) { //print sAMAccountName wprintf(L"\tLogon name: %s\n",hSClmn.pADsValues>CaseIgnoreString); pDSearch->FreeColumn(&hSClmn); } hr = pDSearch>GetColumn(hSearch,arAttributes[2],&hSClmn); if (SUCCEEDED(hr)) { //print user memberships wprintf(L"\tMember of:\n"); //"memberOf" attribute is defined as multivalued for (DWORD i = 0; i < hSClmn.dwNumValues; i++) { wprintf(L"\t %s\n", (hSClmn.pADsValues+i)->CaseIgnoreString); } pDSearch->FreeColumn(&hSClmn); }

47

hr = pDSearch->GetNextRow(hSearch); } pDSearch->CloseSearchHandle(hSearch); pDSearch->Release(); } //free COM library CoUninitialize(); } END CODE03.CPP CODE03.CS using System; using System.DirectoryServices; //.NET classes for ADSI class LDAPSearch { static void Main(string[] args) { //bind to rootDSE to build local Domain NC ADsPath string sDomainPath = "LDAP://" + (string)(new DirectoryEntry("LDAP://rootdse").Properties["defaultNamingContext"][0 ]); //bind to local Domain partition DirectoryEntry dirEntry = new DirectoryEntry(sDomainPath); //create a DirectorySearcher that has Domain NC as search base DirectorySearcher dirSearch = new DirectorySearcher(dirEntry); dirEntry.Dispose(); //set LDAP filter dirSearch.Filter = "(&(ANR=a)((objectCategory=user)))"; //set search scope dirSearch.SearchScope = SearchScope.Subtree; //set referral chasing option dirSearch.ReferralChasing = ReferralChasingOption.All; //set attribute description list string[] attrsToLoad = {"distinguishedName","sAMAccountName","memberOf"}; dirSearch.PropertiesToLoad.AddRange(attrsToLoad); //execute search SearchResultCollection srColl = dirSearch.FindAll(); dirSearch.Dispose(); //process the result foreach(SearchResult sRes in srColl) { //print distinguishedName Console.WriteLine("--> {0}", sRes.Properties[attrsToLoad[0]][0]); //print sAMAccountName

48

Console.WriteLine("\tLogon name: {0}", sRes.Properties[attrsToLoad[1]][0]); //"memberOf" isn't mandatory, we have to check it if (sRes.Properties[attrsToLoad[2]] != null) { Console.WriteLine("\tMember of:"); //"memberOf" attribute is defined as multivalued foreach (string s in sRes.Properties[attrsToLoad[2]]) { //print group name Console.WriteLine("\t {0}",s); } } } srColl.Dispose(); } } END CODE03.CS

ANR (Ambiguous Name Resolution) ANR is a search mechanism that is related with the LDAP protocol and that is used to simplify the syntax of searches that would need a complex filter. ANR is a logical set located in Active Directory that identifies a collection of attributes. Every time a search filter is specified on the ANR attribute, the filter is expanded and all attributes that make up the ANR set are automatically checked in search for the given value. Here is an example of how the comparison (anr=a) is expanded: (|(displayName=a*)(givenName=a*)(legacyExchangeDN=a*) (name=a*)(physicalDeliveryOfficeName=a*) (proxyAddresses=a*)(sAMAccountName=a*)(sn=a*)(uid=a*))

Attributes can be added or removed from the ANR set. This kind of inclusion (or exclusion) may be performed by modifying the Schema, by setting to 1 (or 0) the third bit of the searchFlags meta-attribute (a bitmask) for each object belonging to the attributeSchema class that needs to be added (or removed) to the ANR set. Note: In order to verify which attributes are included in the ANR set, a search can be carried out in the Schema by specifying the following filter: (searchFlags:1.2.840.113556.1.4.804:=4)

The 1.2.840.113556.1.4.804 OID refers to an LDAP control that is used to execute bit-wise comparisons. In this specific example, a search was carried out for attributeSchema objects whose searchFlags attribute had set the third bit to 1 (4 in decimal).

4.2.4 Object Creation in Active Directory Object creation in AD is limited to a few groups of users. Finding out if one has the necessary rights, at least on the container (or on the OU) that is chosen as the new

49

object’s parent, is therefore very important. The following examples (C++ and C#) will show how a group can be created. Caution: Carrying out modification tests in domains that are in use is not advisable; they should be executed in appropriate test domains. CODE04.CPP #include //CoInitialize and CoUninitialize declaration #include //ADSIs #include void wmain () { HRESULT hr; IADs *pADs; IDirectoryObject *pDirObj; IDispatch *pDisp; //new group's Relative Distinguished Name WCHAR sNewGroup[] = L"CN=myCppGroup"; ADSVALUE adsClass, adsSAMName; VARIANT var; WCHAR *sADsPath; //initialize COM library hr = CoInitialize(NULL); if (FAILED(hr)) { wprintf(L"CoInitialize() error: %x\n",hr); return; } /* bind to rootDSE and build ADsPath to the "Users" container in the local Domain NC */ hr = ADsGetObject(L"LDAP://rootDSE",IID_IADs,(void**)&pADs); if (FAILED(hr)) { wprintf(L"ADsGetObject() error: %x\n", hr); CoUninitialize(); return; } VariantInit(&var); hr = pADs->Get(L"defaultNamingContext",&var); pADs->Release(); if (FAILED(hr)) { wprintf(L"IADs::Get() error: %x\n", hr); CoUninitialize(); return; } int len = wcslen(var.bstrVal) + wcslen(L"LDAP://CN=Users,"); sADsPath = (WCHAR*)malloc(len * sizeof(WCHAR)); swprintf(sADsPath,L"LDAP://CN=Users,%s",var.bstrVal); VariantClear(&var); /* bind to the "Users" container using an IDirectorySearch interface*/

50

hr = ADsGetObject(sADsPath,IID_IDirectoryObject,(void**)&pDirObj); if (FAILED(hr)) { wprintf(L"ADsGetObject() error: %x\n", hr); CoUninitialize(); return; } //set attribute definitions for the new object ADS_ATTR_INFO arAttribs[] = { { L"objectClass", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &adsClass, 1}, { L"sAMAccountName", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &adsSAMName, 1} }; //set new object classSchema type adsClass.dwType = ADSTYPE_CASE_IGNORE_STRING; adsClass.CaseIgnoreString = L"group"; /* set new object "sAMAccountName" value the same of sNewGroup, leaving off the "CN=" naming attribute */ adsSAMName.dwType = ADSTYPE_CASE_IGNORE_STRING; adsSAMName.CaseIgnoreString = sNewGroup + 3; //create the group in "Users" container hr = pDirObj->CreateDSObject(sNewGroup,arAttribs, sizeof(arAttribs)/sizeof(ADS_ATTR_INFO),&pDisp); if (FAILED(hr)) { wprintf(L"ADsGetObject() error: %x\n", hr); CoUninitialize(); return; } pDirObj->Release(); pDisp->Release(); //free COM library CoUninitialize(); } END CODE04.CPP CODE04.CS using System; using System.DirectoryServices; //.NET classes for ADSI class ADCreate { static void Main(string[] args) { //bind to rootDSE to build local Domain NC ADsPath string sDomainPath = (string) new DirectoryEntry("LDAP://rootdse").Properties["defaultNamingContext"][0 ]; /* bind to the "Users" container in the local Domain NC; this will be the parent of the new object */ DirectoryEntry deCont = new DirectoryEntry("LDAP://CN=Users," + sDomainPath);

51

//set new object name string sNew = "myCsGroup"; //create the new group object DirectoryEntry deNew = deCont.Children.Add("CN=" + sNew,"group"); deCont.Dispose(); //set "sAMAccountName" value the same as object name deNew.Properties["sAMAccountName"].Add(sNew); //save object creation and attribute update to the directory deNew.CommitChanges(); deNew.Dispose(); } } END CODE04.CS

Note: Object creation takes place only if a value is provided for all its mandatory attributes (see Section 3.4.1 Attributes). Some of these values may not be explicitly defined by the programmer: when committing the creation (the transaction is activated by the SetInfo() call in C++ and by the CommitChanges() in C#), these values are set automatically by deriving them from other parameters or by using default values. For instance, creating a group in a .NET environment we need only the Relative Distinguished Name and the class name to successfully carry out the operation. If one does not want to use default values or in order to avoid potentially difficult situations, other parameters should be included. For instance, if a value for groupType is not specified, the group will be created with global visibility and security type; if a different visibility or type are required, the right flag combinations (as defined in the iads.h header) should be provided, as shown in Table 7. Group Domain Local Distribution Domain Local Security Global Distribution

Flag ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP

Global Security

ADS_GROUP_TYPE_GLOBAL_GROUP | ADS_GROUP_TYPE_SECURITY_ENABLED ADS_GROUP_TYPE_UNIVERSAL_GROUP

Universal Distribution Universal Security

ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP | ADS_GROUP_TYPE_SECURITY_ENABLED ADS_GROUP_TYPE_GLOBAL_GROUP

ADS_GROUP_TYPE_UNIVERSAL_GROUP | ADS_GROUP_TYPE_SECURITY_ENABLED

Table 7

As far as uninitialized attributes that generate “uncomfortable” values are concerned, if the sAMAccoutName value (i.e. the string used as a logon name during

52

the authentication process) is not specified a unique value is automatically generated, e.g. “$h31000-PH4D3ADPULE8” ;this is a valid logon name but, obviously, is not user-friendly.

4.2.5 Creation of Application Directory Partitions At the beginning of this paper, we referred to Application Directory Partitions (ADP). These partitions were created to cover the need for dynamic data publication in a repository with the distribution, replication and security characteristics of Active Directory. Note: In Windows .NET, a new auxiliary class, called dynamicObject, has been defined. It allows the association of a predefined lifetime to a directory element (in the entryTTL attribute). In order to make an object dynamic, a reference to the dynamicObject class in the objectClass attribute of the object itself must be added (dynamic linking). In order to make a whole class of objects dynamic, their definition in the Schema must be changed by adding the dynamicObject class reference to the auxiliaryClass attribute (static linking). ADPs are domainDNS class objects and can contain any object that is already defined in the Schema, except for objects identified as security principals, i.e. users, groups and computers. A domainDNS class instance can be created as a child of another domainDNS object, which means that an Application Partition can be logically nested in another one. The following code section will show how to create an ADP. Apart from the RDN and the class (domainDNS), the only other information that is needed to create an Application Partition is the value for the instanceType attribute. This attribute must be set to 5, which corresponds to the logical OR of the DS_INSTANCETYPE_IS_NC_HEAD and DS_INSTANCETYPE_NC_IS_WRITEABLE flags, that are defined in the ntdsapi.h file and that respectively indicate that the object being created is the root (head) of a Naming Context and that objects may be written, i.e. instantiated inside it. CODE05.CS using System; using System.DirectoryServices; //.NET classes for ADSI class ADCreateADP { static void Main(string[] args) { //bind to rootDSE to build local Domain NC ADsPath string sDomainPath = (string) new DirectoryEntry("LDAP://rootdse").Properties["defaultNamingContext"][0 ]; //bind to local Domain NC, as the parent of the new object DirectoryEntry deCont = new DirectoryEntry("LDAP://" + sDomainPath); //set new ADP's Relative Dinstinguished Name

53

string sNewADP = "DC=myCsADP"; //create a new ADP as a new domainDNS object DirectoryEntry deNewADP = deCont.Children.Add(sNewADP,"domainDNS"); //set "instanceType" as (DS_INSTANCETYPE_IS_NC_HEAD | DS_INSTANCETYPE_NC_IS_WRITEABLE) deNewADP.Properties["instanceType"].Add(5); deNewADP.CommitChanges(); deNewADP.Dispose(); } } END CODE05.CS

This operation automatically triggers a series of procedures that lead to the creation of the crossRef object in the Partitions container inside the Configuration Naming Context, to the addition of the new partition’s Distinguished Name in the nTDSDSA object’s hasMasterNCs attribute (the nTDSDSA object identifies the Directory Service Agent of the DC where the partition is being created), and to the addition of a new zone and of the necessary SRV records in the DNS. Note: The DNS is updated every time an ADP partition replica is added or removed. As previously mentioned, ADPs may contain dynamic data that could be frequently modified, which could generate an undesirable amount of network traffic during replication. For this reason, these special Naming Contexts are replicated on other Domain controllers only following a specific request of the administrator. This operation is carried out by modifying the crossRef object connected with the Application Partition and by adding the Distinguished Name, that identifies the replication DC’s Directory Service Agent, to the msDS-NC-Replica-Locations attribute. Once the replication partners are defined, intra-site replication intervals can be specified by setting the following two attributes of the same cross reference: •



msDS-Replication-Notify-Start-Delay: It allows setting of the time (in seconds) after which the first replication partner must be informed of a modification. If not set, a default value of 5 minutes will be applied. msDS-Replication-Notify-Subsequent-DSA-Delay: It allows setting of the time after which the successive replication partners must be notified. If not set, a default value of 30 seconds will be applied.

Note: Intra-site replication intervals can be personalized by modifying the DC register; this setting has priority over the values defined by the cross reference attributes. On the other hand, ADP inter-site replication follows the same replication schedule that is applicable to Domain partitions.

54

Every Application Directory Partition may have any forest Domain Controller as a replication partner, but it is not replicated in the Global Catalog. Note: ADP can be removed by connecting to one of the .NET Server family DCs and by deleting the crossRef, whose value of the nCName attribute corresponds to the Distinguished Name of the domainDNS connected with the partition to be removed. Upon completion of this operation, the KCC (Knowledge Consistency Checker) triggers a series of procedures that lead to the deletion of the partition replications, of instances of the domainDNS object that was created with the partition, and of all the other objects within. The partition’s DNS zone and its content are also removed. Deleting an ADP does not generate tombstones.

Appendix Additional Programming Examples • • •

ADSQL.CS: Example of a search carried out in Active Directory by using an OleDB provider and SQL-like syntax for the query. WinNT_1.CS: It shows how an Active Directory account can be inserted into one of a machine’s local groups. WinNT_2.CS: It lists the services that are being executed on a machine; if a service name is specified, its status is inverted.

ADSQL.CS using System; using System.Collections; using System.Data; using System.Data.OleDb; class ADSql { static void Main(string[] args) { //set the OLEDB provider for Active Directory OleDbConnection oledb = new OleDbConnection("Provider=ADsDSOObject;"); oledb.Open(); //set attribute description list string searchAttrs = "distinguishedName, objectCategory"; //set an ADsPath for the search base string searchBase = "'LDAP://DC=isqre,DC=net'"; //set filter string searchFilter = "objectClass='user'"; //build SQL query string string searchQuery = "SELECT " + searchAttrs + " FROM " + searchBase + " WHERE " + searchFilter;

55

OleDbDataAdapter oleDA = new OleDbDataAdapter(searchQuery, oledb); //object that will cache the data retrieved from AD DataSet oleDS = new DataSet(); //retrieve data from AD oleDA.Fill(oleDS,"obj"); oledb.Dispose(); oleDA.Dispose(); foreach (DataRow r in oleDS.Tables["obj"].Rows) { //process the result Console.WriteLine("-->"); for (int i = 0; i < r.ItemArray.Length; i++) Console.WriteLine(" {0}",r.ItemArray[i].ToString()); } oleDS.Dispose(); } } END ADSQL.CS WinNT_1.CS using System; using System.DirectoryServices; //.NET classes for ADSI using ActiveDs; // native ADSI class Account2lg { static void Main(string[] args) { // modify writing in your own valid values string host = "10.1.2.77"; // IP address or netbios name string localGroup = "power users"; string domain = "ISQRE"; //domain netbios name, or a DC's IP address or netbios name string domainAccount = "colomboem"; // an existent account //bind to the specified local group object DirectoryEntry de = new DirectoryEntry("WinNT://" + host + "/" + localGroup); //get a native IADsGroup interface on the local group object IADsGroup group = (IADsGroup) de.NativeObject; //add the specified domain account to the local group group.Add("WinNT://" + domain + "/" + domainAccount); de.Dispose(); } } END WinNT_1.CS WinNT_2.CS /* * In order to use ADSI native interfaces, you need to build * the code linking the activeds.tlb template library */ using System; using System.DirectoryServices; //.NET classes for ADSI

56

using ActiveDs; // native ADSI class StopService { static void Main(string[] args) { // modify writing in your own valid values string hostName = "Host1"; //host's IP address or netbios name string serviceName = "alerter"; string newStatus = "0"; // bind to the chosen computer DirectoryEntry de = new DirectoryEntry("WinNT://" hostName);

+

//set a filter to enumerate only "service" objects de.Children.SchemaFilter.Add("service"); Console.WriteLine("Services running on host {0}:",hostName); foreach (DirectoryEntry svc in de.Children) { /* get a native IADsServiceOperation interface on the current service object */ IADsServiceOperations serv = (IADsServiceOperations) svc.NativeObject; if (String.Compare(svc.Name,serviceName,true) == 0) { //the current service is the one indicated above if (serv.Status == 1) { //requested service is stopped serv.Start(); newStatus = "started"; Console.WriteLine(" " + svc.Name); } else if (serv.Status == 4) { //requested service is running serv.Stop(); newStatus = "stopped"; } } else if (serv.Status == 4) //service is running Console.WriteLine(" " + svc.Name); svc.Dispose(); } de.Dispose(); if (newStatus.Length == 1) Console.WriteLine("\nNone operation has been performed on service {0}",serviceName); else Console.WriteLine("\nService {0} has been {1}.", serviceName,newStatus); } } END WinNT_2.CS

57

Tools for AD Management and Analysis •

ADSIEDIT (Support Tools) A graphic tool that allows interaction with AD via “LDAP:” provider. Support Tools are included in the installation CD of the Windows .NET e 2000 operating systems and must be explicitly installed. Once Support Tools are installed, ADSIEDIT is a component of the MMC (Microsoft Management Console). After opening the MMC, choose “File”\“AddRemove Snap-in”. For further information, please refer to the online help.



ADSVW.exe (ADSI SDK) An ADSI application that belongs to the SDK Platform tools, it is one of the most versatile. The SDK Platform may be found at the Microsoft http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ site.



Csvde.exe It is a command line tool used to retrieve/insert data in AD in CSV (Comma Separated Value) format; if executed with no parameters, a help menu is displayed.



LDIFDE.exe (LDAP Data Interchange Format – Directory Exchange) It is a command line tool used to insert and modify objects in LDAP servers. LDIF is a draft Internet standard defined in RFC 2849. For further information, see:  http://support.microsoft.com/default.aspx?scid=KB;ENUS;Q237677&  http://msdn.microsoft.com



LDP.exe (Support Tools) It is a graphic tool that is part of the Support Tools. It is used to query an LDAP directory service. For further information, please refer to the online help.



Ntdsutil.exe It is a powerful and articulated command line tool that is used to interact with the AD database (ntds.dit) directly; it is particularly useful for DSA troubleshooting when the directory service is inactive. Do not use this tool without carefully reading its documentation (see www.microsoft.com/technet) and without thoroughly understanding AD and its domain functions.



Repadmin.exe (Support Tools) It is used to analyze and troubleshoot replication. For further information, see the Support Tools’ help.



Schema Manager This component, called “Active Directory Schema”, is one of the MMC (Microsoft Management Console) snap-ins. Before using it, it must be registered with the Regsvr32 command line tool as follows:

58



C:\>Regsvr32 schmmgmt.dll

In Windows .NET AD version some of the listed tools have been updated to the new features. In particular, ntdsutil.exe and ADSIEDIT allow to add and remove ADP partitions. Furthermore a few new command line tools have been introduced in Windows .NET: • • • • • •

dsadd (to add objects to AD) dsget (to visualize AD objects) dsmod (to modify them) dsmove (to move them) dsquery (to retrieve objects according to specific search criteria) dsrm (to remove objects)

These tools are useful for non-programming interaction with AD, since they work via shell or via shell scripts. For each one of these tools, the /? switch displays a concise help menu. Caution: Most of the tools mentioned above are used not only for reading but also for modifying AD values and must therefore be used with caution. The authors suggest use in test environments only and do not accept responsibility for malfunctioning due to their application.

59

Bibliography Active Directory [1] A.A. V.V. Microsoft Windows 2000 Security Technical Reference, Redmond, Washington, Microsoft Press, 2000. [2] A.A. V.V., Windows 2000 Server Distributed System Guide, in “Windows 2000 Server Resource Kit”, Microsoft Press, Redmond, Washington, 2000. [3] A.A. V.V., Building Enterprise Active Directory Services: Notes from the Field, Microsoft Press, Redmond, Washington, 2000. [4] COULOURIS G., DOLLIMORE J., KINDBERG T., Distributed Systems Concept and Design, Addison-Wesley Publishers, Wokingham, 20012. [5] ISEMINGER D., Active Directory Service for Microsoft Windows 2000 Technical Reference, Microsoft Press, Redmond, 2000.

COM [6] EDDON G., EDDON H., Inside COM+ Base Services, Microsoft Press, Redmond, Washington, 1999.

C# [7] ARCHER T., Inside C#, Microsoft Press, Redmond, Washington 2001. [8] WILLIAMS M., Microsoft Visual C# .NET, Redmond, Washington, 2002.

System Architecture [9] SILBERSCHATZ A., GALVIN P.B., Sistemi Operativi, Addison Wesley Longman Italia, Milano, 19985. [10] SOLOMON D.A., RUSSINOVICH M.E., Inside Windows 2000, Microsoft Press, Redmond, 20003. [11] TANENBAUM A.S., Architettura dei computer un approccio strutturato, UTET Libreria, Torino, 2000. [12] TANENBAUM A.S., Modern Operating Systems, Prentice Hall International Editions, Upper Saddle River, 2001.

TCP/IP [13] A.A. V.V., Windows 2000 Server - TCP/IP Core Networking Guide, in “Windows 2000 Server Resource Kit”, Microsoft Press, Redmond, 2000. [14] COMER D., Internetworking With TCP/IP, Volume 1, Principles Protocols, and Architecture, Prentice Hall, Upper Saddle River, 20004.

60

By the Same Authors and Related to this Topic: Books [15] PINARDI S., ARUANNO T.A., BISIANI R., Windows 2000 & .NET in Action, UTET Libreria, Torino, 2002. (in Italian).

White Papers [16] PINARDI S., FEDRICI A., BISIANI R., Distributed Programming Using C#: a Case Study, Microsoft, 2003. [17] ARUANNO A., PINARDI S., FEDRICI A., BISIANI R., Processes, Threads and Synchronization Matters in Windows .NET and others NT Based Operating Systems, Microsoft, 2003.

For further information about the authors and other related material, see www.isqre.net www.isqre.disco.unimib.it www.mlab.disco.unimib.it

Web References System Engineering http://www.microsoft.com/technet Microsoft’s basic knowledge. www.ntfaq.com An operating approach to system engineering themes in a Microsoft environment.

Programming http://msdn.microsoft.com A programming guide for Microsoft environments. http://www.gotdotnet.com A .NET programmer’s guide with many examples. http://www.15seconds.com/focus/ADSI.htm Examples and programs on ADSI.

61

RFC http://www.ietf.org/rfc.html http://www.rfc-editor.org/cgi-bin/rfcsearch.pl

X.500 http://snad.ncsl.nist.gov/snad-staff/tebbutt/x5eg/tableofcontents2_1.html

62