# 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 libaraies for developing NetWare compatible networking applications on Unix Systems.

The first library is "libsock.a" a 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 standard, 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 examplw 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 to directly to the NOVELL IPX, SPX, WinSOCK or TLI interfaces , they can directly communicated with UNIX applications written to the NetCon BSD SOCKET Interface. This interface provide 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 follow sections 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 and applications a 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 make the very same function call as you will see in the examples to follow.

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

#### Accounting Services

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

#### Bindery Services

The NetWare and NetCon file servers include a 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 there 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 call 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 function 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 ect.......

## 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, demonstrating how the NetCon API's are used in real client/server programs, complete program example will 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's NetWare/XEROX XNS environment. To guarantee portability across any UNIX platform both the Berkeley 4.3BDS socket and the AT&T STREAMS mechanism are utilized in a unique DUAL streams sockets interface. The first example involves Client functions through the BSD socket interface, the second example involves Server functions through the BSD sockets interface. The third example demonstrated the TLI STREAMS NetBeui interface over IPX protocols. The Hardware drivers provided by NetCon conform to AT&T LLI Streams interface specifications with extensions to handle NOVELL's 802.3 standard protocol and Token-Ring protocols with IPX.

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. Programs using the TLI Streams interface must be linked to libnsl_s.a

### 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 accomplish 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 use the UNIX nlist() function to find the location of the server table in the kernel if the table is NULL the ncp_init() is call 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 Perferred 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 request 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 its is used, otherwise the PrimaryConnectionID is used instead. The following example demonstrates how this mechanisms 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 file 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 and 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 function utilize the Berkeley 4.3BSD sockets mechanism to communicate with the IPX protocol stack in the kernel and thus the network it self. The steps required to start a server process are quit simple and straight forward they involve opening a socket (UNIX file descriptor), binding that socket to a 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 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 which 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 can not 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 the 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 the should 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

## NETBEUI/NETBIOS OVER IPX

NetCon provides a NETBEUI/NETBIOS protocol stack with and AT&T Streams TLI interface to user processes. This protocol stack matches the NOVELL's Netbios INT5C interface "netbios.exe" on DOS. This interface provides PEER to PEER connection oriented services and can be used to provide client and server functions on any machine on the network. In this section we will discuss how to setup the netbios INT5C interface on NetWare DOS clients and provide examples of how to use the DOS netbios INT5C interface for client and server processes and how to use the TLI interface for UNIX to communicate with the DOS INT5C client and server processes.

The UNIX System V Development System "Developers Guide" provides complete details on Streams programing and the TLI interface. Users of the TLI interface should be familiar with the Streams Primer and the NSL Network Services library sections of the Developers Guide. Additionally the "DOS INT5C emulation (NetBios)" section of this manual will provide complete details on the NOVELL netbios interface on DOS. The "Netbeui Transport Provider Streams Interface" section of this manual will provide complete details on the NetCon implementation of the NetBeui Transport provider for the TLI Streams interface.

All programs using the TLI Streams interface must be linked to libnsl_s.a.

### USING NOVELL's NETBIOS INT5C ON DOS

In order to use the NOVELL DOS netbios INT5C interface you must load ipx and netX before loading netbios.exe (netbios.exe can be found in your NetWare servers SYS:\PUBLIC directory).

example

         ipx
         net3
         netbios

### USING THE NETCON TLI STREAMS INTERFACE

Before the NetCon TLI Streams NETBEUI/NETBIOS interface can be used the protocol stack must be linked to the hardware driver interface. This is automatically accomplished during startup by a call to the "netcnblink" command from the "/usr/binnetcon.rc" script.

example

         /usr/bin/netcnblink

After the protocol stack and driver interface is linked the NetCon TLI Streams NETBEUI/NETBIOS interface is ready for use.

To use the this interface you must open the device "/dev/ipxnb" for virtual circuit connection oriented services, for netbios datagram services open the device "/dev/ipxnd".

example

         t_open("/dev/ipxnb, 2, &t_info);

To open the datagram device

example

         t_open("/dev/ipxnd, 2 ,&t_info);

### DOS INT5C AND UNIX PROCESSES

The following FOUR program fragments demonstrate how to write client and server programs to the DOS Netbios INT5C interface that will directly communicate with UNIX programs written to the TLI Streams interface. The DOS INT5C CLIENT example will communicate Directly with the UNIX TLI SERVER example conversely the UNIX TLI CLIENT example will communicate directly with the DOS INT5C SERVER.

### DOS INT5C CLIENT

The following example of a DOS client program first registers its name with a Netbios AddName call, then a call (connect request) is sent to the server. After the connection is completed, the client enters a loop and post a netbios receive request which blocks waiting on a in-coming packet. Normally a client program would also contain a netbios send request. This program will communicate directly with the UNIX TLI SERVER or the DOS INT5C SERVER.

example

    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <string.h>
    #include <dos.h>
    #include <conio.h>

    #define  SendName      "SEND121234567891"
    #define  ReceiveName   "RECEIVE123456789"
    struct NCB
    {
       char  NcbCommand;
       char  NcbRetCode;
       char  NcbLSN;
       char  NcbNum;
       void far *NcbBuffer;
       unsigned int NcbLength;
       char  NcbCallName[16];
       char  NcbName[16];
       char  NcbRTO;
       char  NcbSTO;
       void far *NcbPost;
       char  NcbLanNum;
       char  NcbCmdCplt;
       char  NcbReserve[14];
    } Ncb;

    char Buffer[100];

    int DoNetBios(struct NCB *Ncbptr);

    void main()
    {
       char  Lsn;
       int   RetCode;

       printf("\nStarting The Receive Task");

       Ncb.NcbCommand = 0x30;        /* AddName */
       memcpy(Ncb.NcbName, ReceiveName, 16);

       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;

       printf("\n Adding Name");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in Adding Name %4x", RetCode);
          exit(1);
       }

       Ncb.NcbCommand = 0x10;        /* Call */
       memcpy(Ncb.NcbCallName, SendName, 16);
       memcpy(Ncb.NcbName, ReceiveName, 16);
    /*
       Ncb.NcbRTO = 20;
       Ncb.NcbSTO = 20;
    */
       Ncb.NcbRTO = 0;          /* 0 Sec */
       Ncb.NcbSTO = 0;          /* 0 Sec */
       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;

       printf("\n Calling ..");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in Call %4x", RetCode);
          goto DelName;
       }
       Lsn = Ncb.NcbLSN;

       while (1)
       {
          printf("\n Waiting to Receive a Block");
    /*
          printf("\n Hit a Key to Receive a Block");
          while (!kbhit());
          RetCode = getch();
          if (RetCode != `A')
          {
    */
             Ncb.NcbCommand = 0x15;        /* Receive */
             Ncb.NcbPost = NULL;
             Ncb.NcbLanNum  = 0;
             Ncb.NcbBuffer = Buffer;
             Ncb.NcbLength = 50;

             RetCode = DoNetBios(&Ncb);
             if (RetCode)
             {
                printf ("\n Error in Receiveing Data %4x", RetCode);
                break;
             }
             printf ("\n Receive complete %d %s", Ncb.NcbLength, Buffer);
    /*
          }
          else
             break;
    */
       }

       Ncb.NcbCommand = 0x12;        /* Hangup*/
       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;
       Ncb.NcbLSN  = Lsn;

       printf("\n Closing the session ");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in HangUp %4x", RetCode);
       }

    DelName:
       Ncb.NcbCommand = 0x31;        /* Delete Name */
       memcpy(Ncb.NcbName, ReceiveName, 16);

       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;

       printf("\n Deleting Name ");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in Deleting Name %4x", RetCode);
          exit(1);
       }

    }

    int DoNetBios(struct NCB * NcbPtr)
    {
       struct       SREGS   SegRegs;
       union        REGS    InRegs, OutRegs;

       segread(&SegRegs);

       InRegs.x.bx = FP_OFF(NcbPtr);
    /*****   SegRegs.es  = FP_SEG(NcbPtr); ****/
       int86x(0x5c, &InRegs, &OutRegs, &SegRegs);  /* Allocate Memory */

       if(OutRegs.x.ax)
       {
          return (OutRegs.x.ax);             /* No Memory Available */
       }

       return(NcbPtr->NcbRetCode);

    }

### DOS INT5C SERVER

This example like the DOS INT5C CLIENT first issues a netbios AddName call to register its name, then a netbios listen is posted which blocks waiting on a call request from a client. When a call (connect request) is received the program enters a loop and begins to send data to the client. Normally the server program would also have a receive function.

example

    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <string.h>
    #include <dos.h>
    #include <conio.h>

    #define  SendName      "mach1"
    #define  ReceiveName   "RECEIVE123456789"
    struct NCB
    {
       char  NcbCommand;
       char  NcbRetCode;
       char  NcbLSN;
       char  NcbNum;
       void far *NcbBuffer;
       unsigned int NcbLength;
       char  NcbCallName[16];
       char  NcbName[16];
       char  NcbRTO;
       char  NcbSTO;
       void far *NcbPost;
       char  NcbLanNum;
       char  NcbCmdCplt;
       char  NcbReserve[14];
    } Ncb;

    char Buffer[100];

    int DoNetBios(struct NCB *Ncbptr);

    void main(argc, argv)
    int argc;
    char *argv[];
    {
       char  Lsn;
       int   RetCode;
       int i;

         if(argc != 2){
         printf("Usage: setname MACHINE-NAME\n");
         exit(0);
         }

       Ncb.NcbCommand = 0x30;        /* AddName */
       strcpy(Ncb.NcbName, argv[1]);

       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;

       printf("Adding Name: %s\n", Ncb.NcbName);
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in Adding Name %4x", RetCode);
          exit(1);
       }
    exit(0);

       Ncb.NcbCommand = 0x11;        /* Listen */
       Ncb.NcbCallName[0] = `*';
       memcpy(Ncb.NcbName, SendName, 16);
       Ncb.NcbRTO = 0;
       Ncb.NcbSTO = 0;          /* 10 Sec */
       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;

       printf("\n Listening ...");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in Listen Name %4x", RetCode);
          goto DelName;
       }
       Lsn = Ncb.NcbLSN;

       i = 1;
       while (1)
       {

          itoa(i, Buffer, 10);
          strcat(Buffer,". Send Data Buffer             ");

          printf("\n Hit a Key to Send a Block");
          while (!kbhit());
          RetCode = getch();
          if (RetCode != `A')
          {
             Ncb.NcbCommand = 0x14;        /* Send */
             Ncb.NcbPost = NULL;
             Ncb.NcbLanNum  = 0;
             Ncb.NcbBuffer = Buffer;
             Ncb.NcbLength = 28;

             RetCode = DoNetBios(&Ncb);
             if (RetCode)
             {
                printf ("\n Error in Sending Data %4x", RetCode);
                break;
             }
             printf ("\n Send complete %.20s", Buffer);
             i++;
          }
          else
             break;
       }

       Ncb.NcbCommand = 0x12;        /* Hangup*/
       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;
       Ncb.NcbLSN  = Lsn;

       printf("\n Closing the session ");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in HangUp %4x", RetCode);
       }

    DelName:
       Ncb.NcbCommand = 0x31;        /* Delete Name */
       memcpy(Ncb.NcbName, SendName, 16);

       Ncb.NcbPost = NULL;
       Ncb.NcbLanNum  = 0;

       printf("\n Deleting Name ");
       RetCode = DoNetBios(&Ncb);
       if (RetCode)
       {
          printf ("\n Error in Deleting Name %4x", RetCode);
          exit(1);
       }

    }

    int DoNetBios(struct NCB * NcbPtr)
    {
       struct       SREGS   SegRegs;
       union        REGS    InRegs, OutRegs;

       segread(&SegRegs);

       InRegs.x.bx = FP_OFF(NcbPtr);
    /***   SegRegs.es  = FP_SEG(NcbPtr); ***/
       int86x(0x5c, &InRegs, &OutRegs, &SegRegs);  /* Allocate Memory */

       if(OutRegs.x.ax)
       {
          return (OutRegs.x.ax);             /* No Memory Available */
       }

       return(NcbPtr->NcbRetCode);

    }

## UNIX TLI PROCESSES

The following examples of UNIX TLI CLIENT and SERVER will communicate with each other on the same or different machines or directly with DOS INT5C CLIENTS or SERVERS

### UNIX TLI SERVER

This example of a UNIX TLI SERVER follows the recommended steps of first issuing a t_open() on the TLI transport device "/dev/ipxnb". Then the Netbios name is added through a t_bind() call. The server then post a t_listen() which blocks waiting for a connect request (call). Upon receiving a connect request the server issues a new t_open() and t_bind() then a t_accept() to complete the connection. Normally the program would fork() and the parent process would continue to listen for other incoming connect request while the child would accept the new connection and begin data transfer.

example

    #include <sys/tiuser.h>
    #include <sys/stropts.h>
    #include <stdio.h>
    #include <sys/signal.h>
    #include "../../nb/nb_stream.h"
    #include <stdlib.h>

    char *dname;
    char *naddr;
    int fd, afd;
    int last_exit();

    int grpflag;
    main (ac, av)
         int ac;
         char *av [];
    {
         char *naddr = (char *)NULL, *prog;
         struct t_info tinfo;
         register struct t_bind *req, *rep;
         register struct nbaddr *nb;
         signal(SIGHUP, last_exit);
         signal(SIGTERM, last_exit);
         signal(SIGINT, last_exit);
         signal(SIGQUIT, last_exit);

         prog = av [0];
         for (ac, av++; ac > 0; ac, av++) {
              if (**av != `-') {
                   usage:
                   fprintf (stderr,
                        "usage: %s -d <dname-dev> -n <nb-addr> [-g]\n",
                        prog);
                   last_exit ();
              }
              switch (av [0][1]) {

              case `d':
                   ac, av++;
                   dname = av [0];
                   break;

              case `n':
              case `a':
                   ac, av++;
                   naddr = av [0];
                   break;
              case `g':
                   grpflag++;
                   break;

              default:
                   fprintf (stderr, "%s: invalid switch %s\n", av[0]);
                   goto usage;
              }
         }

         printf("Starting the Send Task\n");
         if (dname == (char *)NULL)
              dname = "/dev/nbv";
         if ((fd = t_open (dname, 2, &tinfo)) < 0) {
              t_error ("t_open");
              last_exit ();
         }
         /*
          * Bind to the address specified.
          */

         if ((req = (struct t_bind *)t_alloc (fd, T_BIND, T_ALL))
              == (struct t_bind *)0)
         {
              t_error ("t_alloc req");
              last_exit ();
         }

         if ((rep = (struct t_bind *)t_alloc (fd, T_BIND, T_ALL))
              == (struct t_bind *)0)
         {
              t_error ("t_alloc rep");
              last_exit ();
         }
         printf("Adding Name\n");
         if (naddr == (char *)NULL)
              naddr = "SEND121234567891";
         nb = (struct nbaddr *)req->addr.buf;
         req->addr.len = sizeof (struct nbaddr);
         memset (&nb->nb_name [0], ` `, NBNAMSZ);
         memcpy (&nb->nb_name [0], naddr, strlen (naddr));

         req->qlen = 1;
         if (grpflag)
              nb->nb_type = NBGROUPNM;
         else
              nb->nb_type = 0;

         if (t_bind (fd, req, rep) < 0) {

              t_error ("t_bind");
              last_exit ();
         }
         nb = (struct nbaddr *)rep->addr.buf;
         getcalls (fd);
    }
    getcalls (fd)
         int fd;
    {
         struct t_call *call;
         int afd;
         register struct t_bind *req, *rep;
         char naddr [18];
         register struct nbaddr *nb;
         struct t_info tinfo;

         signal (SIGCLD, SIG_IGN); /* So we don't have zombies */
         if ((call = (struct t_call *)t_alloc (fd, T_CALL, T_ALL)) == 0) {
              t_error ("t_alloc: call");
              last_exit ();
         }
              printf("Listening\n");
              if (t_listen (fd, call) < 0) {
                   t_error ("t_listen");
                   last_exit ();
              }
              if ((afd = t_open (dname, 2, &tinfo)) < 0) {
                   t_error ("t_open2");
                   last_exit ();
              }

              if ((req = (struct t_bind *)t_alloc (afd, T_BIND, T_ALL))
              == (struct t_bind *)0)
              {
                   t_error ("t_alloc req");
                   last_exit ();
              }

              if ((rep = (struct t_bind *)t_alloc (afd, T_BIND, T_ALL))
                   == (struct t_bind *)0)
              {
                   t_error ("t_alloc rep");
                   last_exit ();
              }
              req->addr.len = sizeof (struct nbaddr);
              memset (&nb->nb_name [0], ` `, NBNAMSZ);
              req->qlen = 0;

              if (t_bind (afd, req, rep) < 0) {
                   t_error ("t_bind2");
                   last_exit ();
              }
              if (t_accept (fd, afd, call) < 0) {
                   t_error ("t_accept");
                   last_exit ();
              }
              server (afd);
    }
    server (afd)
    {
         char buf[100];
         char c[80];
         int i, nbytes, flags;

         (void) t_sync (afd);

         i = 1;
         while (1) {
              sprintf(buf, "%d. Send Data Buffer            ", i);
              printf("Hit a Key to Send a Block\n");
              gets(c);
              if(c[0] == `A')
                   break;
              if (t_snd(afd, buf, 28, &flags) < 0) {
                   t_error ("SERV: t_snd");
                   last_exit();
              }
              printf("Send complete %s\n", buf);
              i++;
         }
         last_exit ();
    }
    int
    last_exit()
    {
         t_unbind(fd);
         t_close(fd);
         t_close(afd);
         exit (1);
    }

### UNIX TLI CLIENT

This example like the UNIX TLI SERVER first opens the TLI transport provider with a call to t_open(). Then adds the Netbios name with a call to t_bind() and issues a connect request to the server with a call to t_connect(). After the connect request completes the program enters a loop and post a t_rcv() which block waiting on incoming data. Normally there would also be a t_snd() function to send data.

example

    #include <sys/tiuser.h>
    #include <stdio.h>
    #include "nb_stream.h"
    #include <sys/signal.h>
    #include <fcntl.h>
    #include <sys/termio.h>
    extern int t_errno;

    char *dname;
    char buf[2048];
    char c[2048];
    int grpflag, lfd, mypid; last_exit();

    struct termio tsave,tset;
    char *startup="";
    int fd;
    #define VERSION "1.0"

    main (argc, argv)
         int argc;
         char *argv [];
    {
         char *naddr = (char *)NULL, *laddr = (char *)NULL;
         int i, n, sync, fdflag;
         struct t_info tinfo;
         struct t_info info;
         register struct t_bind *req, *rep;
         register struct nbaddr *nb;
         struct t_call *call, *rcall;
         ioctl(0,TCGETA,&tsave);
         signal(SIGHUP,last_exit);
         signal(SIGTERM,last_exit);
         signal(SIGINT,last_exit);
         signal(SIGQUIT,last_exit);
         printf("Starting the Receive Task\n");

              if(argc == 2)
                   naddr = argv [1];

         if (dname == (char *)NULL)
              dname = "/dev/nbv";
         if ((fd = t_open (dname, 2, &tinfo)) < 0) {
              t_error ("t_open");
              exit (1);
         }
         lfd = fd;
         /*
          * Bind to the address specified.
          */
         if ((req = (struct t_bind *)t_alloc (fd, T_BIND, T_ALL))
              == (struct t_bind *)0)
         {
              t_error ("t_alloc req");
              exit (1);
         }
         if ((rep = (struct t_bind *)t_alloc (fd, T_BIND, T_ALL))
              == (struct t_bind *)0)
         {
              t_error ("t_alloc rep");
              exit (1);
         }

         laddr = "RECEIVE123456789";
         nb = (struct nbaddr *)req->addr.buf;
         req->addr.len = sizeof (struct nbaddr);
         memset (&nb->nb_name [0], ` `, NBNAMSZ);
         memcpy (&nb->nb_name [0], laddr, strlen (laddr));
         req->qlen = 0;
         nb->nb_type = 0;
         printf("Adding Name\n");
         if (t_bind (fd, req, rep) < 0) {
              t_error ("client t_bind");
              exit (1);
         }
         if ((call = (struct t_call *)t_alloc (fd, T_CALL, T_ALL))
              == (struct t_call *)0) {
              t_error ("client t_alloc");
              exit (1);
         }
         if ((rcall = (struct t_call *)t_alloc (fd, T_CALL, T_ALL))
              == (struct t_call *)0) {
              t_error ("client t_alloc");
              exit (1);
         }
         if (naddr == (char *)NULL)
              naddr = "SEND121234567891";
         nb = (struct nbaddr *)call->addr.buf;
         call->addr.len = sizeof (struct nbaddr);
         memset (&nb->nb_name [0], ` `, NBNAMSZ);
         memcpy (&nb->nb_name [0], naddr, strlen (naddr));
         printf("Calling ..\n");
         if (t_connect (fd, call, rcall) < 0) {
              t_error ("t_connect fail");
              exit (1);
         }
         if (sync = t_sync (fd) < 0) {
              t_error ("t_sync failed" );
              exit (1);
         }
         printf ("Connected to %s Waiting on Received Data\n", naddr);
         while(1) {
              if((n = t_rcv (fd, buf, sizeof buf, &fdflag)) > 0) {
                   printf("\n Receive complete length:%d %s", n, buf);
                   continue;
              } else {
                   printf("\n Error in Receiving Data %x", n);
                   last_exit();
              }
         }

    }
    int
    last_exit()
    {
         printf("Closing the session\n");
         t_unbind(fd);
         t_close(fd);
         fprintf(stderr,"NETBIOS Connection closed\n");
         fflush(stderr);
         exit(0);
    }
    usage()
    {
         printf("Usage: receive [hostname]\n");
         exit(1);
    }

### INT5C, TLI AND SOCKETS COMPARISON

Since the NetCon library support DOS INT5C , TLI Streams and BSD Sockets we thought it would be helpful to show a comparison of the functionality of each interface.

FUNCTION INT5C TLI SOCKETS

OPEN - t_open() socket()

SET ADDR/NAME AddName t_bind() bind()

LISTEN Listen t_listen() listen()

CALL Call t_call() connect()

CONNECT Listen t_accept() accept()

SEND DATA Send t_snd() send()

RECEIVE DATA Receive t_rcv recv()

DISCONNECT HangUp t_close() close()

Delete Name Delete Name t_unbind() close()