Novell is now a part of Micro Focus

Using NDS Schema Extensions to Record Application Configuration Data

Articles and Tips: article

Developer Support Engineer
Developer Support

01 Oct 1996

Describes how to use Novell Directory Services (NDS) to record application configuration data. Uses a simple public key encryption application to illustrate that an application can record configuration data in NDS so that users can access for the data irrespective of where the application is executed from.


The structural rules controlling the organization of NDS is called the NDS Schema. From the first release of NetWare 4.0 the NDS Schema has been extendable, allowing application developers to use NDS to record server and application specific information. From as early as December 1993 the author used NDS to record Windows GRP files and workstation configuration information.1 However, it is only recently that the full power of NDS has been realized by the Novell developer community, both internal and external to Novell, with new applications that can utilize NDS.

By using NDS to record application configuration information, an application developer can off load many of the problems concerning the location of the information. Traditionally, application data is recorded in files on a workstation or a server. For an application to access the data, it would need to be executed from the same workstation or locate the data on the server. The former solution of recording application data on the workstation is far from ideal since it ties a user to a particular workstation. If the user needs to use a different workstation, the user's application data will not be available. Recording application data on a server will make the data available to all workstations in the locality of the server. While this method removes the restriction of using a single workstation, it introduces new complications concerning the location of the server, drive mappings, availability of the server, etc.

NDS has the solution to the problem by providing a global resource location service. An application does not need to know the location of the data inside NDS, just the format in which the application recorded the data. The application can then request NDS to locate data, and NDS will search the NDS tree. The process by which the NDS tree is searched is called tree walking. This is the process whereby the client making the request and the servers holding replicas of the NDS tree exchange packets to locate the information. For example, if a local server does not hold a copy of the requested information, the server will return the address of other servers to check. This process is repeated until a server is located that holds a copy of the replica containing the data. Since NDS is a replicated distributed service it would take the failure of several servers for tree walking to fail to find the data.

The process of tree walking is invisible to the application; the application only has to request information using a distinguished name. The distinguished name is a path through the tree that uniquely identifies an object. The NDS tree walking process will then resolve that name into a location. Obtaining the distinguished name of the user currently using an application is straightforward by using the API NWDSWhoAmI. The application can then use the distinguished name to record and save application configuration information specific to that user. Hence, whenever the user uses the application, the application will be able to retrieve and save the user's configuration.

NDS Schema

The NDS Schema controls the structure of the NDS tree by specifying rules controlling properties such as the types of values that can be recorded, the attributes that can be used with each class, which classes can be containers, which classes can be contained in which classes, etc. This information is recorded as part of the NDS tree; however it is not directly viewable like the main tree but is accessed using special APIs. Since the structure of the tree depends on the schema, the schema is recorded on every server in the NDS tree and is synchronized to ensure that it is consistent. The schema affects the whole tree, including the tree root; hence it is necessary to have supervisor rights to the root of the tree to modify the schema. Once the schema has been modified, the modifications will then be synchronized to all servers in the NDS tree. As with all NDS operations it is advisable to have the tree fully synchronized before attempting to update the schema. Use DSTrace to check that all servers respond with All processed = Yes to verify that the tree is fully synchronized.

The schema that is supplied with NetWare is called the base schema. The base schema consists of the standard attribute and class definitions. Each release of NetWare will normally have a new release of the base schema to incorporate new attribute and class definitions used by NetWare utilities such as the NetWare Application Launcher.

For the purposes of this article the schema will be separated into three areas: syntax definitions, attribute definitions, and class definitions. Only attribute and class definitions can be modified; syntax definitions are fixed for all NDS trees. As detailed in August 1996 NDS Q&A, certain modifications to standard classes in the base schema cannot be reversed. Hence, care should always be taken when developing applications that use NDS schema extensions.

Schema extensions have to be registered with NDS Schema Registry, and having registration is also required for Yes certification. This is to ensure that the extensions are unique and compiles with Novell naming standards. To register schema extensions a company can register an extension name of up to eight alphanumeric characters. The extension name can then be used as a prefix in subsequent attribute and class names. Registration can be performed online through the world wide web using the URL http://NetWare.Novell.Com/nds/schema.htm. There is an administrative charge of US$100 for registering an extension name.

Syntax Definitions

The NDS schema has 28 predefined value types or syntaxes. As stated above it is not possible, nor really necessary, to add new syntax types. The available syntax types include the standard types of case ignore string, case exact string, integer, and boolean, plus NDS specific types such as distinguished name, access control list, partition, time stamp. The syntax also controls how values are compared by APIs such as NWDSSearch.

Each syntax is referenced by an integer in the range 0 to 27. The syntax types are enumerated in the file NWDSDEFS.H. The data structures used to represent the syntax types are defined in the file NWDSATTR.H. For example, the syntax SYN_CI_STRING uses a character pointer, while SYN_INTEGER uses a 32-bit signed integer. These are the structures used by the APIs and are not necessarily those used inside NDS buffers nor in communication. It is possible to print out the contents of the Buf_T data field to investigate how data is transmitted between the client and server, or alternatively use LANAnalyzer to check the NCP packets.

Two very important syntax types are SYN_OCTET_STRING and SYN_STREAM. Both types can be used to recorded binary data of no particular structure. The SYN_OCTET_STRING is for short strings (no more than few hundred bytes) and is recorded inside the NDS files. SYN_OCTET_STRING values are modified and retrieved using the standard NDS APIs used by other syntax types. The SYN_STREAM is for storing general data files as NDS values. The data is accessed using special APIs that return a file handle.

Attribute Definitions

Attribute definitions associate a name with a syntax type. Once an attribute has been defined it can then be used as part of a class definition. As can be seen, attribute definitions are independent of class definitions. Hence, an attribute can be used in any class definition, and will always have the same syntax and properties. This is dissimilar to C++ classes where two classes can have a member field with the same name but different type.

Attributes also have associated properties to control how the attribute can be used. Such properties include whether the attribute can hold multiple values, possible range on the values the attribute can hold, whether the attribute should be readable by [Public] irrespectively of rights to the object containing the attribute, and how the attribute should be synchronized. Attribute properties are set when the attribute is defined and cannot be modified without first deleting the attribute and then redefining it. An attribute definition can only be removed if it is not used in any class definition.

Class Definitions

The NDS tree is constructed of objects. The information the objects contain, how the objects are named, and the structure of the tree is controlled by the class definitions recorded in the schema. A class definition can be divided into six sections: class properties, base classes, containment classes, naming attributes, mandatory attributes, and optional attributes. The APIs used to define new classes mirror the structure of the class definition given above by listing the classes and attributes in the same order.

The NDS schema allows a class definition to be derived from other class definitions. In fact, all NDS classes are derived from the class Top, either directly or indirectly through another class definition. The class Top contains two attributes, the mandatory attribute Class Nameand the optional attribute ACL. The new class definition will inherit all the rules and attributes from the base classes listed in the definition. This allows some sections listed above to be empty if nothing additional is being added to the base class definition.

Containment class section lists the classes that objects of the new class can be contained in. It is this section that defines the general structure of the NDS tree. For example, the class Userhas the classes Organizational Unitand Organizationlisted in the containment section.

Naming attributes list the attributes that are used to name objects of this class. Normally there is only a single naming attribute. If there are multiple naming attributes, such as in the class Bindery Object, then a value for each naming attribute has to be provided when naming an object. This can be cumbersome and should be avoided. It is also a good idea to keep to the default naming rules of OUfor container objects and CN for leaf objects so that attribute types can be omitted when entering object names.

Mandatory and optional attributes list the attributes that comprise the object. As the name suggests, mandatory attributes have to be defined when an object it created.

There are two important class properties: effective and containment. A class definition is effective if objects of that class can be created. The NDS schema has several non-effective classes, such as Topand Person. Non-effective classes are used in the definition of effective classes. Since objects of a non-effective class cannot be created, they are not subject to the same definition rules as effective classes. The containment property indicates if the class is allowed to be an NDS container. Since a class definition states which classes it is allowed to be contained in, any class defined as a container can only contain classes defined later to be containable by the container class.

Multiple Tree Support

The new Client32 requesters can authenticate to multiple NDS trees. Hence, NDS-aware applications should now be designed to take advantage of this and allow the user to specify an alternative NDS tree. How this is implemented is up to the application designer. The simplest approach is to have an edit field with a Set Tree button. A more sophisticated approach would be to discover all available NDS trees on the network and present a list of trees to the user. If you happen to be working where there are quite a few trees on the network, it would be better to present a combo box with a list of authenticated trees.

Once the tree name has been entered it can be set as the current tree for a context handle using the API NWDSSetContext with the context key DCK_TREE_NAME. This sets the tree name for that context handle only; hence, it is possible to create a new context handle each time a tree is specified. This approach allows a user to switch between different trees within the application and maintain the same context within each tree. Since the number of open context handles is limited, care should be taken to limit the number context handles created and to check that they are valid.

The name context does not change when setting the tree name in a context handle. Unless the trees have identical structure it is likely that the name context will be invalid. Hence, after changing the tree name, the name context should be set to [Root]or set by using the APIs NWDSGetDefNameContext followed by NWDSSetContext with the context key DCK_NAME_CONTEXT.

Crypt: An RSA Encryption Application

The use of schema extensions and multiple tree support will be demonstrated with a public key encryption application based on the RSA encryption algorithm. The RSA encryption algorithm was developed by Rivest, Shamir, and Adleman in 1977.2 The basis of the RSA algorithm is that it is computationally hard to determine the factors of a number (computationally hard refers to the time it takes, rather than the complexity of the algorithm used). Hence, by choosing two large prime numbers, p and q, it is practically impossible using current technology and algorithms to determine the numbers p and q from their product n = p*q. Rivest, Shamir, and Adleman proved that by using p and q it is possible to derive two numbers r and s such that mrs mod n m for all m n, moreover m cannot be determined from mr mod n and s cannot be determined from r without knowing p or q. In the RSA algorithm, the pair (r,n) is used as a public key and is made available to anyone wishing to encrypt a message. The pair (s,n) is kept secret as the private key to decode the message.

In a practical implementation of RSA encryption the prime numbers p and q would be few hundred digits long. This begs the question of how it is possible to tell that a number is prime while it is hard to find the factors of a composite number. In fact it is fairly easy to determine if a number is composite without finding any of its factors by using the Miller-Rabin test. Details of the Miller-Rabin test can be found in Knuth3. The process of determining r and s from p and q is derived from Euclid's algorithm of finding the highest common factor of two numbers. In fact, only one of r or s has to be calculated, the other can be chosen. Some texts suggest that r can be 3 or 216+1, others suggest the s should be suitably generated random prime. Details of Euclid's algorithm can be found in Blahut4. The process of calculating mr mod n and cs mod n, where c is the encrypted message, is performed using a shift multiply loop similar to the shift add loop used to multiply numbers. Since the result is taken modulo n, each iteration of the shift multiply loop can be reduced modulo n to keep the intermediate results bounded.

The RSA Encryption algorithm used in this example is based on Sedgewick5, with further references to Knuth6. Portions of the RSA Encryption algorithm and derived products have been patented by RSA Data Security Inc and are used under license by Novell. The implementation in Crypt uses 15-bit encryption keys so that the multiplication loop can be implemented using 32-bit longs. With 15-bit keys your data is safe from code breakers for about a milli second.

Crypt Schema Extensions

The Crypt application records the public key for a user in the user's NDS object. In this way it does not matter where users are; they will always be able to find the receiver's public key by using the distinguished name. The client requester will be able to resolve the distinguished name and locate a server with a copy of the object. The private key is also recorded in NDS. This would not normally be the case in a secure system since the private key could then be accessed by system administrators; however, this allows managers to be able to decode messages sent to members of their group-a form of "Clipper"7.

A new NDS class is also defined to allow messages to be sent to a group of users. Users sending messages to the group will access the public key recorded in the object. Members of the group will be allowed to access the private key recorded in the object to decode the message.

The code to modify the NDS Schema is contained within the Crypt application and can be executed from the menu. Normally the schema would be modified during the application installation since this operation is not a normal option for users. Developers should also consider a quick installation option for just performing the schema extension. This is for recovery after a volume crash or similar since some backup software packages cannot perform schema extensions but are able to record extended data. In this case the administrator would extend the schema before recovering the NDS data.

As can be seen from the previous section the RSA keys consist of three items: the public key, the private key, and the modulus. In practical implementations these keys would be a few hundred bytes long and hence would be recorded as SYN_OCTET_STRING values. However, this implementation uses 15-bit keys; hence, the keys can be recorded as SYN_INTEGER. As stated above, the process of retrieving SYN_OCTET_STRING values is identical to that as SYN_INTEGER. The attribute schema extension used by Crypt is given in following table.











The class schema extensions is given in the following table. Crypt adds the three attributes to the base class User, and defines a new class XX_CLASSNAMEbased on the class Group. Since Group already has appropriate values for the Containment, Naming and Mandatory fields, these fields are left empty.

Class name

Base classes














Attribute schema extensions are performed using the API NWDSDefineAttr. This API requires a context handle to identify the tree to modify and a pointer to an attribute information structure. The code in Listing 1 demonstrates how the XX_PUBLICKEY attribute is defined.

Listing 1

NWDSCCODE    ccode ;

DSContext    Context ;

Attr_Info_T  attrInfo ;

    memset(& attrInfo,0,sizeof(attrInfo));& attrInfo,0,sizeof(attrInfo));

    attrInfo.attrFlags   = DS_SINGLE_VALUED_ATTR ;

    attrInfo.attrSyntaxID= SYN_INTEGER ;

    ccode = NWDSDefineAttr(Context,XX_PUBLICKEY,&attrInfo) ;&
    if (ccode && ccode != 0xfffffd99L)

        return false ;

The type DSContext is a C++ wrapper used by the author to create and free context handles. In the above example the ASN.1 fields contained in attrInfo are left blank since they are not required. The return value of 0xfffffd99 indicates that the extension already exists and can be ignored.

Adding the new attributes to the User class is straightforward and uses the API NWDSModifyClassDef. The code in Listing 2 shows how the attributes are added to the class User.

Listing 2

DSContext Context ;

DSBuffer  input ;






    ccode = NWDSModifyClassDef(Context,"User",input);

    if (ccode && ccode != 0xfffffd73L)

        return false ;

The type DSBuffer is a C++ wrapper used by the author to create and free context buffers. The error code 0xfffffd73 indicates that the extension already exists and can be ignored.

Creating new NDS classes is the most complicated step. As stated previously the definition of a class follows the six sections of base classes, containment, naming, mandatory, optional, and properties. The first five sections are recorded in an input buffer, the properties stored in a separate structure. Listing 3 demonstrates how the XX_CLASSNAME structure is defined.

Listing 3

NWDSCCODE    ccode ;

DSContext    Context ;

DSBuffer     input ;

Class_Info_T classInfo ;

    memset(& classInfo,0,sizeof(classInfo));& classInfo,0,sizeof(classInfo));
    classInfo.classFlags = DS_EFFECTIVE_CLASS ;


    // Set Super Classes



    // Set Containment classes


    // Set Naming attributes


    // Set Mandatory attributes


    // Set Optional attributes





    ccode = NWDSDefineClass(Context,XX_CLASSNAME,& classInfo,input) ;& classInfo,input) ;
    if (ccode && ccode != 0xFFFFFD7BL)

        return false ;

In the above example the ASN.1 fields contained in classInfo are left blank since they are not required. The return value of 0xfffffd7b indicates that the extension already exists and is ignored. The call to API NWDSBeginClassItem marks the start of each section. To indicate that a section is empty simply call the function again without adding anything to the input buffer.

Crypt Multiple Tree Support

Crypt provides multiple tree support by offering an edit field and a Set Tree button as shown in Figure 1. When the Set Tree button is clicked a dialog box opens so that the user can enter an alternative tree name and context.

Figure 1: Crypt's multiple tree support.

The current context is displayed in a read only edit field. Alternatively the context could be set to [Root] and the user asked to enter distinguished names.

The code to change the current tree is given in Listing 4. Unfortunately, setting the tree name and name context to invalid values does not return an error; hence, detecting invalid values is hard. To determine if the tree name is suitable for Crypt a call to NWDSCanDSAuthenticate is made to see if the client has the authentication credentials for the tree. If so the default context is obtained, otherwise the tree name is reset to the orginal value.

Listing 4

bool SetCurrentTree(char *treeName)


char defContext[MAX_DN_CHARS + 1] ;

char oldTree[MAX_TREE_NAME_CHARS + 1] ;



    if (NWDSCanDSAuthenticate(Context) == 0 ||



        return false ;



    return true ;


The context handle Context is a global variable so that the tree name and name context is persistent throughout the application.

It could be argued that the requirement for an authenticated connection is not necessary. This is because the public key could be made readable by [Public] by defining the attributes with the DS_PUBLIC_READ property. This property enables any connection to read the attribute as long as the connection knows the distinguished name of the object containing the attribute, (the distinguished name is required since an unauthenticated connection will not have browse rights). In the case of the Crypt application this could be a valid feature allowing a user to encrypt messages for users in NDS trees for which sender has no authentication rights.

Reading and Saving NDS Values

Reading the RSA keys from NDS uses the standard NDS APIs and is given in Listing 5. In this example the attribute value parameters are used to indicate whether the particular value is required. If the return pointer is null then the attribute name is not placed in the input buffer.

Listing 5

bool ReadUserKeys(char * objectName,

                  char * publicAttr, long * publicValue,

                  char * privateAttr, long * privateValue,

                  char * modulasAttr, long * modulasValue)


DSBuffer input ;

DSBuffer output ;

    // Specify which attributes should be read from the object.


    if (publicValue)  NWDSPutAttrName(Context,input,publicAttr);

    if (privateValue) NWDSPutAttrName(Context,input,privateAttr);

    if (modulasValue) NWDSPutAttrName(Context,input,modulasAttr);

    // No iteration is required since the results will fit inside one

    // buffer, however iteration handle must still be used.

    nint32 iteration = NO_MORE_ITERATIONS;

    if (NWDSRead(Context,objectName,DS_ATTRIBUTE_VALUES,

                        false,input,& iteration,output))

        return false ;

    // Read the results from the output buffer.

    nuint32 attrCount  = 0;

    nuint32 valueCount = 0;

    nuint32 syntax = 0 ;

    char    attrName[MAX_SCHEMA_NAME_BYTES+1];

    // Get the number of attribute values returned.

    NWDSGetAttrCount(Context,output,& attrCount) ;& attrCount) ;

    for (int i = 0 ; i < attrCount ; i++){

        // Get the attribute name and number of values.

        NWDSGetAttrName(Context,output,attrName,& valueCount,& syntax) ;& valueCount,& syntax) ;
        long * attrValue = 0 ;

        // Determine which attribute is next.

        if (syntax == SYN_INTEGER){

            if (stricmp(publicAttr, attrName)== 0) attrValue=publicValue ;

            if (stricmp(privateAttr,attrName)== 0) attrValue=privateValue ;

            if (stricmp(modulasAttr,attrName)== 0) attrValue=modulasValue ;


        // Read the attribute values.

        for (int j = 0 ; j < valueCount ; j++)

            NWDSGetAttrVal(Context,output,syntax,attrValue) ;


    return true ;


As can be seen in the above code the values of attrCount and valueCount are still used despite knowing what their values should be. In all cases it is necessary to use the protocol set by NDS APIs and never attempt to skip one of the steps. Also use what has been recorded in the NDS buffer rather than what is expected by the application design.

Saving the RSA keys in NDS is slightly more complicated since to change a value the old value has to be removed. This requires reading the old value from NDS so that the value can be deleted. The function ChangeAttrValue in Listing 6 is used to save a single attribute value. The attribute value of minus one is used to indicate that there was no previous value.

Listing 6

int ChangeAttrValue(pBuf_T input, char * attr,

                    long newValue, long oldValue)


    // Return zero if no modifiction

    if (newValue == oldValue) return 0 ;

    if (oldValue !=  1L){

        NWDSPutChange (Context,input,DS_REMOVE_VALUE,attr) ;

        NWDSPutAttrVal(Context,input,SYN_INTEGER,& oldValue) ;& oldValue) ;

    NWDSPutChange (Context,input,DS_ADD_VALUE,attr) ;

    NWDSPutAttrVal(Context,input,SYN_INTEGER,& newValue) ;& newValue) ;

    return 1 ;


The above function can now be used to save the new attribute values as shown in Listing 7. As can be seen a call is made to ReadUserKeys to get the current value of keys so that the new value can be saved.

Listing 7

bool SaveUserKeys(char * objectName,

                  char * publicAttr, long publicValue,

                  char * privateAttr, long privateValue,

                  char * modulasAttr, long modulasValue)


DSBuffer input ;

DSBuffer output ;

    // Modify access rights to the Crypt attributes so that the

    // public key is readable and private key is hidden.

    AddKeyAttribute(objectName,publicAttr, DS_ATTR_READ) ;

    AddKeyAttribute(objectName,privateAttr,0) ;

    AddKeyAttribute(objectName,modulasAttr,DS_ATTR_READ) ;

    // Set temporary values to  1L to determine later if the attributes

    // are defined in NDS.

    long publicTemp  =  1L,

         privateTemp =  1L,

         modulasTemp =  1L;

    // Read the current values of the attributes.


         publicAttr, & publicTemp,

         privateAttr,& privateTemp,

            modulasAttr,& modulasTemp) ;& modulasTemp) ;

    NWDSInitBuf(Context,DSV_MODIFY_ENTRY,input) ;

    int change = 0 ;

    // Compare the current value with the new value and record

    // modifications in the input buffer.

    change += ChangeAttrValue(input,publicAttr, publicValue, publicTemp) ;

    change += ChangeAttrValue(input,privateAttr,privateValue,privateTemp) ;

    change += ChangeAttrValue(input,modulasAttr,modulasValue,modulasTemp) ;

    return (change)

         ? NWDSModifyObject(Context,objectName,0,0,input) == 0 : true ;


One common error is to attempt to save an attribute value without first adding the attribute to the object. Fortunately, contrary to what is stated in the documentation, it is not an error to attempt to add an existing attribute to an object. The procedure AddKeyAttributes is used to add the RSA key attributes to the object and to modify the object's ACL so that the public key attributes are readable by [Public] and that the private key is hidden. If [Public] access is all that is required then this step can be replaced by defining the public key attributes as DS_PUBLIC_READ.

This allows the attribute to be read by [Public] irrespective of the rights given to given to the object. Alternatively, if encryption system should only be used by a specific group of users then access to the public keys should restricted. This can be done by changing the assignment to acl.subjectName in the following procedure.

Listing 8

bool AddKeyAttribute(char * objectName, char * attrName, long rights)


DSBuffer     input ;

Object_ACL_T acl ;


        // Create the attribute to which rights are being assigned.



        acl.protectedAttrName = attrName ;

        acl.subjectName = DS_PUBLIC_NAME ;

        acl.privileges = rights ;


        NWDSPutAttrVal(Context,input,SYN_OBJECT_ACL,&acl) ;&

        return NWDSModifyObject(Context,objectName,0,0,input) == 0 ;


Crypt Source Files

Crypt was written using Borland's OWL 2.0 library, and hence can only be recompiled with the Borland OWL libraries. However the NDS and RSA code is contained in separate files so writing a DOS, MFC, or UNIX front-end should be straightforward.

The following table lists the source files and their function.





Windowsfront end


Windowsdialog procedures







Finally, there has been no mention on how to create XX_CLASSNAME objects; also to post an encrypted message you have to paste the text from Crypt into an E-mail message. However, with an NWAdmin Snapin and a GroupWise C3POs you will be able to create group key stores and send messages with digitial envelopes directly from the GroupWise. The process of incorporating Crypt into NWAdmin and GroupWise will be subject of another article.


1Buckle, J.: SPOC 4 - NetWare and Windows Management Tool: Clear Technology Corp, Sydney, Australia(April 1994)

2Rivest, R. Shamir, A. Adleman, L.:Communications of the ACM21 (1979): p120-126

3Knuth, D.: Art of Computer Programming Volume 2, Seminumerical Algorithms: p 379

4Blahut, R.: Fast Algorithms for Digital Signal Processing: p 45

5Sedgewick: Algorithms: p 338

6Knuth, D.: ibid: p 386

7Clipper is the term used by the White House for key escrow: recording encryption keys so that law enforcement agencies can read encrypted messages.

* Originally published in Novell AppNotes


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.

© Copyright Micro Focus or one of its affiliates