#include "etherboot.h" #include "proto.h" #include "errno.h" #include "tftp.h" #include "tftpcore.h" /** @file * * TFTP protocol */ /** * Process a TFTP block * * @v state TFTP transfer state * @v tftp_state::block Last received data block * @v tftp_state::blksize Transfer block size * @v data The data block to process * @v buffer The buffer to fill with the data * @ret True Block processed successfully * @ret False Block not processed successfully * @ret tftp_state::block Incremented if applicable * @ret *eof End-of-file marker * @err #PXENV_STATUS_TFTP_INVALID_PACKET_SIZE Packet is too large * @err other As returned by fill_buffer() * * Process a TFTP DATA packet that has been received. If the data * packet is the next data packet in the stream, its contents will be * placed in the #buffer and tftp_state::block will be incremented. * If the packet is the final packet, end-of-file will be indicated * via #eof. * * If the data packet is a duplicate, then process_tftp_data() will * still return True, though nothing will be done with the packet. A * False return value always indicates an error that should abort the * transfer. */ static inline int tftp_process_data ( struct tftp_state *state, struct tftp_data *data, struct buffer *buffer, int *eof ) { unsigned int blksize; /* Check it's the correct DATA block */ if ( ntohs ( data->block ) != ( state->block + 1 ) ) { DBG ( "TFTP: got block %d, wanted block %d\n", ntohs ( data->block ), state->block + 1 ); return 1; } /* Check it's an acceptable size */ blksize = ( ntohs ( data->udp.len ) + offsetof ( typeof ( *data ), udp ) - offsetof ( typeof ( *data ), data ) ); if ( blksize > state->blksize ) { DBG ( "TFTP: oversized block size %d (max %d)\n", blksize, state->blksize ); errno = PXENV_STATUS_TFTP_INVALID_PACKET_SIZE; return 0; } /* Place block in the buffer */ if ( ! fill_buffer ( buffer, data->data, state->block * state->blksize, blksize ) ) { DBG ( "TFTP: could not place data in buffer: %m\n" ); return 0; } /* Increment block counter */ state->block++; /* Set EOF marker */ *eof = ( blksize < state->blksize ); return 1; } /** * Download a file via TFTP * * @v server TFTP server * @v file File name * @v buffer Buffer into which to load file * @ret True File was downloaded successfully * @ret False File was not downloaded successfully * @err #PXENV_STATUS_TFTP_UNKNOWN_OPCODE Unknown type of TFTP block received * @err other As returned by tftp_open() * @err other As returned by tftp_process_opts() * @err other As returned by tftp_ack() * @err other As returned by tftp_process_data() * * Download a file from a TFTP server into the specified buffer. */ static int tftp ( char *url __unused, struct sockaddr_in *server, char *file, struct buffer *buffer ) { struct tftp_state state; union tftp_any *reply; int eof = 0; /* Initialise TFTP state */ memset ( &state, 0, sizeof ( state ) ); state.server = *server; /* Open the file */ if ( ! tftp_open ( &state, file, &reply, 0 ) ) { DBG ( "TFTP: could not open %@:%d/%s : %m\n", server->sin_addr.s_addr, server->sin_port, file ); return 0; } /* Fetch file, a block at a time */ while ( 1 ) { twiddle(); switch ( ntohs ( reply->common.opcode ) ) { case TFTP_DATA: if ( ! tftp_process_data ( &state, &reply->data, buffer, &eof ) ) { tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL ); return 0; } break; case TFTP_OACK: if ( state.block ) { /* OACK must be first block, if present */ DBG ( "TFTP: OACK after block %d\n", state.block ); errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE; tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL ); return 0; } if ( ! tftp_process_opts ( &state, &reply->oack ) ) { DBG ( "TFTP: option processing failed: %m\n" ); tftp_error ( &state, TFTP_ERR_BAD_OPTS, NULL ); return 0; } break; default: DBG ( "TFTP: unexpected opcode %d\n", ntohs ( reply->common.opcode ) ); errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE; tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL ); return 0; } /* If we have reached EOF, stop here */ if ( eof ) break; /* Fetch the next data block */ if ( ! tftp_ack ( &state, &reply ) ) { DBG ( "TFTP: could not get next block: %m\n" ); if ( ! reply ) { tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL ); } return 0; } } /* ACK the final packet, as a courtesy to the server */ tftp_ack_nowait ( &state ); return 1; } struct protocol tftp_protocol __default_protocol = { .name = "tftp", .default_port = TFTP_PORT, .load = tftp, };