aboutsummaryrefslogtreecommitdiffstats
path: root/gpxe/src/net/udp/tftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'gpxe/src/net/udp/tftp.c')
-rw-r--r--gpxe/src/net/udp/tftp.c139
1 files changed, 120 insertions, 19 deletions
diff --git a/gpxe/src/net/udp/tftp.c b/gpxe/src/net/udp/tftp.c
index 19525f79..3de2fb9b 100644
--- a/gpxe/src/net/udp/tftp.c
+++ b/gpxe/src/net/udp/tftp.c
@@ -16,6 +16,8 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
+FILE_LICENCE ( GPL2_OR_LATER );
+
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
@@ -131,6 +133,8 @@ enum {
TFTP_FL_RRQ_MULTICAST = 0x0004,
/** Perform MTFTP recovery on timeout */
TFTP_FL_MTFTP_RECOVERY = 0x0008,
+ /** Only get filesize and then abort the transfer */
+ TFTP_FL_SIZEONLY = 0x0010,
};
/** Maximum number of MTFTP open requests before falling back to TFTP */
@@ -409,6 +413,42 @@ static int tftp_send_ack ( struct tftp_request *tftp ) {
}
/**
+ * Transmit ERROR (Abort)
+ *
+ * @v tftp TFTP connection
+ * @v errcode TFTP error code
+ * @v errmsg Error message string
+ * @ret rc Return status code
+ */
+static int tftp_send_error ( struct tftp_request *tftp, int errcode,
+ const char *errmsg ) {
+ struct tftp_error *err;
+ struct io_buffer *iobuf;
+ struct xfer_metadata meta = {
+ .dest = ( struct sockaddr * ) &tftp->peer,
+ };
+ size_t msglen;
+
+ DBGC2 ( tftp, "TFTP %p sending ERROR %d: %s\n", tftp, errcode,
+ errmsg );
+
+ /* Allocate buffer */
+ msglen = sizeof ( *err ) + strlen ( errmsg ) + 1 /* NUL */;
+ iobuf = xfer_alloc_iob ( &tftp->socket, msglen );
+ if ( ! iobuf )
+ return -ENOMEM;
+
+ /* Build ERROR */
+ err = iob_put ( iobuf, msglen );
+ err->opcode = htons ( TFTP_ERROR );
+ err->errcode = htons ( errcode );
+ strcpy ( err->errmsg, errmsg );
+
+ /* ERR always goes to the peer recorded from the RRQ response */
+ return xfer_deliver_iob_meta ( &tftp->socket, iobuf, &meta );
+}
+
+/**
* Transmit next relevant packet
*
* @v tftp TFTP connection
@@ -416,9 +456,16 @@ static int tftp_send_ack ( struct tftp_request *tftp ) {
*/
static int tftp_send_packet ( struct tftp_request *tftp ) {
- /* Update retransmission timer */
+ /* Update retransmission timer. While name resolution takes place the
+ * window is zero. Avoid unnecessary delay after name resolution
+ * completes by retrying immediately.
+ */
stop_timer ( &tftp->timer );
- start_timer ( &tftp->timer );
+ if ( xfer_window ( &tftp->socket ) ) {
+ start_timer ( &tftp->timer );
+ } else {
+ start_timer_nodelay ( &tftp->timer );
+ }
/* Send RRQ or ACK as appropriate */
if ( ! tftp->peer.st_family ) {
@@ -670,6 +717,7 @@ static int tftp_rx_oack ( struct tftp_request *tftp, void *buf, size_t len ) {
char *end = buf + len;
char *name;
char *value;
+ char *next;
int rc = 0;
/* Sanity check */
@@ -679,26 +727,41 @@ static int tftp_rx_oack ( struct tftp_request *tftp, void *buf, size_t len ) {
rc = -EINVAL;
goto done;
}
- if ( end[-1] != '\0' ) {
- DBGC ( tftp, "TFTP %p received OACK missing final NUL\n",
- tftp );
- rc = -EINVAL;
- goto done;
- }
/* Process each option in turn */
- name = oack->data;
- while ( name < end ) {
- value = ( name + strlen ( name ) + 1 );
+ for ( name = oack->data ; name < end ; name = next ) {
+
+ /* Parse option name and value
+ *
+ * We treat parsing errors as non-fatal, because there
+ * exists at least one TFTP server (IBM Tivoli PXE
+ * Server 5.1.0.3) that has been observed to send
+ * malformed OACKs containing trailing garbage bytes.
+ */
+ value = ( name + strnlen ( name, ( end - name ) ) + 1 );
+ if ( value > end ) {
+ DBGC ( tftp, "TFTP %p received OACK with malformed "
+ "option name:\n", tftp );
+ DBGC_HD ( tftp, oack, len );
+ break;
+ }
if ( value == end ) {
DBGC ( tftp, "TFTP %p received OACK missing value "
"for option \"%s\"\n", tftp, name );
- rc = -EINVAL;
- goto done;
+ DBGC_HD ( tftp, oack, len );
+ break;
}
+ next = ( value + strnlen ( value, ( end - value ) ) + 1 );
+ if ( next > end ) {
+ DBGC ( tftp, "TFTP %p received OACK with malformed "
+ "value for option \"%s\":\n", tftp, name );
+ DBGC_HD ( tftp, oack, len );
+ break;
+ }
+
+ /* Process option */
if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 )
goto done;
- name = ( value + strlen ( value ) + 1 );
}
/* Process tsize information, if available */
@@ -707,6 +770,14 @@ static int tftp_rx_oack ( struct tftp_request *tftp, void *buf, size_t len ) {
goto done;
}
+ /* Abort request if only trying to determine file size */
+ if ( tftp->flags & TFTP_FL_SIZEONLY ) {
+ rc = 0;
+ tftp_send_error ( tftp, 0, "TFTP Aborted" );
+ tftp_done ( tftp, rc );
+ return rc;
+ }
+
/* Request next data block */
tftp_send_packet ( tftp );
@@ -729,11 +800,18 @@ static int tftp_rx_data ( struct tftp_request *tftp,
struct io_buffer *iobuf ) {
struct tftp_data *data = iobuf->data;
struct xfer_metadata meta;
- int block;
+ unsigned int block;
off_t offset;
size_t data_len;
int rc;
+ if ( tftp->flags & TFTP_FL_SIZEONLY ) {
+ /* If we get here then server doesn't support SIZE option */
+ rc = -ENOTSUP;
+ tftp_send_error ( tftp, 0, "TFTP Aborted" );
+ goto done;
+ }
+
/* Sanity check */
if ( iob_len ( iobuf ) < sizeof ( *data ) ) {
DBGC ( tftp, "TFTP %p received underlength DATA packet "
@@ -741,14 +819,17 @@ static int tftp_rx_data ( struct tftp_request *tftp,
rc = -EINVAL;
goto done;
}
- if ( data->block == 0 ) {
+
+ /* Calculate block number */
+ block = ( ( bitmap_first_gap ( &tftp->bitmap ) + 1 ) & ~0xffff );
+ if ( data->block == 0 && block == 0 ) {
DBGC ( tftp, "TFTP %p received data block 0\n", tftp );
rc = -EINVAL;
goto done;
}
+ block += ( ntohs ( data->block ) - 1 );
/* Extract data */
- block = ( ntohs ( data->block ) - 1 );
offset = ( block * tftp->blksize );
iob_pull ( iobuf, sizeof ( *data ) );
data_len = iob_len ( iobuf );
@@ -934,7 +1015,7 @@ static int tftp_socket_deliver_iob ( struct xfer_interface *socket,
/** TFTP socket operations */
static struct xfer_interface_operations tftp_socket_operations = {
.close = ignore_xfer_close,
- .vredirect = xfer_vopen,
+ .vredirect = xfer_vreopen,
.window = unlimited_xfer_window,
.alloc_iob = default_xfer_alloc_iob,
.deliver_iob = tftp_socket_deliver_iob,
@@ -961,7 +1042,7 @@ static int tftp_mc_socket_deliver_iob ( struct xfer_interface *mc_socket,
/** TFTP multicast socket operations */
static struct xfer_interface_operations tftp_mc_socket_operations = {
.close = ignore_xfer_close,
- .vredirect = xfer_vopen,
+ .vredirect = xfer_vreopen,
.window = unlimited_xfer_window,
.alloc_iob = default_xfer_alloc_iob,
.deliver_iob = tftp_mc_socket_deliver_iob,
@@ -1093,6 +1174,26 @@ struct uri_opener tftp_uri_opener __uri_opener = {
};
/**
+ * Initiate TFTP-size request
+ *
+ * @v xfer Data transfer interface
+ * @v uri Uniform Resource Identifier
+ * @ret rc Return status code
+ */
+static int tftpsize_open ( struct xfer_interface *xfer, struct uri *uri ) {
+ return tftp_core_open ( xfer, uri, TFTP_PORT, NULL,
+ ( TFTP_FL_RRQ_SIZES |
+ TFTP_FL_SIZEONLY ) );
+
+}
+
+/** TFTP URI opener */
+struct uri_opener tftpsize_uri_opener __uri_opener = {
+ .scheme = "tftpsize",
+ .open = tftpsize_open,
+};
+
+/**
* Initiate TFTM download
*
* @v xfer Data transfer interface