Initial (untested) implementation of TFTP over the new UDP API.
[people/xl0/gpxe.git] / src / net / udp / tftp.c
1 /*
2  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <strings.h>
23 #include <byteswap.h>
24 #include <errno.h>
25 #include <assert.h>
26 #include <vsprintf.h>
27 #include <gpxe/async.h>
28 #include <gpxe/tftp.h>
29
30 /** @file
31  *
32  * TFTP protocol
33  *
34  */
35
36 /** A TFTP option */
37 struct tftp_option {
38         /** Option name */
39         const char *name;
40         /** Option processor
41          *
42          * @v tftp      TFTP connection
43          * @v value     Option value
44          * @ret rc      Return status code
45          */
46         int ( * process ) ( struct tftp_session *tftp, const char *value );
47 };
48
49 /**
50  * Process TFTP "blksize" option
51  *
52  * @v tftp              TFTP connection
53  * @v value             Option value
54  * @ret rc              Return status code
55  */
56 static int tftp_process_blksize ( struct tftp_session *tftp,
57                                   const char *value ) {
58         char *end;
59
60         tftp->blksize = strtoul ( value, &end, 10 );
61         if ( *end ) {
62                 DBG ( "TFTP %p got invalid blksize \"%s\"\n", tftp, value );
63                 return -EINVAL;
64         }
65         DBG ( "TFTP %p blksize=%d\n", tftp, tftp->blksize );
66
67         return 0;
68 }
69
70 /**
71  * Process TFTP "tsize" option
72  *
73  * @v tftp              TFTP connection
74  * @v value             Option value
75  * @ret rc              Return status code
76  */
77 static int tftp_process_tsize ( struct tftp_session *tftp,
78                                 const char *value ) {
79         char *end;
80
81         tftp->tsize = strtoul ( value, &end, 10 );
82         if ( *end ) {
83                 DBG ( "TFTP %p got invalid tsize \"%s\"\n", tftp, value );
84                 return -EINVAL;
85         }
86         DBG ( "TFTP %p tsize=%ld\n", tftp, tftp->tsize );
87
88         return 0;
89 }
90
91 /** Recognised TFTP options */
92 static struct tftp_option tftp_options[] = {
93         { "blksize", tftp_process_blksize },
94         { "tsize", tftp_process_tsize },
95         { NULL, NULL }
96 };
97
98 /**
99  * Process TFTP option
100  *
101  * @v tftp              TFTP connection
102  * @v name              Option name
103  * @v value             Option value
104  * @ret rc              Return status code
105  */
106 static int tftp_process_option ( struct tftp_session *tftp,
107                                  const char *name, const char *value ) {
108         struct tftp_option *option;
109
110         for ( option = tftp_options ; option->name ; option++ ) {
111                 if ( strcasecmp ( name, option->name ) == 0 )
112                         return option->process ( tftp, value );
113         }
114
115         DBG ( "TFTP %p received unknown option \"%s\" = \"%s\"\n",
116               tftp, name, value );
117
118         return -EINVAL;
119 }
120
121 /** Translation between TFTP errors and internal error numbers */
122 static const uint8_t tftp_errors[] = {
123         [TFTP_ERR_FILE_NOT_FOUND]       = PXENV_STATUS_TFTP_FILE_NOT_FOUND,
124         [TFTP_ERR_ACCESS_DENIED]        = PXENV_STATUS_TFTP_ACCESS_VIOLATION,
125         [TFTP_ERR_ILLEGAL_OP]           = PXENV_STATUS_TFTP_UNKNOWN_OPCODE,
126 };
127
128 /**
129  * Mark TFTP session as complete
130  *
131  * @v tftp              TFTP connection
132  * @v rc                Return status code
133  */
134 static void tftp_done ( struct tftp_session *tftp, int rc ) {
135
136         /* Stop the retry timer */
137         stop_timer ( &tftp->timer );
138
139         /* Close UDP connection */
140         udp_close ( &tftp->udp );
141
142         /* Mark async operation as complete */
143         async_done ( &tftp->aop, rc );
144 }
145
146 /**
147  * Send next packet in TFTP session
148  *
149  * @v tftp              TFTP connection
150  */
151 static void tftp_send_packet ( struct tftp_session *tftp ) {
152         start_timer ( &tftp->timer );
153         udp_senddata ( &tftp->udp );
154 }
155
156 /**
157  * Handle TFTP retransmission timer expiry
158  *
159  * @v timer             Retry timer
160  * @v fail              Failure indicator
161  */
162 static void tftp_timer_expired ( struct retry_timer *timer, int fail ) {
163         struct tftp_session *tftp =
164                 container_of ( timer, struct tftp_session, timer );
165
166         if ( fail ) {
167                 tftp_done ( tftp, -ETIMEDOUT );
168         } else {
169                 tftp_send_packet ( tftp );
170         }
171 }
172
173 /**
174  * Mark TFTP block as received
175  *
176  * @v tftp              TFTP connection
177  * @v block             Block number
178  */
179 static void tftp_received ( struct tftp_session *tftp, unsigned int block ) {
180
181         /* Stop the retry timer */
182         stop_timer ( &tftp->timer );
183
184         /* Update state to indicate which block we're now waiting for */
185         tftp->state = block;
186
187         /* Send next packet */
188         tftp_send_packet ( tftp );
189 }
190
191 /**
192  * Transmit RRQ
193  *
194  * @v tftp              TFTP connection
195  * @v buf               Temporary data buffer
196  * @v len               Length of temporary data buffer
197  * @ret rc              Return status code
198  */
199 static int tftp_send_rrq ( struct tftp_session *tftp, void *buf, size_t len ) {
200         struct tftp_rrq *rrq = buf;
201         void *data;
202         void *end;
203
204         DBG ( "TFTP %p requesting \"%s\"\n", tftp, tftp->filename );
205
206         data = rrq->data;
207         end = ( buf + len );
208         if ( data > end )
209                 goto overflow;
210         data += snprintf ( data, ( end - data ),
211                            "%s%coctet%cblksize%c%d%ctsize%c0",
212                            tftp->filename, 0, 0, 0, tftp->blksize, 0, 0 ) + 1;
213         if ( data > end )
214                 goto overflow;
215         rrq->opcode = htons ( TFTP_RRQ );
216
217         return udp_send ( &tftp->udp, buf, ( data - buf ) );
218
219  overflow:
220         DBG ( "TFTP %p RRQ out of space\n", tftp );
221         return -ENOBUFS;
222 }
223
224 /**
225  * Receive OACK
226  *
227  * @v tftp              TFTP connection
228  * @v buf               Temporary data buffer
229  * @v len               Length of temporary data buffer
230  * @ret rc              Return status code
231  */
232 static int tftp_rx_oack ( struct tftp_session *tftp, void *buf, size_t len ) {
233         struct tftp_oack *oack = buf;
234         char *end = buf + len;
235         char *name;
236         char *value;
237         int rc;
238
239         /* Sanity check */
240         if ( len < sizeof ( *oack ) ) {
241                 DBG ( "TFTP %p received underlength OACK packet length %d\n",
242                       tftp, len );
243                 return -EINVAL;
244         }
245         if ( end[-1] != '\0' ) {
246                 DBG ( "TFTP %p received OACK missing final NUL\n", tftp );
247                 return -EINVAL;
248         }
249
250         /* Process each option in turn */
251         name = oack->data;
252         while ( name < end ) {
253                 value = ( name + strlen ( name ) + 1 );
254                 if ( value == end ) {
255                         DBG ( "TFTP %p received OACK missing value for option "
256                               "\"%s\"\n", tftp, name );
257                         return -EINVAL;
258                 }
259                 if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 )
260                         return rc;
261                 name = ( value + strlen ( value ) + 1 );
262         }
263
264         /* Mark as received block 0 (the OACK) */
265         tftp_received ( tftp, 0 );
266
267         return 0;
268 }
269
270 /**
271  * Receive DATA
272  *
273  * @v tftp              TFTP connection
274  * @v buf               Temporary data buffer
275  * @v len               Length of temporary data buffer
276  * @ret rc              Return status code
277  */
278 static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) {
279         struct tftp_data *data = buf;
280         unsigned int block;
281         size_t data_len;
282
283         /* Sanity check */
284         if ( len < sizeof ( *data ) ) {
285                 DBG ( "TFTP %p received underlength DATA packet length %d\n",
286                       tftp, len );
287                 return -EINVAL;
288         }
289
290         /* Pass to callback */
291         block = ntohs ( data->block );
292         data_len = ( len - offsetof ( typeof ( *data ), data ) );
293         tftp->callback ( tftp, block, data->data, data_len );
294
295         /* Mark block as received */
296         tftp_received ( tftp, block );
297
298         /* Finish when final block received */
299         if ( data_len < tftp->blksize )
300                 tftp_done ( tftp, 0 );
301
302         return 0;
303 }
304
305 /**
306  * Transmit ACK
307  *
308  * @v tftp              TFTP connection
309  * @v buf               Temporary data buffer
310  * @v len               Length of temporary data buffer
311  * @ret rc              Return status code
312  */
313 static int tftp_send_ack ( struct tftp_session *tftp ) {
314         struct tftp_ack ack;
315
316         ack.opcode = htons ( TFTP_ACK );
317         ack.block = htons ( tftp->state );
318         return udp_send ( &tftp->udp, &ack, sizeof ( ack ) );
319 }
320
321 /**
322  * Receive ERROR
323  *
324  * @v tftp              TFTP connection
325  * @v buf               Temporary data buffer
326  * @v len               Length of temporary data buffer
327  * @ret rc              Return status code
328  */
329 static int tftp_rx_error ( struct tftp_session *tftp, void *buf, size_t len ) {
330         struct tftp_error *error = buf;
331         unsigned int err;
332         int rc = 0;
333
334         /* Sanity check */
335         if ( len < sizeof ( *error ) ) {
336                 DBG ( "TFTP %p received underlength ERROR packet length %d\n",
337                       tftp, len );
338                 return -EINVAL;
339         }
340
341         DBG ( "TFTP %p received ERROR packet with code %d, message \"%s\"\n",
342               tftp, ntohs ( error->errcode ), error->errmsg );
343         
344         /* Determine final operation result */
345         err = ntohs ( error->errcode );
346         if ( err < ( sizeof ( tftp_errors ) / sizeof ( tftp_errors[0] ) ) )
347                 rc = -tftp_errors[err];
348         if ( ! rc )
349                 rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION;
350
351         /* Close TFTP session */
352         tftp_done ( tftp, rc );
353
354         return 0;
355 }
356
357 /**
358  * Transmit data
359  *
360  * @v conn              UDP connection
361  * @v buf               Temporary data buffer
362  * @v len               Length of temporary data buffer
363  * @ret rc              Return status code
364  */
365 static int tftp_senddata ( struct udp_connection *conn,
366                            void *buf, size_t len ) {
367         struct tftp_session *tftp = 
368                 container_of ( conn, struct tftp_session, udp );
369
370         if ( tftp->state < 0 ) {
371                 return tftp_send_rrq ( tftp, buf, len );
372         } else {
373                 return tftp_send_ack ( tftp );
374         }
375 }
376
377 /**
378  * Receive new data
379  *
380  * @v udp               UDP connection
381  * @v data              Received data
382  * @v len               Length of received data
383  * @v st_src            Partially-filled source address
384  * @v st_dest           Partially-filled destination address
385  */
386 static int tftp_newdata ( struct udp_connection *conn, void *data, size_t len,
387                           struct sockaddr_tcpip *st_src __unused,
388                           struct sockaddr_tcpip *st_dest __unused ) {
389         struct tftp_session *tftp = 
390                 container_of ( conn, struct tftp_session, udp );
391         struct tftp_common *common = data;
392         
393         if ( len < sizeof ( *common ) ) {
394                 DBG ( "TFTP %p received underlength packet length %d\n",
395                       tftp, len );
396                 return -EINVAL;
397         }
398
399         /* Filter by TID.  Set TID on first response received */
400         if ( tftp->tid ) {
401                 if ( tftp->tid != st_src->st_port ) {
402                         DBG ( "TFTP %p received packet from wrong port "
403                               "(got %d, wanted %d)\n", tftp,
404                               ntohs ( st_src->st_port ), ntohs ( tftp->tid ) );
405                         return -EINVAL;
406                 }
407         } else {
408                 tftp->tid = st_src->st_port;
409                 DBG ( "TFTP %p using remote port %d\n", tftp,
410                       ntohs ( tftp->tid ) );
411                 udp_connect_port ( &tftp->udp, tftp->tid );
412         }
413
414         /* Filter by source address */
415         if ( memcmp ( st_src, udp_peer ( &tftp->udp ),
416                       sizeof ( *st_src ) ) != 0 ) {
417                 DBG ( "TFTP %p received packet from foreign source\n", tftp );
418                 return -EINVAL;
419         }
420
421         switch ( common->opcode ) {
422         case htons ( TFTP_OACK ):
423                 return tftp_rx_oack ( tftp, data, len );
424         case htons ( TFTP_DATA ):
425                 return tftp_rx_data ( tftp, data, len );
426         case htons ( TFTP_ERROR ):
427                 return tftp_rx_error ( tftp, data, len );
428         default:
429                 DBG ( "TFTP %p received strange packet type %d\n", tftp,
430                       ntohs ( common->opcode ) );
431                 return -EINVAL;
432         };
433 }
434
435 /** TFTP UDP operations */
436 static struct udp_operations tftp_udp_operations = {
437         .senddata = tftp_senddata,
438         .newdata = tftp_newdata,
439 };
440
441 /**
442  * Initiate TFTP download
443  *
444  * @v tftp              TFTP session
445  * @ret aop             Asynchronous operation
446  */
447 struct async_operation * tftp_get ( struct tftp_session *tftp ) {
448         int rc;
449
450         assert ( tftp->filename != NULL );
451         assert ( tftp->callback != NULL );
452         assert ( tftp->udp.peer.st_family != 0 );
453
454         /* Initialise TFTP session */
455         tftp->udp.udp_op = &tftp_udp_operations;
456         tftp->timer.expired = tftp_timer_expired;
457         tftp->state = -1;
458         tftp->blksize = TFTP_DEFAULT_BLKSIZE;
459
460         /* Open UDP connection */
461         if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) {
462                 async_done ( &tftp->aop, rc );
463                 goto out;
464         }
465
466         /* Transmit initial RRQ */
467         tftp_send_packet ( tftp );
468
469  out:
470         return &tftp->aop;
471 }