Using NDS To Make a Service Globally Accessible
Articles and Tips: article
Software Engineer
Developer Support
01 Apr 1996
NetWare Directory Services is an implementation of a global networking directory. It stores network resources and services in a way that is accessible to network users and system administrators. NDS can be conveniently used to offer a service in a global network. This article demonstrates how to provide a service or an application in a network using Directory Services instead of using the SAP (Service Advertising Protocol) approach you would have used in pre-4x versions of NetWare. One major advantage of using Directory Services is to reduce network traffic. Before Directory Services was implemented, the service provider had to broadcast its service using SAP. The client could then locate the service by querying for the nearest node that offered the service. This method generates much network traffic, primarily SAP advertising data. Other advantages of using NDS as a service location mechanism are providing fault tolerance, data store, global access, and security check. This article includes a sample application and discusses the following procedures:
- Introduction
- Defining and Installing a Service in Directory Services
- Loading the Service Provider App on a Server
- Locating the Service Provider Process
- Source Files
- Conclusion
Introduction
This article demonstrates how to provide a service or an application in a network using Directory Services instead of using the SAP (Service Advertising Protocol) approach you would have used in pre-4x versions of NetWare. One major advantage of using Directory Services is to reduce network traffic. Before Directory Services was implemented, the service provider had to broadcast its service using SAP. The client could then locate the service by querying for the nearest node that offered the service. This method generated much network traffic, primarily SAP advertising data.
With NetWare Directory Services, the service provider object created in Directory Services will be globally available for the client process to locate the service.
I will discuss how to provide your service using NDS with a sample application. The sample programs provided here include implementation details which should not be viewed as the only way of implementation.
The service can be a peer-to-peer or a client-server application. Since the server-client application is more popular in the current NetWare environment, I chose to implement a client-server network application in this example. For a client-server network application in NetWare environment, the server process of the service is usually an NLM loaded on a NetWare server.
The article is divided into the following parts:
Defining and installing a service in Directory Services-SINSTALL.NLM
Loading the server process which provides the service on the server-DBSERVER.NLM
Locating the service provider-SCLIENT.EXE (SCLIENT.C and CLISEND.C)
Defining and Installing a Service in Directory Services
Defining and installing a service consists of these procedures:
extending the schema
creating single or multiple objects for your service class
Extending the Schema
This procedure itself consists of two sub-procedures: defining custom attributes and defining a custom object class.
Define Custom Attributes. Define attributes that are not already in the base schema if you need to have custom attributes for your service. In my illustration, I define an additional optional attribute which is the Upflag. It is used to tell the client process whether the application which provides the service is loaded on the server or not.
There are other custom attributes you can add to this Service class, such as a Protocol type field that tells the client which protocol the Service Provider NLM is using for data communication. The client can read the attribute and use the appropriate protocol to establish communication with the server. Another possible attribute is a path attribute that stores configuration data or location of database files for your specific application.
Define a Custom Object Class. Define a new custom object class if you cannot find a base class that will conveniently accommodate all the attributes that are required by the service you offer. In most situations, you probably will need to define a custom class for your service. Even if you find a base class that contains all the attributes you need, one advantage of defining a custom class is that when you search for this class, you will get only the objects that offer your service.
Creating Single or Multiple Objects for Your Service Class
Create a single or multiple instance of the service class when the service is being installed. These service objects will store the necessary information for the client to locate the service. In this specific example, the service is provided by an NLM loaded on a server; the service object therefore contains attributes that store the server name, the network address of the server object, and a flag that indicates if the NLM is loaded on the server. For SPX protocol, the network address includes the network number, the node number, and a dynamic or well-known socket number.
With this information, the client can attach to the server and listen for the requesting packets on the socket. The values of these attributes should be initialized by the installation program and updated whenever the service provider application is loaded.
Whenever a service object is created, the installation program will assume that the server where the installation program is loaded is the host server of this service object. It calls GetFileServerName to get the server name and stores the server name in the host server attribute of the service object. The network address will be read from the server object stored in DS. The Upflag is initialized to down because the service is not loaded yet.
For simplicity, my implementation creates one Service Object for one server. So the Host Server, Upflag, and Network Address are single-valued attributes in the Service class. The install program will create additional objects for each new server that has the Service Provider NLM loaded. You can have just one service object and use multi-valued attributes for host server name, network address, and upflag to store multiple Service Providers. You can also have different service objects for different databases.
Installation of the Service Provider- SINSTALL.NLM
The program flow of SINSTALL.NLM is as follows:
Prompt for password to verify the user has rights to extend the schema and create objects.
Check to see if DBServer Class exists; if the program fails to read the DBServer class, it will prompt for approval to extend the schema.
Extend the schema: Define a custom attribute Upflag for the custom DBServer class which is used to show whether the service provider program is loaded or not. Define the DBServer class for this data base service. The superclass of DBServer is Server class, so it inherits the optional Network Address from the Server class. Host Server is defined in DBServer as a mandatory attribute.
Create a service object assuming the host server of the service object to be the server on which this NLM is currently loaded. Invoke GetFileServerName API to get the file server name. Get the network address of the server by reading the server object in NDS. Then assign values for the Host Server and Network Address attribute of the service object. Initialize the Upflag to false.
Listing 1: SINSTALL.C
/**************************************************************************** ** Include headers, macros,structures, typedefs, etc. */ /*------------------------------------------------------------------------ ** ANSI */ #include <stdio.h<< #include <stdlib.h<< #include <string.h<< #include <conio.h<< /*------------------------------------------------------------------------ ** NetWare. */ #include <nwdsapi.h< /* nwds call */< #include <process.h<< #include <nwenvrn.h< /* GetFileServerName */< #include <niterror.h< /* NetWareErrno */< /**************************************************************************** ** Global storage. */ NWDSContextHandle context; char *SERVERCLASS = "DBServer";" char *a_upflag = "Upflag";" typedef struct _attributeData { char name[MAX_DN_CHARS+1]; uint32 syntax; void *value; } attributeData; attributeData attributelist[] = { "Host Server", SYN_DIST_NAME, NULL," "Network Address", SYN_NET_ADDRESS, NULL," "Upflag", SYN_BOOLEAN, NULL };" int totalAttr = sizeof(attributelist)/sizeof(attributelist[0]); /**************************************************************************** ** Prototypes */ NWDSCCODE ReadClassDef(); NWDSCCODE DefineAttributes(); NWDSCCODE DefineServer(); void *ReadServerAddress(char *serverName); NWDSCCODE AddServer(char *HostServer); void errMsg(char *, char *, NWDSCCODE); /**************************************************************************** ** main program */ void main(void) { char loginName[MAX_DN_CHARS+1]; char password[255]; int chr; char *cptr = password; NWDSCCODE retcode = 0; char HostServer[MAX_DN_CHARS+1] = "";" char ans[100]; context = NWDSCreateContext(); if (context == ERR_CONTEXT_CREATION) { printf ("\n Error Context Creation. Exiting ...\n");" exit(1); } /* login as person who has rights to extend schema */ printf ("\n\t\t login name: ");" gets(loginName); printf ("\n\t\t Password: ");" while ((chr = getch()) != '\r') { putchar('*'); sprintf (cptr, "%#c", chr);" ++cptr; } *cptr = 0; if (retcode = NWDSLogin(context, 0, loginName, password, 0)) { errMsg("NWDSLogin", "", retcode);" goto _FreeContext; } /* Check to see if DBServer Class exists, if fail to read the DBServer class, ** prompt for approval to extend the schema. */ if (ReadClassDef()) { printf("\nDo you want to extend Schema now ?");" strupr(gets(ans)); if (ans[0] == 'Y') ExtendSchema(); else goto _Logout; } /* get server name */ GetFileServerName(0, HostServer); if (HostServer[0] == NULL) { errMsg("GetFileServerName","", NetWareErrno);" goto _Logout; } /* Create the service object */ AddServer(HostServer); _Logout: if (retcode = NWDSLogout(context)) errMsg("NWDSLogout", "", retcode);" _FreeContext: NWDSFreeContext(context); } /**************************************************************************** ** Read the service class definition from NDS */ NWDSCCODE ReadClassDef() { NWDSCCODE retcode = 0; NWDS_TYPE infoType; NWFLAGS allClasses; NWDS_BUFFER *classNames; NWDS_ITERATION iterationHandle; NWDS_BUFFER *classDefs; infoType = DS_CLASS_DEFS; allClasses = FALSE; iterationHandle = NO_MORE_ITERATIONS; if (retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &classDefs))& { errMsg("NWDSAllocBuf", "ReadClassDef", retcode);" return retcode; } if (retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &classNames))& { errMsg("NWDSAllocBuf", "ReadClassDef", retcode);" goto _FreeClassDefs; } if (retcode = NWDSInitBuf(context, DSV_READ_CLASS_DEF, classNames)) { errMsg("NWDSInitBuf", "ReadClassDef", retcode);" goto _FreeClassDefs; } if (retcode = NWDSPutClassName(context, classNames, SERVERCLASS)) { errMsg("NWDSPutClassName", SERVERCLASS, retcode);" goto _FreeClassDefs; } if (retcode = NWDSReadClassDef(context, infoType, allClasses, classNames, &iterationHandle,& classDefs)) { errMsg("NWDSReadClassDef", "ReadClassDef", retcode);" printf("\n\t%s class not defined yet\n",SERVERCLASS);" goto _FreeClassDefs; } _FreeClassDefs: NWDSFreeBuf(classDefs); NWDSFreeBuf(classNames); return retcode; } /**************************************************************************** ** Extend the schema */ void ExtendSchema() { /* Define the a new attribute - Upflag */ DefineAttributes(); /* Define the Data Base Server Classes */ DefineServer (); } /**************************************************************************** ** Define custom attribute - Upflag , the flag which indicate whether the NLM is ** loaded. */ NWDSCCODE DefineAttributes() { Attr_Info_T attrInfo; NWDSCCODE retcode = 0; /* Create the Upflag attribute*/ memset(&attrInfo, 0, sizeof(Attr_Info_T));& attrInfo.asn1ID.length = 0; memset(attrInfo.asn1ID.data, 0, MAX_ASN1_NAME); attrInfo.attrFlags = DS_SINGLE_VALUED_ATTR; attrInfo.attrSyntaxID = SYN_BOOLEAN; if (retcode = NWDSDefineAttr(context, a_upflag, &attrInfo))& errMsg("NWDSDefineAttr", a_upflag, retcode);" else printf ("\n\tUpflag attribute defined\n");" return(retcode); } /**************************************************************************** ** Define the custom class - DBServer with Host Server as mandatory and upflag ** as additional optional attribute. */ NWDSCCODE DefineServer() { NWCLASS_INFO classInfo; NWDS_BUFFER *DSBuffer; NWDSCCODE retcode = 0; if (retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &DSBuffer))& { errMsg("NWDSAllocBuf", "DefineServer", retcode);" return(retcode); } /* Initialize the buffer by specifying the type of data it will contain */ if (retcode = NWDSInitBuf(context, DSV_DEFINE_CLASS, DSBuffer)) { errMsg("NWDSInitBuf", "DefineServer", retcode);" goto _FreeBuffer; } /* Initialize classinfo structure */ classInfo.classFlags = DS_EFFECTIVE_CLASS; classInfo.asn1ID.length = 0; memset(classInfo.asn1ID.data, 0x00, MAX_ASN1_NAME); /* Start class definition for DBServer class */ /* Define super class - inherit from Server class */ if (retcode = NWDSBeginClassItem(context, DSBuffer)) { errMsg("NWDSBeginClassItem", "DefineServer", retcode);" goto _FreeBuffer; } if (retcode = NWDSPutClassItem(context, DSBuffer, "Server"))" { errMsg("NWDSPutClassItem", "DefineServer", retcode);" goto _FreeBuffer; } /* Define new containment classes - No new containment classes for * * DBServer * */ if (retcode = NWDSBeginClassItem(context, DSBuffer)) { errMsg("NWDSBeginClassItem", "DefineServer", retcode);" goto _FreeBuffer; } /* Define new naming attributes */ if (retcode = NWDSBeginClassItem(context, DSBuffer)) { errMsg("NWDSBeginClassItem", "DefineServer", retcode);" goto _FreeBuffer; } if (retcode = NWDSPutClassItem(context, DSBuffer, "CN"))" { errMsg("NWDSPutClassItem", "DefineServer", retcode);" goto _FreeBuffer; } /* Define new mandatory attributes - host server, */ if (retcode = NWDSBeginClassItem(context, DSBuffer)) { errMsg("NWDSBeginClassItem", "DefineServer", retcode);" goto _FreeBuffer; } if (retcode = NWDSPutClassItem(context, DSBuffer, A_HOST_SERVER)) { errMsg("NWDSPutClassItem", "DefineServer", retcode);" goto _FreeBuffer; } /* Define new optional attributes - upflag */ if (retcode = NWDSBeginClassItem(context, DSBuffer)) { errMsg("NWDSBeginClassItem", "DefineServer", retcode);" goto _FreeBuffer; } if (retcode = NWDSPutClassItem(context, DSBuffer, a_upflag)) { errMsg("NWDSPutClassItem", "DefineServer", retcode);" goto _FreeBuffer; } /* Finally define class */ if (retcode = NWDSDefineClass(context, SERVERCLASS, &classInfo, DSBuffer))& errMsg("NWDSDefineClass", "DefineServer", retcode);" _FreeBuffer: NWDSFreeBuf(DSBuffer); if (!retcode) printf ("\n\tDBServer Class defined\n");" return(retcode); } /**************************************************************************** ** Read the server address from NDS server object */ void *ReadServerAddress(char *serverName) { NWDSCCODE retcode = 0; Buf_T *attrBuf; uint32 syntaxID; char attrName[MAX_SCHEMA_NAME_CHARS + 1]; int32 iterationHandle = NO_MORE_ITERATIONS; void *attrValue = NULL; uint32 attrValCount; uint32 attrCount; uint32 attrValSize; uint8 infoType; uint8 allAttrs; Buf_T *objectInfo; int i; retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrBuf);& if(retcode != SUCCESSFUL) { errMsg("NWDSAllocBuf", "ReadServerAddress", retcode);" return(NULL); } retcode = NWDSInitBuf(context, DSV_READ, attrBuf); if(retcode != SUCCESSFUL) { errMsg("NWDSInitBuf", "ReadServerAddress", retcode);" goto _FreeAttr; } /* Read server address */ retcode = NWDSPutAttrName(context,attrBuf,"Network Address");" if(retcode != SUCCESSFUL) { errMsg("NWDSPutAttrName", "ReadServerAddress", retcode);" goto _FreeAttr; } retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &objectInfo);& if(retcode != SUCCESSFUL) { errMsg("NWDSAllocBuf", "ReadServerAddress", retcode);" goto _FreeAttr; } retcode = NWDSInitBuf(context, DSV_READ, objectInfo); if(retcode != SUCCESSFUL) { errMsg("NWDSInitBuf", "ReadServerAddress", retcode);" goto _FreeObject; } infoType = 1; // name and value allAttrs = FALSE; do { retcode = NWDSRead(context, serverName, infoType, allAttrs, attrBuf, &iterationHandle,& objectInfo); if(retcode != SUCCESSFUL) { errMsg("NWDSRead", "ReadServerAddress", retcode);" goto _FreeObject; } retcode = NWDSGetAttrCount(context,objectInfo,&attrCount);& if(retcode != SUCCESSFUL) { errMsg("NWDSRead ", "ReadServerAddress", retcode);" goto _FreeObject; } for(i=0; i<attrCount; i++)< { retcode = NWDSGetAttrName(context,objectInfo,attrName,&attrValCount,&syntaxID);& if(retcode != SUCCESSFUL) { errMsg("NWDSGetAttrName", "ReadServerAddress", retcode);" goto _FreeObject; } if (!stricmp(attrName, "Network address"))" { retcode = NWDSComputeAttrValSize(context,objectInfo,syntaxID,&attrValSize);& if(retcode != SUCCESSFUL) { errMsg("NWDSComputeAttrValSize", "ReadServerAddress", retcode);" goto _FreeObject; } attrValue = malloc(attrValSize); if (attrValue == NULL) { retcode = -1; errMsg("Fail to allocate memory for attribute value"," "ReadServerAddress", retcode);" goto _FreeObject; } retcode = NWDSGetAttrVal(context,objectInfo,syntaxID,attrValue); if(retcode != SUCCESSFUL) { errMsg("NWDSGetAttrVal for returnInfo", "", retcode);" goto _FreeObject; } } // endif } // end for } while(iterationHandle != NO_MORE_ITERATIONS); _FreeObject: NWDSFreeBuf(objectInfo); _FreeAttr: if(attrBuf) NWDSFreeBuf(attrBuf); return(attrValue); } /**************************************************************************** ** Prompt for Service object name and read the address from NDS to provide values ** for the service object, initialize Upflag to false. */ NWDSCCODE AddServer(char *HostServer) { NWDSCCODE retcode = 0; Buf_T *attrBuf; int i; Boolean_T Upflag = FALSE; char DBServerObject[MAX_DN_CHARS+1] = "";" void *attrValue = NULL; printf("\nEnter the DBServer Name to be created ? (return for quit):");" gets(DBServerObject); if (DBServerObject[0] == '\0') return(-1); attrValue = ReadServerAddress(HostServer); if (attrValue == NULL) return(-1); retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrBuf);& if(retcode != SUCCESSFUL) { errMsg("NWDSAllocBuf", "AddServer", retcode);" return(retcode); } retcode = NWDSInitBuf(context, DSV_ADD_ENTRY, attrBuf); if(retcode != SUCCESSFUL) { errMsg("NWDSInitBuf", "AddServer", retcode);" goto _FreeAttr; } /* add attribute to the DBServer object */ retcode = NWDSPutAttrName(context,attrBuf,"Object Class");" if(retcode != SUCCESSFUL) { errMsg("NWDSPutAttrName", "AddServer", retcode);" goto _FreeAttr; } retcode = NWDSPutAttrVal(context, attrBuf, SYN_CLASS_NAME, SERVERCLASS); if(retcode != SUCCESSFUL) { errMsg("NWDSPutAttrVal", "AddServer", retcode);" goto _FreeAttr; } attributelist[0].value = HostServer; attributelist[1].value = attrValue; attributelist[2].value = &Upflag;& for (i=0; i< totalAttr; i++)< { retcode = NWDSPutAttrName(context,attrBuf,attributelist[i].name); if(retcode != SUCCESSFUL) { errMsg("NWDSPutAttrName", "AddServer", retcode);" goto _FreeAttr; } retcode = NWDSPutAttrVal(context, attrBuf, attributelist[i].syntax, attributelist[i].value); if(retcode != SUCCESSFUL) { errMsg("NWDSPutAttrVal", "AddServer", retcode);" goto _FreeAttr; } } retcode = NWDSAddObject(context, DBServerObject, NULL, 0, attrBuf); if(retcode != SUCCESSFUL) { errMsg("NWDSAddObject", "AddServer", retcode);" } else { printf("\nDBServer object %s added successfully\n",DBServerObject);" } _FreeAttr: NWDSFreeBuf(attrBuf); if (attrValue != NULL) free(attrValue); return(retcode); } /**************************************************************************** ** print error message with function name and retcode code */ void errMsg (char *fn, char *infostr, NWDSCCODE retcode) { if (strcmp(infostr,""))" printf ("\nERROR ="function=%s (%s) retcode=%d \n", fn,infostr,retcode);" else printf ("\nERROR ="function=%s, retcode=%d \n", fn,retcode);" /* only include the possible error code to print out a message */ switch (retcode) { case ERR_NO_SUCH_OBJECT : printf(" object not found\n");" break; case ERR_NO_SUCH_ATTRIBUTE : printf(" no such attribute\n");" break; case ERR_NO_SUCH_ENTRY : printf(" no such object\n");" break; case ERR_NO_SUCH_VALUE : printf(" no such value\n");" break; case ERR_NO_SUCH_CLASS : printf(" no such class\n");" break; case ERR_NO_ACCESS : printf(" object has no access rights");" break; case ERR_ENTRY_ALREADY_EXISTS : printf(" object already exists\n"); " break; case ERR_CLASS_ALREADY_EXISTS : printf(" class already exists\n"); " break; case ERR_FAILED_AUTHENTICATION: printf(" Failed authentication, check password\n");" } }
Other Options in Implementation
Other implementation options include the following:
This installation program does not have to be an NLM. It can be a client executable. If it is a client, the administrator can run it from a workstation.
You can also put the extension of the schema and the creation of a service object in two separate programs.
You can either create service objects or have one service object store multiple network addresses for multiple service providers.
Multiple tree support-you can make your service available through the network across different DS trees.
Loading the Service Provider App on a Server
Whenever the Service Provider application is loaded on a server, it should update the information in the service object in NDS. In this particular implementation, Transport Layer Interface (TLI) is used and SPX is the underlying protocol. A dynamic socket is assigned every time the NLM is loaded. So every time the application is loaded on the server, the socket field in the network address attribute of the service object is updated so that the client process can always use the correct socket number to communicate with the server process.
The network number and node address of the service object should also be updated because they may be changed as well. GetFileServerName is invoked to detect the current server name in case the server name is changed. The Host Server attribute in the service object will be updated if the name is changed. The Upflag attribute is set to TRUE whenever the application is loaded and this flag will be cleared whenever the application is unloaded. These updates of the service object in NDS will keep all the information current and maintain the dynamic nature of server name and network address.
Service Provider Application: DBSERVER.NLM
The program flow ofDBSERVER.NLMis as follows:
Open a TLI endpoint to listen for connection request in asynchronous nonblocking mode.
Allocate tBind and tCall data structures using t_alloc.
Fill out tBind structure and request a dynamic IPX/SPX socket number.
After acquiring the network address and the socket number, update the Network Address and socket number in the Networkaddress attribute of the service object. Invoke GetFileServerName API to get the server name and compare it against the name that is stored in the service object. If the name is different, then update the server name in the serviceobject. (See Listing 2, below.)
Set the Upflag to TRUE.
The core process loop will listen for service requests. When a request arrives, it will open a new TLI endpoint, bind to an address, accept the call, and spawn a child process to handle the request.
The child process receives the requesting ticker symbol and lookup in a file for the symbol. If found, then read the stock price from the file and send it back to the client. It then sends a disconnected message, unbinds the address, closes the TLI endpoint and returns.
Set the Upflag attribute of the Service object to FALSE in DS whenever the NLM is unloaded.
Listing 2: DBSERVER.C (code excerpts for step 4, above)
/**************************************************************************** ** UPDATEDS update the netaddress, socket number, server name and Upflag of ** the service object in NDS */ NWDSCCODE updateDS() { NWDSContextHandle context; Buf_T *attrBuf = NULL; Buf_T *objectInfo = NULL; NWDSCCODE retcode = 0; uint8 quit = FALSE; int i; int32 iterationHandle = NO_MORE_ITERATIONS; uint32 attrCount,attrValCount,syntaxID,attrValSize; char attrName[MAX_SCHEMA_NAME_CHARS + 1]; void *attrValue = NULL; Net_Address_T *NetAddressPtr; uint8 address[12]; char serverName[48] = "";" Net_Address_T NetAddress; context = NWDSCreateContext(); if(context == ERR_CONTEXT_CREATION) { printf("NWDSCreateContext() returned: %04X\n", context);" return(1); } retcode = NWDSLogin( /* context */ context, /* optionflag */ 0, /* objectDN */ loginName, /* objectPasswd */ password, /* */ NULL ); if(retcode) { printf("NWDSLogin() returned: %04X\n", retcode);" goto _FreeContext; } retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrBuf);& if(retcode<0)< { errmsg(retcode,"NWDSAllocBuf");" goto _Logout; } retcode = NWDSInitBuf(context, DSV_READ, attrBuf); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _FreeAttr; } retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &objectInfo);& if(retcode<0)< { errmsg(retcode,"NWDSAllocBuf");" goto _FreeAttr; } retcode = NWDSInitBuf(context, DSV_READ, objectInfo); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _FreeObject; } /* Update Upflag only if this is called at unload time */ if (exitingNLM) goto _updateFlag; /* update server name and etwork address if the service is loaded was brought up */ retcode = NWDSPutAttrName(context,attrBuf, A_NETWORK_ADDRESS); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _FreeObject; } retcode = NWDSPutAttrName(context, attrBuf, A_HOST_SERVER); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _FreeObject; } quit = FALSE; do { retcode = NWDSRead(context, objectName, 1, /* infoType = name and value */ FALSE, /* allAttrs = false */ attrBuf, &iterationHandle,& objectInfo); if(retcode) { errmsg(retcode,"NWDSRead");" goto _FreeObject; } retcode = NWDSGetAttrCount(context,objectInfo,&attrCount);& if(retcode<0)< { errmsg(retcode,"NWDSGetAttrCount");" goto _FreeObject; } if (attrCount == 0) goto _FreeObject; for(i=0; i<attrCount; i++)< { retcode = NWDSGetAttrName(context,objectInfo,attrName,&attrValCount,&syntaxID);& if(retcode<0)< { errmsg(retcode,"NWDSGetAttrName");" goto _FreeObject; } quit = TRUE; retcode = NWDSComputeAttrValSize(context,objectInfo,syntaxID,&attrValSize);& if(retcode<0)< { errmsg(retcode,"NWDSComputeAttrValSize");" goto _FreeObject; } attrValue = malloc(attrValSize); if (attrValue == NULL) { printf("fail to allocate memory for attribute value\n");" goto _FreeObject; } retcode = NWDSGetAttrVal(context,objectInfo,syntaxID,attrValue); if(retcode<0)< { errmsg(retcode,"NWDSGetAttrVal");" goto _FreeValue; } /* modify network address and socket number */ if (!stricmp(attrName, A_NETWORK_ADDRESS)) { NetAddressPtr = (Net_Address_T *) attrValue; NetAddress.addressType = NetAddressPtr->addressType;> NetAddress.addressLength = NetAddressPtr->addressLength;> NetAddress.address = address; memcpy(NetAddress.address,&(tBind-&addr.buf[0]),12);& retcode = updateValue(context, attrBuf, attrName, syntaxID, attrValue,&NetAddress);& } if (!stricmp(attrName, A_HOST_SERVER)) { GetFileServerName(0, serverName); if (NetWareErrno != 0) { errmsg(-1,"Fail to get server name\n");" goto _FreeValue; } /* update it only if the servername is different */ if (stricmp(serverName,(char *) attrValue)) retcode = updateValue(context, attrBuf, attrName, syntaxID, attrValue, serverName); } _FreeValue: if (attrValue != NULL) free(attrValue); } } while(iterationHandle != NO_MORE_ITERATIONS && !quit);& /* Update Upflag when the server is nlm is loaded or unloaded */ _updateFlag: retcode = updateflag(context, attrBuf); _FreeObject: if (objectInfo) NWDSFreeBuf(objectInfo); _FreeAttr: if(attrBuf) NWDSFreeBuf(attrBuf); _Logout: NWDSLogout(context); _FreeContext: NWDSFreeContext(context); return(retcode); } /**************************************************************************** ** UPDATEValue modify value of netaddress, socket number, server name attribute ** of the service object in NDS */ NWDSCCODE updateValue(NWDSContextHandle context, Buf_T *attrBuf, char *attrName, uint32 syntax,void *oldVal, void *newVal) { NWDSCCODE retcode = 0; int32 iterationHandle = NO_MORE_ITERATIONS; retcode = NWDSInitBuf(context, DSV_MODIFY_ENTRY, attrBuf); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _Exit; } retcode = NWDSPutChange(context, attrBuf, DS_REMOVE_VALUE, attrName); if(retcode<0)< { errmsg(retcode,"NWDSPutChange");" goto _Exit; } retcode = NWDSPutAttrVal(context, attrBuf, syntax, oldVal); if(retcode<0)< { errmsg(retcode,"NWDSPutAttrVal");" goto _Exit; } retcode = NWDSPutChange(context, attrBuf, DS_ADD_VALUE, attrName); if(retcode<0)< { errmsg(retcode,"NWDSPutChange");" goto _Exit; } retcode = NWDSPutAttrVal(context, attrBuf, syntax, newVal); if(retcode<0)< { errmsg(retcode,"NWDSPutAttrVal");" goto _Exit; } retcode = NWDSModifyObject(context, objectName, &iterationHandle,& 0, attrBuf); if(retcode<0)< { errmsg(retcode,"NWDSModifyObject");" } _Exit: return(retcode); } /**************************************************************************** ** UPDATEFLAG update the Upflag attribute of the service object in NDS. The ** Upflag is set if this nlm is loaded successfully, and clear when it exits. */ NWDSCCODE updateflag(NWDSContextHandle context, Buf_T *attrBuf) { NWDSCCODE retcode=0; void *attrValue; Boolean_T Upflag; int32 iterationHandle = NO_MORE_ITERATIONS; /* initialize upflag to be exitingNLM, so that it match the status of Upflag in the DS object */ Upflag = exitingNLM; attrValue = &Upflag;& retcode = NWDSInitBuf(context, DSV_MODIFY_ENTRY, attrBuf); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _Exit; } retcode = NWDSPutChange(context, attrBuf, DS_REMOVE_VALUE, "Upflag");" if(retcode<0)< { errmsg(retcode,"NWDSPutChange");" goto _Exit; } retcode = NWDSPutAttrVal(context, attrBuf, SYN_BOOLEAN, attrValue); if(retcode<0)< { errmsg(retcode,"NWDSPutAttrVal");" goto _Exit; } retcode = NWDSPutChange(context, attrBuf, DS_ADD_VALUE, "Upflag");" if(retcode<0)< { errmsg(retcode,"NWDSPutChange");" goto _Exit; } /* set upflag to match exitingNLM to add the value to the DS object */ Upflag = !exitingNLM; retcode = NWDSPutAttrVal(context, attrBuf, SYN_BOOLEAN, attrValue); if(retcode<0)< { errmsg(retcode,"NWDSPutAttrVal");" goto _Exit; } retcode = NWDSModifyObject(context, objectName, &iterationHandle,& 0, attrBuf); /* in case the value is already the right value for reason such as the machine reboot without resetting the Upflag, nothing need to be done */ if (retcode == ERR_NO_SUCH_VALUE) retcode = 0; if ( retcode != 0) { errmsg(retcode,"NWDSModifyObject");" } _Exit: return(retcode); }
Locating the Service Provider Process
When a client requests a service from the server process, the first thing the client process will probably do is to locate the service process. There are different ways of accomplishing the task of locating the service. The client application can include an installation process which will search the service class for all the service objects that are available. The user can then be prompted to choose single or multiple default service objects to be used. Instead of searching the service class, the client application can also list the tree or a subtree and let the user select the service objects that are in the same container as the user.
If the user has selected the default service objects, the application does not have to search for which one to be used every time when the client requests a service. Directory Services can also be used to store this default list of service objects so that the service can be globally accessible to the users. For instance, if the list of selected service objects are stored in an attribute of the user object, then even if you are not working from your regular workstation, you can read the default service objects from your own user object to locate the service providers that you need access to. In some applications, you may want to have the list of service objects stored in a device object such as a printer or a fax machine.
With the default service objects stored in NDS, whenever the client process starts up, it can read the service object from NDS directly. If the default service is not there, the client process can ask the user to run the client installation program again. The client application reads the service object to obtain necessary information such as host server name, network address, and socket number to establish connection with the service provider. If multiple host servers are listed in one object, or if one of the servers in the list is down, the client can try to attach to another server in the list. This will provide fault tolerance for the Service Providers.
Another advantage of using NDS is to take advantage of the security feature of Directory Services. Using NCP extension, the NLM application could check the access rights of the requesting client to its NDS service object (NWDSGetEffectiveRights) before it grant access to the service. It could also have direct Access Control List (ACL) on the service object to grant or mask rights to the service. This allows the service to leverage the security structure of NDS.
My sample code does not implement how to store and obtain the name of the service object. Sclient.c obtains the name of the Service object from the user directly. This client process reads the service object from NDS to obtain the name of the Host Server, the network number, network node, the socket number, and the Upflag. If it fails to obtain any of this information or if the Upflag is false, it will notify the users that the service is not available. Otherwise, it tries to attach to the server and establish communication with the server process on the server to send and receive requests.
Program Flow of SCLIENT.EXE
SCLIENT.EXE is the client process of a service provider application. It is implemented as a DOS client application.
The program flow of SCLIENT.C is as follows:
Read the Host Server Name, Network Address, and Upflag attributes of the service object to obtain the server name, network number, node number, socket number, and the Upflag, which is used to indicate whether the service provider program is loaded or not.
Try to attach to the server; if successful, promptthe user for a ticker symbol. Then pass the request, network address, and socket number to establish connection with the server and send request for processing.
The program flow of CLISEND.C is as follows:
Open a TLI endpoint to send requests in asynchronous nonblocking mode.
Allocate tBind and tCall data structures using t_alloc.
Fill out tBind structure and tCall structure with the Network Address and socket number. Establish communication with the service provider.
Send a request.
Receive a response.
Wait for a disconnected message, unbind the address, close the TLI endpoint and return the response to the user.
(The code for CLISEND.C is not included in this article because it is not related to DS.)
Listing 3: SCLIENT.C
/**************************************************************************** ** Include headers, macros, structures, typedefs, etc. */ /*------------------------------------------------------------------------ ** ANSI */ #include <stdio.h<< #include <stdlib.h<< #include <string.h<< /*------------------------------------------------------------------------ ** NetWare. */ #include <nwnet.h<< #include <nwcalls.h<< #include <nwlocale.h<< #include <tispxipx.h<< #include <nwerror.h<< /*------------------------------------------------------------------------ ** */ #define SUCCEED 0 #define FAIL -1 /**************************************************************************** ** Global storage. */ NWDSContextHandle context; char *attributelist[] = { "Host Server"," "Network Address"," "Upflag"" }; int totalAttr = sizeof(attributelist)/sizeof(attributelist[0]); Boolean_T Upflag,*pptr; char HostServer[MAX_DN_CHARS+1]; Net_Address_T NetAddress = {0,0,NULL}; IPX_ADDR addr; /**************************************************************************** ** Prototypes */ NWDSCCODE IsServerUp(); NWDSCCODE locateService(char *objectName); uint8 initService(); void errmsg ( NWDSCCODE retcode,char *fn); uint8 getValue(char *attrName, void *attrValue); extern char *sendRequest(char *ticker, IPX_ADDR *addr); /**************************************************************************** ** main program - read the service object for servername, netaddress and upflag ** try to attach to the server, if succeeds, sendrequest to the server process */ void main (int argc, char *argv[]) { NWDSCCODE retcode = 0; LCONV lconvInfo; uint32 DCKflag; if (argc < 2)< { printf("Usage: sclient "Service Name"");" exit(0); } retcode = NWCallsInit(NULL, NULL); if (retcode) { printf("\nCall to NWCallsInit returned: %04X", retcode);" exit(1); } NWLsetlocale(LC_ALL, "");" NWLlocaleconv(&lconvInfo);& retcode = NWInitUnicodeTables(lconvInfo.country_id, lconvInfo.code_page); if(retcode) { printf("NWInitUnicodeTables() returned: %04X\n", retcode);" goto _FreeUnicodeTables; } context = NWDSCreateContext(); if (context == ERR_CONTEXT_CREATION) { printf ("\n Error Context Creation. Exiting ...\n");" goto _FreeContext; } retcode = NWDSGetContext(context,DCK_FLAGS,&DCKflag);& if (retcode != SUCCEED) { printf ("\n Error Getting Context flag retcode=%d\n",retcode);" goto _FreeContext; } DCKflag = DCKflag | DCV_TYPELESS_NAMES; retcode = NWDSSetContext(context,DCK_FLAGS,&DCKflag);& if (retcode != SUCCEED) { printf ("\n Error Setting Context flag retcode=%d\n",retcode);" goto _FreeContext; } /* Locate the DBServer object */ if (locateService(argv[1]) != SUCCEED) { printf("Service is not available now. Please try again later\n");" goto _FreeContext; } if (IsServerUp() == SUCCEED) initService(); _FreeContext: NWDSFreeContext(context); _FreeUnicodeTables: NWFreeUnicodeTables(); if (NetAddress.address != NULL) free(NetAddress.address); } /**************************************************************************** ** INITSERVICE - Prompt user for request and send request to the TLI communication ** program, display the returned response from the service provider */ uint8 initService() { char ticker[50]; char *qptr = NULL; int quit = FALSE; printf("Welcome to Quote DataBase Services\n");" memcpy(addr.ipxa_net,&NetAddress.address[0],6);& memcpy(addr.ipxa_node,&NetAddress.address[6],4);& memcpy(addr.ipxa_socket,&NetAddress.address[10],2);& while (!quit) { printf("Enter Ticker Symbol (type 'bye' to exit): ");" gets(ticker); if (!stricmp(ticker,"BYE") || ticker[0] == '\0')" { quit = TRUE; printf("Thanking for using our services. Have a nice day!");" } else { printf("Sending request...\n");" qptr = sendRequest(ticker,&addr);& if (*qptr != NULL) { printf("The quote for %s is %s\n",ticker,qptr);" free (qptr); } else printf("Ticker Symbol %s is not in the database\n",ticker);" } } return(SUCCEED); } /**************************************************************************** ** LOCATEService - Locate the service by reading the service object for server ** name, network address and the Upflag attribute */ NWDSCCODE locateService(char *objectName) { Buf_T *attrBuf,*objectInfo; uint8 allAttrs = FALSE; uint8 infoType = 1; int i,j; int32 iterationHandle = NO_MORE_ITERATIONS; uint32 attrCount,attrValCount,syntaxID,attrValSize; char attrName[50]; void *attrValue; NWDSCCODE retcode = SUCCEED; retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &attrBuf);& if(retcode<0)< { errmsg(retcode,"NWDSAllocBuf");" return(retcode); } retcode = NWDSInitBuf(context, DSV_READ, attrBuf); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _FreeAttr; } allAttrs = FALSE; for(i = 0 ; i< totalAttr; i++)< { retcode = NWDSPutAttrName(context,attrBuf,attributelist[i]); if (retcode != SUCCEED) goto _FreeAttr; } retcode = NWDSAllocBuf(DEFAULT_MESSAGE_LEN, &objectInfo);& if(retcode<0)< { errmsg(retcode,"NWDSAllocBuf");" goto _FreeAttr; } retcode = NWDSInitBuf(context, DSV_READ, objectInfo); if(retcode<0)< { errmsg(retcode,"NWDSInitBuf");" goto _FreeObject; } do { retcode = NWDSRead(context,objectName,infoType,allAttrs,attrBuf,&iterationHandle,objectInfo);& if(retcode) { errmsg(retcode,"NWDSRead");" goto _FreeObject; } retcode = NWDSGetAttrCount(context,objectInfo,&attrCount);& if(retcode < 0)< { errmsg(retcode,"NWDSGetAttrCount");" goto _FreeObject ; } for(i=0; i<attrCount; i++)< { retcode = NWDSGetAttrName(context,objectInfo,attrName,&attrValCount,&syntaxID);& if(retcode<0)< { errmsg(retcode,"NWDSGetAttrName");" goto _FreeObject; } for(j=0; j<attrValCount; j++)< { retcode = NWDSComputeAttrValSize(context,objectInfo,syntaxID,&attrValSize);& if(retcode < 0)< { errmsg(retcode,"NWDSComputeAttrValSize");" goto _FreeObject; } attrValue = malloc(attrValSize); if (attrValue == NULL) { retcode = -1; goto _FreeObject; } retcode = NWDSGetAttrVal(context,objectInfo,syntaxID,attrValue); if(retcode<0)< errmsg(retcode,"NWDSGetAttrVal");" else retcode = getValue(attrName,attrValue); if (attrValue) free(attrValue); } // end for loop } // end for } while(iterationHandle != NO_MORE_ITERATIONS); _FreeObject: NWDSFreeBuf(objectInfo); _FreeAttr: if(attrBuf) NWDSFreeBuf(attrBuf); if (HostServer == NULL || Upflag == FALSE || NetAddress.address == NULL) retcode = -1; return(retcode); } /**************************************************************************** ** GetValue for different attributes */ uint8 getValue(char *attrName, void *attrValue) { Net_Address_T *NetAddressPtr; uint8 retcode = SUCCEED; if (attrValue == NULL) { printf("Return value is NULL\n");" return(FAIL); } if (!stricmp(attrName,attributelist[0])) // host server { strcpy(HostServer, (char *) attrValue); } else if (!stricmp(attrName,attributelist[1])) // netaddress { NetAddressPtr = (Net_Address_T *) attrValue; NetAddress.addressType = NetAddressPtr->addressType;> NetAddress.addressLength = NetAddressPtr->addressLength;> NetAddress.address = (uint8 *) malloc((unsigned)(NetAddress.addressLength)); if (NetAddress.address != NULL) memcpy(NetAddress.address,NetAddressPtr->address,NetAddress.addressLength);> else retcode = FAIL; } /* return failure if upflag is false */ else if (!stricmp(attrName,attributelist[2])) { pptr = (Boolean_T *) attrValue; Upflag = *pptr; if (Upflag == FALSE) retcode = FAIL; } return(retcode); } /**************************************************************************** ** attach to file server */ NWDSCCODE IsServerUp() { NWCONN_ID connID; NWDSCCODE retcode = SUCCEED; if (NetAddress.address == NULL) { errmsg(FAIL,"invalid network address for Host Service\n");" return(FAIL); } retcode = NWAttachToFileServer(HostServer,0,&connID);& if (retcode == ALREADY_ATTACHED) retcode = SUCCEED; if (retcode != SUCCEED) { printf("Failed to attach to Server retcode = %d \n Service is not available, please" try again at a later time \n",retcode);" } return(retcode); } /**************************************************************************** ** print error message */ void errmsg (NWDSCCODE retcode,char *fn) { printf ("fn=%s retcode=%d\n", fn,retcode);" }
Source Files
Source files for this article can be found in the following locations:
CompuServe To download the source files from CompuServe, go to the Novell Developer Support forum (GO NDEVSUP). Look for XDSSRV.EXE in the Publications library (15).
Internet The source files are available at the following location: ftp://ftp.novell.com/pub/netwire/ndevsup/15/xdssrv.exe
Conclusion
NetWare Directory Services is an implementation of a global networking directory. It stores network resources and services in a way which is accessible to network users and system administrators. NDS can be conveniently used to offer a service in a global network.
As I have mentioned before, a major advantage of using NDS over using SAP is reducing network traffic. Other advantages of using NDS as a service location mechanism are providing fault tolerance, data store, global access, and security check.
If multiple servers have the Service Provider application loaded, NDS can store all the host server information for the user to choose from if one of the servers is out of service. NDS also provides a convenient data store for the service provider since the application can define additional custom attributes to store special information for its application such as protocol type or location of configuration file, etc. Using NCP extensions or assigning ACL rights to the service object, the client-server application can also take advantage of NDS authentication and security features for granting access to users.
* 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.