/* * Copyright (C) 2007 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** @file * * File transfer protocol * */ FEATURE ( FEATURE_PROTOCOL, "FTP", DHCP_EB_FEATURE_FTP, 1 ); /** * FTP states * * These @b must be sequential, i.e. a successful FTP session must * pass through each of these states in order. */ enum ftp_state { FTP_CONNECT = 0, FTP_USER, FTP_PASS, FTP_TYPE, FTP_SIZE, FTP_PASV, FTP_RETR, FTP_WAIT, FTP_QUIT, FTP_DONE, }; /** * An FTP request * */ struct ftp_request { /** Reference counter */ struct refcnt refcnt; /** Data transfer interface */ struct interface xfer; /** URI being fetched */ struct uri *uri; /** FTP control channel interface */ struct interface control; /** FTP data channel interface */ struct interface data; /** Current state */ enum ftp_state state; /** Buffer to be filled with data received via the control channel */ char *recvbuf; /** Remaining size of recvbuf */ size_t recvsize; /** FTP status code, as text */ char status_text[5]; /** Passive-mode parameters, as text */ char passive_text[24]; /* "aaa,bbb,ccc,ddd,eee,fff" */ /** File size, as text */ char filesize[20]; }; /** * Free FTP request * * @v refcnt Reference counter */ static void ftp_free ( struct refcnt *refcnt ) { struct ftp_request *ftp = container_of ( refcnt, struct ftp_request, refcnt ); DBGC ( ftp, "FTP %p freed\n", ftp ); uri_put ( ftp->uri ); free ( ftp ); } /** * Mark FTP operation as complete * * @v ftp FTP request * @v rc Return status code */ static void ftp_done ( struct ftp_request *ftp, int rc ) { DBGC ( ftp, "FTP %p completed (%s)\n", ftp, strerror ( rc ) ); /* Close all data transfer interfaces */ intf_shutdown ( &ftp->data, rc ); intf_shutdown ( &ftp->control, rc ); intf_shutdown ( &ftp->xfer, rc ); } /***************************************************************************** * * FTP control channel * */ /** An FTP control channel string */ struct ftp_control_string { /** Literal portion */ const char *literal; /** Variable portion * * @v ftp FTP request * @ret string Variable portion of string */ const char * ( *variable ) ( struct ftp_request *ftp ); }; /** * Retrieve FTP pathname * * @v ftp FTP request * @ret path FTP pathname */ static const char * ftp_uri_path ( struct ftp_request *ftp ) { return ftp->uri->path; } /** * Retrieve FTP user * * @v ftp FTP request * @ret user FTP user */ static const char * ftp_user ( struct ftp_request *ftp ) { static char *ftp_default_user = "anonymous"; return ftp->uri->user ? ftp->uri->user : ftp_default_user; } /** * Retrieve FTP password * * @v ftp FTP request * @ret password FTP password */ static const char * ftp_password ( struct ftp_request *ftp ) { static char *ftp_default_password = "ipxe@ipxe.org"; return ftp->uri->password ? ftp->uri->password : ftp_default_password; } /** FTP control channel strings */ static struct ftp_control_string ftp_strings[] = { [FTP_CONNECT] = { NULL, NULL }, [FTP_USER] = { "USER ", ftp_user }, [FTP_PASS] = { "PASS ", ftp_password }, [FTP_TYPE] = { "TYPE I", NULL }, [FTP_SIZE] = { "SIZE ", ftp_uri_path }, [FTP_PASV] = { "PASV", NULL }, [FTP_RETR] = { "RETR ", ftp_uri_path }, [FTP_WAIT] = { NULL, NULL }, [FTP_QUIT] = { "QUIT", NULL }, [FTP_DONE] = { NULL, NULL }, }; /** * Parse FTP byte sequence value * * @v text Text string * @v value Value buffer * @v len Length of value buffer * * This parses an FTP byte sequence value (e.g. the "aaa,bbb,ccc,ddd" * form for IP addresses in PORT commands) into a byte sequence. @c * *text will be updated to point beyond the end of the parsed byte * sequence. * * This function is safe in the presence of malformed data, though the * output is undefined. */ static void ftp_parse_value ( char **text, uint8_t *value, size_t len ) { do { *(value++) = strtoul ( *text, text, 10 ); if ( **text ) (*text)++; } while ( --len ); } /** * Move to next state and send the appropriate FTP control string * * @v ftp FTP request * */ static void ftp_next_state ( struct ftp_request *ftp ) { struct ftp_control_string *ftp_string; const char *literal; const char *variable; /* Move to next state */ if ( ftp->state < FTP_DONE ) ftp->state++; /* Send control string if needed */ ftp_string = &ftp_strings[ftp->state]; literal = ftp_string->literal; variable = ( ftp_string->variable ? ftp_string->variable ( ftp ) : "" ); if ( literal ) { DBGC ( ftp, "FTP %p sending %s%s\n", ftp, literal, variable ); xfer_printf ( &ftp->control, "%s%s\r\n", literal, variable ); } } /** * Handle an FTP control channel response * * @v ftp FTP request * * This is called once we have received a complete response line. */ static void ftp_reply ( struct ftp_request *ftp ) { char status_major = ftp->status_text[0]; char separator = ftp->status_text[3]; DBGC ( ftp, "FTP %p received status %s\n", ftp, ftp->status_text ); /* Ignore malformed lines */ if ( separator != ' ' ) return; /* Ignore "intermediate" responses (1xx codes) */ if ( status_major == '1' ) return; /* If the SIZE command is not supported by the server, we go to * the next step. */ if ( ( status_major == '5' ) && ( ftp->state == FTP_SIZE ) ) { ftp_next_state ( ftp ); return; } /* Anything other than success (2xx) or, in the case of a * repsonse to a "USER" command, a password prompt (3xx), is a * fatal error. */ if ( ! ( ( status_major == '2' ) || ( ( status_major == '3' ) && ( ftp->state == FTP_USER ) ) ) ){ /* Flag protocol error and close connections */ ftp_done ( ftp, -EPROTO ); return; } /* Parse file size */ if ( ftp->state == FTP_SIZE ) { size_t filesize; char *endptr; /* Parse size */ filesize = strtoul ( ftp->filesize, &endptr, 10 ); if ( *endptr != '\0' ) { DBGC ( ftp, "FTP %p invalid SIZE \"%s\"\n", ftp, ftp->filesize ); ftp_done ( ftp, -EPROTO ); return; } /* Use seek() to notify recipient of filesize */ DBGC ( ftp, "FTP %p file size is %zd bytes\n", ftp, filesize ); xfer_seek ( &ftp->xfer, filesize ); xfer_seek ( &ftp->xfer, 0 ); } /* Open passive connection when we get "PASV" response */ if ( ftp->state == FTP_PASV ) { char *ptr = ftp->passive_text; union { struct sockaddr_in sin; struct sockaddr sa; } sa; int rc; sa.sin.sin_family = AF_INET; ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_addr, sizeof ( sa.sin.sin_addr ) ); ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_port, sizeof ( sa.sin.sin_port ) ); if ( ( rc = xfer_open_socket ( &ftp->data, SOCK_STREAM, &sa.sa, NULL ) ) != 0 ) { DBGC ( ftp, "FTP %p could not open data connection\n", ftp ); ftp_done ( ftp, rc ); return; } } /* Move to next state and send control string */ ftp_next_state ( ftp ); } /** * Handle new data arriving on FTP control channel * * @v ftp FTP request * @v iob I/O buffer * @v meta Data transfer metadata * @ret rc Return status code * * Data is collected until a complete line is received, at which point * its information is passed to ftp_reply(). */ static int ftp_control_deliver ( struct ftp_request *ftp, struct io_buffer *iobuf, struct xfer_metadata *meta __unused ) { char *data = iobuf->data; size_t len = iob_len ( iobuf ); char *recvbuf = ftp->recvbuf; size_t recvsize = ftp->recvsize; char c; while ( len-- ) { c = *(data++); if ( ( c == '\r' ) || ( c == '\n' ) ) { /* End of line: call ftp_reply() to handle * completed reply. Avoid calling ftp_reply() * twice if we receive both \r and \n. */ if ( recvbuf != ftp->status_text ) ftp_reply ( ftp ); /* Start filling up the status code buffer */ recvbuf = ftp->status_text; recvsize = sizeof ( ftp->status_text ) - 1; } else if ( ( ftp->state == FTP_PASV ) && ( c == '(' ) ) { /* Start filling up the passive parameter buffer */ recvbuf = ftp->passive_text; recvsize = sizeof ( ftp->passive_text ) - 1; } else if ( ( ftp->state == FTP_PASV ) && ( c == ')' ) ) { /* Stop filling the passive parameter buffer */ recvsize = 0; } else if ( ( ftp->state == FTP_SIZE ) && ( c == ' ' ) ) { /* Start filling up the file size buffer */ recvbuf = ftp->filesize; recvsize = sizeof ( ftp->filesize ) - 1; } else { /* Fill up buffer if applicable */ if ( recvsize > 0 ) { *(recvbuf++) = c; recvsize--; } } } /* Store for next invocation */ ftp->recvbuf = recvbuf; ftp->recvsize = recvsize; /* Free I/O buffer */ free_iob ( iobuf ); return 0; } /** FTP control channel interface operations */ static struct interface_operation ftp_control_operations[] = { INTF_OP ( xfer_deliver, struct ftp_request *, ftp_control_deliver ), INTF_OP ( intf_close, struct ftp_request *, ftp_done ), }; /** FTP control channel interface descriptor */ static struct interface_descriptor ftp_control_desc = INTF_DESC ( struct ftp_request, control, ftp_control_operations ); /***************************************************************************** * * FTP data channel * */ /** * Handle FTP data channel being closed * * @v ftp FTP request * @v rc Reason for closure * * When the data channel is closed, the control channel should be left * alone; the server will send a completion message via the control * channel which we'll pick up. * * If the data channel is closed due to an error, we abort the request. */ static void ftp_data_closed ( struct ftp_request *ftp, int rc ) { DBGC ( ftp, "FTP %p data connection closed: %s\n", ftp, strerror ( rc ) ); /* If there was an error, close control channel and record status */ if ( rc ) { ftp_done ( ftp, rc ); } else { ftp_next_state ( ftp ); } } /** FTP data channel interface operations */ static struct interface_operation ftp_data_operations[] = { INTF_OP ( intf_close, struct ftp_request *, ftp_data_closed ), }; /** FTP data channel interface descriptor */ static struct interface_descriptor ftp_data_desc = INTF_DESC_PASSTHRU ( struct ftp_request, data, ftp_data_operations, xfer ); /***************************************************************************** * * Data transfer interface * */ /** FTP data transfer interface operations */ static struct interface_operation ftp_xfer_operations[] = { INTF_OP ( intf_close, struct ftp_request *, ftp_done ), }; /** FTP data transfer interface descriptor */ static struct interface_descriptor ftp_xfer_desc = INTF_DESC_PASSTHRU ( struct ftp_request, xfer, ftp_xfer_operations, data ); /***************************************************************************** * * URI opener * */ /** * Check validity of FTP control channel string * * @v string String * @ret rc Return status code */ static int ftp_check_string ( const char *string ) { char c; /* The FTP control channel is line-based. Check for invalid * non-printable characters (e.g. newlines). */ while ( ( c = *(string++) ) ) { if ( ! isprint ( c ) ) return -EINVAL; } return 0; } /** * Initiate an FTP connection * * @v xfer Data transfer interface * @v uri Uniform Resource Identifier * @ret rc Return status code */ static int ftp_open ( struct interface *xfer, struct uri *uri ) { struct ftp_request *ftp; struct sockaddr_tcpip server; int rc; /* Sanity checks */ if ( ! uri->host ) return -EINVAL; if ( ! uri->path ) return -EINVAL; if ( ( rc = ftp_check_string ( uri->path ) ) != 0 ) return rc; if ( uri->user && ( ( rc = ftp_check_string ( uri->user ) ) != 0 ) ) return rc; if ( uri->password && ( ( rc = ftp_check_string ( uri->password ) ) != 0 ) ) return rc; /* Allocate and populate structure */ ftp = zalloc ( sizeof ( *ftp ) ); if ( ! ftp ) return -ENOMEM; ref_init ( &ftp->refcnt, ftp_free ); intf_init ( &ftp->xfer, &ftp_xfer_desc, &ftp->refcnt ); intf_init ( &ftp->control, &ftp_control_desc, &ftp->refcnt ); intf_init ( &ftp->data, &ftp_data_desc, &ftp->refcnt ); ftp->uri = uri_get ( uri ); ftp->recvbuf = ftp->status_text; ftp->recvsize = sizeof ( ftp->status_text ) - 1; DBGC ( ftp, "FTP %p fetching %s\n", ftp, ftp->uri->path ); /* Open control connection */ memset ( &server, 0, sizeof ( server ) ); server.st_port = htons ( uri_port ( uri, FTP_PORT ) ); if ( ( rc = xfer_open_named_socket ( &ftp->control, SOCK_STREAM, ( struct sockaddr * ) &server, uri->host, NULL ) ) != 0 ) goto err; /* Attach to parent interface, mortalise self, and return */ intf_plug_plug ( &ftp->xfer, xfer ); ref_put ( &ftp->refcnt ); return 0; err: DBGC ( ftp, "FTP %p could not create request: %s\n", ftp, strerror ( rc ) ); ftp_done ( ftp, rc ); ref_put ( &ftp->refcnt ); return rc; } /** FTP URI opener */ struct uri_opener ftp_uri_opener __uri_opener = { .scheme = "ftp", .open = ftp_open, };