646638aba76cbc9612f1ef451b9737aad6d151e0
[people/xl0/gpxe.git] / src / net / tcp / ftp.c
1 #include <stdint.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <assert.h>
6 #include <errno.h>
7 #include <byteswap.h>
8 #include <gpxe/socket.h>
9 #include <gpxe/tcpip.h>
10 #include <gpxe/in.h>
11 #include <gpxe/xfer.h>
12 #include <gpxe/open.h>
13 #include <gpxe/uri.h>
14 #include <gpxe/ftp.h>
15
16 /** @file
17  *
18  * File transfer protocol
19  *
20  */
21
22 /**
23  * FTP states
24  *
25  * These @b must be sequential, i.e. a successful FTP session must
26  * pass through each of these states in order.
27  */
28 enum ftp_state {
29         FTP_CONNECT = 0,
30         FTP_USER,
31         FTP_PASS,
32         FTP_TYPE,
33         FTP_PASV,
34         FTP_RETR,
35         FTP_QUIT,
36         FTP_DONE,
37 };
38
39 /**
40  * An FTP request
41  *
42  */
43 struct ftp_request {
44         /** Reference counter */
45         struct refcnt refcnt;
46         /** Data transfer interface */
47         struct xfer_interface xfer;
48
49         /** URI being fetched */
50         struct uri *uri;
51         /** FTP control channel interface */
52         struct xfer_interface control;
53         /** FTP data channel interface */
54         struct xfer_interface data;
55
56         /** Current state */
57         enum ftp_state state;
58         /** Buffer to be filled with data received via the control channel */
59         char *recvbuf;
60         /** Remaining size of recvbuf */
61         size_t recvsize;
62         /** FTP status code, as text */
63         char status_text[5];
64         /** Passive-mode parameters, as text */
65         char passive_text[24]; /* "aaa,bbb,ccc,ddd,eee,fff" */
66 };
67
68 /**
69  * Free FTP request
70  *
71  * @v refcnt            Reference counter
72  */
73 static void ftp_free ( struct refcnt *refcnt ) {
74         struct ftp_request *ftp =
75                 container_of ( refcnt, struct ftp_request, refcnt );
76
77         DBGC ( ftp, "FTP %p freed\n", ftp );
78
79         uri_put ( ftp->uri );
80         free ( ftp );
81 }
82
83 /**
84  * Mark FTP operation as complete
85  *
86  * @v ftp               FTP request
87  * @v rc                Return status code
88  */
89 static void ftp_done ( struct ftp_request *ftp, int rc ) {
90
91         DBGC ( ftp, "FTP %p completed (%s)\n", ftp, strerror ( rc ) );
92
93         /* Close all data transfer interfaces */
94         xfer_nullify ( &ftp->xfer );
95         xfer_close ( &ftp->xfer, rc );
96         xfer_nullify ( &ftp->control );
97         xfer_close ( &ftp->control, rc );
98         xfer_nullify ( &ftp->data );
99         xfer_close ( &ftp->data, rc );
100 }
101
102 /*****************************************************************************
103  *
104  * FTP control channel
105  *
106  */
107
108 /**
109  * FTP control channel strings
110  *
111  * These are used as printf() format strings.  Since only one of them
112  * (RETR) takes an argument, we always supply that argument to the
113  * snprintf() call.
114  */
115 static const char * ftp_strings[] = {
116         [FTP_CONNECT]   = "",
117         [FTP_USER]      = "USER anonymous\r\n",
118         [FTP_PASS]      = "PASS etherboot@etherboot.org\r\n",
119         [FTP_TYPE]      = "TYPE I\r\n",
120         [FTP_PASV]      = "PASV\r\n",
121         [FTP_RETR]      = "RETR %s\r\n", 
122         [FTP_QUIT]      = "QUIT\r\n",
123         [FTP_DONE]      = "",
124 };
125
126 /**
127  * Handle control channel being closed
128  *
129  * @v control           FTP control channel interface
130  * @v rc                Reason for close
131  *
132  * When the control channel is closed, the data channel must also be
133  * closed, if it is currently open.
134  */
135 static void ftp_control_close ( struct xfer_interface *control, int rc ) {
136         struct ftp_request *ftp =
137                 container_of ( control, struct ftp_request, control );
138
139         DBGC ( ftp, "FTP %p control connection closed: %s\n",
140                ftp, strerror ( rc ) );
141
142         /* Complete FTP operation */
143         ftp_done ( ftp, rc );
144 }
145
146 /**
147  * Parse FTP byte sequence value
148  *
149  * @v text              Text string
150  * @v value             Value buffer
151  * @v len               Length of value buffer
152  *
153  * This parses an FTP byte sequence value (e.g. the "aaa,bbb,ccc,ddd"
154  * form for IP addresses in PORT commands) into a byte sequence.  @c
155  * *text will be updated to point beyond the end of the parsed byte
156  * sequence.
157  *
158  * This function is safe in the presence of malformed data, though the
159  * output is undefined.
160  */
161 static void ftp_parse_value ( char **text, uint8_t *value, size_t len ) {
162         do {
163                 *(value++) = strtoul ( *text, text, 10 );
164                 if ( **text )
165                         (*text)++;
166         } while ( --len );
167 }
168
169 /**
170  * Handle an FTP control channel response
171  *
172  * @v ftp               FTP request
173  *
174  * This is called once we have received a complete response line.
175  */
176 static void ftp_reply ( struct ftp_request *ftp ) {
177         char status_major = ftp->status_text[0];
178         char separator = ftp->status_text[3];
179
180         DBGC ( ftp, "FTP %p received status %s\n", ftp, ftp->status_text );
181
182         /* Ignore malformed lines */
183         if ( separator != ' ' )
184                 return;
185
186         /* Ignore "intermediate" responses (1xx codes) */
187         if ( status_major == '1' )
188                 return;
189
190         /* Anything other than success (2xx) or, in the case of a
191          * repsonse to a "USER" command, a password prompt (3xx), is a
192          * fatal error.
193          */
194         if ( ! ( ( status_major == '2' ) ||
195                  ( ( status_major == '3' ) && ( ftp->state == FTP_USER ) ) ) ){
196                 /* Flag protocol error and close connections */
197                 ftp_done ( ftp, -EPROTO );
198         }
199
200         /* Open passive connection when we get "PASV" response */
201         if ( ftp->state == FTP_PASV ) {
202                 char *ptr = ftp->passive_text;
203                 union {
204                         struct sockaddr_in sin;
205                         struct sockaddr sa;
206                 } sa;
207                 int rc;
208
209                 sa.sin.sin_family = AF_INET;
210                 ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_addr,
211                                   sizeof ( sa.sin.sin_addr ) );
212                 ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_port,
213                                   sizeof ( sa.sin.sin_port ) );
214                 if ( ( rc = xfer_open_socket ( &ftp->data, SOCK_STREAM,
215                                                &sa.sa, NULL ) ) != 0 ) {
216                         DBGC ( ftp, "FTP %p could not open data connection\n",
217                                ftp );
218                         ftp_done ( ftp, rc );
219                         return;
220                 }
221         }
222
223         /* Move to next state */
224         if ( ftp->state < FTP_DONE )
225                 ftp->state++;
226
227         /* Send control string */
228         if ( ftp->state < FTP_DONE ) {
229                 DBGC ( ftp, "FTP %p sending ", ftp );
230                 DBGC ( ftp, ftp_strings[ftp->state], ftp->uri->path );
231                 xfer_printf ( &ftp->control, ftp_strings[ftp->state],
232                               ftp->uri->path );
233         }
234 }
235
236 /**
237  * Handle new data arriving on FTP control channel
238  *
239  * @v control           FTP control channel interface
240  * @v data              New data
241  * @v len               Length of new data
242  *
243  * Data is collected until a complete line is received, at which point
244  * its information is passed to ftp_reply().
245  */
246 static int ftp_control_deliver_raw ( struct xfer_interface *control,
247                                      const void *data, size_t len ) {
248         struct ftp_request *ftp =
249                 container_of ( control, struct ftp_request, control );
250         char *recvbuf = ftp->recvbuf;
251         size_t recvsize = ftp->recvsize;
252         char c;
253         
254         while ( len-- ) {
255                 c = * ( ( char * ) data++ );
256                 switch ( c ) {
257                 case '\r' :
258                 case '\n' :
259                         /* End of line: call ftp_reply() to handle
260                          * completed reply.  Avoid calling ftp_reply()
261                          * twice if we receive both \r and \n.
262                          */
263                         if ( recvsize == 0 )
264                                 ftp_reply ( ftp );
265                         /* Start filling up the status code buffer */
266                         recvbuf = ftp->status_text;
267                         recvsize = sizeof ( ftp->status_text ) - 1;
268                         break;
269                 case '(' :
270                         /* Start filling up the passive parameter buffer */
271                         recvbuf = ftp->passive_text;
272                         recvsize = sizeof ( ftp->passive_text ) - 1;
273                         break;
274                 case ')' :
275                         /* Stop filling the passive parameter buffer */
276                         recvsize = 0;
277                         break;
278                 default :
279                         /* Fill up buffer if applicable */
280                         if ( recvsize > 0 ) {
281                                 *(recvbuf++) = c;
282                                 recvsize--;
283                         }
284                         break;
285                 }
286         }
287
288         /* Store for next invocation */
289         ftp->recvbuf = recvbuf;
290         ftp->recvsize = recvsize;
291
292         return 0;
293 }
294
295 /** FTP control channel operations */
296 static struct xfer_interface_operations ftp_control_operations = {
297         .close          = ftp_control_close,
298         .vredirect      = xfer_vopen,
299         .seek           = ignore_xfer_seek,
300         .window         = unlimited_xfer_window,
301         .alloc_iob      = default_xfer_alloc_iob,
302         .deliver_iob    = xfer_deliver_as_raw,
303         .deliver_raw    = ftp_control_deliver_raw,
304 };
305
306 /*****************************************************************************
307  *
308  * FTP data channel
309  *
310  */
311
312 /**
313  * Handle FTP data channel being closed
314  *
315  * @v data              FTP data channel interface
316  * @v rc                Reason for closure
317  *
318  * When the data channel is closed, the control channel should be left
319  * alone; the server will send a completion message via the control
320  * channel which we'll pick up.
321  *
322  * If the data channel is closed due to an error, we abort the request.
323  */
324 static void ftp_data_closed ( struct xfer_interface *data, int rc ) {
325         struct ftp_request *ftp =
326                 container_of ( data, struct ftp_request, data );
327
328         DBGC ( ftp, "FTP %p data connection closed: %s\n",
329                ftp, strerror ( rc ) );
330         
331         /* If there was an error, close control channel and record status */
332         if ( rc )
333                 ftp_done ( ftp, rc );
334 }
335
336 /**
337  * Handle data delivery via FTP data channel
338  *
339  * @v xfer              FTP data channel interface
340  * @v iobuf             I/O buffer
341  * @v meta              Data transfer metadata, or NULL
342  * @ret rc              Return status code
343  */
344 static int ftp_data_deliver_iob ( struct xfer_interface *data,
345                                   struct io_buffer *iobuf,
346                                   struct xfer_metadata *meta __unused ) {
347         struct ftp_request *ftp =
348                 container_of ( data, struct ftp_request, data );
349         int rc;
350
351         if ( ( rc = xfer_deliver_iob ( &ftp->xfer, iobuf ) ) != 0 ) {
352                 DBGC ( ftp, "FTP %p failed to deliver data: %s\n",
353                        ftp, strerror ( rc ) );
354                 return rc;
355         }
356
357         return 0;
358 }
359
360 /** FTP data channel operations */
361 static struct xfer_interface_operations ftp_data_operations = {
362         .close          = ftp_data_closed,
363         .vredirect      = xfer_vopen,
364         .seek           = ignore_xfer_seek,
365         .window         = unlimited_xfer_window,
366         .alloc_iob      = default_xfer_alloc_iob,
367         .deliver_iob    = ftp_data_deliver_iob,
368         .deliver_raw    = xfer_deliver_as_iob,
369 };
370
371 /*****************************************************************************
372  *
373  * Data transfer interface
374  *
375  */
376
377 /**
378  * Close FTP data transfer interface
379  *
380  * @v xfer              FTP data transfer interface
381  * @v rc                Reason for close
382  */
383 static void ftp_xfer_closed ( struct xfer_interface *xfer, int rc ) {
384         struct ftp_request *ftp =
385                 container_of ( xfer, struct ftp_request, xfer );
386
387         DBGC ( ftp, "FTP %p data transfer interface closed: %s\n",
388                ftp, strerror ( rc ) );
389         
390         ftp_done ( ftp, rc );
391 }
392
393 /** FTP data transfer interface operations */
394 static struct xfer_interface_operations ftp_xfer_operations = {
395         .close          = ftp_xfer_closed,
396         .vredirect      = ignore_xfer_vredirect,
397         .seek           = ignore_xfer_seek,
398         .window         = unlimited_xfer_window,
399         .alloc_iob      = default_xfer_alloc_iob,
400         .deliver_iob    = xfer_deliver_as_raw,
401         .deliver_raw    = ignore_xfer_deliver_raw,
402 };
403
404 /*****************************************************************************
405  *
406  * URI opener
407  *
408  */
409
410 /**
411  * Initiate an FTP connection
412  *
413  * @v xfer              Data transfer interface
414  * @v uri               Uniform Resource Identifier
415  * @ret rc              Return status code
416  */
417 static int ftp_open ( struct xfer_interface *xfer, struct uri *uri ) {
418         struct ftp_request *ftp;
419         struct sockaddr_tcpip server;
420         int rc;
421
422         /* Sanity checks */
423         if ( ! uri->path )
424                 return -EINVAL;
425         if ( ! uri->host )
426                 return -EINVAL;
427
428         /* Allocate and populate structure */
429         ftp = zalloc ( sizeof ( *ftp ) );
430         if ( ! ftp )
431                 return -ENOMEM;
432         ftp->refcnt.free = ftp_free;
433         xfer_init ( &ftp->xfer, &ftp_xfer_operations, &ftp->refcnt );
434         ftp->uri = uri_get ( uri );
435         xfer_init ( &ftp->control, &ftp_control_operations, &ftp->refcnt );
436         xfer_init ( &ftp->data, &ftp_data_operations, &ftp->refcnt );
437         ftp->recvbuf = ftp->status_text;
438         ftp->recvsize = sizeof ( ftp->status_text ) - 1;
439
440         DBGC ( ftp, "FTP %p fetching %s\n", ftp, ftp->uri->path );
441
442         /* Open control connection */
443         memset ( &server, 0, sizeof ( server ) );
444         server.st_port = htons ( uri_port ( uri, FTP_PORT ) );
445         if ( ( rc = xfer_open_named_socket ( &ftp->control, SOCK_STREAM,
446                                              ( struct sockaddr * ) &server,
447                                              uri->host, NULL ) ) != 0 )
448                 goto err;
449
450         /* Attach to parent interface, mortalise self, and return */
451         xfer_plug_plug ( &ftp->xfer, xfer );
452         ref_put ( &ftp->refcnt );
453         return 0;
454
455  err:
456         DBGC ( ftp, "FTP %p could not create request: %s\n", 
457                ftp, strerror ( rc ) );
458         ftp_done ( ftp, rc );
459         ref_put ( &ftp->refcnt );
460         return rc;
461 }
462
463 /** FTP URI opener */
464 struct uri_opener ftp_uri_opener __uri_opener = {
465         .scheme = "ftp",
466         .open   = ftp_open,
467 };