summaryrefslogtreecommitdiffstats
path: root/src/proto/tftp.c
blob: 3f6bf35ab99e1f4824cdc4bbae9ce99d1e101d89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#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,
};