NDS Application Development Using C++
Articles and Tips: article
Technical Support Engineer
Asia Pacific Support
01 Jan 1995
This DevNote describes a library of C++ classes which can be used as an interface to NetWare Directory Services (NDS). These classes provide an improved platform for NDS application development by providing an object oriented approach to software development and by using the more advanced coding techniques available through the C++ language.
Introduction
This article describes a library of C++ classes which can be used as an interface to NetWare Directory Services (NDS). These classes provide an improved platform for NDS application development.
The class library is based on the function prototypes and structure definitions given in the Novell Software Developer's Kit (SDK). The library provides an object oriented approach to development by embedding the principle structures in the NDS SDK into C++ classes and defining a class hierarchy. The NDS SDK functions that form the application programmer's interface (API) are then defined as member functions to the classes. Hence the NDS API functions are split into logical groups based on the classes they serve. This approach also reduces the number of parameters required to call the API functions since the member functions are automatically associated with the class data. The original prototypes and structures declared in the NDS SDK are still available if they are required.
The NDS SDK is included in both the NetWare Client SDK and the NetWare Server SDK. Both the Client SDK and Server SDK are contained on the Novell SDK CD-ROM. The Client SDK is used to produce DOS, Windows and OS/2 applications. The Server SDK is used to produce NetWare Loadable Modules (NLMs). Both SDKs contain the same set of header files, however libraries provided differ according to the destination platform.
This article assumes the reader is experienced in C++ development and has access to the Novell SDK CD-ROM.
An article in the next issue of DevNotes will provide example code showing how to use the class libraries.
NDS Structure
NetWare Directory Services were introduced with NetWare 4.0 to coordinate the administration of an enterprise wide network. It acts as a global resource that can contain information on users, servers, volumes, work groups etc. The information recorded in the NDS is automatically distributed across the network and replicated on multiple servers for security.
Data recorded in the NDS are arranged as objects in a hierarchical tree so that the logical associations between data are represented. The hierarchical representation used by the NDS is similar to the directory file structure used by DOS, where objects can be contained within objects and objects are named according to the path from the object to the top of the hierarchy. While there are similarities between directory file structure and the NDS, the analogy should not be taken too far since there are several semantic and syntactic differences between them. For example directory file structure consists of two types of objects, namely directories and files, while the NDS consists of many types of objects.
The rules controlling the organization of data in the NDS are recorded in the NDS Schema. The NDS Schema consists of a fixed set of value syntaxes, a predefined set of object attributes and a predefined set of object types. Applications can add new attributes and types to the NDS Schema. Hence an application can use the NDS as an object database which will automatically replicate the data across the network and distribute the data to all users in the NDS.
Value syntaxes are used to specify how the data should be represented and are similar to the standard types available in C++. Examples of syntaxes are CI String and Integer. It is not possible to add or modify the set of syntaxes.
Attributes are used to define fields inside objects. Each attribute is associated with a single syntax, plus other information such as whether the attribute can hold multiple values. Examples of attributes are CN and Group Membership, which have syntaxes of CI String and DN String respectively. Attributes are defined independently of object type definitions, hence an attribute is associated with the same syntax irrespective of which object type the attribute is in.
Object types consist of mandatory and optional attributes. Examples of object types are User and Group, both of which have the CN attribute as mandatory. Object types also have a set of rules controlling where objects of that type can be placed in the NDS tree. These rules state which object types the object can be subordinate to, and whether the object is allowed to contain other objects.
Locale Information and Unicode Tables
NetWare Directory Services stores character data in a 16 bit format called Unicode. The Unicode format allows the NDS to handle the larger character sets used by some languages. All character data exchanged between the NDS and the workstation is in Unicode format. One of the first actions of an NDS application is to load the appropriate Unicode conversion tables for the workstation's locale. By default character data is automatically translated between Unicode and the application's local format once the Unicode conversion tables have been loaded. The automatic translation can be disabled if this incurs too much overhead. If the automatic translation is disabled then the responsibility of translation falls on the application.
Developers normally do not need to be concerned with Unicode translation once the Unicode tables have been loaded. If the application is going to be used in locales that use multi-byte character sets then the developer must not use functions that assume single-byte formats. The standard functions supplied in CTYPE.H and STRING.H have been duplicated in NWLOCALE.H and UNICODE.H to handle multi-byte character sets. The Unicode tables should be freed once they are no longer needed (normally when the application terminates).
NDS API Set
The NDS API consists of approximately 160 functions divided into four sections. The following table lists the sections and the C++ class used to access the API.
Section
|
C++Class
|
Auditing (DirectoryServices) |
DSAudit |
Connection(Directory Services) |
DSConn |
Directory ServicesAccessAuthentication Contextand Buffer ManagementPartition ManagementSchema ManagementMiscellaneous |
DSBufferDSBufferDSBufferDSPartitionDSSchemaDSBuffer |
Unicode |
DSLocale |
The include files supplied with the NDS Client SDK have been designed so that they can be used for DOS, Windows and OS/2 development. This is achieved by defining abstract data types (ADT) according to the platform being used and declaring all the API prototypes using the ADTs. The following table lists the principle header files used for NDS development. An NDS application would normally have to include the first four files in the table.
HeaderFiles
|
Description
|
NWCALDEF.H |
NetWare ADTsdefinitions, NetWare macro definitions |
NWDSTYPES.H |
NDS ADTs definitions |
NWALIAS.H |
NetWare ADTdefinitions used in API prototypes |
NWDSDEFS.H |
NDS macro definition |
NWDSERR.H |
NDS error codes |
NWNET.H |
Includes allNDS header files |
All the header files have an extern "C" declarations to make them C++ compatible. The NDS libraries are contained in two files, NWLOCALE contain the unicode functions and NWNET contains the rest.
Directory Contexts
NetWare Directory Services API calls that involve character data or interact with the NDS tree require additional information to be supplied. This is to indicate the default context and whether the character data should be translated to Unicode. This information is collectively held in a structure called the directory context and is used to pass the following details:
default context
whether alias objects should be dereferenced
whether character data should be translated to and from Unicode
whether object names should be given in canonical form
whether object names should be given in typeless form.
When a directory context is created, the default context is obtained from the NET.CFG file and the four options listed above are performed. The directory context should be freed once it is no longer needed (normally when the application terminates).
The information in a directory context can be read and modified. Care should be taken when changing the default context. This can be set to an arbitrary string, unlike the CX command which will not set the default context to an illegal value. If the default context is set to an illegal value then most API calls that rely on the directory context will fail.
The same naming conventions apply to the NDS API calls as to the NetWare command line utilities. That is leading and trailing periods can be used to modify the default context and the type qualifiers such as CN and OU can be inserted into the name to override the default typing.
Directory Buffers
NetWare Directory Services API functions use buffers for sending and receiving data between the application and the NDS. For example, a typical sequence in reading an object's attributes would be to initialize an input buffer, load the buffer with the list of desired attributes, execute the read object command, and finally unload the attribute values from an output buffer. Given that the output buffer is finite, it is sometimes necessary to call the read function repeatedly to get subsequent values. The above protocol of using input and output buffers provides a general interface for transferring data.
Memory for directory buffers is allocated using an NDS API call. The suggested amount of memory to allocate is given by the constant DEFAULT_MESSAGE_LEN which is set to 4096 bytes. This value is normally more than sufficient for any NDS application. The only exception under normal circumstances would be when there is a single attribute value that overflows the buffer. Directory buffers should be freed once they are no longer needed (normally when the application terminates).
Class Hierarchy
The NDS C++ Library consists of 15 classes described in the following table.
Class
|
Description
|
DSLocale |
Control ofunicode tables |
DSContext |
Control ofdirectory contents |
DSInput |
Access to bufferinput commands |
DSOutput |
Access to bufferoutput commands |
DSFilter |
Access to bufferfilter commands |
DSBuffer |
Access to buffercommands |
DSConn |
Access to connectioncommands |
DSAudit |
Access to auditingcommands |
DSSchema |
Access to schemacommands |
DSPartition |
Access to partitioncommands |
DSConnBuffer |
Access to connectionand buffer commands |
DSIteration |
Control ofresults from iteration commands |
DSValue |
Control ofNDS values |
DSFile |
Control ofNDS stream/file values |
DSObject |
Control ofNDS objects |
Many of the base classes have been defined as virtual base classes to avoid multiple instances of a base object appearing in a derived object. All objects have constructors and destructors to allocate and release NDS resources. Assignment operators and initialization constructors have been declared for all classes, however they are not defined for most classes. This is to stop objects being assigned or transferred where such an action would be inappropriate or dangerous.
The class hierarchy is given Figure 1.
Figure 1: This is the class hierarchy.
Example Program
Listing 1a is a copy of a sample program from the Novell SDK CD-ROM for listing the NDS objects in a specific or the default directory context. The program is written in C and requires goto statements to release resources when recovering from errors. Listing 1b is the equivalent program using the NDS C++ libraries. Error recovery in this program is handled by the individual classes, hiding the complexity from actual program.
The example also illustrates how iteration is handled by the NDS C++ Library. NDS functions that require an iteration handle have been replaced by a member function that takes a pointer to an object of type DSIteration. The DSIteration class has several virtual member functions which are called by the iteration member function to transfer the results from the output buffer to the application. In the example a simple class called DSListIteration has been derived from DSIteration to pipe the object names to the output stream.
Each base class has a member field Status of type NWDSCCODE to record error conditions. The current error status can be obtained by using the member function status(). All member functions that execute NDS API calls record the function's condition code in the Status field.
Listing 1a
#include <stdio.h<< #include <conio.h<< #include <nwnet.h<< #include <nwcalls.h<< #include <nwlocale.h<< void main(int argc, char *argv[]) { NWDSContextHandle dContext; NWDS_ITERATION iterHandle = -1L; NWOBJECT_INFO objectInfo; NWDS_BUFFER *outBuf; NWDSCCODE ccode; NWCOUNT objectCount, attrCount; char objectName[MAX_DN_CHARS]; char nContext[MAX_DN_CHARS]; int i; ccode = NWCallsInit(NULL,NULL); if (ccode) exit(1); ccode = NWInitUnicodeTables(001,437); if (ccode) exit(1); dContext = NWDSCreateContext(); if (dContext == ERR_CONTEXT_CREATION) goto _FreeUnicodeTables; if(argc == 2){ ccode = NWDSSetContext(dContext,DCK_NAME_CONTEXT,argv[1]); if (ccode < 0) goto _FreeContext;< } ccode = NWDSGetContext(dContext,DCK_NAME_CONTEXT,(void *)nContext); if (ccode) goto _FreeContext; ccode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN,&outBuf);& if (ccode < 0) goto _FreeContext;< do { ccode = NWDSList(dContext,"",&iterHandle,outBuf);& if (ccode < 0) break;< ccode = NWDSGetObjectCount(dContext,outBuf,&objectCount);& if (ccode < 0) break;< printf("Objects found under \"%s\":\n", nContext); for(i = 0; i < objectCount; i++){< ccode = NWDSGetObjectName(dContext,outBuf, objectName,&attrCount,&objectInfo);& if (ccode < 0) exit(1);< printf("%d. %s\n",(i + 1),objectName); } } while(iterHandle != -1); _FreeContext: NWDSFreeContext(dContext); _FreeUnicodeTables: NWFreeUnicodeTables(); }
Listing 1b
#include <iostream.h<< #include "dslocale.h" #include "dsbuffer.h" #include "dsiterat.h" class DSListIteration : public DSIteration { protected: WORD Count ; public: NWDSCCODE InitLoop() { return Count = 0 ; } NWDSCCODE InitObject(NWPSTR object, void * info) { cout << ++Count << ". " << object << "\n" ;< return 0 ; } } ; int main(int argc, char * argv[]) { DSLocale dsLocale ; DSListIteration dsListIteration ; DSBuffer dsBuffer(argc == 2 ? argv[1] : "") ; char context[MAX_DN_BYTES] ; if (dsLocale.status() || dsBuffer.status()) return 0 ; dsBuffer.GetContext(context) ; cout << "Objects found under \"" << context << "\":\n" ;< dsBuffer.List("",& dsListIteration) ;& return 0 ; }
DSLocale Class
As stated earlier the Unicode conversion tables have to be loaded before an application can access the NDS, and should be released prior to an application terminating. These tables are used to convert character data between the general Unicode format used by the NDS and the local format used by the application. The Unicode tables are loaded by executing the Unicode API NWInitUnicodeTables(), specifying the local code page. The local code page is obtained by executing the Internationalization Services APIs NWLsetlocale() and NWLlocaleconv().
For the Unicode tables to be successfully loaded they must be located in one of the following directories:
current working directory
directory the application was loaded from
directory named NLS beneath the load directory
workstation's search path.
Normally an NLS directory is installed in SYS:LOGIN, SYS:PUBLIC, SYS:SYSTEM and in the WINDOWS and NWCLIENT directory on a workstation. Hence any application installed in those directories will automatically find the Unicode tables. If the Unicode tables are not located using the default paths then the application must search for them. To do this an application can map a drive to SYS:PUBLIC\NLS and enter the drive onto the search drive vector, or search for an NLS directory off any drive letter. The DSLocale class uses the latter method by setting the current drive to Z:, Y:, etc. and trying to change directory to NLS. The member function used to test a single drive is given in Listing 2.
Listing 2
NWDSCCODE DSLocalefoundUnicodeTables(int drive) { if (setdisk(drive) == -1) return 0 ; if ((Status = NWInitUnicodeTables(info.country_id,info.code_page))==0) return 1 ; if (chdir("NLS") == -1) return 0 ; Status = NWInitUnicodeTables(info.country_id,info.code_page) ; chdir("..") ; return Status == 0 ; }
The first call to NWInitUnicodeTables() in Listing 2 will be redundant 99% of the time since the drive being tested would be a search drive and hence has been tested when NWInitUnicodeTables() is called in the DSLocale constructor.
The DSLocale class uses a private static member field to stop the Unicode tables being reloaded. The tables are only released when all DSLocale objects are destroyed. The class does not provide access to any Unicode or Internationalization Services functions since these functions do not require any locale data as parameters.
DSIteration Class
NetWare Directory Services functions such as NWDSRead(), NWDSSearch() etc. use an iteration handle to return NDS data that is too large to fit in the output buffer supplied to the function. Hence calls to these functions are normally nested inside do loops so that the function is called a sufficient number of times.
Retrieving data from the output buffer may require triple nested for loops to iterate through objects, attributes and values. The retrieval of data is further complicated since an object's values can be spread over several function calls, requiring an application to check when initializing the for loops whether this is a new object or the continuation of an old object.
To simplify the retrieval of data the NDS C++ Library uses an object of a class derived from DSIteration to channel the data from the buffer to the application. The NDS API functions that use an iteration handle have an equivalent function in the NDS C++ Library that takes a pointer to a DSIteration class. Functions that use DSIteration have a defined protocol controlling how they use the iteration object. The complete iteration protocol is given in Listing 3. The DSIteration functions skip the parts of the protocol that are not applicable to the function. For example the function DSBufferRead() applies to a single object and hence does not use the InitObject() and ExitObject()functions.
Listing 3
DSClassIterationFunction(..., DSIteration * iteration ) { iteration->InitLoop()> for each object iteration->InitObject(objectName,objectInfo)> for each attribute iteration->SetAttrName(attrName,syntax)> for each value iteration->SetAttrValue(syntax,data)> iteration->ExitObject(objectName)> iteration->ExitLoop(status)> }
The definitions of DSIterationInitObject, DSIterationSetAttrName and DSIterationSetAttrValue record the parameters in DSIteration member fields. To perform any processing of the data, a class derived from DSIteration is required to override the base functions. The derived class can then transfer the data to global memory or output the data directly, an example of this was given in Listing 1b.
A more general method would be to associate the iteration object with a destination object. This can be achieved by giving the iteration object a reference or pointer to the destination object when the iteration object is constructed, or to derive the destination class from DSIteration. An example of the second method is given in Listing 7.
The iteration can be closed by returning a non zero number from a DSIteration function. Since NDS condition codes use negative numbers it is suggested that only positive numbers are used by iteration functions to close the iteration. If the application does close the iteration with a positive value then the NDS API NWDSCloseIteration() is executed to release any resources used to control the iteration. An example of closing the iteration during a search is given in Listing 8.
DSContext Class
The DSContext class is used to create and release NDS contexts. All member functions apart from the constructor and destructor are declared in line. The inline functions are equivalent to the NDS API calls with the NWDSContextHandle plus any other superfluous parameters removed. Normally a developer would not need to create an explicit instance of a DSContext object, but use classes derived from DSContext such as DSBuffer. An example of using a stand alone instance of a DSContext object is given in Listing 4, where the object is used to translate distinguished names to typed relative names.
Listing 4
DSContext dsContext(subContext) ; char distName[MAX_DN_BYTES] ; char canName [MAX_DN_BYTES] ; char abbName [MAX_DN_BYTES] ; DWORD flags = 0 ; // ... dsContext.GetContext(DCK_FLAGS,& flags) ;& flags &= ~ DCV_TYPELESS_NAMES ;& dsContext.SetContext(DCK_FLAGS,& flags) ;& if (dsContext.CanonicalizeName(distName,canName) == 0 &&& dsContext.AbbreviateName (canName, abbName) == 0) cout << abbName << endl ;< else cout << "Error " << hex << dsContext.status()< << " while abbreviating name " << distName << endl ;<
DSBuffer Class
Due to the variable and sometimes large size of NDS objects, the NDS APIs use buffers to transfer data between the application and the NDS. Buffers are normally used to perform the following tasks:
transfer data to the NDS (input buffers)
transfer data from the NDS (output buffers)
transfer filter expressions to the NDS (filter buffers).
To reflect this distinction the DSBuffer class consists of three base classes, DSInput, DSOutput and DSFilter. Each base class has a buffer and the subset of APIs necessary for its task defined as member functions. The member functions defined in the base class are accessible to DSBuffer, hence when calling a buffer API it is not necessary to state the directory context or buffer since these are implicit in the DSBuffer object.
The default size for an NDS buffer is given by the constant DEFAULT_MESSAGE_LEN which is set to 4096 bytes. The size of the buffers used by the DSBuffer class can be specified when the object is constructed. The prototype for the DSBuffer constructor is given in Listing 5.
Listing 5
DSBufferDSBuffer(NWPSTR context = 0, DWORD flags = DS_CONTEXT_FLAGS, WORD inputSize = DEFAULT_MESSAGE_LEN, WORD outputSize = DEFAULT_MESSAGE_LEN, WORD filterSize = DEFAULT_MESSAGE_LEN)
The flags field is used to set the directory context flags and is passed onto the DSContext constructor. The default value given in the prototype is equivalent to the default value used by the NDS. An example of using a DSBuffer object to create a new NDS User object is given in Listing 6. Only DSInput commands are used in this example since an input buffer is all that is required to create NDS objects.
Listing 6
char objName[MAX_DN_CHARS]; char surname[MAX_DN_CHARS]; char phone[32]; // ... dsBuffer.InitBuf(DSV_ADD_ENTRY); dsBuffer.PutAttrName("Object Class",SYN_CLASS_NAME,"User") ; dsBuffer.PutAttrName("Surname",SYN_CI_STRING,surname) ; dsBuffer.PutAttrName("Telephone Number",SYN_TEL_NUMBER,phone) ; if (dsBuffer.AddObject(objName) == 0) cout << "User << objName << "created successfully\n" ;< else cout << "Error " << hex << dsBuffer.status()< << " while creating user " << objName << endl ;<
Searching the NDS requires all three buffers. The filter buffer is used to specify which objects should be located, the input buffer is used to specify which attributes should be obtained and the output buffer is used to return the attribute values. An example of searching the NDS for users' telephone numbers is given in Listing 7. As in the example given in Listing 1b the output buffer is not accessed directly, but via a class derived from DSIteration.
Listing 7
#include <iostream.h<< #include <string.h<< #include "dslocale.h" #include "dsbuffer.h" #include "dsiterat.h" #define PHONE_SIZE 32 #define LOGIN_SIZE 64 #define FULL_SIZE 127 NWPSTR UserObjectAttributes[] = {"CN","Full Name","Telephone Number",0} ; class DSUserObject : public DSIteration { char LoginName[LOGIN_SIZE] ; char FullName [FULL_SIZE ] ; char Telephone[PHONE_SIZE] ; WORD Index ; public: NWDSCCODE InitObject(NWPSTR object, void * info) { LoginName[0] = FullName[0] = Telephone[0] = 0 ; return DSIterationInitObject(object,info) ; } NWDSCCODE SetAttrName(NWPSTR attrName, NWSYNTAX_ID syntax) ; NWDSCCODE SetAttrValue(NWSYNTAX_ID syntax, void * data) ; NWDSCCODE ExitObject(NWPSTR object) { cout << object << endl << * this ;< return DSIterationExitObject(object) ; } friend ostream & operator&&(ostream & s, DSUserObject & u) ;& } ; NWDSCCODE DSUserObjectSetAttrName(NWPSTR attrName, NWSYNTAX_ID) { for (Index = 0 ; UserObjectAttributes[Index] ; Index++) if (stricmp(UserObjectAttributes[Index],attrName) == 0)break ; return 0 ; } NWDSCCODE DSUserObjectSetAttrValue(NWSYNTAX_ID, void * data) { switch (Index) { case 0: strncpy(LoginName,(NWPSTR) data,LOGIN_SIZE) ; break ; case 1: strncpy(FullName, (NWPSTR) data,FULL_SIZE) ; break ; case 2: strncpy(Telephone,(NWPSTR) data,PHONE_SIZE) ; break ; } return 0 ; } ostream & operator&&(ostream & s, DSUserObject & user)& { return s << "Login\t" << user.LoginName << "\n"< << "Name\t" << user.FullName << "\n"< << "Phone\t" << user.Telephone << "\n" ;< } int main(int argc, char * argv[]) { DSLocale dsLocale ; DSUserObject dsUserObject ; DSBuffer dsBuffer(argc == 3 ? argv[2] : "") ; if (dsLocale.status() || dsBuffer.status()) return 0 ; dsBuffer.InitBuf(DSV_SEARCH) ; dsBuffer.PutAttrName(UserObjectAttributes) ; dsBuffer.InitFilter() ; dsBuffer.AddFilterToken(FTOK_ANAME,"Object Class",SYN_CLASS_NAME) ; dsBuffer.AddFilterToken(FTOK_EQ) ; dsBuffer.AddFilterToken(FTOK_AVAL,"USER",SYN_CLASS_NAME) ; dsBuffer.AddFilterToken(FTOK_AND) ; dsBuffer.AddFilterToken(FTOK_ANAME,"CN",SYN_CLASS_NAME) ; dsBuffer.AddFilterToken(FTOK_EQ) ; dsBuffer.AddFilterToken(FTOK_AVAL,argv[1],SYN_CI_STRING) ; dsBuffer.ExitFilter() ; if (dsBuffer.status() == 0) dsBuffer.Search("",DS_SEARCH_SUBTREE,1,1,0,&dsUserObject) ;& else cout << "Error " << hex << dsBuffer.status()< << " while constructing filter\n" ;< return 0 ; }
Modification of NDS attribute values is a two stage process. First the current value of the attribute must be read and recorded. Once the current value has been obtained it can be modified by removing the value and adding the new value. This approach is necessary because NDS attributes can contain multiple values, and hence the NDS needs to know which value is being modified. An example of modifying an object's access control list is given in Listing 8. The class DSACLIteration is used to scan the ACL until an entry with the desired subject name and attribute is found. If an entry is found then the entry is removed from the ACL and the modified entry is inserted.
Listing 8
struct ObjectACL { NWPSTR PtrSubjectAttr ; NWPSTR PtrSubjectName ; DWORD SubjectRights ; char SubjectAttr[MAX_SCHEMA_NAME_BYTES] ; char SubjectName[MAX_DN_BYTES] ; } ; class DSACLIteration : public DSIteration { ObjectACL ACL ; DWORD OldRights ; DWORD NewRights ; int Found ; int Count ; public: DSACLIteration(NWPSTR name, NWPSTR attr, DWORD rights) ; NWDSCCODE InitLoop() {Found = Count = 0 ; return 0 ;} NWDSCCODE SetAttrValue(NWSYNTAX_ID, void *) ; ObjectACL * oldACL() {ACL.SubjectRights = OldRights ; return & ACL ;}& ObjectACL * newACL() {ACL.SubjectRights = NewRights ; return & ACL ;}& int found() { return Found ; } int count() { return Count ; } DWORD oldRights() { return OldRights ; } DWORD newRights() { return NewRights ; } } ; DSACLIterationDSACLIteration(NWPSTR name, NWPSTR attr, DWORD rights) { strcpy(ACL.SubjectName,name) ; ACL.PtrSubjectName = ACL.SubjectName ; strcpy(ACL.SubjectAttr,attr) ; ACL.PtrSubjectAttr = ACL.SubjectAttr ; ACL.SubjectRights = NewRights = rights ; } NWDSCCODE DSACLIterationSetAttrValue(NWSYNTAX_ID, void * data) { ObjectACL * value = (ObjectACL *) data ; Count ++ ; OldRights = value->SubjectRights ;> Found = (stricmp(value->PtrSubjectAttr,ACL.PtrSubjectAttr) == 0 >>> stricmp(value->PtrSubjectName,ACL.PtrSubjectName) == 0) ;> return Found ; } /* ** GrantAccess(object,subName,subAttr,subRights) ** ** Modify the ACL of 'object' so that 'subName' has 'subRights' ** over the 'subAttr' attribute of 'object'. */ NWDSCCODE GrantAccess(NWPSTR object, NWPSTR subName, NWPSTR subAttr, DWORD subRights) { DSBuffer dsBuffer ; DSACLIteration dsACLObject(subName,subAttr,subRights) ; DWORD flags ; dsBuffer.InitBuf(DSV_READ); dsBuffer.PutAttrName("ACL") ; // Read object's ACL attribute looking for an existing ACL value. // If a value with the same subject name and attribute is found // then the read is stopped and DSBufferRead() returns 1. if (dsBuffer.Read(object,1,0,& dsACLObject) & 0)& return dsBuffer.status() ; // Determine if an ACL value already exists with the desired // rights exists. if (dsACLObject.found() && dsACLObject.oldRights() == subRights)& return 0 ; dsBuffer.InitBuf(DSV_MODIFY_ENTRY) ; if (dsACLObject.count() == 0){ // Add ACL attribute to the object dsBuffer.PutChange(DS_ADD_ATTRIBUTE,"ACL") ; } if (dsACLObject.found()){ // Remove old ACL value from ACL attribute dsBuffer.DelAttrValue("ACL",SYN_OBJECT_ACL,dsACLObject.oldACL()) ; } // Add new ACL value to ACL attribute dsBuffer.AddAttrValue("ACL",SYN_OBJECT_ACL,dsObject.newACL()) ; return dsBuffer.ModifyObject(object) ; }
Downloading the Class Library
The NDS class library can be downloaded from Compuserve=s NOVLIB forum, Library 11. It is in a self-extracting arvchive file named DN511.EXE.
* Originally published in Novell AppNotes
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.