Novell Home

DeveloperNet University's NDS101 Using C

Articles and Tips: article

LAWRENCE V. FISHER
Senior Research Engineer
Developer Information

01 Nov 1998


Enables a C/C++ programmer to use Novell's NetWare libraries to build a Win 32 client-side-only application that can read NDS directory information. Second example in DeveloperNet University's NDS101 category.

Introduction

This is the second example in DeveloperNet University's NDS101 category. The information below describes what this example intends to accomplish and what you will need in order to build its sample application.


Objective
Using this example as a guide, a C/C++ programmer will be able to use Novell's NetWare libraries to build a Win32 client-side-only application that can read NDS directory information.

Prerequisites

The student must have entry level C programming skills (other DeveloperNet University examples at this level will have prerequisites unique to their environment).

Required Items

A Win32 development environment.

The Novell Libraries for C and documentation installed. Novell's Libraries for C install is available along with a free DeveloperNet subscription from:http://developer.novell.com/ndk/

The downloaded libraries will also include libraries for Novell server-side development (NLMs). This example is concerned only with the client-side development, so ignore the NLM materials completely.

Optional Items

Novell's NetWare Administrator or ConsoleOne NDS Administration tools available along with a free DeveloperNet subscription from:http://developer.novell.com/ndk/

Required Setup

The programmer must have authenticated, Client32 access (be able to log in) to at least one NDS tree. The object to be read will be the programmer's user object in the tree. Attempts to read values from another object may fail because the programmer may not have the read right for the other object. We intend to discuss security issues in later examples.

The NDS101 GUI

In order to keep the code and explanations simple, we provide simple GUIs for all DeveloperNet University examples. We assume you are already familiar with programming the GUI on your platform. Figure 1 shows the GUI provided for NDS101 using C.

Figure 1: The GUI is kept seperate from the example code discussed in DeveloperNet University articles.

When the user clicks the "Read Attribute" button, the GUI code will execute the example code to read the attribute specified by "Attribute Name" from the object specified by "Object Name" in the tree specified by "Tree Name."

Figure 2: You can modify the example code so that the GUI will call your experiments.

Figure 2 shows a code snippet that could be used to perform an NDS read operation. As you can see, there really isn't too much code necessary to read a value from NDS. However, you need to understand some things before you can adapt the code to your own purposes. Namely, you need to understand the following basic concepts:

  1. NDS trees

  2. NDS object context

  3. Attribute fields and their syntaxes

Read the following three subsections to learn about these topics. Or, if you are already familiar with NDS, skip to the end of the article for a detailed source code listing and explanations.

NDS Trees

Note: The following discussion uses NDS terms; these terms may be different from those used in environments other than C, such as the NWDir bean.

An NDS tree can be looked at like a database containing directory information. The directory's entries are called objects (like database records), which contain information about entities on the network. Figure 3 shows some different types of NDS objects.

Each tree maintains its own distributed NDS database of objects, usually on more than one server. NDS administrators generally use a network administration application provided by Novell to view and help manage their trees. Novell provides an application called NetWare Administrator that can be run from Windows clients and a Java application called ConsoleOne for the server and other platforms that can be used for NDS administration.

The information in an NDS tree does not describe the physical layout of the network. Instead, an NDS tree view describes arbitrary groups of hardware and software network entities that reflect the organization of the business. The network layout view, on the other hand, usually only depicts the physical connections between the network's hardware entities.

Figure 3: An NDS tree shows how the administrator wants to look at the entities on the network.

NDS Object Context

The object type an administrator selects when creating an object to represent a network entity depends on the purpose of the entity as the administrator understands it. Each object contains the specific information needed for it to perform its role in the network. There are many standard object types provided by Novell, and often a programmer may need to add a new object type for special use by an application.

User objects contain information about the people who operate the client stations on the network. In NetWare Administrator and ConsoleOne, an object can be double-clicked to display and edit its information. This information is read from the object's attributes. NDS attributes are discussed later.

Tree administrators arbitrarily create, edit, and delete objects on a tree as they determine that it is necessary. If you have never used NetWare Administrator or ConsoleOne to perform these tasks and have admin rights on a tree, now would be a good time to log in and try them before continuing with this article.

Organization objects like the Developer Org object in Figure 3 are often the highest-level container in the tree. There can be multiple Organization objects, but the administrator for this tree has created only one. Organizational units such as the Tech Info and Education objects in Figure 3 are the mid-level containers in the tree.

Organizations and Organizational Units are called containers. Administrators nest containers to represent the structure within the organization. Organizations and Organizational Units have no physical presence on the network. You can see that there are no entities in the network layout which correspond to the Organization or Organizational unit objects. They are simply conceptual NDS devices designed to help organize directory information.

Administrators generally create user objects inside of Organizational Unit objects representing the groups that they work in. The path of Organization and Organizational Units leading to an object is actually a part of its name, like a fully qualified path name in a file system.

An object's fully qualified path name in NDS is called its NDS name context. Figure 4 shows the different kinds of name contexts that an object can have in an NDS tree.

Figure 4: Objects can be named by the paths leading to their location in the tree.

The first two name contexts listed in Figure 4 are referred to as distinguished name contexts because, as complete paths, they can distinguish between the Joe in Accounting and the Joe in Marketing. If the types for each step along the way are specified, the context is also referred to as typeful (CN refers to a leaf object's "Common Name").

A relative name context on the other hand, requires that the accessing station's current context be set so that it can complete a partial reference in order to fully distinguish a target object. For example, in the relative context example in Figure 4, current context would need to be set to "Accounts_Receivable.Accounting.Novell".

NDS Attributes

Objects are composed of attributes. Before an object can be created, the tree must possess knowledge of the attributes that should go into the object's type. These object type descriptions are called classes. Like a recipe, an NDS class describes the composition of the objects created from it. For those of you familiar with object-oriented programming, classes in NDS are very analogous to classes in C++ or Java in that you instantiate NDS objects from their NDS class definitions. Figure 5 shows a partial list of the attributes in a User object.

Figure 5: A user object has many more attributes than those shown here.

When an object is created, it must be initialized with all of its mandatory attributes and have values for them. However, an object may or may not have any of its optional attributes. Attempting to read an optional attribute value from an object which doesn't have that attribute yet will fail.

Attributes are based upon Syntaxes. Syntaxes are special typedefs created by Novell to be the data types used in attribute definitions. Every attribute definition is built upon a syntax. The syntax specified for an attribute definition defines the data format for values that can be put into that attribute type. You need to know an attribute's syntax in order to anticipate the type of value that you will receive back from a read operation.

Finally, the Source Code Listing

The following is a source code listing for NDS101 using Novell Directory Access Protocol (NDAP) with C. Note that the code has been simplified by not checking for errors. The buildable project from our download site will contain the necessary error checking, includes, and so on. All Novell data types and functions are in bold. The large italic, double-underlined numbers refer to explanations after the code listing.

Novell provides C libraries for all kinds of purposes. This example uses the NWNet library to access the Client32 DLLs installed on a NetWare client machine. Among other things, the Client32 DLLs provide NDAP NDS tree access functionality. You can install Novell's Libraries for C from the Web: http://developer.novell.com/ndk/

Once you have installed the NetWare SDK, make sure that you add the appropriate path names to your development environment so that it can access the NetWare headers and NWNet.lib before you attempt to build this sample application.

Before you run the application, make sure that you have logged in to your company's NetWare 4.1 or above network. This is important because, under the covers your NetWare Client32 installation receives the read request from the simple NDS101 application and sends it over the existing connection to the NDS tree.

1

    LPSTR objectName    = "lFisher.cdr.prv.novell";

    LPSTR attributeName = "CN";



    void getFieldStringValue(LPSTR theBuffer)

    {   NWDSContextHandle context;

        Buf_T             *responseBuf = NULL;

        Buf_T             *requestBuf = NULL;

        unsigned int      attrIndex;

        int32             iterHandle;

        uint32            totAttr, valCount, synID, flags;

        char              attrName[MAX_DN_CHARS];

2

        NWDSCreateContextHandle(&context);&
        NWDSSetContext(context, DCK_NAME_CONTEXT, objectName );

        NWDSGetContext(context, DCK_FLAGS, (void *)&flags);&
        flags |= DCV_TYPELESS_NAMES;

        flags |= DCV_XLATE_STRINGS;

        flags |= DCV_DEREF_ALIASES;

        flags |= DCV_DEREF_BASE_CLASS;

        WDSSetContext (context, DCK_FLAGS, (void *)&flags );&
3

    //  The output buffer doesn't need to be initialized.

        NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &responseBuf);&


    //  The requestBuf is inited for DSV_READ & set to name of target attr

        NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &requestBuf);&
        NWDSInitBuf(context, DSV_READ, &requestBuf );&
        NWDSPutAttrName( context, requestBuf, attributeName );

4

    //  Read the data.

        NWDSRead(   context, "", DS_ATTRIBUTE_VALUES,

                                 FALSE, requestBuf, &iterHandle, responseBuf);&


    //  pull stuff out of responseBuf. First, get num of attrs in buffer

        NWDSGetAttrCount(context, responseBuf, &totAttr);&
        NWDSGetAttrName(context, responseBuf, attrName, &valCount, &synID);&
        NWDSGetAttrVal(context, responseBuf, synID,theBuffer);



        NWDSCloseIteration(iterHandle);



        NWDSFreeBuf (responseBuf);

        NWDSFreeBuf (requestBuf);

        NWDSFreeContext (context);

    }
  1. The objectName and attributeName variables in the sample source code must be changed to describe the target NDS object, and its location in the NDS tree. This sample recommends that you target your user object for the read operation, because your company's network administrators have probably already given you the right to read its attributes. You may not have the right to read attribute values from other entries in your directory.

    Obtain the distinguished name for your user object. You can obtain the fully qualified distinguished name for your user object from the login window that you use when you log in to the network. Go there now to obtain your username and context and form a typeless distinguished name for your user object. Enter the distinguished name into objectName in the example source code.

  1. Note: Different environments use different delimiter characters. For instance, when using Novell's Java beans, the NDS context name delimiter will be the `\' character.

  2. Different environments may reverse the order of names in a distinguished name. For example in Novell's NWDir Java bean, names will be in reverse order of the names in this example.

    Select an attribute. For our sample code, we need to choose an attribute that we know will always exist in the object, like a mandatory attribute. Also, because our GUI interface expects a String, we need an attribute with a syntax that can be viewed as a String. Some syntaxes have no human readable String value.

    The user object's "CN" (common name) attribute is ideal for our purposes because it is mandatory (meaning it will exist in every user object), and it uses the "Case Ignore String" syntax. Enter the common name attribute's name "CN" as the attributeName value in the example source code.

  1. All NDAP NDS C APIs require a reference to a context data structure as a parameter. Some of the API requests are handled locally and so don't use the context. NDS C APIs that actually send a request to somehow modify the NDS tree's database require a context parameter for one of two purposes.

    • To describe the location and type of the target object. This enables NDS to find the right object to write to, read from, and so on.

    • To describe the user object of the person who logged in to the connection being used to make the request. In this case, NDS searches the target object's Access Control List (ACL) to determine if the authenticated user specified by the context parameter is listed and then determine if that user has the necessary rights to perform the requested operation. Security is described in greater detail in later examples.

    In the case of this example, since the target is your user object, the context we create will describe both the target object and the user making the request.

    Figure 6 shows the members of the NDS context returned by NWDSCreateContextHandle( ). As you can see, it contains considerably more than just a naming path for an object in a tree. However, we are only concerned with its name context and flags because the other fields have been initialized.

    Figure 6: Context fields tell NDS how to treat a request.

    For example, if you have already logged in to a tree, the DCK_TREE_NAME will be set. Other connection information will be pre-initialized as well.

    Just to make sure that the context flags are set the way we want them, we set them explicitly. Then we set the context's name to the objectName you entered earlier. In this example, the objectName must be fully distinguished.

    1. All NDS requests are composed in an NDS buffer which is then sent to the NDS tree to be handled. Every NDS request buffer has a very precise construction. In the case of reading an attribute, NDS will expect a DSV_READ type buffer that contains the names of the attributes to be read.

      Our example allocates two buffers, one to contain the read operation request and one to contain the results. The request buffer is initialized for the DSV_READ operation and then, since this example reads only a single attribute, a single attribute name is installed with the NWDSPutAttrName( ) function.

      Notice that the example doesn't use wsprintf( ) or some other ANSI utility function to write directly into the buffer. This is because NDAP NDS buffers are hands-off for the programmer. You must use NDS routines to build NDS buffers and to access their contents.

      The response buffer doesn't need to be initialized. NDS will do that automatically when it satisfies the request.

    2. NWDSRead( )'s first two parameters establish the name and location of the object to be read. In our example, the context in the first parameter fully describes the target object. However, the name value in the context parameter could be set to partially define a target object and rely on an additional context name string in the second parameter to fully resolve its path.

      NWDSRead( )'s third parameter specifies that we want the values for the read attributes, not just their names to be returned in the response buffer. The fourth parameter is the request buffer containing the attribute names that are to be read, in our case just one.

      The fifth parameter is what is called an iteration handle. Applications must sometimes repeat an NDS request to read or scan information multiple times in order to have it completed because, at a lower level, multiple NDS network transactions may be required to obtain the desired information. In these NDS calls, an iteration handle is used to manage the process state of the request. Before the operation loop, the iteration handle is initialized to NO_MORE_ITERATIONS. The operation is complete when NDS sets the iteration handle to NO_MORE_ITERATIONS, which is a negative one.

      NWDSRead( ) is just such an iterative NDS request. However, in our example we inserted only one attribute name into the request buffer and we are reading from a single object. For this reason we can keep things simple by not performing the nested read loops that are needed to unpack most response buffers. Later, more complex DeveloperNet University examples will describe these loops in detail.

      NWDSRead( )'s sixth parameter is the buffer that will contain the attribute value that will be returned from NDS after the read operation.

      After the read operation, the NWDSGetAttrCount call looks in the response buffer to determine how many attribute values it contains. Since we know that there is only a single attribute value in the buffer and we know what kind of value it is, you might think that calls like NWDSGetAttrCount( ) and NWDSGetAttrName( ) are unnecessary. However, these routines still need to be called because, under the covers, NDS is accessing and manipulating the buffer's contents with a buffer pointer. The value of this pointer is adjusted as a side-effect of using calls like NWDSGetAttributeCount( ). If you don't follow the recipe when unpacking an NDS buffer, it simply won't work.

      The CN attribute is a multi-valued attribute. This means that multiple iterations could be required to obtain all of its values. Since we are only interested in the first value, we call NWDSCloseIteration( ) after the first buffer has been unpacked to ensure that all iteration resources are released. Then the context is freed, the buffers are freed, and the routine terminates.

Conclusion

If you have read this article and performed the lab, you have learned about NDS trees, objects, contexts, attributes, and NDS syntaxes. You have also learned how to write a C application that can read attribute values from an NDS object using Novell's Directory Access Protocol (NDAP) (as long as the user has been granted the right to do so).

After this brief introduction, we hope that you have discovered that NDS programming really isn't difficult.

As you experiment with the code example, try rebuilding it to read the "Telephone Number" attribute.

Note: Later NDS101 examples present other easy ways to develop to NDS, like ActiveX.

For more information about other examples in this series, refer to: http://developer.novell.com/education/

To download the project corresponding to this article, refer to: http://www.novell.com/coolsolutions/tools/15761.html


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.

© 2014 Novell