Update TFTP to use a struct buffer rather than a callback.
[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                 DBGC ( tftp, "TFTP %p got invalid blksize \"%s\"\n",
63                        tftp, value );
64                 return -EINVAL;
65         }
66         DBGC ( tftp, "TFTP %p blksize=%d\n", tftp, tftp->blksize );
67
68         return 0;
69 }
70
71 /**
72  * Process TFTP "tsize" option
73  *
74  * @v tftp              TFTP connection
75  * @v value             Option value
76  * @ret rc              Return status code
77  */
78 static int tftp_process_tsize ( struct tftp_session *tftp,
79                                 const char *value ) {
80         char *end;
81
82         tftp->tsize = strtoul ( value, &end, 10 );
83         if ( *end ) {
84                 DBGC ( tftp, "TFTP %p got invalid tsize \"%s\"\n",
85                        tftp, value );
86                 return -EINVAL;
87         }
88         DBGC ( tftp, "TFTP %p tsize=%ld\n", tftp, tftp->tsize );
89
90         return 0;
91 }
92
93 /** Recognised TFTP options */
94 static struct tftp_option tftp_options[] = {
95         { "blksize", tftp_process_blksize },
96         { "tsize", tftp_process_tsize },
97         { NULL, NULL }
98 };
99
100 /**
101  * Process TFTP option
102  *
103  * @v tftp              TFTP connection
104  * @v name              Option name
105  * @v value             Option value
106  * @ret rc              Return status code
107  */
108 static int tftp_process_option ( struct tftp_session *tftp,
109                                  const char *name, const char *value ) {
110         struct tftp_option *option;
111
112         for ( option = tftp_options ; option->name ; option++ ) {
113                 if ( strcasecmp ( name, option->name ) == 0 )
114                         return option->process ( tftp, value );
115         }
116
117         DBGC ( tftp, "TFTP %p received unknown option \"%s\" = \"%s\"\n",
118                tftp, name, value );
119
120         return -EINVAL;
121 }
122
123 /** Translation between TFTP errors and internal error numbers */
124 static const uint8_t tftp_errors[] = {
125         [TFTP_ERR_FILE_NOT_FOUND]       = PXENV_STATUS_TFTP_FILE_NOT_FOUND,
126         [TFTP_ERR_ACCESS_DENIED]        = PXENV_STATUS_TFTP_ACCESS_VIOLATION,
127         [TFTP_ERR_ILLEGAL_OP]           = PXENV_STATUS_TFTP_UNKNOWN_OPCODE,
128 };
129
130 /**
131  * Mark TFTP session as complete
132  *
133  * @v tftp              TFTP connection
134  * @v rc                Return status code
135  */
136 static void tftp_done ( struct tftp_session *tftp, int rc ) {
137
138         /* Stop the retry timer */
139         stop_timer ( &tftp->timer );
140
141         /* Close UDP connection */
142         udp_close ( &tftp->udp );
143
144         /* Mark async operation as complete */
145         async_done ( &tftp->aop, rc );
146 }
147
148 /**
149  * Send next packet in TFTP session
150  *
151  * @v tftp              TFTP connection
152  */
153 static void tftp_send_packet ( struct tftp_session *tftp ) {
154         start_timer ( &tftp->timer );
155         udp_senddata ( &tftp->udp );
156 }
157
158 /**
159  * Handle TFTP retransmission timer expiry
160  *
161  * @v timer             Retry timer
162  * @v fail              Failure indicator
163  */
164 static void tftp_timer_expired ( struct retry_timer *timer, int fail ) {
165         struct tftp_session *tftp =
166                 container_of ( timer, struct tftp_session, timer );
167
168         if ( fail ) {
169                 tftp_done ( tftp, -ETIMEDOUT );
170         } else {
171                 tftp_send_packet ( tftp );
172         }
173 }
174
175 /**
176  * Mark TFTP block as received
177  *
178  * @v tftp              TFTP connection
179  * @v block             Block number
180  */
181 static void tftp_received ( struct tftp_session *tftp, unsigned int block ) {
182
183         /* Stop the retry timer */
184         stop_timer ( &tftp->timer );
185
186         /* Update state to indicate which block we're now waiting for */
187         tftp->state = block;
188
189         /* Send next packet */
190         tftp_send_packet ( tftp );
191 }
192
193 /**
194  * Transmit RRQ
195  *
196  * @v tftp              TFTP connection
197  * @v buf               Temporary data buffer
198  * @v len               Length of temporary data buffer
199  * @ret rc              Return status code
200  */
201 static int tftp_send_rrq ( struct tftp_session *tftp, void *buf, size_t len ) {
202         struct tftp_rrq *rrq = buf;
203         void *data;
204         void *end;
205
206         DBGC ( tftp, "TFTP %p requesting \"%s\"\n", tftp, tftp->filename );
207
208         data = rrq->data;
209         end = ( buf + len );
210         if ( data > end )
211                 goto overflow;
212         data += ( snprintf ( data, ( end - data ),
213                              "%s%coctet%cblksize%c%d%ctsize%c0",
214                              tftp->filename, 0, 0, 0,
215                              tftp->request_blksize, 0, 0 ) + 1 );
216         if ( data > end )
217                 goto overflow;
218         rrq->opcode = htons ( TFTP_RRQ );
219
220         return udp_send ( &tftp->udp, buf, ( data - buf ) );
221
222  overflow:
223         DBGC ( tftp, "TFTP %p RRQ out of space\n", tftp );
224         return -ENOBUFS;
225 }
226
227 /**
228  * Receive OACK
229  *
230  * @v tftp              TFTP connection
231  * @v buf               Temporary data buffer
232  * @v len               Length of temporary data buffer
233  * @ret rc              Return status code
234  */
235 static int tftp_rx_oack ( struct tftp_session *tftp, void *buf, size_t len ) {
236         struct tftp_oack *oack = buf;
237         char *end = buf + len;
238         char *name;
239         char *value;
240         int rc;
241
242         /* Sanity check */
243         if ( len < sizeof ( *oack ) ) {
244                 DBGC ( tftp, "TFTP %p received underlength OACK packet "
245                        "length %d\n", tftp, len );
246                 return -EINVAL;
247         }
248         if ( end[-1] != '\0' ) {
249                 DBGC ( tftp, "TFTP %p received OACK missing final NUL\n",
250                        tftp );
251                 return -EINVAL;
252         }
253
254         /* Process each option in turn */
255         name = oack->data;
256         while ( name < end ) {
257                 value = ( name + strlen ( name ) + 1 );
258                 if ( value == end ) {
259                         DBGC ( tftp, "TFTP %p received OACK missing value "
260                                "for option \"%s\"\n", tftp, name );
261                         return -EINVAL;
262                 }
263                 if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 )
264                         return rc;
265                 name = ( value + strlen ( value ) + 1 );
266         }
267
268         /* Mark as received block 0 (the OACK) */
269         tftp_received ( tftp, 0 );
270
271         return 0;
272 }
273
274 /**
275  * Receive DATA
276  *
277  * @v tftp              TFTP connection
278  * @v buf               Temporary data buffer
279  * @v len               Length of temporary data buffer
280  * @ret rc              Return status code
281  */
282 static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) {
283         struct tftp_data *data = buf;
284         unsigned int block;
285         size_t data_offset;
286         size_t data_len;
287         int rc;
288
289         /* Sanity check */
290         if ( len < sizeof ( *data ) ) {
291                 DBGC ( tftp, "TFTP %p received underlength DATA packet "
292                        "length %d\n", tftp, len );
293                 return -EINVAL;
294         }
295
296         /* Fill data buffer */
297         block = ntohs ( data->block );
298         data_offset = ( ( block - 1 ) * tftp->blksize );
299         data_len = ( len - offsetof ( typeof ( *data ), data ) );
300         if ( ( rc = fill_buffer ( tftp->buffer, data->data, data_offset,
301                                   data_len ) ) != 0 ) {
302                 DBGC ( tftp, "TFTP %p could not fill data buffer: %s\n",
303                        tftp, strerror ( rc ) );
304                 tftp_done ( tftp, rc );
305                 return rc;
306         }
307
308         /* Mark block as received */
309         tftp_received ( tftp, block );
310
311         /* Finish when final block received */
312         if ( data_len < tftp->blksize )
313                 tftp_done ( tftp, 0 );
314
315         return 0;
316 }
317
318 /**
319  * Transmit ACK
320  *
321  * @v tftp              TFTP connection
322  * @v buf               Temporary data buffer
323  * @v len               Length of temporary data buffer
324  * @ret rc              Return status code
325  */
326 static int tftp_send_ack ( struct tftp_session *tftp ) {
327         struct tftp_ack ack;
328
329         ack.opcode = htons ( TFTP_ACK );
330         ack.block = htons ( tftp->state );
331         return udp_send ( &tftp->udp, &ack, sizeof ( ack ) );
332 }
333
334 /**
335  * Receive ERROR
336  *
337  * @v tftp              TFTP connection
338  * @v buf               Temporary data buffer
339  * @v len               Length of temporary data buffer
340  * @ret rc              Return status code
341  */
342 static int tftp_rx_error ( struct tftp_session *tftp, void *buf, size_t len ) {
343         struct tftp_error *error = buf;
344         unsigned int err;
345         int rc = 0;
346
347         /* Sanity check */
348         if ( len < sizeof ( *error ) ) {
349                 DBGC ( tftp, "TFTP %p received underlength ERROR packet "
350                        "length %d\n", tftp, len );
351                 return -EINVAL;
352         }
353
354         DBGC ( tftp, "TFTP %p received ERROR packet with code %d, message "
355                "\"%s\"\n", tftp, ntohs ( error->errcode ), error->errmsg );
356         
357         /* Determine final operation result */
358         err = ntohs ( error->errcode );
359         if ( err < ( sizeof ( tftp_errors ) / sizeof ( tftp_errors[0] ) ) )
360                 rc = -tftp_errors[err];
361         if ( ! rc )
362                 rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION;
363
364         /* Close TFTP session */
365         tftp_done ( tftp, rc );
366
367         return 0;
368 }
369
370 /**
371  * Transmit data
372  *
373  * @v conn              UDP connection
374  * @v buf               Temporary data buffer
375  * @v len               Length of temporary data buffer
376  * @ret rc              Return status code
377  */
378 static int tftp_senddata ( struct udp_connection *conn,
379                            void *buf, size_t len ) {
380         struct tftp_session *tftp = 
381                 container_of ( conn, struct tftp_session, udp );
382
383         if ( tftp->state < 0 ) {
384                 return tftp_send_rrq ( tftp, buf, len );
385         } else {
386                 return tftp_send_ack ( tftp );
387         }
388 }
389
390 /**
391  * Receive new data
392  *
393  * @v udp               UDP connection
394  * @v data              Received data
395  * @v len               Length of received data
396  * @v st_src            Partially-filled source address
397  * @v st_dest           Partially-filled destination address
398  */
399 static int tftp_newdata ( struct udp_connection *conn, void *data, size_t len,
400                           struct sockaddr_tcpip *st_src __unused,
401                           struct sockaddr_tcpip *st_dest __unused ) {
402         struct tftp_session *tftp = 
403                 container_of ( conn, struct tftp_session, udp );
404         struct tftp_common *common = data;
405         
406         if ( len < sizeof ( *common ) ) {
407                 DBGC ( tftp, "TFTP %p received underlength packet length %d\n",
408                        tftp, len );
409                 return -EINVAL;
410         }
411
412         /* Filter by TID.  Set TID on first response received */
413         if ( tftp->tid ) {
414                 if ( tftp->tid != st_src->st_port ) {
415                         DBGC ( tftp, "TFTP %p received packet from wrong port "
416                                "(got %d, wanted %d)\n", tftp,
417                                ntohs ( st_src->st_port ), ntohs ( tftp->tid ));
418                         return -EINVAL;
419                 }
420         } else {
421                 tftp->tid = st_src->st_port;
422                 DBGC ( tftp, "TFTP %p using remote port %d\n", tftp,
423                        ntohs ( tftp->tid ) );
424                 udp_connect_port ( &tftp->udp, tftp->tid );
425         }
426
427         /* Filter by source address */
428         if ( memcmp ( st_src, udp_peer ( &tftp->udp ),
429                       sizeof ( *st_src ) ) != 0 ) {
430                 DBGC ( tftp, "TFTP %p received packet from foreign source\n",
431                        tftp );
432                 return -EINVAL;
433         }
434
435         switch ( common->opcode ) {
436         case htons ( TFTP_OACK ):
437                 return tftp_rx_oack ( tftp, data, len );
438         case htons ( TFTP_DATA ):
439                 return tftp_rx_data ( tftp, data, len );
440         case htons ( TFTP_ERROR ):
441                 return tftp_rx_error ( tftp, data, len );
442         default:
443                 DBGC ( tftp, "TFTP %p received strange packet type %d\n", tftp,
444                        ntohs ( common->opcode ) );
445                 return -EINVAL;
446         };
447 }
448
449 /** TFTP UDP operations */
450 static struct udp_operations tftp_udp_operations = {
451         .senddata = tftp_senddata,
452         .newdata = tftp_newdata,
453 };
454
455 /**
456  * Initiate TFTP download
457  *
458  * @v tftp              TFTP session
459  * @ret aop             Asynchronous operation
460  */
461 struct async_operation * tftp_get ( struct tftp_session *tftp ) {
462         int rc;
463
464         assert ( tftp->filename != NULL );
465         assert ( tftp->buffer != NULL );
466         assert ( tftp->udp.peer.st_family != 0 );
467
468         /* Initialise TFTP session */
469         if ( ! tftp->request_blksize )
470                 tftp->request_blksize = TFTP_MAX_BLKSIZE;
471         tftp->blksize = TFTP_DEFAULT_BLKSIZE;
472         tftp->tsize = 0;
473         tftp->tid = 0;
474         tftp->state = -1;
475         tftp->udp.udp_op = &tftp_udp_operations;
476         tftp->timer.expired = tftp_timer_expired;
477
478         /* Open UDP connection */
479         if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) {
480                 async_done ( &tftp->aop, rc );
481                 goto out;
482         }
483
484         /* Transmit initial RRQ */
485         tftp_send_packet ( tftp );
486
487  out:
488         return &tftp->aop;
489 }