Novell is now a part of Micro Focus

Programming NDS with NetWare Loadable Modules (NLMs), Part 2

Articles and Tips: article

Adapted from a DeveloperNet University Tutorial

01 May 2000


Part 1 of this article, published in the April 2000 issue of Developer Notes, covered the following topics:

  • Create a Class in NDS

For an overview of NDS concepts, see http://developer.novell.com/education/tutorials/nlm_nds/nds_tutorial.html

For the online version of this tutorial, see L_NLM_OS_NDS1.html

This section covers the following:

Using NDS

Open the recent project NDS.mcp, as shown in Figure 1.

Figure 1: Opening NDS.mcp.

Select the Target setting ICON in the project screen. Select NLM Target.You should see the screen shown in Figure 2.

Figure 2: NLM Target.

We should now look at how to read, write, and modify Novell's Directory. You need to remember that this is instructional code and not release production code. You might be able to cut and paste some sections of this code, but you need to add error handling and verification.

 /***************************************************************************

Copyright (c) 1998 Novell, Inc. All Rights Reserved.

THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK.
PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
CUSTOMERS WITH RESPECT TO THIS CODE.
****************************************************************************/
#include <stdio.h>
#include <nwdsapi.h>
#include <nwnet.h>
#include <string.h>
#include <stdlib.h>
#include <nwmalloc.h>
#include <signal.h>
#include <nwthread.h>

void ModifyClass(NWDSContextHandle con);
void RemoveAttribute(NWDSContextHandle con);
void RemoveClass(NWDSContextHandle con);
void CreateClass(NWDSContextHandle con);
void CreateAttribute(NWDSContextHandle con);
void Login(NWDSContextHandle con);
NWDSContextHandle SetContext(char *c);
void GetPassword(char *password, int size);
void CreateObject(NWDSContextHandle con);
void RemoveObject(NWDSContextHandle con);
void ModifyObjectName(NWDSContextHandle con);
void MySignalHandler(int SigType);
void PollLoop(void *pointer);
void ModifyObject(NWDSContextHandle con);
void Search(NWDSContextHandle con);

nstr8 *Names[5], *New[5];
nstr8 strObjectContext[MAX_DN_CHARS+1];
int } threadID;
int } mainThread;
int } ShutDownFlag = 0;
int } dontCreate = 0;
NWDSContextHandle context;

void main()
{
int newThread;
threadID = GetThreadGroupID();
mainThread = GetThreadID();
signal(SIGTERM, MySignalHandler);
context = SetContext("[ROOT]");
Login(context);

CreateAttribute(context);
delay(2000);
CreateClass(context);
delay(2000);
ModifyClass(context);
delay(2000);
CreateObject(context);
delay(2000);
ModifyObjectName(context);
delay(2000);
ModifyObject(context);
newThread = BeginThread(PollLoop, NULL, 0, &context);
SuspendThread(mainThread);
}

// Gets a context handle and sets it to [ROOT]
NWDSContextHandle SetContext(char *c)
{
NWDSContextHandle con;
NWDSCCODE cCode;
char value[10];

if (NWDSCreateContextHandle (&con) != 0)
{
printf("Failed\n");
exit(-1);
}
if((cCode = NWDSSetContext(con, DCK_NAME_CONTEXT, c)) != 0)
{
printf("Set context failed. %d\n", cCode);
}
else
{
if((cCode = NWDSGetContext(con, DCK_NAME_CONTEXT, value)) == 0)
{
printf("Context = %s\n", value);
}
}
}return(con);
}

 // Login to NDS
void Login(NWDSContextHandle con)
{
nstr8 admin[MAX_DN_CHARS+1];
nstr8 password[256];
NWDSCCODE cCode;

printf("Login to NDS\n------------\n");
printf("Enter Admin User (e.g. .admin.orgname)\nLogin: ");
gets(admin);
GetPassword(password, 255);

if((cCode = NWDSLogin(con, 0, admin, password, 0)) != 0)
{
printf("Login failed:%d\n", cCode);
NWDSFreeContext(con);
exit(-1);
}


}

void GetPassword(char *password, int size) {
int index=-1;
WORD x,y; printf("Password: ");
y=wherey(); // find current row

do {
index++;
// find current column
x=wherex();
// make the input cursor visible
DisplayInputCursor();
// get a character
password[index]=(char)getch();

// if cr then null terminate it
if(password[index]==0x0D)
password[index]='\0';
// Backspace
else if(password[index]==0x08)
{
// if there are any characters left
if (index > 0)
{
index-=2; // backup BS and previous char
gotoxy((WORD)(x-1),y); // backup
putch(' '); } // clear character
gotoxy((WORD)(x-1),y); // backup
}
else
{ // at the start then reset index
index=-1;
}
}
// normal character
else
putch('*');
} // while not null terminated and the size is OK
while(password[index] && index<size);

// if not null terminated, do it now
if(password[index])
password[index]=NULL;
putch('\n');
}

 // Creates three Attributes
void CreateAttribute(NWDSContextHandle con)
{
NWDSCCODE cCode;
Attr_Info_T } attrInfo;

memset(&attrInfo, (int) '\0', sizeof(Attr_Info_T));
attrInfo.asn1ID.length = 0;
memset(attrInfo.asn1ID.data, 0, MAX_ASN1_NAME);
attrInfo.attrFlags|= DS_SINGLE_VALUED_ATTR;
attrInfo.attrFlags|= DS_STRING_ATTR;
attrInfo.attrSyntaxID = SYN_CI_STRING;
if(((cCode = NWDSDefineAttr(con, "Test:Profile Name", &attrInfo)) != 0) &&

(cCode != ERR_ATTRIBUTE_ALREADY_EXISTS))
{
printf("%d\n", cCode); }
else
{
printf("Test:Profile Name Attribute successfully created.\n");
}
memset(&attrInfo, (int) '\0', sizeof(Attr_Info_T));
attrInfo.asn1ID.length = 0;
memset(attrInfo.asn1ID.data, 0, MAX_ASN1_NAME);
attrInfo.attrFlags |= DS_SINGLE_VALUED_ATTR;
attrInfo.attrFlags|= DS_STRING_ATTR;
attrInfo.attrSyntaxID = SYN_CI_STRING;
if(((cCode = NWDSDefineAttr(con, "Test:Bookmarks", &attrInfo)) != 0) && (cCode
!= ERR_ATTRIBUTE_ALREADY_EXISTS))
{
printf("%d\n", cCode);
}
else
{
printf("Test:Bookmarks Attribute successfully created.\n");
}
memset(&attrInfo, (int) '\0', sizeof(Attr_Info_T));
attrInfo.asn1ID.length = 0;
memset(attrInfo.asn1ID.data, 0, MAX_ASN1_NAME);
attrInfo.attrFlags |= DS_SINGLE_VALUED_ATTR;
attrInfo.attrFlags|= DS_STRING_ATTR;
attrInfo.attrSyntaxID = SYN_CI_STRING;
if(((cCode = NWDSDefineAttr(con, "Test:Modify Class", &attrInfo)) != 0) &&
(cCode != ERR_ATTRIBUTE_ALREADY_EXISTS))
{
printf("%d\n", cCode);
}
else
{
printf("Test:Modify Class Attribute successfully created.\n");
}

}

// Creates a class using two of the new attributes
void CreateClass(NWDSContextHandle con)
{
NWDSCCODE cCode;
Class_Info_T } classInfo;
pBuf_T classBuf;

printf("Creating Bookmark Class.\n");
//delay(5000);
memset(&classInfo, (int) '\0', sizeof(Class_Info_T));
if((cCode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &classBuf)) != 0)
{
printf("NWDSAllocBuf failed:%d\n", cCode);
}
else
{
if((cCode = NWDSInitBuf(con, DSV_DEFINE_CLASS, classBuf)) != 0)
{
printf("NWDSInitBuf failed:%d\n", cCode);
}
classInfo.classFlags |= DS_EFFECTIVE_CLASS;

if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)
{
printf("NWDSBeginClassItem super failed:%d\n", cCode);
}
if((cCode = NWDSPutClassItem(con, classBuf, "Top")) != 0)
{
printf("NWDSPutClassItem super failed:%d\n", cCode);
}
if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)
{
printf("NWDSBeginClassItem containment failed:%d\n", cCode);
}
if((cCode = NWDSPutClassItem(con, classBuf, "Organization")) != 0)
{
printf("NWDSPutClassItem containment failed:%d\n", cCode);
}
if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)
{
printf("NWDSBeginClassItem naming failed:%d\n", cCode);
}
if((cCode = NWDSPutClassItem(con, classBuf, "CN")) != 0)
{
printf("NWDSPutClassItem naming failed:%d\n", cCode);
}
if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)
{
printf("NWDSBeginClassItem mandatory failed:%d\n", cCode);
}
if((cCode = NWDSPutClassItem(con, classBuf, "CN")) != 0)
{
printf("NWDSPutClassItem mandatory failed:%d\n", cCode);
}
if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)
{
printf("NWDSBeginClassItem optional failed:%d\n", cCode);
}
if((cCode = NWDSPutClassItem(con, classBuf, "Test:Profile Name")) != 0)
{
printf("NWDSPutClassItem optional failed:%d\n", cCode);
}
if((cCode = NWDSPutClassItem(con, classBuf, "Test:Bookmarks")) != 0)
{
printf("NWDSPutClassItem optional failed:%d\n", cCode);
}

if(((cCode = NWDSDefineClass(con, "Test:Bookmark", &classInfo, classBuf)) != 0
&& (cCode != ERR_CLASS_ALREADY_EXISTS))
{
printf("NWDSDefineClass failed:%d\n", cCode);
}
else
{
printf("Bookmark Class added.\n");
}

NWDSFreeBuf(classBuf);
}

First we will discuss the include files.

Include Files

The nwdsapi.h file is used to access the DS.NLM APIs. In other words, NetWare Directory Service Application Programmer Interface functions are found when we link the prototypes found in the nwdsapi header file.

The nwmalloc.h file is used to access memory. NLM programming requires you to allocate and then release the memory when the function completes.

Threads

nwthread.h is needed to access the functions in THREADS.NLM. I will not explain all of the threads processing that occurs in NLM programming. This program should get you started on releasing the CPU for other processes.

threadID = GetThreadGroupID();

mainThread = GetThreadID();

signal(SIGTERM, MySignalHandler);



newThread = BeginThread(PollLoop, NULL, 0, &context);&
SuspendThread(mainThread);

}

First, the program needs to get the number that is going to be assigned to the group of threads being used. Then, the actual thread that is assigned a thread identification number from the operating system, is stored in mainThread. When the program enters a delay, the program stops execution for the specified milliseconds and then the CPU is returned to the program by the thread identification number assigned to the NLM.

The BeginThread allows you to create a new thread for the RunList that is handled by NetWare.

For more information about thread processing, see the following:

  • Novell Research--Handicapping a Thread: Death to Your Application?

  • http://support.novell.com/techcenter/articles/dnd19960502.html

  • Novell Research--Features of the Novell Kernel Services Programming Environment for NLMs: Part Two

  • http://support.novell.com/techcenter/articles/dnd19991004.html

  • Novell Research--Adding Console Commands to NetWare (Select "Handling Console Commands")

  • http://support.novell.com/techcenter/articles/dnd19960401.html

  • Novell Research--Code Break (Select "Tech Tips: CDM Development, NetWare Thread Priority and More")

  • http://developer.novell.com/research/devnotes/2000/january/acbframe.htm

  • Novell Developer Kit--CLIB samples

  • http://developer.novell.com/ndk/doc/samplecode/clib_sample/index.htm

Context

Before I explain the code, you need to understand context. Think back to the discussion about how an NLM is like a DLL. You learned that multiple programs can use an NLM. In a Windows program, you click on the MS-DOS icon to create a DOS shell on your machine. The C:\WINDOWS directory is often the default. When you type CD \, you go to what is called the ROOT of the directory. Typing DIR at the ROOT will show you a series of programs and other directories. Your context at this location is ROOT. When the machine is powered on, it looks for files at specific locations. If you changed directory to \WINDOWS and deleted all of the *.INI files, the machine would have errors when you powered on the machine the next time. Windows is looking for .INI files at a pre-specified context. If you have used DOS in the past, you should already know how to navigate in a context system. The same is true for Novell Directory Services (NDS). You set the area that you want to access by specifying the context. The child directory is located by the parent. To remove a parent, the program must delete all of the children first. After all of the dependencies are removed, you can then delete the parent NDS object.

// Gets a context handle and sets it to [ROOT]

NWDSContextHandle SetContext(char *c)

{

NWDSContextHandle con;

NWDSCCODE cCode;

if (NWDSCreateContextHandle (&con) != 0)&
{ exit(-1); }

if((cCode = NWDSSetContext(con, DCK_NAME_CONTEXT, c)) != 0)

{ } printf("Set context failed. %d\n", cCode); }

else

{

if((cCode = NWDSGetContext(con, DCK_NAME_CONTEXT, value)) == 0)

{

printf("Context = %s\n", value);

}

return(con);

}

NWDSCreateContextHandle is found in the directory services header nwdsapi. The program exits when an error occurs. The program cannot create anything if it does not know where to create it. You must have access to NDS to get a handle. The handle is then used to position to the location with the NWDSSetContext API. The program positions to ROOT. The DCK_NAME_CONTEXT is a flag that is used to position using a name. Next, the program gets the context and displays it to the console. The program returns the NWDSContextHandle that will be used by the other functions in the program. You will notice that this example code has no goto statements in it. It is generally accepted that goto programming should rarely be used.

Authenticating to NDS

To access NDS you must have sufficient rights to add, modify and delete objects in NDS. These rights come from your user profile in NDS at the time you log into NDS. I suggest that you use admin on your test server. Admin has all of the rights you will need to run this NLM. You also need to be aware of security issues at Authentication. The console asks for a login and shows an example. You must start the user name with a period "." followed by user and context of the user. Use ConsoleOne to gather the information for the user. Use gets() to accept input from the server keyboard. The GetPassword() function is used to hide the password when input. NWDSLogin() has five parameters. The parameters are context, optionsFlag, object name, password, and validity period. You should be certain not to put the password in a program for security reasons. The NLM exits on failure.

Creating an Attribute in NDS

An attribute is similar to a field in a database system. An attribute has characteristics. These characteristics are defined by flags. For example, DS_SINGLE_VALUED_ATTR specifies that the field can only hold one value. The value will change when an addition is made to the field. Telephone number in the User class is multi-valued field.

DS_STRING_ATTR and SYN_CI_STRING specifies that the field is case-insensitive.

Notice that NWDSDefineAttr has three parameters. The context, the name of the attribute or field, and the memory allocation that holds type flags. You should notice that this code does not perform error trapping and error handling. This instructional code is a starting point for you to learn about NDS programming. NDS will return an error if the attribute is already found in the specified context.

Creating a Class in NDS

}

if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)

{

printf("NWDSBeginClassItem naming failed:%d\n", cCode);

}

if((cCode = NWDSPutClassItem(con, classBuf, "CN")) != 0)

{

printf("NWDSPutClassItem naming failed:%d\n", cCode);

}

if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)

{

printf("NWDSBeginClassItem mandatory failed:%d\n", cCode);

}

if((cCode = NWDSPutClassItem(con, classBuf, "CN")) != 0)

{

printf("NWDSPutClassItem mandatory failed:%d\n", cCode);

}

if((cCode = NWDSBeginClassItem(con, classBuf)) != 0)

{

printf("NWDSBeginClassItem optional failed:%d\n", cCode);

}

if((cCode = NWDSPutClassItem(con, classBuf, "Test:Profile Name")) != 0)

{

printf("NWDSPutClassItem optional failed:%d\n", cCode);

}

if((cCode = NWDSPutClassItem(con, classBuf, "Test:Bookmarks")) != 0)

{

printf("NWDSPutClassItem optional failed:%d\n", cCode);

}



if(((cCode = NWDSDefineClass(con, "Test:Bookmark", &classInfo, classBuf)) != 0) && (cCode != ERR_CLASS_ALREADY_EXISTS))&
{

printf("NWDSDefineClass failed:%d\n", cCode);

}

else

{

printf("Bookmark Class added.\n");

}



NWDSFreeBuf(classBuf);

}



}

A class is similar to a file in a database system. A class has attributes. These attributes must have been created before using this function. As with all NDS APIs the request is sent to DS.NLM and a success or failure is returned. The modification to NDS must be prepared before making the NDS call.

The first thing that needs to be done is to allocate memory for the APIs. You should make sure that you free this memory upon completion of the NLM.

NWDSInitBuff with the DS_DEFINE_CLASS is used to prepare NDS for a new class at the specified context. DS_EFFECTIVE_CLASS in NWDSBeginClassItem informs NDS that this class can be deleted from NDS. In other word, the program can create the class. A noneffective class, like Top, cannot be created because it already exists in NDS. Top cannot be deleted from NDS. NWDSPutClassItem places the class at the Top and Organization location in NDS. You should be able to find these fields in the ConsoleOne SchemaManager.

A class needs attributes. The mandatory attribute of CN, common name, is placed next in the class. The program will add optional attributes Test:Profile Name and Test:Bookmarks to the memory that the program will pass to NDS. These attributes must exist at this point.

Finally, the program then makes a call to DS.NLM with NWDSDefineClass to create the class. Check ConsoleOne SchemaManager for the Bookmark class.

Part 3 of this series will cover such topics as the following:

  • Adding attributes

  • Creating data

  • Changing objects

* 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.

© Copyright Micro Focus or one of its affiliates