/*
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  getvend.c
 * Purpose: Get information out of BOOTP/DHCP answer
 * Entries: get_vend
 *
 **************************************************************************
 *
 * Copyright (C) 1998-2003 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: getvend.c,v 1.4 2003/01/25 23:29:41 gkminix Exp $
 */


#include <general.h>
#include <kernel/net.h>
#include <kernel/arpa.h>
#include <kernel/romlib.h>
#include "bootp.h"



/*
 **************************************************************************
 * 
 * Variables local to this module
 */
static int lastbuf = -1;		/* buffer with last requested ID */
static int lastid;			/* last request ID */
static int overload;			/* overload flags */
static unsigned char *pxeptr;		/* pointer to PXE vendor record */
static unsigned char *lastptr;		/* pointer to last requested ID */



/*
 **************************************************************************
 * 
 * Find a tag in the BOOTP/DHCP record.
 *
 */
static unsigned char *find_tag(id, startp, endp)
int id;
unsigned char *startp;
unsigned char *endp;
{
  register unsigned char *cp = startp;

  while (*cp != VEND_END && cp < endp) {
	if (*cp != VEND_NOP) {
		if (*cp == id)
			break;
		cp += *(cp + 1) + 1;
	}
	cp++;
  }
  return(*cp == id ? cp + 1 : NULL);
}



/*
 **************************************************************************
 * 
 * Find a tag either in the vendor information area or the boot filename
 * or server name fields.
 */
static unsigned char *search_tag(id, lastp)
int id;
unsigned char *lastp;
{
  register unsigned char *cp;
  unsigned char *startp, *endp;
  unsigned char *retp = NULL;

  /* Setup start and end pointers */
  startp = bootp_bufs[cur_bootp_buf]->bp_vend + VM_SIZE;
  endp = (unsigned char *)(bootp_bufs[cur_bootp_buf]) +
						bootp_sizes[cur_bootp_buf];

  /* Search for the tag in the vendor extension area */
  if (lastp == NULL)
	retp = find_tag(id, startp, endp);
  else if (lastp >= startp && lastp < endp)
	retp = find_tag(id, lastp, endp);

  /* Only search overload areas for valid tags, not NOP, END etc. */
  if (id != VEND_NOP && id != VEND_END && id != VEND_OVERLOAD) {
	/* Search for the tag in the filename area if buffer is overloaded */
	if (retp == NULL && (overload & VEND_OVR_FILE)) {
		cp = bootp_bufs[cur_bootp_buf]->bp_file;
		if (lastp == NULL)
			retp = find_tag(id, cp, cp + BOOTP_FILE_SIZE);
		else if (lastp >= cp && lastp < (cp + BOOTP_FILE_SIZE))
			retp = find_tag(id, lastp, cp + BOOTP_FILE_SIZE);
	}

	/* Search for the tag in the server name area if buffer is overloaded */
	if (retp == NULL && (overload & VEND_OVR_SNAME)) {
		cp = bootp_bufs[cur_bootp_buf]->bp_sname;
		if (lastp == NULL)
			retp = find_tag(id, cp, cp + BOOTP_SNAME_SIZE);
		else if (lastp >= cp && lastp < (cp + BOOTP_SNAME_SIZE))
			retp = find_tag(id, lastp, cp + BOOTP_SNAME_SIZE);
	}
  }
  return(retp);
}



/*
 **************************************************************************
 * 
 * Return the overload flag of a BOOTP/DHCP buffer
 */
static void check_overload()
{
  register unsigned char *cp;

  overload = 0;
  cp = search_tag(VEND_OVERLOAD, NULL);
  if (cp != NULL) {
	cp++;
	overload = *cp;
  }
}



/*
 **************************************************************************
 * 
 * Check if we have PXE vendor extensions
 */
static void check_pxe()
{
  register unsigned char *cp;

  pxeptr = NULL;
  cp = search_tag(VEND_CLASS, NULL);
  if (cp != NULL && *cp == VEND_PXE_STRLEN) {
	cp++;
	if (!memcmp(cp, VEND_PXE_STRING, VEND_PXE_STRLEN))
		pxeptr = search_tag(VEND_VENDOR, NULL);
  }
}



/*
 **************************************************************************
 * 
 * Return information from the vendor area of a BOOTP/DHCP record. Note
 * that this will work only with RFC1048-compliant messages.
 * It will return a pointer to the length byte of the tag.
 *
 */
unsigned char *get_vend(id)
int id;
{
  register unsigned char *cp;
  unsigned char *endp;
  unsigned char *retp = NULL;

  cp = (unsigned char *)(bootp_bufs[cur_bootp_buf]);
  if (cp != NULL) {
	cp = bootp_bufs[cur_bootp_buf]->bp_vend;
	if (*cp) {
		/* Check if we are accessing the same buffer as before */
		if (lastbuf != cur_bootp_buf) {
			lastbuf = cur_bootp_buf;
			lastptr = NULL;
			check_overload();
			check_pxe();
		} else {
			/* Determine start address for new search */
			if (lastid != id)
				lastptr = NULL;
			else if (lastptr != NULL)
				lastptr += *lastptr + 1;
		}

		/* Now actually search for the tag, NOP just recycles */
		if (id == VEND_NOP)
			retp = NULL;
		else if (id < VEND_PXE_BASE)
			retp = search_tag(id, lastptr);
		else if (pxeptr != NULL) {
			cp = pxeptr;
			endp = cp + *cp + 1;
			cp++;
			if (lastptr == NULL)
				retp = find_tag(id & VEND_PXE_MASK, cp, endp);
			else if (lastptr >= cp && lastptr < endp)
				retp = find_tag(id & VEND_PXE_MASK,
								lastptr, endp);
		}
	}
  }
  lastid = id;
  lastptr = retp;
  return(retp);
}



syntax highlighted by Code2HTML, v. 0.9.1