Programmer's Guide Using the NetCon APIs

NetCon Network Services Library Overview

Introduction

It is the intent of the NetCon Developers system Library to provide a vital link to NOVELL NetWare for UNIX users and programs. The NetCon Developement System provides developers with two complete librarys for developing NetWare compatible networking applications on Unix Systems.

The first library, "libsock.a", is an Ultra-High Performance Socket library that is 100% compatible with BSD 4.4 SOCKETS. This is the primary interface for all of NetCon's programs. It was chosen over the TLI Streams interface for its performance, compatibility and very low system overhead. This library is 100% compatable with all NetWare API's including IPX/SPX, WinSock, TLI etc...

The second library is "libnc.a" the NetWare compatible Network service libraries that allows programs to make NetWare NCP and BINDERY calls directly to local and remote NetWare or NetCon servers.

We are trying to provide application and systems programmers with a rich set of APPLICATION PROGRAM INTERFACE (API's) complying with UNIX , DOS and OSI standards, including BSD Sockets, AT&T Streams, TLI and LLI, NOVELL NCP , and XEROX SPP/IPP(IPX) protocols.

This programers guide contains the following sections covering these two libraries and other topics.

1.) Programmer's Guide/USING THE NETCON APIs LIBNC

2.) Programmer's Guide/SOCKETS

3.) Programmer's Reference/LIBNC

4.) Programmer's Reference/SOCKETS

These libraries can be used in the following example combinations to provide transparent client server application across different operating system and platforms.

EXAMPLES

1.) DOS/Windows/NT or OS/2 client applications can be written directly to the NOVELL IPX, SPX, WinSOCK or TLI interfaces. They can directly communicate with UNIX applications written to the NetCon BSD SOCKET Interface. This interface provides both datagram (IPX) services for Ultra high-performance and connection oriented (SPX) services orderly release for critical mission critical applications such as OLTP and SQL.

3.) UNIX applications can be written to the BSD sockets and libnc interface and communicated directly with a NOVELL NetWare Server or Client. This interface provides the highest possible network speed.

In the following sections we will provide real examples of how various NetCon programs use the SOCKET library "libsock.a" and the Network services library "libnc.a".

Mapping NetWare Interface Library to UNIX and NetCon

One of the key features of NetCon is that it provides UNIX users an applications interface to the NOVELL NetWare operating system. We have tried to provide this interface in very standard UNIX ways i.e.; sockets, TLI, NetBeui/NetBios etc.. and provide a complete functional subset of applicable functionality. All the NetCon software i.e.; Client and Server utilizes this interface and makes the very same function calls as you will see in the examples to follow.

For those users and programmers familiar with NOVELL's NetWare 386 "C" Interface Library we provide the following mapping for the NetWare 386 "C" Interface functions and services to UNIX and/or NetCon functions.

Accounting Services

All users are treated as UNIX users, so all UNIX user accounting services are available to applications.

Bindery Services

The NetWare and NetCon file servers include property and bindery databases. On NetCon these files are "/usr/lib/netcon/netcobj.dat" and "/usr/lib/netcon/netprop.dat". The bindery services on Netcon are accessed through library calls in libnc.a. These calls have very much the same syntax as the NetWare calls, allowing for the differences between NetWare and UNIX. In other words the Bindery Services function calls and their arguments are essentially the same for NetCon and NetWare. For a complete list and reference see the "Programmer's Reference/libnc.

Communication Services

The NetWare IPX Communication function calls map directly to UNIX socket calls. See the BSD Sockets Programming interface section of the "Programmers Guide/Sockets" for complete details.

Connection Services

A functionally complete subset of connection services and task management function calls are provided in the libnc section of this Programmers guide.

Directory and File Services

A subset of directory and file services calls are provided. See the libnc section of this Programmers guide.

File Server Environment Services

A subset of server environment services function calls are provided. See the libnc section of this guide for details.

Service Advertising Protocol

Programs can use the functions in libnc to inform clients of the server's presence on the network.

Most other applicable NetWare 386 C interface functions map directly to standard "C" and UNIX library functions such as Character Manipulation functions, Conversion functions, Execution threads/fork(), Math functions, Memory Allocation, I/O services, Strings etc.......

USING THE NETCON APIs

INTRODUCTION

This section describes in detail how to use the NetCon API's. Fragments of real code used by NetCon are provided in each of the step by step examples. These examples demonstrate how the NetCon API's are used in real client/server programs, complete program example are provided in the directory "/usr/lib/netcon/src" . All NetCon API's are based on strict UNIX internetworking standards adapted to the specifics of the NOVELL NetWare/XEROX XNS environment. The first example involves Client functions through the BSD socket interface, the second example involves Server functions through the BSD sockets interface.

Users of this section should be familiar with the "UNIX System V Development System Developer's Guide" and The "Sockets Programmer's Guide". The Sockets Programmer's Guide and reference manual are provided in separate sections of this manual the "Developers Guide" is provided as a part of the UNIX V development system.

All programs using the sockets interface and libnc must be linked to libnc.a and libsocket.a. These libraries are provided as part of the NetCon Development system.

GETTING STARTED

Before the NetCon API's can be used the hardware driver must be linked to the IPX protocol stack, the network must be configured and the address set. These functions are automatically configured during installation and executed each time the UNIX system reboots. The "/usr/bin/netcon.rc" script (linked to /etc/rc2.d/X90netcon) executes the command;

example:

     /usr/bin/netclink /dev/wdn0 /dev/str_ether

Opens and links the Western Digital and 802.3 drivers, similar to the UNIX command slink.

The Command netcconfig;

example:

     /usr/bin/netcconfig str0 ns "X1" up -trailers

Configures the protocol stack opened above as address family AF_NS (Xerox XNS/NOVELL IPX), sets the state to UP running, sets the network address to hex 1, sets the card address to the burnt in address, and sets address trailers to off. The interface is now fully functional. This command is similar to the UNIX commands "nconfig" and "ifconfig".

CLIENT SERVICES

GETTING A LIST OF NETWARE SERVERS

The first thing a Client process must do is establish a list of known servers in the kernel. This is normally accomplished automatically during start-up by the "/usr/bin/netcpass" and other daemons. The netcpass daemon issues a "ncp_init()" function call to build the kernel server table.

example:

     if(ncp_init()) {
          printf(("Not Connected to any File server!\n");
          return -1;
     }

This call sends out a find nearest server request and builds the kernel server table from the replies it receives. This call should be issued ONLY ONCE at start-up, but can be issued at any time if the kernel server table is NULL.

Once the kernel server table is built a program can access it through the UNIX "nlist" function.

example

#include <nlist.h>
#include "netncp.h"

char *sys = "/unix";
char *core = "/dev/kmem";
char *sname;
int corefd;
u_int objtype = OBJ_SERVER;
struct nlist nl[2];
typedef struct {
     char sname[64];
} SNAME;

get_server_list ()
{
     struct NET_server srv_buf, *srv;
     int size, i = 0;
     SNAME serv_name[64];

     nl [0].n_name = "NCPnwserver";
     if ((nlist (sys, nl) < 0)  || nl [0].n_value == 0)
     {
          fprintf (stderr, "nlist failed\n");
          return -1;
     }
     if ((corefd = open (core, 2)) < 0) {
          perror ("open core");
          return -1;
     }
     if (lseek (corefd, nl [0].n_value, 0) == -1) {
          perror ("lseek core");
          return -1;
     }
     size = sizeof(int *);
     if (read (corefd, &srv, size) != size) {
          perror ("read core");
          return -1;
     }
     if(srv == (struct NET_server *)0) {
          if(ncp_init()){
               printf("Not Connected to any file server!\n\r");
               return -1;
          }
          sleep(2);
          if (lseek (corefd, nl [0].n_value, 0) == -1) {
               perror ("lseek core");
               return -1;
          }
          if (read (corefd, &srv, size) != size) {
               perror ("read core");
               return -1;
          }
          if(srv == (struct NET_server *)0) {
               printf("Not Connected to any file server!\n\r");
               return -1;
          }
     }
     for(i = 0; srv; srv = srv->ns_next, ++i) {
          if (lseek (corefd, srv, 0) == -1) {
               perror ("lseek core");
               return -1;
          }
          if (read (corefd, &srv_buf, sizeof(srv_buf))
                              != sizeof(srv_buf)) {
               perror ("read core");
               return -1;
          }
          srv = &srv_buf;
          strcpy(serv_name[i].sname, srv->ns_sname);
     }
     close(corefd);
}

The above function uses the UNIX nlist() function to find the location of the server table in the kernel if the table is NULL the ncp_init() is called to build the table. As each server name is read from the kernel it is written into the array serv_name. This array of server names can now be used by application programs to attach and login to the various servers.

ATTACHING TO A SERVER

To attach (connect) to a NetWare server issue to ncp_connect() function call.

example

     int connid;
     char *serv = serv_name[0].name;
     if(connid = ncp_connect(serv) < 0)
          return -1;

The ncp_connect() function like many NetCon functions first opens a UNIX file descriptor with a socket() call and then issues a specific ioctl() on that file descriptor.

example

ncp_connect(server)
{
     char devname[256];
     int i, fd;

     if ((fd = socket (AF_NS, SOCK_DGRAM, 0)) < 0)
          return -1;
     if (net_ioctl_fd (fd, "NCP", NCP_SYSC_CONNECT, (int *)&server)) {
          close(fd);
          return -1;
     }
     SetPrimaryConnectionID(fd);
     return fd;
}

net_ioctl_fd(fd, proto, cmd, arg)
char *proto;
int cmd;
int *arg;
{
     struct {
          char *proto;
          int cmd, *arg;
     } uap;
     uap.proto = proto; uap.cmd = cmd; uap.arg = arg;
     return(ioctl (fd, SYSC_NETADMIN, (int *)&uap));
}

The ncp_connect() function call sets or resets the Primary Connection ID every time a new connection is made. This ID is used for file operation and other functions such NWopen, NWseek, NWread, NWclose, NWclose, ect. See the "SETTING and GETTING CONNECTION IDs" chapter later in this section for more information.

LOGIN TO THE NETWARE SERVER

After the connection is established on the UNIX file descriptor to the NetWare server, the next step is to provide a valid login name and password to the servers new connection, this is accomplished with the ncp_login() call.

example

     char login[50], password[50];
     /* login and password must be upper case NULL               terminated strings */
     if (ncp_login(connid, login, password) < 0)
          return -1;

Another method of loging into the server is to use the DEFAULT users and groups that are setup in the "/usr/lib/netcon/netcpasswd" file. To use the default user login, pass NULL arguments to the ncp_login functions.

example

          if (ncp_login(connid, NULL, NULL) < 0)
               return -1;

After the login has been successfully completed we can now perform any number of file, print or bindery server functions over the connection you can even issue NCP calls directly .

MOUNTING A NETWARE FILE SERVER VOLUME

To mount a NetWare server's Volume and directory as a unix directory use the net_mount() function call.

NOTE; It is not necessary to connect or login to the file server before issuing the net_mount() call. This call will make it own connection within the UNIX kernel and login each UNIX user dynamically as they attempt to use the mounted directory. The login and passwords that are used are based on the mappings found in the "usr/lib/netcon/netcpasswd" file.

example

     char *remote_dir = "NETWARE:SYS:/";
     char *local_dir = "/mnt";
     int readonly_flag = 0;
     if(net_mount ("NCP", 0, remote_dir, local_dir,                readonly_flag) < 0)
          return -1;

OPENING/CLOSING A FILE ON A NETWARE SERVER

Any file on the NetWare server may be opened directly with a call to NWopen(). The following example opens the SUPERVISOR login script file. The file is opened on the Preferred or PrimaryConnectionID set up by the ncp_connect() function call.

example

     char *file = "SYS:MAIL/1/LOGIN"
     int fd, buf_length;
     u_long off = 0;
     u_char buffer[32,768];

     if(fd = NWopen(file,1) < 0)
          return -1;
     if(NWlseek(fd,  off, SEEK_SET) < 0)
          return -1;
     if(NWread(fd, &buf[0], sizeof (buf)) < 0)
          return -1;
     if(NWwrite(fd, &buf[0], 1024) < 0 )
          return -1;
     NWclose(fd);

The primary use of these file operations by the NetCon software is to read the print queue and print server configuration information and to write new print queue requests. But they can be used for any purpose on any file on the Netware server providing you have rights to that file.

CREATING A DIRECTORY

example

     char *path = "SYS:/TMP"
     int error, rights = 0xff;
     handle = 0;
     if (error = CreateDir( path, rights, handle))
          return -1;

This function will create the directory "TMP" in the root directory of the "SYS" volume on the server with the Preferred or Primary connection ID.

SETTING AND GETTING CONNECTION IDs

Each time a new connection is established with a server using the ncp_connect() function call, the PrimaryConnectionID is reset to the new connection id. This PrimaryConnectionID is automatically used by all requests made after the connection is established. You can override the PrimaryConnectionID by setting the PreferredConnectionID or changing the PrimaryConnectionID. Each request first checks the PerferredConnectionID if one exists it is used, otherwise the PrimaryConnectionID is used instead. The following example demonstrates how this mechanism operates.

example

/*
 * Sends request to the default connection and returns the error code
 * returned by the server. Returns 0 on Success.
 */
NetwareRequest(code, req, reqlen, reply, replen)
u_int code;
u_char *req, *reply;
int reqlen, *replen;
{
     int fd, status, err;

     if((fd = GetDefaultConnectionID()) < 0)
          return EBADF;
     if((err = ncp_request(fd, code, req, reqlen, reply, replen, &status))
                                             < 0)
          return status ? status & 0xff : err;
     return err;
}

static int _PreferredConnectionID = -1;
static int _PrimaryConnectionID = -1;

GetDefaultConnectionID()
{
     if(_PreferredConnectionID >= 0)
          return _PreferredConnectionID;
     return _PrimaryConnectionID;
}

GetPreferredConnectionID()
{
     return _PreferredConnectionID;
}

SetPreferredConnectionID(id)
{
     _PreferredConnectionID = id;
}

GetPrimaryConnectionID()
{
     return _PrimaryConnectionID;
}

SetPrimaryConnectionID(id)
{
     _PrimaryConnectionID = id;
}

BINDERY SERVICES

NetCon provides a complete set of library functions to manipulate the NetWare Bindery and property databases. These functions will allow a program to add, change, list and delete objects, properties and trustees from the databases on the NetWare server. For complete details on the bindery functions refer to the Libnc Reference section later in this manual.

Bindery function calls:

     
     AddBinderyObjectToSet()
     AddTrustee()
     ChangeBinderyObjectPassword()
     CheckMembership()
     CreateBinderyObject()
     CreateProperty()
     DeleteBinderyObject()
     GetBinderyObjectID()
     GetBinderyObjectName()
     MapTrustee()
     ReadPropertyValue()
     RemoveBinderyObjectFromSet()
     ScanBinderyObject()
     ScanProperty()
     WriteProperty()

Supported Bindery OBJECTS;

     OBJ_SERVER,     NetWare Server.
     OBJ_VT,     NVT Virtual terminal Server.
     OBJ_PRINTSERVER, NetWare Print Server.
     OBJ_PRINTQUEUE,     Print Queue.
     OBJ_JOBSERVER,      Job Server.
     OBJ_GATEWAYS,     Gateways.
     OBJ_USERS,          NetWare USERS.
     OBJ_GROUPS,          NetWare GROUPS.

Supported PROPERTIES;

     ACCT_LOCKOUT,
     ACCOUNT_SERVER,
     GROUPS_I'M_IN,
     GROUP_MEMBERS,
     IDENTIFICATION,
     LOGIN_CONTROL,
     MISC_LOGIN_INFO,
     NET_ADDRESS,
     OBJ_SUPERVISORS,
     OLD_PASSWORDS,
     PASSWORD,
     SECURITY_EQUALS,
     REGISTER,
     USER_DEFAULTS,

Complete examples of how to use the NetCon bindery services are provided in the sample source files "/usr/lib/netcon/src/adduser.c" and "/usr/lib/netcon/src/slist.c". These files demonstrate how to add a new user, scan and list various bindery objects.

SERVER SERVICES

The NetCon Server library calls function very much the same way as the Client in that they require that the hardware driver and protocol stack be linked and configured with the proper network address. All the server functions utilize the Berkeley 4.3BSD sockets mechanism to communicate with the IPX protocol stack in the kernel and thus the network itself. The steps required to start a server process are quite simple and straightforward. They involve opening a socket (UNIX file descriptor), binding that socket to an address and then listening on that socket for a call or connection request then establishing a connection to the calling system. After the Connection is established data transfer can occur.

OPENING A SOCKET

The first step in establishing a server process is to open a UNIX file descriptor this is accomplished by the socket() system call.

example

     static int conn_fd;
     if((conn_fd = socket(AF_NS, SOCK_DGRAM, 0)) < 0) {
          perror("socket");
          exit(0);
     }

This call open a UNIX file descriptor for address family AF_NS (XEROX XNS, NOVELL IPX) type datagram with the default protocol.

BINDING THE SOCKET TO AN ADDRESS

After the socket is open the server process must establish the complete address it wishes to use to receive a call (connection request) on. In the case of XNS/IPX protocols the address consists of three components

1) The network number: A 4 byte field whose value is set by the netcconfig command during start-ups (network number).

2) The network interface card address: This is read from the card during start-up (node number).

3) The unique port number the server process wishes to listen on.

There are a number of predefined reserved ports that the NetCon and NetWare servers use. You cannot use these same port numbers. The port numbers must be unique for each server process. Just to be confusing the port number is also referred to as the XNS/IPX socket numbers (port or socket number).

Predefined reserved Port numbers;

File Server port ,               0x0451
Service Advertising Protocal (SAP),     0x0452
Routing Information (RIP),          0x0453
NetBeui,                    0x0455
Diagnostic port,               0x0456      
NCP local port,                    0x4003
KeepAlive local port,               0x4004
SAP local port,                    0x4006
NVT virtual terminal port,          0x8063

Other Server processes should use well-known or unused ports beginning at 0x8000. Remember, these port numbers specify and identify the server and client processes within each machine (node) that are communicating with each other so they must be unique.

A typical XNS/IPX address

example

     0001:0000c0403d:0x451

We must now bind the socket to the correct address.

example

     struct sockaddr_ns ncp_laddr;
     ncp_laddr.sns_family = AF_NS;
     ncp_laddr.sns_addr.x_port = htons(0x0451);
     if(bind(conn_fd, &ncp_laddr, sizeof(ncp_laddr)) < 0) {
          perror("NCP:bind");
          done();
     }

In this example we are binding to the local NCP server port, the local network number and local node address will be set automatically by the protocol stack.

LISTENING FOR CALLS/REQUESTS

After the server process is bound to the correct local address we can now enter the main server loop and start to listen for connection requests.

example

     for(;;) {
          alen = sizeof(faddr);
          if ((len = recvfrom(conn_fd, &NCPrequest_buf [0], REQ_BUFSIZE,0, &faddr, &alen)) < 0) {
               if(errno == EINTR)
                    continue;
               exit(0);
          }
          cp = &NCPrequest_buf [0];
          if(cp[0] == 0x11 && cp[1] == 0x11) {
               /*
                * connect request
                */
               if((conn = creat_new_conn(&faddr)) == NULL_CONN) {
                    continue;
               }
               if((conn->nc_pid = fork()) < 0) {
                    destroy_conn(conn);
                    continue;
               }
               if(conn->nc_pid == 0) {
                    /*
                     * child process  connects and starts processing requests
                     */
                    close(conn_fd);
                    new_connection(&NCPrequest_buf [0], conn->nc_id, &faddr);
                    /* NOT REACHED */
                    exit(1);
               }
               /*
                * parent process starts a new listen
                */
               continue;
               }
          }
     }

In the above example the server process enters a loop and issues an recvfrom() function call. The call blocks until a packet is received that is addressed to the port the server has bound to. Upon receiving a packet the process first checks if is a valid connect request and if so a new connection structure is allocated and linked to the end of the already existing connection structures. The process now forks a child process and the original parent process continues to listen for other connection requests. The child process is being called with three arguments, the packet, connection id and the complete remote address of the machine where the connection request came from.

ESTABLISHING A CONNECTION

The actual connection between the server process and the calling client process is established in the forked child process. You will note from the above example that before the call is made to new_connection() the original socket opened to listen for connection requests is closed in the child process. So the first thing a child process must do is open a new socket, then establish a connection to the foreign (remote) address and bind to the correct local address. Once that is accomplished the child process can enter a loop and begin to listen for requests. Once the requests are received they should be processed by the server and the appropriate reply sent back to the client.

example

new_connection(conndata, conn_id, faddr)
u_char *conndata;
int conn_id;
struct sockaddr_ns *faddr;     /* address of client */
{
     register struct NET_req *req = &NETrequest;
     int len, last_seq, i, alen;
     u_char *cp;
     register fhandle_t *fh;
     /*
      * create a new socket to accept further requests on this
      * connection.
      */
     if((conn_fd = socket(AF_NS, SOCK_DGRAM, 0)) < 0) {
          perror("newconn:socket");
          exit (1);
     }
     /*
      * connect to the destination address
      */
     if(connect(conn_fd, faddr, sizeof(*faddr)) < 0) {
          perror("newconn:connect");
          exit (1);
     }
     len = sizeof(ncp_laddr);
     if(getsockname(conn_fd, &ncp_laddr, &len) < 0) {
          perror("newconn:getsockname");
          exit (1);
     }
     ncp_laddr.sns_addr.x_port = htons(NSPORT_NCP);
     /*
      * bind to our local address
      */
     if(bind(conn_fd, &ncp_laddr, sizeof(ncp_laddr)) < 0) {
          perror("newconn:bind");
          exit (1);
     }
     /*
      * listen for NCP requests from the client
      */
     for(;;) {
          if ((len = recvfrom(conn_fd, &req->nr_reqbuf [0], REQ_BUFSIZE,0, &req->nr_faddr, &alen)) < 0) {
               if(errno == EINTR)
                    continue;
               exit (1);
          }
          /* PROCESS REQUEST  and SEND REPLY */
          ncp_reply(req);
/*
 * Fill up the NCP header and send the reply to the client
 */
ncp_reply(req)
register struct NET_req *req;
{
     register u_char *cp, *cp1, *pkt;
     int pktlen;

     pktlen = req->nr_replylen + NCP_REPLY_HDR_SZ;
     pkt = cp = &req->nr_replydata [-NCP_REPLY_HDR_SZ];
     *cp++ = 0x33; *cp++ = 0x33;
     cp1 = &req->nr_reqbuf [2];  /* skip past request type 0x2222 field */

     /* fill in seq#, conn# and task# fields */
     *cp++ = *cp1++;     *cp++ = *cp1++;     *cp++ = *cp1++;
     *cp++ = 0;
     if(req->nr_ncperror)
          *cp++ = req->nr_ncperror;
     else if(req->nr_error)
          *cp++ = cvt_to_ncp_error(req->nr_error);
     else
          *cp++ = 0;
     *cp++ = 0;          /* connection status */
     /*
      * send this reply to the client
      */
     
     if(send(conn_fd, &pkt[0], pktlen, 0) < 0) {
          perror("ncp_reply-sendto");
     }
}

Complete source code for an actual NVT server process can be found in "/usr/lib/netcon/src/tmain.c" and "/usr/lib/netcon/src/tconn.c".

DIRECT NCP CALLS

TLI AND SOCKETS COMPARISON

Since the NetCon library supports BSD Sockets we thought it would be helpful to show a comparison of the functionality of each interface.

FUNCTION TLI SOCKETS

OPEN t_open() socket()

SET ADDR/NAME t_bind() bind()

LISTEN t_listen() listen()

CALL t_call() connect()

CONNECT t_accept() accept()

SEND DATA t_snd() send()

RECEIVE DATA t_rcv recv()

DISCONNECT t_close() close()

Delete Address t_unbind() close()