/* vim: set filetype=c:
 *
 **************************************************************************
 *
 * Boot-ROM-Code to load an operating system across a TCP/IP network.
 *
 * Module:  tftp.c86
 * Purpose: Get a file with TFTP protocol
 * Entries: tftp_open, tftp_get, tftp_close, tftp_read_file
 *
 **************************************************************************
 *
 * Copyright (C) 1995-2007 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: tftp.c86,v 1.11 2007/01/06 18:30:46 gkminix Exp $
 */


#include <common.h86>
#include <romconfig.h86>
#include <i386/string.h86>
#include <i386/conio.h86>
#include <i386/misc.h86>
#include <i386/ctype.h86>
#include <pxe/general.h86>
#include <pxe/tftp.h86>
#include "headers/net.h86"
#include "headers/arpa.h86"
#include "headers/funcs.h86"
#include "headers/pxe.h86"
#include "tftp.h86"



/*
 **************************************************************************
 * 
 * Definitions for the open status flag:
 */
#define FLAG_UDP_CLOSED		0	/* UDP connection closed	*/
#define FLAG_TFTP_CLOSED	1	/* TFTP connection closed	*/
#define FLAG_TFTP_OPEN		2	/* TFTP connection opened	*/



/*
 **************************************************************************
 * 
 * Global variables:
 */

/* Buffers */
static union {				/* Input buffer			*/
	__u8           buf[INPBUFSIZE];
	struct tftphdr hdr;
} inpbuf;

static union {				/* ACK output buffer		*/
	__u8           buf[ACKBUFSIZE];
	struct tftphdr hdr;
} ackbuf;


/* Global variables for directly accessing this module */
unsigned long   filesize;
unsigned short  srvrtout;
unsigned short  clnttout;
         __u8  *tftpbuf = &(inpbuf.hdr.th_data[0]);
         size_t tftpbufsize;
         size_t tftpbuflen;
         bool_t tftpoflag;


/* Variables local to this module */
static unsigned short firstblock;	/* First received data block	*/
static unsigned short curblock;		/* Last received data block	*/
static unsigned short tftp_sport = 0;	/* TFTP server port number	*/
static unsigned short tftp_cport;	/* TFTP client port number	*/
static unsigned short tftp_tout = 0;	/* TFTP read timeout		*/
static short errval;			/* error status value		*/
static short openstate;			/* Open status			*/
static t_ipaddr mcastaddr;		/* Multicast IP address		*/
static bool_t mcastflag;		/* Flag if multicast transfer	*/
static bool_t mcastmaster;		/* Flag if multicast master	*/
static unsigned short mcastport;	/* Multicast server port	*/

/* Definition of option strings */
static __u8 blksizestr[] = BLKSIZE_STR;
static __u8 tsizestr[] = TSIZE_STR;
static __u8 toutstr[] = TOUT_STR;
static __u8 mcaststr[] = MCAST_STR;



/*
 **************************************************************************
 * 
 * Skip everything in a string until reaching a certain character
 */
static __u8 *skip(__u8 *startp, __u8 endchar)
{
  /* The following seems strange but gives smaller results with GCC */
  while (TRUE) {
	if (!*startp || *startp == endchar)
		break;
	startp++;
  }
  return(startp);
}



/*
 **************************************************************************
 * 
 * Convert a string into a long integer value
 */
static __u8 *getlong(__u8 *startp, unsigned long *retval, __u8 endchar)
{
  register __u8 *cp asm("esi") = startp;
  unsigned long val = 0;

  while (TRUE) {
	if (!*cp || *cp == endchar)
		break;
	if (*cp >= '0' && *cp <= '9') {
		val = val * 10;
		val += (*cp - '0');
	} else {
		val = 0;
		cp = skip(cp, endchar);
		break;
	}
	cp++;
  }
  *retval = val;
  return(cp);
}



/*
 **************************************************************************
 * 
 * Convert a string into an IP number
 */
static __u8 *getip(__u8 *startp, __u8 *retval, __u8 endchar)
{
  register __u8 *cp = startp;
  unsigned long l;
  short i;

  for (i = 0; i < 4; i++) {
	cp = getlong(cp, &l, (i < 3 ? '.' : endchar));
	if (l == 0 || l > 255) {
		*((t_ipaddr *)retval) = htonl(IP_ANY);
		cp = skip(cp, endchar);
		break;
	}
	retval[i] = (__u8)(l & 0x00ff);
  }
  return(cp);
}



/*
 **************************************************************************
 * 
 * Convert an unsigned integer value into a string
 */
static __u8 *putnum(__u8 *startp, unsigned short val)
{
  register __u8 *cp1 asm("esi");
  register __u8 *cp2 asm("edi") = startp;

  /* Convert number into string */
  cp1 = (__u8 *)__numcvt((unsigned long)val, 10);

  /* Copy string into destination buffer */
  while (*cp1)
	*cp2++ = *cp1++;
  return(cp2);
}



/*
 **************************************************************************
 * 
 * Handle an options string returned by the server. Note that this routine
 * can be called anytime during a transfer, when we are running in multicast
 * mode. However, the server is not allowed to change any option except the
 * multicast master flag once a transfer has been started.
 */
static bool_t handle_options(__u8 *startp, __u8 *endp)
{
#define isopen	(openstate == FLAG_TFTP_OPEN)

  register __u8 *cp asm("esi") = startp;
  unsigned long l;
  t_ipaddr maddr;
  bool_t mcastoption = FALSE;

  /* Analyze options string */
  while (cp < endp) {
	if (!memcmp(cp, blksizestr, sizeof(BLKSIZE_STR))) {
		/* Handle blocksize option */
		cp += sizeof(BLKSIZE_STR);
		cp = getlong(cp, &l, '\0');
		if (l < DEFSEGSIZE || l > MAXSEGSIZE ||
		    (isopen && (size_t)l != tftpbufsize))
			return(FALSE);
		tftpbufsize = (size_t)l;
	} else if (!memcmp(cp, tsizestr, sizeof(TSIZE_STR))) {
		/* Handle filesize option */
		cp += sizeof(TSIZE_STR);
		cp = getlong(cp, &l, '\0');
		if (l == 0 ||
		    (isopen && l != filesize))
			return(FALSE);
		filesize = l;
	} else if (!memcmp(cp, toutstr, sizeof(TOUT_STR))) {
		/* Handle timeout option */
		cp += sizeof(TOUT_STR);
		cp = getlong(cp, &l, '\0');
		if (l == 0)
			return(FALSE);
	} else if (!memcmp(cp, mcaststr, sizeof(MCAST_STR))) {
		/* Handle multicast option */
		if (!mcastflag)
			return(FALSE);
		cp += sizeof(MCAST_STR);
		if (*cp != ',') {
			cp = getip(cp, (__u8 *)&maddr, ',');
			if (!IN_MULTICAST(ntohl(maddr)) ||
			    (isopen && maddr != mcastaddr))
				return(FALSE);
			mcastaddr = maddr;
		}
		cp++;
		if (*cp != ',') {
			cp = getlong(cp, &l, ',');
			if (l < 1024 || l > 65535 ||
			    (isopen &&
			     htons((short)(l & 0xffff)) != mcastport))
				return(FALSE);
			mcastport = htons((short)(l & 0xffff));
		}
		cp++;
		cp = getlong(cp, &l, '\0');
		if ((l != 0 && l != 1) || (mcastmaster && l == 0)) {
			/* Oops, can't change back from master to non-master */
			return(FALSE);
		}
		mcastmaster = (bool_t)(l & 0x0001);
		mcastoption = TRUE;
	} else
		return(FALSE);
	cp++;
  }

  /* Check if multicast option not set */
  if (mcastflag && !mcastoption) {
	if (isopen)
		return(FALSE);
	mcastflag = FALSE;
  }
  return(TRUE);

#undef isopen
}



/*
 **************************************************************************
 * 
 * Send a tftp request packet
 */
static short send_req(const __u8 *fname, size_t fnamlen)
{
#define OPTSIZE	(sizeof(OCTET_STR) + sizeof(BLKSIZE_STR) + 4 + \
				sizeof(TSIZE_STR) + 2 + sizeof(MCAST_STR) + 2)

  register __u8 *cp asm("esi");

  /* Check that the filename is not too long */
  if (fnamlen > (DEFSEGSIZE - sizeof(inpbuf.hdr.th_op) - OPTSIZE - 2))
	return(PXENV_STATUS_FAILURE);

  /*
   * Setup the output buffer with the request code, the file name,
   * all options and the name of the data format.
   */
  memset(&inpbuf, 0, sizeof(inpbuf));
  inpbuf.hdr.th_op = htons(TFTP_RRQ);
  cp = (__u8 *)&(inpbuf.hdr.th_block);
  for (; *fname && fnamlen > 0; fnamlen--)
	*cp++ = *fname++;
  cp++;
  memcpy(cp, OCTET_STR, sizeof(OCTET_STR));
  cp += sizeof(OCTET_STR);
  if (tftpbufsize != DEFSEGSIZE) {
	memcpy(cp, blksizestr, sizeof(BLKSIZE_STR));
	cp = putnum(cp + sizeof(BLKSIZE_STR), tftpbufsize) + 1;
  }
  memcpy(cp, tsizestr, sizeof(TSIZE_STR));
  cp += sizeof(TSIZE_STR);
  *cp++ = '0';
  cp++;
  if (srvrtout > 0 && srvrtout <= MAX_SRVR_TIMEOUT) {
	memcpy(cp, toutstr, sizeof(TOUT_STR));
	cp = putnum(cp + sizeof(TOUT_STR), srvrtout) + 1;
  }
  if (mcastflag) {
	memcpy(cp, mcaststr, sizeof(MCAST_STR));
	cp += sizeof(MCAST_STR);
  }

  /* Finally send the request */
  return(udp_write(MK_FP(getds(), inpbuf.buf), (size_t)(cp - inpbuf.buf)));
}



/*
 **************************************************************************
 * 
 * Send a tftp acknowledge packet
 */
static void send_reply(__u16 code, unsigned short block)
{
  size_t len;

  memset(&ackbuf, 0, sizeof(ackbuf));
  ackbuf.hdr.th_op = code;
  ackbuf.hdr.th_block = htons(block);
  len = (size_t)(ackbuf.hdr.th_data - (__u8 *)&(ackbuf.hdr));
  if (code == TFTP_ERROR)
	/* Error text is the null string */
	len++;
  (void)udp_write(MK_FP(getds(), ackbuf.buf), len);
}

#define send_ack(block)	send_reply(htons(TFTP_ACK), (block))
#define send_nak(err)	send_reply(htons(TFTP_ERROR), (err))



/*
 **************************************************************************
 * 
 * Receive a TFTP data packet. It returns FALSE if the connection should be
 * closed physically.
 */
static bool_t rcv_packet(void)
{
#define th_data_ofs ((size_t)(inpbuf.hdr.th_data - (__u8 *)&(inpbuf.hdr)))

  register short status;
  register unsigned short tout;
  bool_t isopen = TRUE;

  /* Determine timeout for packet */
  tout = (tftp_tout > 0) ? tftp_tout :
		(clnttout > 0) ? clnttout :
			(srvrtout > 0 && srvrtout <= MAX_SRVR_TIMEOUT) ?
				(srvrtout * 5 / 2) : TFTP_CLNT_TIMEOUT;
  if (tout > MAX_CLNT_TIMEOUT)
	tout = MAX_CLNT_TIMEOUT;

  /* Read packet with timeout */
  tftpbuflen = sizeof(inpbuf.buf);
  errval = udp_read(MK_FP(getds(), inpbuf.buf), &tftpbuflen,
							tout, CHR_ESC, FALSE);
  if (errval == PXENV_STATUS_ARP_TIMEOUT) {
	errval = PXENV_STATUS_TFTP_READ_TIMEOUT;
	goto rcv_ack;
  } else if (errval != PXENV_STATUS_SUCCESS) {
	errval = PXENV_STATUS_TFTP_READ_CANCELED;
	status = ENOSPACE;
	goto rcv_nak;
  }

  /* Check that the packet has a correct length */
  if (tftpbuflen < th_data_ofs) {
	errval = PXENV_STATUS_TFTP_INV_PACKET;
	goto rcv_ret;
  }
  tftpbuflen -= th_data_ofs;
  if (tftpbuflen > tftpbufsize) {
	errval = PXENV_STATUS_TFTP_INV_PACKET;
	goto rcv_ret;
  }

  /*
   * A packet with block number 1 is different from all others, because
   * it can be a normal data packet (if the server does not understand
   * TFTP options), but it can also be a OACK packet. If it is NOT an
   * option acknowledgment packet, we have to turn off the multicast
   * flag.
   * If we are a multicast client, an OACK packet can arrive at any
   * time. However, the server is only allowed to change the multicast
   * master flag (this will be checked in the handle_options() function).
   * If we do receive an OACK packet with a multicast option, we have
   * to acknowledge the last properly received packet (which is zero
   * in case we have't received anything at all yet).
   */
  if (inpbuf.hdr.th_op == htons(TFTP_OACK)) {
	if (openstate == FLAG_TFTP_OPEN && !mcastflag) {
		/*
		 * The server sent another options string while the
		 * connection is already open, and we are not doing
		 * multicasting. This can happen if the server didn't
		 * get our initial ACK, so resend it.
		 */
		tftpbuflen = 0xFFFF;
		goto rcv_ack;
	}
	if (!handle_options((__u8 *)&(inpbuf.hdr.th_block),
				(__u8 *)&(inpbuf.hdr.th_block) + tftpbuflen)) {
		errval = PXENV_STATUS_TFTP_ERROR_OP;
		status = EBADOPT;
		goto rcv_nak;
	}
	tftpbuflen = 0xFFFF;
	goto rcv_ack;
  } else if (curblock == 0 && openstate != FLAG_TFTP_OPEN)
	mcastflag = FALSE;

  /* Check if we got an error packet. This will terminate the connection */
  if (inpbuf.hdr.th_op == htons(TFTP_ERROR)) {
	if (inpbuf.hdr.th_error == htons(ENOTFOUND))
		errval = PXENV_STATUS_TFTP_INVALID_FILE;
	else if (inpbuf.hdr.th_error == htons(EACCESS))
		errval = PXENV_STATUS_TFTP_ACCESS;
	else if (inpbuf.hdr.th_error == htons(EBADOP))
		errval = PXENV_STATUS_TFTP_ERROR_OP;
	else
		errval = PXENV_STATUS_FAILURE;
	isopen = FALSE;
	goto rcv_ret;
  }

  /* Check if we got a valid data packet at all. We only need to ack data */
  if (inpbuf.hdr.th_op != htons(TFTP_DATA)) {
	errval = PXENV_STATUS_TFTP_UNKNOWN_OP;
	goto rcv_ack;
  }

  /* Check if the block number of the data packet is correct */
  if (inpbuf.hdr.th_block != htons(curblock + 1))
	errval = PXENV_STATUS_TFTP_TOO_MANY;
  else
	curblock++;

  /*
   * Ack the current packet. This will also resynchronize the sender if
   * the block was wrong. This should only be done for unicast transfers,
   * or if we are a multicast master.
   */
rcv_ack:
  if (statuscallout != FNULL && statuscallout != (__fptr)(-1)) {
	if (errval == PXENV_STATUS_SUCCESS && callout(curblock) != 0) {
		errval = PXENV_STATUS_TFTP_READ_CANCELED;
		status = ENOSPACE;
		goto rcv_nak;
	}
  }
  if (!mcastflag || mcastmaster)
	send_ack(curblock);
  goto rcv_ret;

  /*
   * Send a NAK for the current packet. This should be done in any case,
   * even if we are a multicast client.
   */
rcv_nak:
  isopen = FALSE;
  tftpbuflen = 0;
  send_nak(status);

rcv_ret:
  return(isopen);

#undef th_data_ofs
}



/*
 **************************************************************************
 * 
 * Open a TFTP connection
 */
short tftp_open(t_ipaddr server, t_ipaddr gateway, __u8 *fname, size_t fnamlen,
								short sport)
{
  register short retry;

  /* Set default transfer block size */
  if (tftpbufsize == 0 || tftpbufsize > MAXSEGSIZE)
	tftpbufsize = MAXSEGSIZE;
  else if (tftpbufsize < DEFSEGSIZE)
	tftpbufsize = DEFSEGSIZE;

  /* Set default port */
  if (tftp_sport == 0) {
	if (sport == 0)
		tftp_sport = htons(TFTP_S_PORT);
	else
		tftp_sport = htons(sport);
  }

  /* Try to open a TFTP connection to the server */
  curblock = 0;
  firstblock = 0;
  filesize = -1L;
  errval = PXENV_STATUS_TFTP_CANNOT_OPEN;
  for (retry = 0; openstate == FLAG_UDP_CLOSED && retry < TFTP_RETRY; retry++) {
	/*
	 * First open a new socket. The local port will be the same as the
	 * transaction ID, which should be a random number and different
	 * between each retry.
	 */
	tftp_cport = htons(TFTP_C_PORT + random(0x003f) + ((retry * 64) & 0x1c0));
	if (udp_open(server, gateway, tftp_cport, tftp_sport) != PXENV_STATUS_SUCCESS)
		break;
	openstate = FLAG_TFTP_CLOSED;

	/*
	 * Send the file request block, and then wait for the first data
	 * block. If there is no response to the query, retry it with
	 * another transaction ID, so that all old packets get discarded
	 * automatically.
	 */
	if ((errval = send_req(fname, fnamlen)) == PXENV_STATUS_SUCCESS) {
		tftpbufsize = DEFSEGSIZE;
		if (rcv_packet() && errval == PXENV_STATUS_SUCCESS) {
			openstate = FLAG_TFTP_OPEN;
			if (mcastflag)
				udp_set_mcast(mcastaddr, mcastport);
			return(PXENV_STATUS_SUCCESS);
		}
	}

	/* If an error occurred, retries are useless */
	openstate = FLAG_UDP_CLOSED;
	(void)udp_close();
	if (errval != PXENV_STATUS_TFTP_READ_TIMEOUT)
		break;
	errval = PXENV_STATUS_TFTP_CANNOT_OPEN;
	tftpbufsize = DEFSEGSIZE;
  }
  tftpbufsize = 0;
  return(errval);
}



/*
 **************************************************************************
 *
 * Read the next data packet from a TFTP connection
 */
short tftp_get(void)
{
  register short retry;

  /*
   * Don't do anything if no TFTP connection is active. However, in
   * multicast mode, the TFTP status gets set to closed after reading
   * the last packet of the file, but we might still need to read some
   * data from the beginning of the file. Therefore, we just recycle the
   * read counter in that case.
   */
  if (mcastflag && openstate == FLAG_TFTP_CLOSED) {
	openstate = FLAG_TFTP_OPEN;
	tftpbuflen = 0xFFFF;
	curblock = 0;
  } else if (openstate != FLAG_TFTP_OPEN)
	return(PXENV_STATUS_TFTP_CANNOT_READ);

  /*
   * If the block number is 0 then we are still dealing with the first
   * data block after opening a connection. If the data size is smaller
   * than the current block size just close the connection again.
   * However, if the last received packet was an option ack, we still
   * have to read the data packet. In that case, tftpbuflen is negative.
   */
  if (curblock == 0 && tftpbuflen != 0xFFFF) {
	curblock++;
	goto get_ok;
  }

  /*
   * Wait for the next data packet. If no data packet is coming in,
   * resend the ACK for the last packet to restart the sender. Maybe
   * he didn't get our first ACK.
   */
  errval = PXENV_STATUS_TFTP_CANNOT_READ;
  for (retry = 0; retry < TFTP_RETRY; retry++) {
	if (!rcv_packet())
		break;
	if (errval == PXENV_STATUS_SUCCESS) {
		if (tftpbuflen == 0xFFFF) {
			/*
			 * We received an OACK packet during a multicast
			 * transfer, so recycle to get the next data
			 * block.
			 */
			retry = 0;
			continue;
		}
		if (firstblock == 0)
			firstblock = curblock;
		goto get_ok;
	}
  }
  openstate = FLAG_TFTP_CLOSED;
  return(errval);

get_ok:
  if (tftpbuflen < tftpbufsize ||
      (mcastflag && (curblock + 1) == firstblock))
	openstate = FLAG_TFTP_CLOSED;
  return(PXENV_STATUS_SUCCESS);
}



/*
 **************************************************************************
 *
 * Close a TFTP connection
 */
short tftp_close(void)
{
  /* We always succeed in closing an already closed connection */
  if (openstate == FLAG_UDP_CLOSED)
	return(PXENV_STATUS_SUCCESS);

  /*
   * If the connection is still open, read the next packet because the
   * file size can be a multiple of the TFTP packet size, in which case
   * the reading process has already stopped reading while the TFTP server
   * is still waiting to close the connection with a null-sized packet. 
   * If the next packet does not indicate a termination of the TFTP connection,
   * send an error packet.
   */
  errval = PXENV_STATUS_SUCCESS;
  if (openstate == FLAG_TFTP_OPEN &&
      !(curblock == 0 && tftpbuflen < tftpbufsize)) {
	if (!rcv_packet() || errval != PXENV_STATUS_SUCCESS)
		errval = PXENV_STATUS_TFTP_CANNOT_READ;
	else if (tftpbuflen >= tftpbufsize)
		send_nak(ENOSPACE);
  }

  /* Close UDP connection */
  if (udp_close() != PXENV_STATUS_SUCCESS && errval == PXENV_STATUS_SUCCESS)
	errval = PXENV_STATUS_FAILURE;
  openstate = FLAG_UDP_CLOSED;
  tftpbufsize = 0;
  return(errval);
}



/*
 **************************************************************************
 *
 * Read a file into memory. This is the only function which can use
 * multicast TFTP transfers.
 */
short tftp_read_file(t_tftp_read_file *params)
{
  register short ret;
  unsigned long loadaddr, topmem, maxaddr;

  /* Set top of buffer memory */
  maxaddr = 0;
  topmem = params->buffer + params->bufsize;

  /* Set timeout */
  tftp_tout = (unsigned short)(params->opentimeout);

  /*
   * Try to read with RFC multicast. If the server doesn't understand
   * the multicast option, it will switch us back to unicast auto-
   * matically by not returning the multicast option. All the multicast
   * parameters like IP address etc. will be set when receiving the
   * options packet.
   */
  mcastflag = TRUE;
  mcastmaster = FALSE;

  /* Open TFTP connection and read all blocks successively */
  if ((ret = tftp_open(params->server, params->gateway, params->filename,
			sizeof(params->filename), 0)) == PXENV_STATUS_SUCCESS) {
	tftp_tout = (unsigned short)(params->reopendelay);
	while (TRUE) {
		if ((ret = tftp_get()) != PXENV_STATUS_SUCCESS)
			break;
		loadaddr = params->buffer + ((curblock - 1) * tftpbufsize);
		if ((loadaddr + tftpbuflen) > topmem) {
			ret = PXENV_STATUS_TFTP_TOO_MANY;
			break;
		}
		if (!lmove(loadaddr, tftpbuf, tftpbuflen)) {
			ret = PXENV_STATUS_MCOPY_PROBLEM;
			break;
		}
		if ((loadaddr + tftpbuflen) > maxaddr)
			maxaddr = loadaddr + tftpbuflen;
		if (tftpbuflen < tftpbufsize) {
			ret = PXENV_STATUS_SUCCESS;
			break;
		}
	}
	params->bufsize = maxaddr - params->buffer;
	tftp_close();
  }
  mcastflag = FALSE;
  tftp_tout = 0;
  return(ret);
}



/*
 **************************************************************************
 *
 * TFTP open PXE function
 */
static short pxe_tftp_open(t_tftp_open *params)
{
  short status;

  /* Check that we are allowed to open a TFTP connection */
  if (udpoflag)
	return(PXENV_STATUS_UDP_OPEN);
  if (tftpoflag)
	return(PXENV_STATUS_TFTP_OPEN);

  /* Open TFTP connection */
  tftpbufsize = params->pktsize;
  if (tftpbufsize < DEFSEGSIZE)
	tftpbufsize = DEFSEGSIZE;
  else if (tftpbufsize > MAXSEGSIZE)
	tftpbufsize = MAXSEGSIZE;
  if ((status = tftp_open(params->server, params->gateway,
       params->filename, sizeof(params->filename),
       params->port)) == PXENV_STATUS_SUCCESS)
	tftpoflag = TRUE;
  return(status);

}



/*
 **************************************************************************
 *
 * TFTP close PXE function
 */
static short pxe_tftp_close(t_tftp_close *params)
{
  short status;

  /* Check that we are allowed to close the TFTP connection */
  if (!tftpoflag)
	return(PXENV_STATUS_TFTP_CLOSED);

  /* Close TFTP connection */
  tftp_sport = 0;
  tftpbufsize = 0;
  if ((status = tftp_close()) == PXENV_STATUS_SUCCESS)
	tftpoflag = FALSE;
  return(status);
}



/*
 **************************************************************************
 *
 * TFTP read data PXE function
 */
static short pxe_tftp_read_data(t_tftp_read_data *params)
{
  short ret = PXENV_STATUS_TFTP_TOO_MANY;

  /* Check that we are allowed to read data from TFTP connection */
  if (!tftpoflag)
	return(PXENV_STATUS_TFTP_CLOSED);

  /* Read data from open TFTP connection */
  if (params->bufsize >= tftpbufsize &&
      (ret = tftp_get()) == PXENV_STATUS_SUCCESS) {
	fmemcpy(params->buffer, MK_FP(getds(), tftpbuf), tftpbuflen);
	params->bufsize = tftpbuflen;
	params->packetno = curblock;
  }
  return(ret);
}



/*
 **************************************************************************
 *
 * TFTP read file PXE function.
 */
static short pxe_tftp_read_file(t_tftp_read_file *params)
{
  /* Check if we are allowed to read a file */
  if (udpoflag)
	return(PXENV_STATUS_UDP_OPEN);
  if (tftpoflag)
	return(PXENV_STATUS_TFTP_OPEN);

  /* Simply call our local function to read the file */
  return(tftp_read_file(params));
}



/*
 **************************************************************************
 *
 * Get file size.
 */
static short pxe_get_file_size(t_tftp_get_file_size *params)
{
  short ret = PXENV_STATUS_FAILURE;

  /* No TFTP connection is allowed to be open */
  if (udpoflag)
	return(PXENV_STATUS_UDP_OPEN);
  if (tftpoflag)
	return(PXENV_STATUS_TFTP_OPEN);

  /* Open and close TFTP connection. This will get us the file size */
  tftp_sport = 0;
  tftpbufsize = DEFSEGSIZE;
  if (tftp_open(params->server, params->gateway, params->filename,
		sizeof(params->filename), 0) == PXENV_STATUS_SUCCESS &&
      tftp_close() == PXENV_STATUS_SUCCESS &&
      filesize != (unsigned long)(-1L)) {
	params->filesize = filesize;
	ret = PXENV_STATUS_SUCCESS;
  }
  tftpbufsize = 0;
  return(ret);
}



/*
 **************************************************************************
 *
 * Function table for TFTP PXE functions
 */
struct functab tftp_func_tab = {
	TFTP_MINNUM, TFTP_MAXNUM,
	{{ pxe_tftp_open,	sizeof(t_tftp_open)},
	 { pxe_tftp_close,	sizeof(t_tftp_close)},
	 { pxe_tftp_read_data,	sizeof(t_tftp_read_data)},
	 { pxe_tftp_read_file,	sizeof(t_tftp_read_file)},
	 { pxe_get_file_size,	sizeof(t_tftp_get_file_size)}
	}
};

