www.pudn.com > bootpLib_for_VxWORKS.rar > bootpLib.c


/* bootpLib.c - BOOTP client library */

/* Copyright 1984 - 1999 Wind River Systems, Inc. */
#include "copyright_wrs.h"

/*
modification history
--------------------
01v,17mar99,spm  added support for identical unit numbers (SPR #20913)
01u,04sep98,ham  corrected lack of params for etherInputHookAdd(),SPR#21909
01t,28aug98,n_s  corrected MAC address comparison in bootpInputHook. spr #20902
01s,17jul97,dgp  doc: correct unsupported interfaces per SPR 8940
02c,14dec97,jdi  doc: cleanup.
02b,10dec97,gnn  making man page fixes
02a,08dec97,gnn  END code review fixes
01z,03dec97,spm  corrected parameter description for bootpMsgSend (SPR #9401);
                 minor changes to man pages and code spacing
01y,03oct97,gnn  removed references to endDriver global.
01x,25sep97,gnn  SENS beta feedback fixes
01w,26aug97,spm  fixed bootpParamsGet - gateway not retrieved (SPR #9137)
01v,12aug97,gnn  changes necessitated by MUX/END update.
01u,30apr97,jag  man page edit for function bootParamsGet()
01t,07apr97,spm  changed BOOTP interface to DHCP style: all options supported
01s,17dec96,gnn  added code to handle the new etherHooks and END stuff.
01r,08nov96,spm  Updated example of bootpParamsGet() for SPR 7120
01q,22sep96,spm  Fixed SPR 7120: added support for gateways to bootpParamsGet()
01p,01feb96,gnn	 added the end of vendor data (0xff) to the request packet
		 we send
01o,16jan94,rhp  fix typo in library man page
01n,17oct94,rhp  remove docn reference to bootpdLib (SPR#2351)
01n,22sep92,jdi  documentation cleanup.
01m,14aug92,elh  documentation changes.
01l,11jun92,elh  modified parameters to bootpParamsGet.
01k,26may92,rrr  the tree shuffle
		  -changed includes to have absolute path from h/
01j,16apr92,elh	 moved routines shared by icmp to icmpLib.
01i,28feb92,elh  ansified.
01h,27aug91,elh  rewritten to use standard bootp protocol,
		 redesigned to be more modular, rewrote documentation.
01g,15aug90,dnw  added slot parameter to bootpParamsGet()
	   +hjb  fixed bug in bootpForwarder() not setting hw addr in arp ioctl
01f,12aug90,dnw  changed bootpParamsGet() to check every tick for reply message
                 instead of every 4 seconds, for faster response.
		 changed bootpParamsGet() to print '.' every time it broadcasts
                 request.
01e,12aug90,hjb  major redesign and implementation of the protocol
01d,07may90,hjb  made bootp IP checksum portable; modifications to the protocol
		 for better forwarding service.
01c,19apr90,hjb  minor fixups bootpRequestSend(), added protocol extension
		 to solve the routing problem when bootp forwarder is used.
01b,11apr90,hjb  de-linted.
01a,11mar90,hjb  written.
*/

/*
DESCRIPTION
This library implements the client side of the Bootstrap Protocol
(BOOTP).  This network protocol allows the dynamic configuration of 
the target's boot parameters at boot time.  This is in contrast to using the 
boot information encoded in system non-volatile RAM or ROM.  
Thus, at boot time, BOOTP goes over the network to get an IP address, 
a boot file name, and the boot host's IP address.  

The actual transfer of the boot image is handled by a file transfer 
protocol, such as TFTP or FTP, or by an RSH command.

To access BOOTP services, you can use either the high-level interface
supported by bootpParamsGet(), or the low-level interface supported
by bootpMsgSend().

HIGH-LEVEL INTERFACE
The bootpParamsGet() routine provides the highest level interface to BOOTP.  
It accepts a parameter descriptor structure that allows the retrieval of any 
combination of the options described in RFC 1533 (if supported by the 
BOOTP server and if specified in the database). During system boot, the routine 
obtains the boot file, the Internet address, and the host Internet address.  
It also obtains the subnet mask and the Internet address of an IP router, 
if available.

LOW-LEVEL INTERFACE
The bootpMsgSend() routine provides a lower-level interface to BOOTP.  It 
accepts and returns a BOOTP message as a parameter.  This interface is more
flexible because it gives the caller direct access to the data in the
BOOTP request/reply messages. For example, if the BOOTP message includes 
implementation-specific options not defined in an RFC, the caller can
use bootpMsgSend() to retrieve them from the vendor-specific field in 
the BOOTP message.  The bootpParamsGet() routine already provides 
all defined options.

EXAMPLE
The following code provides and example of how to use bootpParamsGet():  
.CS
    #include "bootpLib.h"

    struct bootpParams 	bootParams;

    struct in_addr 	clntAddr;
    struct in_addr 	hostAddr;
    char 		bootFile [FILENAME_SIZE];
    int 		subnetMask;
    struct in_addr_list routerList;
    struct in_addr 	gateway;

    char        clntAddr [INET_ADDR_LEN];
    char        bootServer [INET_ADDR_LEN];
    char 	bootFile [SIZE_FILE];
    int         fileSize; 
    int         subnetMask;
    char        gateway [INET_ADDR_LEN];
 
    bzero ( (char *)&clntAddr, sizeof (struct in_addr));
    bzero ( (char *)&hostAddr, sizeof (struct in_addr));
    bzero (bootFile, FILENAME_SIZE);
    subnetMask  = 0;
    bzero ( (char *)&gateway, sizeof (struct in_addr));

    /@ Set all pointers in parameter descriptor to NULL. @/

    bzero ((char *)&bootParams, sizeof (struct bootpParams));

    /@ Set pointers corresponding to desired options. @/

    bootParams.clientAddr = &clntAddr;
    bootParams.bootHostAddr = &hostAddr;
    bootParams.bootfile = pBootFile;

    bootParams.netmask = (struct in_addr *)&subnetMask;
    routerlist.addr = &gateway;
    routerlist.num = 1;
    bootParams.routers = &routerlist;

    if (bootpParamsGet ("ln0", 0, 0, &bootParams) == ERROR)
        return (ERROR);
.CE

NOTE
Certain targets (typically those with no NVRAM) construct their 
Ethernet address based on the target's IP address.  An IP address
must be entered for these targets in order to boot over the network.
The remaining information can be obtained with BOOTP.

BOOTP is not supported over the following network interfaces: if_sl (SLIP) 
and if_ie (Sun IE driver).
if_sl (SLIP) and if_ppp (PPP).

INCLUDE FILES: bootpLib.h

SEE ALSO: bootLib, RFC 951, RFC 1542, RFC 1533,
.pG "Network"

INTERNAL

The BOOTP implementation uses driver input hooks to access the network 
interface. The link-level access that mechanism provides is required since 
the network is not initially available during boot time because the Internet 
address is unknown.

The diagram below defines the structure chart of bootpLib.


                                   (network interface driver)
                 |                           |
                 v                           v
	    bootpParamsGet	      bootpInputHook
	     / 		\		     |
	    |		|		     v
	    v		v	     
      bootpMsgSend   bootpParamsFill
*/

/* includes */

#include "vxWorks.h"
#include "bootpLib.h"
#include "netinet/in.h"

#include "netinet/udp.h"
#include "netinet/in_var.h"
#include "netinet/if_ether.h"

#include "etherLib.h"
#include "taskLib.h"
#include "string.h"
#include "stdio.h"
#include "errno.h"
#include "sysLib.h"
#include "tickLib.h"
#include "icmpLib.h"
#include "inetLib.h"

#include "end.h"
#include "muxLib.h"
#include "ipProto.h"
    
/* defines */

#define ETHER_ADDR_TYPE		1		/* ethernet type   */
#define ETHER_ADDR_LEN		6  		/* ethernet length  */

/* globals */

LOCAL struct
    {
    struct ip		ih;		/* IP header		*/
    struct udphdr	uh;		/* UDP header		*/
    BOOTP_MSG		bp;    		/* Bootp message	*/
    } bootpMsg;

u_long	bootpBroadcastAddr = INADDR_BROADCAST;		/* bootp broadcast   */
int 	bootpReXmitSecs    = INIT_BOOTP_DELAY; 		/* bootp delay */

/* locals */

LOCAL BOOTP_MSG	* 	pBootpReply;			/* pointer to reply  */
LOCAL BOOL		bootpReplyReceived; 		/* received reply?   */
LOCAL char		inputBuffer [ETHERMTU];		/* message buffer    */
							/* 1048 cookie type  */
LOCAL u_char		magicCookie1048 [4] = VM_RFC1048;

#define VEOF_RFC1048 {255}

LOCAL u_char		endOfVend[1] = VEOF_RFC1048;

/* forward declarations */

LOCAL BOOL bootpInputHook (struct ifnet * pIf, char *einput, int length);
extern	   in_broadcast ();

/******************************************************************************
*
* bootpParamsGet - retrieve boot parameters using BOOTP
*
* This routine transmits a BOOTP request message over the network interface
* associated with .  This interface must already be attached and
* initialized prior to calling this routine.
*
* A non-zero value for  specifies an alternate BOOTP server port.
* A zero value means the default BOOTP server port (67).
*
*  specifies a timeout value in ticks.  If no reply is received 
* within this period, an error is returned.  Specify zero for an infinite
*  value.
*
*  is a structure pointer to a `bootpParams' structure that
* you can use to indicate the parameters of interest to you. The `bootpParams' 
* structure is defined as follows:
*
* .CS
*    struct bootpParams
*        {
*        struct in_addr *            clientAddr;
*        struct in_addr *            bootHostAddr;
*        char *                      bootfile;
*        char *                      serverName;
*
*        struct in_addr *            netmask;
*        unsigned short *            timeOffset;
*        struct in_addr_list *       routers;
*        struct in_addr_list *       timeServers;
*        struct in_addr_list *       nameServers;
*        struct in_addr_list *       dnsServers;
*        struct in_addr_list *       logServers;
*        struct in_addr_list *       cookieServers;
*        struct in_addr_list *       lprServers;
*        struct in_addr_list *       impressServers;
*        struct in_addr_list *       rlpServers;
*        char *                      clientName;
*        unsigned short *            filesize;
*        char *                      dumpfile;
*        char *                      domainName;
*        struct in_addr *            swapServer;
*        char *                      rootPath;
*        char *                      extoptPath;
*        unsigned char *             ipForward;
*        unsigned char *             nonlocalSourceRoute;
*        struct in_addr_list *       policyFilter;
*        unsigned short *            maxDgramSize;
*        unsigned char *             ipTTL;
*        unsigned long *             mtuTimeout;
*        struct ushort_list *        mtuTable;
*        unsigned short *            intfaceMTU;
*        unsigned char *             allSubnetsLocal;
*        struct in_addr *            broadcastAddr;
*        unsigned char *             maskDiscover;
*        unsigned char *             maskSupplier;
*        unsigned char *             routerDiscover;
*        struct in_addr *            routerDiscAddr;
*        struct in_addr_list *       staticRoutes;
*        unsigned char *             arpTrailers;
*        unsigned long *             arpTimeout;
*        unsigned char *             etherPacketType;
*        unsigned char *             tcpTTL;
*        unsigned long *             tcpInterval;
*        unsigned char *             tcpGarbage;
*        char *                      nisDomain;
*        struct in_addr_list *       nisServers;
*        struct in_addr_list *       ntpServers;
*        char *                      vendString;
*        struct in_addr_list *       nbnServers;
*        struct in_addr_list *       nbddServers;
*        unsigned char *             nbNodeType;
*        char *                      nbScope;
*        struct in_addr_list *       xFontServers;
*        struct in_addr_list *       xDisplayManagers;
*        char *                      nispDomain;
*        struct in_addr_list *       nispServers;
*        struct in_addr_list *       ipAgents;
*        struct in_addr_list *       smtpServers;
*        struct in_addr_list *       pop3Servers;
*        struct in_addr_list *       nntpServers;
*        struct in_addr_list *       wwwServers;
*        struct in_addr_list *       fingerServers;
*        struct in_addr_list *       ircServers;
*        struct in_addr_list *       stServers;
*        struct in_addr_list *       stdaServers; 
*        };
* .CE
*
* This structure allows the retrieval of any BOOTP option specified in
* RFC 1533. The list of 2-byte (unsigned short) values is defined as:
*
* .CS
*    struct ushort_list
*        {
*        unsigned char       num;
*        unsigned short *    shortlist;
*        };
* .CE
*
* The IP address lists use the following similar definition:
*
* .CS
*    struct in_addr_list
*        {
*        unsigned char       num;
*        struct in_addr *    addrlist;
*        };
* .CE
*
* When these lists are present, the routine stores values retrieved from
* the BOOTP reply in the location indicated by the `shortlist' or `addrlist'
* members.  The amount of space available is indicated by the `num' member.
* When the routine returns, the `num' member indicates the actual number of
* entries retrieved.  In the case of `bootpParams.policyFilter.num' 
* and `bootpParams.staticRoutes.num', the `num' member value should be 
* interpreted as the number of IP address pairs requested and received.
*
* The following members of the `bootpParams' structure are also used for 
* both input and output:
*
* .IP `clientAddr' 
* Contains a pointer that holds the client's Internet address.  
* On input, if it contains a non-NULL value, it is interpreted as a 
* pointer to an Internet address of type `struct in_addr' and passed 
* on to the BOOTP server in the `bp_ciaddr' member of the BOOTP 
* message structure (BOOTP_MSG).  The server will use it as a lookup 
* field into the BOOTP database.  When a reply is received, the client's
* assigned Internet address is copied to the `clientAddr' member.
*
* .IP `bootHostAddr' 
* Contains a pointer that holds the host's IP address.  On input, 
* if it contains a non-NULL value, it is interpreted as the host where 
* the BOOTP message is to be sent.  Note that this host must be local
* to the  network.  If NULL, the BOOTP message is sent to the local
* broadcast address.  On return, the host's IP address is copied to
* the `bootHostAddr' member.
*
* On input, if the `bootpParams.bootfile' member  points to a non-empty 
* string, the contents are passed to the BOOTP server in the `bp_file' 
* member of the BOOTP message structure (BOOTP_MSG).  When a reply is 
* received, the file name retrieved from the BOOTP server is copied to 
* the `bootpParams.bootfile' member as a NULL-terminated string.
*
* .LP
* The remaining elements in the BOOTP parameters descriptor are used to select
* options for retrieval from the BOOTP server.  The BOOTP library attempts to
* retrieve the values for any options whose corresponding field pointers are
* non-NULL values.  To obtain these parameters, the BOOTP server must support
* the vendor-specific options described in RFC 1048 (or its successors) and the 
* corresponding parameters must be specified in the BOOTP server database. 
* Where meaningful, the values are returned in host byte order. 
*
* The BOOTP request issued during system startup attempts to retrieve a subnet
* mask for the boot device, in addition to the host and client addresses,
* and the boot file name.
*
* RETURNS: OK, or ERROR if unsuccessful.
*
* SEE ALSO: bootLib, RFC 1048, RFC 1533
*/

STATUS bootpParamsGet
    (
    char * 			ifName,		/* network interface name    */
    int				port,		/* optional port number	     */
    u_int 			timeOut,	/* timeout in ticks          */
    struct bootpParams * 	pBootpParams 	/* parameters descriptor     */
    )
    {
    BOOTP_MSG		bootpMessage;	/* bootp message		*/
    struct in_addr	ipDest;		/* ip dest address 		*/
    int length;

    /* fill in bootp message */

    bzero ((char *) &bootpMessage, sizeof (BOOTP_MSG));

    /* Check for client IP address. */

    if (pBootpParams->clientAddr != NULL)
        bootpMessage.bp_ciaddr = *pBootpParams->clientAddr;

    /* Check for host address, or use broadcast address. */

    if (pBootpParams->bootHostAddr == NULL || 
        pBootpParams->bootHostAddr->s_addr == 0)
   	ipDest.s_addr = htonl (bootpBroadcastAddr);
    else
   	ipDest = *pBootpParams->bootHostAddr;

    /* Check for partial boot file. */

    if (pBootpParams->bootfile != NULL)
        {
        length = strlen (pBootpParams->bootfile);
        if (length > SIZE_FILE)
            length = SIZE_FILE;
        (void) strncpy ( (char *) bootpMessage.bp_file, pBootpParams->bootfile,
                        length);
        }

    /* Fill in RFC1048 magic cookie if vendor-specific info requested. */

    if (bootpOptionCheck (pBootpParams))
        {
        bcopy ( (char *) magicCookie1048, (char *) bootpMessage.bp_vend,
               sizeof (magicCookie1048));
        bcopy ( (char *) endOfVend, (char *) bootpMessage.bp_vend +
               sizeof(magicCookie1048), sizeof(endOfVend));
        }

    /* send bootp message */

    if (bootpMsgSend (ifName, &ipDest, port, &bootpMessage, timeOut) == ERROR)
        return (ERROR);

    /* Fill in any options requested by user and provided by server. */

    bootpParamsFill (pBootpParams);

    return (OK);
    }

/******************************************************************************
*
* bootpOptionCheck - check if user wants optional parameters
*
* This routine is called to determine whether the magic cookie specified 
* in RFC 1048 should be inserted into the vendor specifications field of 
* a BOOTP request.  The cookie is inserted if the user has set any pointer 
* in the parameter descriptor which correspond to BOOTP options to a 
* non-NULL value.
*
* RETURNS: TRUE if options requested, or FALSE otherwise.
*
* ERRNO: N/A
*
* NOMANUAL
*/

BOOL bootpOptionCheck
    (
    struct bootpParams * 	pBootpParams 	/* parameters descriptor     */
    )
    {
    BOOL result;

    result = FALSE;

    if (pBootpParams->netmask != NULL)
        result = TRUE;
    else if (pBootpParams->timeOffset != NULL)
        result = TRUE;
    else if (pBootpParams->routers != NULL)
        result = TRUE;
    else if (pBootpParams->timeServers != NULL)
        result = TRUE;
    else if (pBootpParams->nameServers != NULL)
        result = TRUE;
    else if (pBootpParams->dnsServers != NULL)
        result = TRUE;
    else if (pBootpParams->logServers != NULL)
        result = TRUE;
    else if (pBootpParams->cookieServers != NULL)
        result = TRUE;
    else if (pBootpParams->lprServers != NULL)
        result = TRUE;
    else if (pBootpParams->impressServers != NULL)
        result = TRUE;
    else if (pBootpParams->rlpServers != NULL)
        result = TRUE;
    else if (pBootpParams->clientName != NULL)
        result = TRUE;
    else if (pBootpParams->filesize != NULL)
        result = TRUE;
    else if (pBootpParams->dumpfile != NULL)
        result = TRUE;
    else if (pBootpParams->domainName != NULL)
        result = TRUE;
    else if (pBootpParams->swapServer != NULL)
        result = TRUE;
    else if (pBootpParams->rootPath != NULL)
        result = TRUE;
    else if (pBootpParams->extoptPath != NULL)
        result = TRUE;
    else if (pBootpParams->ipForward != NULL)
        result = TRUE;
    else if (pBootpParams->nonlocalSourceRoute != NULL)
        result = TRUE;
    else if (pBootpParams->policyFilter != NULL)
        result = TRUE;
    else if (pBootpParams->maxDgramSize != NULL)
        result = TRUE;
    else if (pBootpParams->ipTTL != NULL)
        result = TRUE;
    else if (pBootpParams->mtuTimeout != NULL)
        result = TRUE;
    else if (pBootpParams->mtuTable != NULL)
        result = TRUE;
    else if (pBootpParams->intfaceMTU != NULL)
        result = TRUE;
    else if (pBootpParams->allSubnetsLocal != NULL)
        result = TRUE;
    else if (pBootpParams->broadcastAddr != NULL)
        result = TRUE;
    else if (pBootpParams->maskDiscover != NULL)
        result = TRUE;
    else if (pBootpParams->maskSupplier != NULL)
        result = TRUE;
    else if (pBootpParams->routerDiscover != NULL)
        result = TRUE;
    else if (pBootpParams->routerDiscAddr != NULL)
        result = TRUE;
    else if (pBootpParams->staticRoutes != NULL)
        result = TRUE;
    else if (pBootpParams->arpTrailers != NULL)
        result = TRUE;
    else if (pBootpParams->arpTimeout != NULL)
        result = TRUE;
    else if (pBootpParams->etherPacketType != NULL)
        result = TRUE;
    else if (pBootpParams->tcpTTL != NULL)
        result = TRUE;
    else if (pBootpParams->tcpInterval != NULL)
        result = TRUE;
    else if (pBootpParams->tcpGarbage != NULL)
        result = TRUE;
    else if (pBootpParams->nisDomain != NULL)
        result = TRUE;
    else if (pBootpParams->nisServers != NULL)
        result = TRUE;
    else if (pBootpParams->ntpServers != NULL)
        result = TRUE;
    else if (pBootpParams->vendString != NULL)
        result = TRUE;
    else if (pBootpParams->nbnServers != NULL)
        result = TRUE;
    else if (pBootpParams->nbddServers != NULL)
        result = TRUE;
    else if (pBootpParams->nbNodeType != NULL)
        result = TRUE;
    else if (pBootpParams->nbScope != NULL)
        result = TRUE;
    else if (pBootpParams->xFontServers != NULL)
        result = TRUE;
    else if (pBootpParams->xDisplayManagers != NULL)
        result = TRUE;
    else if (pBootpParams->nispDomain != NULL)
        result = TRUE;
    else if (pBootpParams->nispServers != NULL)
        result = TRUE;
    else if (pBootpParams->ipAgents != NULL)
        result = TRUE;
    else if (pBootpParams->smtpServers != NULL)
        result = TRUE;
    else if (pBootpParams->pop3Servers != NULL)
        result = TRUE;
    else if (pBootpParams->nntpServers != NULL)
        result = TRUE;
    else if (pBootpParams->wwwServers != NULL)
        result = TRUE;
    else if (pBootpParams->fingerServers != NULL)
        result = TRUE;
    else if (pBootpParams->ircServers != NULL)
        result = TRUE;
    else if (pBootpParams->stServers != NULL)
        result = TRUE;
    else if (pBootpParams->stdaServers != NULL)
        result = TRUE;

    return (result);
    }

/******************************************************************************
*
* bootpParamsFill - copy requested BOOTP options
*
* This routine fills in the non-NULL fields in the given parameter descriptor
* with the corresponding entries from the received BOOTP message.  It is only
* called internally after bootpMsgSend() completes successfully.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* NOMANUAL
*/

void bootpParamsFill
    (
    struct bootpParams * 	pBootpParams 	/* parameters descriptor     */
    )
    {
    int 	loop;
    int 	limit;
    u_char * 	cp;
    int length;
    int number;

    /* Copy entries from message body if requested by user. */

        /* Fill in assigned IP address. */

    if (pBootpParams->clientAddr != NULL)
        *pBootpParams->clientAddr = pBootpReply->bp_yiaddr;

        /* Fill in host IP address. */

    if (pBootpParams->bootHostAddr != NULL)
        *pBootpParams->bootHostAddr = pBootpReply->bp_siaddr;
 
        /* Fill in boot file. */

    if (pBootpParams->bootfile != NULL)
        {
        if (pBootpReply->bp_file [0] == EOS)
            {
            printf ("\nno boot file specified.  Check file permissions");
            printf (" (must be world readable).\n");
            pBootpParams->bootfile[0] = EOS;
            }
        else
            (void)strcpy (pBootpParams->bootfile,
                          (char *)pBootpReply->bp_file);
        }

        /* Fill in server name. */

    if (pBootpParams->serverName != NULL)
        if (pBootpReply->bp_sname[0] == EOS)
            pBootpParams->serverName[0] = EOS;
        else
            strcpy(pBootpParams->serverName, pBootpReply->bp_sname);

    /* Fill in optional entries requested by user, if present in reply. */

        /* Retrieve subnet mask. */

    if (pBootpParams->netmask != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_SUBNET_MASK, &length);
        if (cp != NULL)
            bcopy ( (char *) cp, (char *)pBootpParams->netmask, length);
        else
            bzero ( (char *)pBootpParams->netmask, sizeof (struct in_addr));
        }

        /* Retrieve time offset. */

    if (pBootpParams->timeOffset != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_TIME_OFFSET, &length);
        if (cp != NULL)
            *pBootpParams->timeOffset = ntohs (*(unsigned short *)cp);
        else
            *pBootpParams->timeOffset = 0;
        }

        /* Retrieve IP addresses of IP routers, up to number requested. */

    if (pBootpParams->routers != NULL && 
        pBootpParams->routers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_GATEWAY, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->routers->num < number) ?
                     pBootpParams->routers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp, 
                       (char *)&pBootpParams->routers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->routers->num = limit;
        }

        /* Retrieve IP addresses of time servers, up to number requested. */

    if (pBootpParams->timeServers != NULL &&
        pBootpParams->timeServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_TIME_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->timeServers->num < number) ?
                     pBootpParams->timeServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->timeServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->timeServers->num = limit;
        }

        /* Retrieve IP addresses of name servers, up to number requested. */

    if (pBootpParams->nameServers != NULL &&
        pBootpParams->nameServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NAME_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->nameServers->num < number) ?
                     pBootpParams->nameServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->nameServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->nameServers->num = limit;
        }

        /* Retrieve IP addresses of DNS servers, up to number requested. */

    if (pBootpParams->dnsServers != NULL && 
        pBootpParams->dnsServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_DNS_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->dnsServers->num < number) ?
                     pBootpParams->dnsServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->dnsServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->dnsServers->num = limit;
        }

        /* Retrieve IP addresses of log servers, up to number requested. */

    if (pBootpParams->logServers != NULL && 
        pBootpParams->logServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_LOG_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->logServers->num < number) ?
                     pBootpParams->logServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->logServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->logServers->num = limit;
        }

        /* Retrieve IP addresses of cookie servers, up to number requested. */

    if (pBootpParams->cookieServers != NULL && 
        pBootpParams->cookieServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_COOKIE_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->cookieServers->num < number) ?
                     pBootpParams->cookieServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->cookieServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->cookieServers->num = limit;
        }

        /* Retrieve IP addresses of LPR servers, up to number requested. */

    if (pBootpParams->lprServers != NULL && 
        pBootpParams->lprServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_LPR_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->lprServers->num < number) ?
                     pBootpParams->lprServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->lprServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->lprServers->num = limit;
        }

        /* Retrieve IP addresses of Impress servers, up to number requested. */

    if (pBootpParams->impressServers != NULL && 
        pBootpParams->impressServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_IMPRESS_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->impressServers->num < number) ?
                     pBootpParams->impressServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->impressServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->impressServers->num = limit;
        }

        /* Retrieve IP addresses of RLP servers, up to number requested. */

    if (pBootpParams->rlpServers != NULL && 
        pBootpParams->rlpServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_RLP_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->rlpServers->num < number) ?
                     pBootpParams->rlpServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->rlpServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->rlpServers->num = limit;
        }

        /* Retrieve hostname of client. */

    if (pBootpParams->clientName != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_HOSTNAME, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->clientName, length);
            pBootpParams->clientName [length] = EOS;
            }
        else
            pBootpParams->clientName[0] = EOS;
        }

        /* Retrieve size of boot file. */

    if (pBootpParams->filesize != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_BOOTSIZE, &length);
        if (cp != NULL)
            *pBootpParams->filesize = ntohs (*(unsigned short *)cp);
        else
            *pBootpParams->filesize = 0;
        }

        /* Retrieve name of dump file. */

    if (pBootpParams->dumpfile != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MERIT_DUMP, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->dumpfile, length);
            pBootpParams->dumpfile [length] = EOS;
            }
        else
            pBootpParams->dumpfile[0] = EOS;
        }

        /* Retrieve name of DNS domain. */

    if (pBootpParams->domainName != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_DNS_DOMAIN, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->domainName, length);
            pBootpParams->domainName [length] = EOS;
            }
        else
            pBootpParams->domainName[0] = EOS;
        }

        /* Retrieve IP address of swap server. */

    if (pBootpParams->swapServer != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_SWAP_SERVER, &length);
        if (cp != NULL)
            bcopy ( (char *)cp, (char *)pBootpParams->swapServer, length);
        else
            bzero ( (char *)pBootpParams->swapServer, sizeof (struct in_addr));
        }

        /* Retrieve pathname of root disk. */

    if (pBootpParams->rootPath != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ROOT_PATH, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->rootPath, length);
            pBootpParams->rootPath [length] = EOS;
            }
        else
            pBootpParams->rootPath[0] = EOS;
        }

        /* Retrieve pathname of extended options file. */

    if (pBootpParams->extoptPath != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_EXTENSIONS_PATH, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->extoptPath, length);
            pBootpParams->extoptPath [length] = EOS;
            }
        else
            pBootpParams->extoptPath[0] = EOS;
        }

        /* Retrieve IP forwarding option. */

    if (pBootpParams->ipForward != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_IP_FORWARD, &length);
        if (cp != NULL)
            *pBootpParams->ipForward = *cp;
        else
            *pBootpParams->ipForward = 0;
        }

        /* Retrieve non-local source routing option. */

    if (pBootpParams->nonlocalSourceRoute != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NONLOCAL_SRCROUTE, 
                           &length);
        if (cp != NULL)
            *pBootpParams->nonlocalSourceRoute = *cp;
        else
            *pBootpParams->nonlocalSourceRoute = 0;
        }

        /* Retrieve IP addresses and masks for policy filter option. */

    if (pBootpParams->policyFilter != NULL && 
        pBootpParams->policyFilter->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_POLICY_FILTER, &length);
        if (cp != NULL)
            {
            /* Find number of pairs to retrieve. */

            number = length / (2 * sizeof (struct in_addr));
            limit = (pBootpParams->policyFilter->num < number) ?
                     pBootpParams->policyFilter->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->policyFilter->addrlist[2 * loop],
                       2 * sizeof (struct in_addr));
                cp += 2 * sizeof (struct in_addr);
                }
            }
        pBootpParams->policyFilter->num = limit;
        }

        /* Retrieve size of maximum IP datagram. */

    if (pBootpParams->maxDgramSize != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MAX_DGRAM_SIZE, &length);
        if (cp != NULL)
            *pBootpParams->maxDgramSize = ntohs (*(unsigned short *)cp);
        else
            *pBootpParams->maxDgramSize = 0;
        }

        /* Retrieve default IP time-to-live value. */

    if (pBootpParams->ipTTL != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_DEFAULT_IP_TTL, &length);
        if (cp != NULL)
            *pBootpParams->ipTTL = *cp;
        else
            *pBootpParams->ipTTL = 0;
        }

        /* Retrieve value for path MTU aging timeout. */

    if (pBootpParams->mtuTimeout != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MTU_AGING_TIMEOUT, 
                           &length);
        if (cp != NULL)
            *pBootpParams->mtuTimeout = ntohs (*(unsigned long *)cp);
        else
            *pBootpParams->mtuTimeout = 0;
        }

        /* Retrieve table of MTU sizes. */

    if (pBootpParams->mtuTable != NULL && 
        pBootpParams->mtuTable->shortlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MTU_PLATEAU_TABLE, 
                           &length);
        if (cp != NULL)
            {
            number = length / sizeof (unsigned short);
            limit = (pBootpParams->mtuTable->num < number) ?
                     pBootpParams->mtuTable->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                pBootpParams->mtuTable->shortlist [loop] = 
                    ntohs (*(unsigned short *)cp);
                cp += sizeof (unsigned short);
                }
            }
        pBootpParams->mtuTable->num = limit;
        }

        /* Retrieve interface MTU. */

    if (pBootpParams->intfaceMTU != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_IF_MTU, &length);
        if (cp != NULL)
            *pBootpParams->intfaceMTU = ntohs (*(unsigned short *)cp);
        else
            *pBootpParams->intfaceMTU = 0;
        }

        /* Retrieve all subnets local option. */

    if (pBootpParams->allSubnetsLocal != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ALL_SUBNET_LOCAL, 
                           &length);
        if (cp != NULL)
            *pBootpParams->allSubnetsLocal = *cp;
        else
            *pBootpParams->allSubnetsLocal = 0;
        }

        /* Retrieve broadcast IP address. */

    if (pBootpParams->broadcastAddr != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_BRDCAST_ADDR, &length);
        if (cp != NULL)
            bcopy ( (char *) cp, (char *)pBootpParams->broadcastAddr, length);
        else
            bzero ( (char *)pBootpParams->broadcastAddr, 
                    sizeof (struct in_addr));
        }

        /* Retrieve mask discovery option. */

    if (pBootpParams->maskDiscover != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MASK_DISCOVER, &length);
        if (cp != NULL)
            *pBootpParams->maskDiscover = *cp;
        else
            *pBootpParams->maskDiscover = 0;
        }

        /* Retrieve mask supplier option. */

    if (pBootpParams->maskSupplier != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MASK_SUPPLIER, &length);
        if (cp != NULL)
            *pBootpParams->maskSupplier = *cp;
        else
            *pBootpParams->maskSupplier = 0;
        }

        /* Retrieve router discovery option. */

    if (pBootpParams->routerDiscover != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ROUTER_DISCOVER, &length);
        if (cp != NULL)
            *pBootpParams->routerDiscover = *cp;
        else
            *pBootpParams->routerDiscover = 0;
        }

        /* Retrieve IP address for router solicitation. */

    if (pBootpParams->routerDiscAddr != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ROUTER_SOLICIT, &length);
        if (cp != NULL)
            bcopy ( (char *) cp, (char *)pBootpParams->routerDiscAddr, length);
        else
            bzero ( (char *)pBootpParams->routerDiscAddr, 
                   sizeof (struct in_addr));
        }

        /* Retrieve static routing table. */

    if (pBootpParams->staticRoutes != NULL && 
        pBootpParams->staticRoutes->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_STATIC_ROUTE, &length);
        if (cp != NULL)
            {
            /* Find number of pairs to retrieve. */

            number = length / (2 * sizeof (struct in_addr));
            limit = (pBootpParams->staticRoutes->num < number) ?
                     pBootpParams->staticRoutes->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->staticRoutes->addrlist[2 * loop],
                       2 * sizeof (struct in_addr));
                cp += 2 * sizeof (struct in_addr);
                }
            }
        pBootpParams->staticRoutes->num = limit;
        }

        /* Retrieve ARP trailer encapsulation option. */

    if (pBootpParams->arpTrailers != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_TRAILER, &length);
        if (cp != NULL)
            *pBootpParams->arpTrailers = *cp;
        else
            *pBootpParams->arpTrailers = 0;
        }

        /* Retrieve value for ARP cache timeout. */

    if (pBootpParams->arpTimeout != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ARP_CACHE_TIMEOUT, 
                           &length);
        if (cp != NULL)
            *pBootpParams->arpTimeout = ntohs (*(unsigned long *)cp);
        else
            *pBootpParams->arpTimeout = 0;
        }

        /* Retrieve Ethernet encapsulation option. */

    if (pBootpParams->etherPacketType != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ETHER_ENCAP, &length);
        if (cp != NULL)
            *pBootpParams->etherPacketType = *cp;
        else
            *pBootpParams->etherPacketType = 0;
        }

        /* Retrieve default TCP time-to-live value. */

    if (pBootpParams->tcpTTL != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_DEFAULT_TCP_TTL, &length);
        if (cp != NULL)
            *pBootpParams->tcpTTL = *cp;
        else
            *pBootpParams->tcpTTL = 0;
        }

        /* Retrieve value for TCP keepalive interval. */

    if (pBootpParams->tcpInterval != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_KEEPALIVE_INTER, &length);
        if (cp != NULL)
            *pBootpParams->tcpInterval = ntohs (*(unsigned long *)cp);
        else
            *pBootpParams->tcpInterval = 0;
        }

        /* Retrieve value for TCP keepalive garbage option. */

    if (pBootpParams->tcpGarbage != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_KEEPALIVE_GARBA, &length);
        if (cp != NULL)
            *pBootpParams->tcpGarbage = *cp;
        else
            *pBootpParams->tcpGarbage = 0;
        }

        /* Retrieve NIS domain name. */

    if (pBootpParams->nisDomain != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NIS_DOMAIN, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->nisDomain, length);
            pBootpParams->nisDomain [length] = EOS;
            }
        else
            pBootpParams->nisDomain[0] = EOS;
        }

        /* Retrieve IP addresses of NIS servers, up to number requested. */

    if (pBootpParams->nisServers != NULL && 
        pBootpParams->nisServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NIS_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->nisServers->num < number) ?
                     pBootpParams->nisServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->nisServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->nisServers->num = limit;
        }

        /* Retrieve IP addresses of NTP servers, up to number requested. */

    if (pBootpParams->ntpServers != NULL && 
        pBootpParams->ntpServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NTP_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->ntpServers->num < number) ?
                     pBootpParams->ntpServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->ntpServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->ntpServers->num = limit;
        }

        /* Retrieve vendor specific information. */

    if (pBootpParams->vendString != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_VENDOR_SPEC, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->vendString, length);
            pBootpParams->vendString [length] = EOS;
            }
        else
            pBootpParams->vendString[0] = EOS;
        }

        /* Retrieve IP addresses of NetBIOS name servers. */

    if (pBootpParams->nbnServers != NULL && 
        pBootpParams->nbnServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NBN_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->nbnServers->num < number) ?
                     pBootpParams->nbnServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->nbnServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->nbnServers->num = limit;
        }

        /* Retrieve IP addresses of NetBIOS datagram distribution servers. */

    if (pBootpParams->nbddServers != NULL && 
        pBootpParams->nbddServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NBDD_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->nbddServers->num < number) ?
                     pBootpParams->nbddServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->nbddServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->nbddServers->num = limit;
        }

        /* Retrieve value for NetBIOS Node Type option. */

    if (pBootpParams->nbNodeType != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NB_NODETYPE, &length);
        if (cp != NULL)
            *pBootpParams->nbNodeType = *cp;
        else
            *pBootpParams->nbNodeType = 0;
        }

        /* Retrieve NetBIOS scope. */

    if (pBootpParams->nbScope != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NB_SCOPE, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->nbScope, length);
            pBootpParams->nbScope [length] = EOS;
            }
        else
            pBootpParams->nbScope[0] = EOS;
        }

        /* Retrieve IP addresses of X Window font servers. */

    if (pBootpParams->xFontServers != NULL && 
        pBootpParams->xFontServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_XFONT_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->xFontServers->num < number) ?
                     pBootpParams->xFontServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->xFontServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->xFontServers->num = limit;
        }

        /* Retrieve IP addresses of X Window Display Manager systems. */

    if (pBootpParams->xDisplayManagers != NULL && 
        pBootpParams->xDisplayManagers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_XDISPLAY_MANAGER, 
                           &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->xDisplayManagers->num < number) ?
                     pBootpParams->xDisplayManagers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->xDisplayManagers->addrlist[loop],
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->xDisplayManagers->num = limit;
        }

        /* Retrieve NIS+ domain name. */

    if (pBootpParams->nispDomain != NULL)
        {
        length = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NISP_DOMAIN, &length);
        if (cp != NULL)
            {
            bcopy ( (char *)cp, pBootpParams->nispDomain, length);
            pBootpParams->nispDomain [length] = EOS;
            }
        else
            pBootpParams->nispDomain[0] = EOS;
        }

        /* Retrieve IP addresses of NIS+ servers. */

    if (pBootpParams->nispServers != NULL && 
        pBootpParams->nispServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NISP_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->nispServers->num < number) ?
                     pBootpParams->nispServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->nispServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->nispServers->num = limit;
        }

        /* Retrieve IP addresses of Mobile IP Home Agents. */

    if (pBootpParams->ipAgents != NULL && 
        pBootpParams->ipAgents->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_MOBILEIP_HA, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->ipAgents->num < number) ?
                     pBootpParams->ipAgents->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->ipAgents->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->ipAgents->num = limit;
        }

        /* Retrieve IP addresses of SMTP servers. */

    if (pBootpParams->smtpServers != NULL && 
        pBootpParams->smtpServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_SMTP_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->smtpServers->num < number) ?
                     pBootpParams->smtpServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->smtpServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->smtpServers->num = limit;
        }

        /* Retrieve IP addresses of POP3 servers. */

    if (pBootpParams->pop3Servers != NULL && 
        pBootpParams->pop3Servers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_POP3_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->pop3Servers->num < number) ?
                     pBootpParams->pop3Servers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->pop3Servers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->pop3Servers->num = limit;
        }

        /* Retrieve IP addresses of NNTP servers. */

    if (pBootpParams->nntpServers != NULL && 
        pBootpParams->nntpServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_NNTP_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->nntpServers->num < number) ?
                     pBootpParams->nntpServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->nntpServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->nntpServers->num = limit;
        }

        /* Retrieve IP addresses of World Wide Web servers. */

    if (pBootpParams->wwwServers != NULL && 
        pBootpParams->wwwServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_WWW_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->wwwServers->num < number) ?
                     pBootpParams->wwwServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->wwwServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->wwwServers->num = limit;
        }

        /* Retrieve IP addresses of finger servers. */

    if (pBootpParams->fingerServers != NULL && 
        pBootpParams->fingerServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_FINGER_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->fingerServers->num < number) ?
                     pBootpParams->fingerServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->fingerServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->fingerServers->num = limit;
        }

        /* Retrieve IP addresses of Internet Relay Chat servers. */

    if (pBootpParams->ircServers != NULL && 
        pBootpParams->ircServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_IRC_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->ircServers->num < number) ?
                     pBootpParams->ircServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->ircServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->ircServers->num = limit;
        }

        /* Retrieve IP addresses of StreetTalk servers. */

    if (pBootpParams->stServers != NULL && 
        pBootpParams->stServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_ST_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->stServers->num < number) ?
                     pBootpParams->stServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->stServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->stServers->num = limit;
        }

        /* Retrieve IP addresses of STDA servers. */

    if (pBootpParams->stdaServers != NULL && 
        pBootpParams->stdaServers->addrlist != NULL)
        {
        length = 0;
        limit = 0;

        cp = bootpTagFind (pBootpReply->bp_vend, TAG_STDA_SERVER, &length);
        if (cp != NULL)
            {
            number = length / sizeof (struct in_addr);
            limit = (pBootpParams->stdaServers->num < number) ?
                     pBootpParams->stdaServers->num : number;
            for (loop = 0; loop < limit; loop++)
                {
                bcopy ( (char *)cp,
                       (char *)&pBootpParams->stdaServers->addrlist[loop], 
                       sizeof (struct in_addr));
                cp += sizeof (struct in_addr);
                }
            }
        pBootpParams->stdaServers->num = limit;
        }
    return;
    }

/******************************************************************************
*
* bootpMsgSend - send a BOOTP request message
*
* This routine sends the BOOTP message indicated by  using the 
* network interface specified by .  The  argument specifies 
* the destination IP address.  In most cases, the broadcast address
* (255.255.255.255) is used.  However, this parameter also accepts the IP
* address of a particular BOOTP server.  That server must reside on the
* same subnet as the specified network interface.
*
* A non-zero value for  specifies an alternate BOOTP server port.
* Otherwise, the default port (67) is used.
*
* This routine always sets the values of the `bp_op', `bp_xid', and `bp_secs' 
* members in the BOOTP message structure, but it allows the caller to assign 
* values to any of the other members.  However, if the `bp_hlen' member is 0, 
* the routine uses the Ethernet address of the specified network interface 
* for the `bp_chaddr' member and sets `bp_type' to 1 and `bp_hlen' to 6 as 
* required for that address.
*
* The bootpMsgSend() routine will retransmit the BOOTP message if it gets no
* reply.  The retransmission time increases exponentially but is bounded
* by the number of ticks specified in the  parameter.  If no reply is
* received within this period, an error is returned.  A value of zero specifies
* an infinite timeout value.
*
* NOTE: If `bp_ciaddr' is specified, the BOOTP server may assume that the
* client will respond to an ARP request.
*
* RETURNS: OK, or ERROR.
*
* ERRNO
*  S_bootpLib_INVALID_ARGUMENT
*  S_bootpLib_NO_BROADCASTS
*  S_bootpLib_TIME_OUT
*/

STATUS bootpMsgSend
    (
    char *		ifName,		/* network interface name 	*/
    struct in_addr *	pIpDest,	/* destination IP address	*/
    int			port,		/* port number 			*/
    BOOTP_MSG *		pBootpMsg,	/* pointer to BOOTP message 	*/
    u_int		timeOut		/* timeout in ticks 		*/
    )
    {
    FAST int		tickCount;	/* tick counter			*/
    FAST int		tickStart;	/* start of transmissions	*/
    FAST int		retransmitSecs;	/* retransmission time		*/
    int			backOffTicks;	/* semi random backoff time	*/
    struct in_addr	ipSrc;		/* source 			*/
    struct ifnet *	pIf;		/* pointer to interface 	*/
    BOOL		firstXmit = TRUE;/* first transmission  	*/

    END_OBJ * 	pEnd;

    if ((pBootpMsg == NULL) || ((pIf = ifunit (ifName)) == NULL))
    						/* validate args */
        {
        errno = S_bootpLib_INVALID_ARGUMENT;
        return (ERROR);
        }

    pEnd = endFindByName (pIf->if_name, pIf->if_unit);

    /* if destination is a broadcast, then be sure the interface allows it */

    if ( (in_broadcast (*pIpDest, pIf)) && 
        ( (pIf->if_flags & IFF_BROADCAST) == 0))
        {
        errno = S_bootpLib_NO_BROADCASTS;
        return (ERROR);
        }

    pIf->if_flags |= (IFF_UP | IFF_RUNNING);       

    /* initialize values */

    bootpReplyReceived	= FALSE;
    tickStart		= tickGet ();
    retransmitSecs 	= bootpReXmitSecs;

    /* if source is a broadcast then set address to zero */

    ipSrc.s_addr  = in_broadcast (pBootpMsg->bp_ciaddr, pIf) ?
                    htons ((u_short) 0) : pBootpMsg->bp_ciaddr.s_addr;
    
    /*
     *  generate a (semi) random backoff time between 1 & 5 seconds, then
     *  convert it to ticks.
     */

    backOffTicks = ( ( ( (struct arpcom *)pIf)->ac_enaddr [5]  +
    		    ( (struct arpcom *)pIf)->ac_enaddr [4]  +
    		    ( (struct arpcom *)pIf)->ac_enaddr [3]) % 5) *
                    sysClkRateGet ();

    bzero ( (char *)&bootpMsg, sizeof (bootpMsg));

    /* fill in BOOTP message */

    bootpMsg.bp 	= *pBootpMsg;
    bootpMsg.bp.bp_op 	= BOOTREQUEST; 		/* operation       */
    bootpMsg.bp.bp_xid 	= tickGet ();		/* transmission id */

    /* If hardware len is 0, then use Ethernet address as hardware address. */

    if (bootpMsg.bp.bp_hlen == 0)
        {
    	bootpMsg.bp.bp_htype = ETHER_ADDR_TYPE;
    	bootpMsg.bp.bp_hlen  = ETHER_ADDR_LEN;
   	bcopy ( (char *) ( (struct arpcom *)pIf)->ac_enaddr,
               (char *)bootpMsg.bp.bp_chaddr, ETHER_ADDR_LEN);
        }

    /* fill in the UDP header */

    bootpMsg.uh.uh_sport = htons ((u_short) IPPORT_BOOTPC);
    bootpMsg.uh.uh_dport = (port == 0) ? htons ((u_short) IPPORT_BOOTPS) :
                                         htons ((u_short) port);
    bootpMsg.uh.uh_ulen	 = htons (sizeof (bootpMsg.uh) + sizeof (bootpMsg.bp));
    bootpMsg.uh.uh_sum	 = 0;

    ipHeaderCreate (IPPROTO_UDP, &ipSrc, pIpDest, &bootpMsg.ih, 
                    sizeof (bootpMsg));

    /* get ready for incoming packets */

    if (pEnd)
        {
        if (etherInputHookAdd (bootpInputHook, pIf->if_name, pIf->if_unit)
            == ERROR)
            return (ERROR);  
        }
    else
        {
        if (etherInputHookAdd (bootpInputHook, NULL, 0) == ERROR)
            return (ERROR);  
        }

    FOREVER
        {
        if (etherSend (pIf, &bootpMsg.ih, sizeof (bootpMsg)) == ERROR)
            {
            if (pEnd)
                etherInputHookDelete (bootpInputHook, pIf->if_name, 
                                      pIf->if_unit);
            else
                etherInputHookDelete (bootpInputHook);
            return (ERROR);
            }

        /* calculate rexmit time */

        tickCount = retransmitSecs * sysClkRateGet ();

        if (firstXmit)    /* add backoff */
            {
            tickCount += backOffTicks;
            firstXmit = FALSE;
            }

        /* wait for a reply */

        do
            {
            if (bootpReplyReceived)    /* got reply !! */
                {
                if (pEnd)
                    etherInputHookDelete (bootpInputHook, pIf->if_name,
                                          pIf->if_unit);
                else
                    etherInputHookDelete (bootpInputHook);
                bcopy ( (char *)pBootpReply, (char *)pBootpMsg,
                       sizeof (BOOTP_MSG));
                return (OK);
                }

            taskDelay (1);

            if ( (timeOut != 0) && (--timeOut == 0))
                {            /* user timer expired - bail */
                errno = S_bootpLib_TIME_OUT;
                if (pEnd)
                    etherInputHookDelete (bootpInputHook, pIf->if_name,
                                          pIf->if_unit);
                else
                    etherInputHookDelete (bootpInputHook);
                return (ERROR);
                }
            }
        while (tickCount-- > 0);

        retransmitSecs = min (retransmitSecs << 1, MAX_BOOTP_DELAY);
        bootpMsg.bp.bp_secs = htons ( (u_short) ( (tickGet () - tickStart) /
                                                     sysClkRateGet ()));

        }
    }

/*******************************************************************************
*
* bootpInputHook - input hook to filter out the BOOTP reply message
*
* This routine filters out the BOOTP reply message from incoming Ethernet
* traffic.  This function is called by the network interface driver when
* a new input frame comes in from the network.  It is "hooked" into the
* driver via `etherHook' routines.
*
* RETURNS:
* TRUE indicating the Ethernet frame is handled by this routine and
* no further processing need be done by the network interface driver.
* FALSE indicating the ethernet frame is to be handled by the network
* interface driver.
*
* NOMANUAL
*/

LOCAL BOOL bootpInputHook
    (
    struct ifnet *		pIf, 	/* network interface pointer	*/
    FAST char *			einput, /* input data frame pointer	*/
    FAST int			length	/* input data length 		*/
    )

    {
    FAST struct ether_header *	eh; 	/* pointer to ethernet header	*/
    FAST struct udphdr *	udph;	/* pointer to udp header 	*/
    int				bufLen; /* buffer length 		*/

    if (bootpReplyReceived)    		/* ignore it - we got a reply */
        return (FALSE);

    eh = (struct ether_header *) einput;

    if ( (length <= SIZEOF_ETHERHEADER) ||
        (ntohs (eh->ether_type) != ETHERTYPE_IP))
        return (FALSE);

    /*
     *  copy the input packet in the most conservative way
     *  to accommodate board specific memory access requirements.
     */

    bufLen = length - SIZEOF_ETHERHEADER;
    bcopyBytes ( (char *) ( (u_char *)einput + SIZEOF_ETHERHEADER),
                inputBuffer, bufLen);


    udph = (struct udphdr *)ipHeaderVerify ( (struct ip *)inputBuffer, bufLen, 
                                            IPPROTO_UDP);
    if (udph == NULL)
        return (FALSE);

    if (ntohs (udph->uh_dport) != IPPORT_BOOTPC)
        return (FALSE);

    pBootpReply = (BOOTP_MSG *) ( (u_char *)udph + sizeof (struct udphdr));

    /* must be a boot reply, addressed to me, with the right id.  */

    if ( (pBootpReply->bp_op != BOOTREPLY) ||
        (bcmp ( (char *)pBootpReply->bp_chaddr,
                  (char *) bootpMsg.bp.bp_chaddr, ETHER_ADDR_LEN) != 0) ||
        (pBootpReply->bp_xid != bootpMsg.bp.bp_xid))
        return (FALSE);

    bootpReplyReceived = TRUE;
    return (TRUE);
    }

/*******************************************************************************
*
* bootpTagFind - find data for a BOOTP options tag
*
* This routine finds the data associated with tag  in the 
* vendor-specific member of a BOOTP message.  The  parameter must 
* be a valid 1533 vendor-tag value.  Only  values that have data 
* associated with them are considered valid (for example, TAG_END 
* and TAG_PAD are not valid  values because they have no data).
* The  parameter is a pointer to the beginning of the 
* vendor-specific member in the BOOTP message.  If  is found 
* in , the length of the associated data gets placed in  
* and a pointer to the data is returned.
*
* INTERNAL
* The vendor information field is divided into extendable tagged subfields.
* Tags that have no data, consist of a single tag octet and are one octet
* in length.  All other tags have a one tag octet, a length octet and length
* octets of data.  For a more complete description of the tags and how they
* are parsed, please refer to RFC 1048 or its successors. All tags defined
* through RFC 1533 are supported.
*
* RETURNS: A pointer to tag data if successful, otherwise NULL.
*
* ERRNO
*  S_bootpLib_INVALID_ARGUMENT
*  S_bootpLib_INVALID_COOKIE
*  S_bootpLib_INVALID_TAG
*  S_bootpLib_PARSE_ERROR
*
* NOMANUAL
*/

u_char * bootpTagFind
    (
    u_char *		pVend,		/* vendor specific information  */
    int			tag,		/* tag to be located 		*/
    int *		pSize		/* return size of data		*/
    )

    {
    u_char *		cp;		/* character pointer 		*/
    u_char *		pData;		/* pointer to data		*/
    int 		sizeData;	/* size if data			*/

    if ( (pSize == NULL) || (pVend == NULL)) 	/* validate arguments */
        {
        errno = S_bootpLib_INVALID_ARGUMENT;
        return  (NULL);
        }

    if ( (tag <= TAG_PAD) || (tag >= TAG_END))	/* validate tag */
        {
        errno = S_bootpLib_INVALID_TAG;
        return (NULL);
        }

     /* validate RFC 1048 cookie */

    if (bcmp ( (char *)magicCookie1048, (char *)pVend,
               sizeof (magicCookie1048)) != 0)
        {
        errno = S_bootpLib_INVALID_COOKIE;
        return (NULL);
        }

    pData = pVend + sizeof (magicCookie1048);	/* move past cookie */
    sizeData = SIZE_VEND - sizeof (magicCookie1048);

    /* loop to find tag */

    for (cp = pData; (*cp != (u_char)TAG_END) && (cp < pData + sizeData); cp++)
        {
        if (*cp == (u_char) TAG_PAD)		/* do nothing -  pad */
            continue;

        if ( (cp + 1) >= (pData + sizeData))	/* no for length */
            {
            errno = S_bootpLib_PARSE_ERROR;
            break;
            }

        *pSize = *(cp + 1);

        /* no room for data */

        if ( (cp + 2 + *pSize) > (pData + sizeData))
            {
            errno = S_bootpLib_PARSE_ERROR;
            break;
            }

        if (*cp == (u_char)tag)		/* found desired tag */
            return (cp + 2);

        cp += *pSize + 1;			/* move past the data */
        }

    *pSize = 0;
    return (NULL);
    }