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