/* ************************************************************************** * * Boot-ROM-Code to load an operating system across a TCP/IP network. * * Module: tftp.c * Purpose: Get a file with TFTP protocol * Entries: tftp_open, tftp_get, tftp_close * ************************************************************************** * * Copyright (C) 1995-2003 Gero Kuhlmann * * 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.c,v 1.7 2003/03/09 00:24:27 gkminix Exp $ */ #include #include #include #include #include #include #include "tftp.h" /* ************************************************************************** * * Definitions for the open 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 */ unsigned char buf[INPBUFSIZE]; struct tftphdr hdr; } inpbuf; static union { /* ACK output buffer */ unsigned char buf[ACKBUFSIZE]; struct tftphdr hdr; } ackbuf; /* Global variables for directly accessing this module */ unsigned char *tftpbuf = &(inpbuf.hdr.th_data); unsigned int tftpbufsize; unsigned int tftpbuflen; unsigned long filesize; unsigned long srvrtout; int tftpoflag; /* Variables local to this module */ static int tftp_sport = 0; /* TFTP server port number */ static int tftp_tout = TFTP_TIMEOUT; /* TFTP read timeout */ static int currblock; /* Current data block */ static int openflag; /* Open status */ static int errval; /* error status value */ /* Definition of option strings */ static unsigned char blksizestr[] = BLKSIZE_STR; static unsigned char tsizestr[] = TSIZE_STR; static unsigned char toutstr[] = TOUT_STR; /* ************************************************************************** * * Convert a string into a long integer value */ static unsigned char *getlong(startp, retval) unsigned char *startp; unsigned long *retval; { register unsigned char *cp = startp; unsigned long val = 0L; while (*cp) { if (*cp >= '0' && *cp <= '9') { val = val * 10; val += (*cp - '0'); } cp++; } *retval = val; return(cp); } /* ************************************************************************** * * Convert an unsigned integer value into a string */ static unsigned char *putnum(startp, val) unsigned char *startp; unsigned int val; { register unsigned char *cp; unsigned char buf[8]; /* Convert number into string */ cp = &buf[7]; while (val > 0) { *cp-- = (val % 10) + '0'; val /= 10; } /* Copy string into destination buffer */ if (cp == &buf[7]) *startp++ = '0'; else while (cp < &buf[7]) *startp++ = *++cp; return(startp); } /* ************************************************************************** * * Handle an options string */ static int handle_options(startp, endp) unsigned char *startp; unsigned char *endp; { register unsigned char *cp = startp; unsigned long longval; while (cp < endp) { if (!memcmp(cp, blksizestr, sizeof(BLKSIZE_STR))) { /* Handle blocksize option */ cp += sizeof(BLKSIZE_STR); cp = getlong(cp, &longval); if (longval < DEFSEGSIZE || longval > MAXSEGSIZE) return(FALSE); tftpbufsize = (unsigned int)longval; } else if (!memcmp(cp, tsizestr, sizeof(TSIZE_STR))) { /* Handle filesize option */ cp += sizeof(TSIZE_STR); cp = getlong(cp, &filesize); } else if (!memcmp(cp, toutstr, sizeof(TOUT_STR))) { /* Handle timeout option */ cp += sizeof(TOUT_STR); cp = getlong(cp, &srvrtout); } else return(FALSE); cp++; } return(TRUE); } /* ************************************************************************** * * Send a tftp request packet */ static int send_req(fname, fnamlen) unsigned char *fname; int fnamlen; { #define OPTSIZE (sizeof(OCTET_STR) + sizeof(BLKSIZE_STR) + 4 + \ sizeof(TSIZE_STR) + 2) register unsigned char *cp; char *octet; /* 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 = (unsigned char *)&(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++; memcpy(cp, toutstr, sizeof(TOUT_STR)); cp = putnum(cp + sizeof(TOUT_STR), tftp_tout) + 1; /* Finally send the request */ return(udp_write(inpbuf.buf, getds(), (int)(cp - inpbuf.buf))); } /* ************************************************************************** * * Send a tftp acknowledge packet */ static int send_reply(code, block) int block; { int len; memset(&ackbuf, 0, sizeof(ackbuf)); ackbuf.hdr.th_op = code; ackbuf.hdr.th_block = htons(block); len = (int)(ackbuf.hdr.th_data - (unsigned char *)&(ackbuf.hdr)); if (code == TFTP_ERROR) /* Error text is the null string */ len++; return(udp_write(ackbuf.buf, getds(), 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 int rcv_packet(block) int block; { #define th_data_ofs ((int)(inpbuf.hdr.th_data - (unsigned char *)&(inpbuf.hdr))) int status; int isopen = TRUE; /* Read packet with timeout */ errval = PXENV_STATUS_SUCCESS; tftpbuflen = sizeof(inpbuf.buf); status = udp_read(inpbuf.buf, getds(), &tftpbuflen, tftp_tout, CHR_ESC); if (status == PXENV_STATUS_ARP_TIMEOUT) { errval = PXENV_STATUS_TFTP_READ_TIMEOUT; goto rcv_ack; } else if (status != PXENV_STATUS_SUCCESS) { errval = PXENV_STATUS_TFTP_READ_CANCELED; isopen = FALSE; tftpbuflen = 0; send_nak(ENOSPACE); goto rcv_ret; } /* Handle option acknowledgement packet */ if (inpbuf.hdr.th_op == htons(TFTP_OACK)) { if (block != 1 || !handle_options((unsigned char *)&(inpbuf.hdr.th_block), (unsigned char *)&(inpbuf.hdr) + tftpbuflen)) { errval = PXENV_STATUS_TFTP_ERROR_OP; isopen = FALSE; tftpbuflen = 0; send_nak(EBADOPT); goto rcv_ret; } block = 0; tftpbuflen = 0xFFFF; goto rcv_ack; } /* Check that the packet has a correct length */ if (tftpbuflen < th_data_ofs) { errval = PXENV_STATUS_FAILURE; goto rcv_ret; } tftpbuflen -= th_data_ofs; if (tftpbuflen > tftpbufsize) { errval = PXENV_STATUS_FAILURE; goto rcv_ret; } /* 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(block)) errval = PXENV_STATUS_TFTP_TOO_MANY; /* * Ack the current packet. This will also resynchronize the sender if * the block was wrong. */ rcv_ack: if (errval != PXENV_STATUS_SUCCESS) { block--; send_ack(block); } else if ((errval = send_ack(block)) != PXENV_STATUS_SUCCESS) isopen = FALSE; rcv_ret: return(isopen); #undef th_data_ofs } /* ************************************************************************** * * Open a TFTP connection */ int tftp_open(server, gateway, fname, fnamlen) t_ipaddr server; t_ipaddr gateway; unsigned char *fname; int fnamlen; { register int retry, tid; /* Set default transfer block size */ if (tftpbufsize == 0) tftpbufsize = MAXSEGSIZE; else if (tftpbufsize < DEFSEGSIZE) tftpbufsize = DEFSEGSIZE; /* Set default port */ if (tftp_sport == 0) tftp_sport = htons(TFTP_S_PORT); /* Try to open a TFTP connection to the server */ currblock = 0; filesize = -1L; srvrtout = -1L; errval = PXENV_STATUS_TFTP_CANNOT_OPEN; for (retry = 0; openflag == 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. */ tid = htons(TFTP_C_PORT + random(0x003f) + ((retry * 64) & 0x1c0)); if (udp_open(server, gateway, tid, tftp_sport) != PXENV_STATUS_SUCCESS) break; openflag = 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(1) && errval == PXENV_STATUS_SUCCESS) { openflag = FLAG_TFTP_OPEN; return(PXENV_STATUS_SUCCESS); } } /* If an error occurred, retries are useless */ openflag = FLAG_UDP_CLOSED; if (errval != PXENV_STATUS_TFTP_READ_TIMEOUT) { udp_close(); break; } tftpbufsize = DEFSEGSIZE; errval = PXENV_STATUS_TFTP_CANNOT_OPEN; if (udp_close() != PXENV_STATUS_SUCCESS) break; } tftpbufsize = 0; return(errval); } /* ************************************************************************** * * Read the next data packet from a TFTP connection */ int tftp_get() { register int retry; /* Don't do anything if no TFTP connection is active. */ if (openflag != 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 (currblock == 0 && tftpbuflen != 0xFFFF) { currblock++; if (tftpbuflen < tftpbufsize) openflag = FLAG_TFTP_CLOSED; return(PXENV_STATUS_SUCCESS); } /* * 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(currblock + 1)) break; if (errval == PXENV_STATUS_SUCCESS) { currblock++; if (tftpbuflen < tftpbufsize) openflag = FLAG_TFTP_CLOSED; return(PXENV_STATUS_SUCCESS); } } openflag = FLAG_TFTP_CLOSED; return(errval); } /* ************************************************************************** * * Close a TFTP connection */ int tftp_close() { /* We always succeed in closing an already closed connection */ if (openflag == 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 (openflag == FLAG_TFTP_OPEN && !(currblock == 0 && tftpbuflen < tftpbufsize)) { if (!rcv_packet(currblock + 1) || errval != PXENV_STATUS_SUCCESS || (tftpbuflen >= tftpbufsize && !send_nak(ENOSPACE))) errval = PXENV_STATUS_TFTP_CANNOT_READ; } /* Close UDP connection */ if (udp_close() != PXENV_STATUS_SUCCESS && errval == PXENV_STATUS_SUCCESS) errval = PXENV_STATUS_FAILURE; openflag = FLAG_UDP_CLOSED; tftpbufsize = 0; return(errval); } /* ************************************************************************** * * Read a file into memory */ int tftp_read_file(params) t_tftp_read_file *params; { register int ret; unsigned long loadaddr, topmem; /* Set loading address */ loadaddr = params->buffer; topmem = loadaddr + params->bufsize; /* Set timeout */ if (params->opentimeout > 0) tftp_tout = params->opentimeout; /* Open TFTP connection and read all blocks successively */ if ((ret = tftp_open(params->server, params->gateway, params->filename, sizeof(params->filename))) == PXENV_STATUS_SUCCESS) { while (TRUE) { if ((ret = tftp_get()) != PXENV_STATUS_SUCCESS) break; if ((loadaddr + tftpbufsize ) > topmem) { ret = PXENV_STATUS_TFTP_TOO_MANY; break; } if (!lmove(loadaddr, tftpbuf, tftpbuflen)) { ret = PXENV_STATUS_MCOPY_PROBLEM; break; } loadaddr += tftpbuflen; if (tftpbuflen < tftpbufsize) { ret = PXENV_STATUS_SUCCESS; break; } } params->bufsize = loadaddr - params->buffer; tftp_close(); } tftp_tout = TFTP_TIMEOUT; return(ret); } /* ************************************************************************** * * TFTP open PXE function */ static int pxe_tftp_open(params) t_tftp_open *params; { int 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 */ tftp_sport = params->port; 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))) == PXENV_STATUS_SUCCESS) tftpoflag = TRUE; return(status); } /* ************************************************************************** * * TFTP close PXE function */ static int pxe_tftp_close(params) t_tftp_close *params; { int 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 int pxe_tftp_read_data(params) t_tftp_read_data *params; { int 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->bufofs, params->bufseg, tftpbuf, tftpbuflen); params->bufsize = tftpbuflen; params->packetno = currblock; } return(ret); } /* ************************************************************************** * * TFTP read file PXE function */ int pxe_tftp_read_file(params) 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 int pxe_get_file_size(params) t_tftp_get_file_size *params; { int 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)) == PXENV_STATUS_SUCCESS && tftp_close() == PXENV_STATUS_SUCCESS && filesize != -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)} } };