source: bootcd/isolinux/syslinux-6.03/gpxe/src/net/tcp/ftp.c @ dd1be7c

Last change on this file since dd1be7c was e16e8f2, checked in by Edwin Eefting <edwin@datux.nl>, 3 years ago

bootstuff

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