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