summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMichael Brown2006-04-30 14:02:07 +0200
committerMichael Brown2006-04-30 14:02:07 +0200
commitaec0e127d2d9aad667612a35dbf8b8ccf8cc001c (patch)
tree0a81238be2655837e08eb9c43d650f7598d49551 /src
parentAdd a temporary snprintf, so that safely-written code can at least (diff)
downloadipxe-aec0e127d2d9aad667612a35dbf8b8ccf8cc001c.tar.gz
ipxe-aec0e127d2d9aad667612a35dbf8b8ccf8cc001c.tar.xz
ipxe-aec0e127d2d9aad667612a35dbf8b8ccf8cc001c.zip
Proof-of-concept FTP implementation
Diffstat (limited to 'src')
-rw-r--r--src/include/gpxe/ftp.h68
-rw-r--r--src/net/tcp/ftp.c263
2 files changed, 331 insertions, 0 deletions
diff --git a/src/include/gpxe/ftp.h b/src/include/gpxe/ftp.h
new file mode 100644
index 00000000..f2db5a13
--- /dev/null
+++ b/src/include/gpxe/ftp.h
@@ -0,0 +1,68 @@
+#ifndef _GPXE_FTP_H
+#define _GPXE_FTP_H
+
+/** @file
+ *
+ * File transfer protocol
+ *
+ */
+
+#include <stdint.h>
+#include <gpxe/tcp.h>
+
+enum ftp_state {
+ FTP_CONNECT = 0,
+ FTP_USER,
+ FTP_PASS,
+ FTP_TYPE,
+ FTP_PASV,
+ FTP_RETR,
+ FTP_QUIT,
+ FTP_DONE,
+};
+
+/**
+ * An FTP request
+ *
+ */
+struct ftp_request {
+ /** TCP connection for this request */
+ struct tcp_connection tcp;
+ /** File to download */
+ const char *filename;
+ /** Callback function
+ *
+ * @v data Received data
+ * @v len Length of received data
+ *
+ * This function is called for all data received from the
+ * remote server.
+ */
+ void ( *callback ) ( char *data, size_t len );
+ /** Completion indicator
+ *
+ * This will be set to a non-zero value when the transfer is
+ * complete. A negative value indicates an error.
+ */
+ int complete;
+
+ /** Current state */
+ enum ftp_state state;
+ /** Amount of current message already transmitted */
+ size_t already_sent;
+ /** 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[4];
+ /** Passive-mode parameters, as text */
+ char passive_text[24]; /* "aaa,bbb,ccc,ddd,eee,fff" */
+
+ /** TCP connection for the data channel */
+ struct tcp_connection tcp_data;
+};
+
+extern void ftp_connect ( struct ftp_request *ftp );
+
+#endif
diff --git a/src/net/tcp/ftp.c b/src/net/tcp/ftp.c
new file mode 100644
index 00000000..3e6da7bc
--- /dev/null
+++ b/src/net/tcp/ftp.c
@@ -0,0 +1,263 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vsprintf.h>
+#include <assert.h>
+#include <errno.h>
+#include <gpxe/ftp.h>
+
+/** @file
+ *
+ * File transfer protocol
+ *
+ */
+
+const char *ftp_strings[] = {
+ [FTP_CONNECT] = "",
+ [FTP_USER] = "USER anonymous\r\n",
+ [FTP_PASS] = "PASS etherboot@etherboot.org\r\n",
+ [FTP_TYPE] = "TYPE I\r\n",
+ [FTP_PASV] = "PASV\r\n",
+ [FTP_RETR] = "RETR %s\r\n",
+ [FTP_QUIT] = "QUIT\r\n",
+ [FTP_DONE] = "",
+};
+
+static inline struct ftp_request *
+tcp_to_ftp ( struct tcp_connection *conn ) {
+ return container_of ( conn, struct ftp_request, tcp );
+}
+
+static inline struct ftp_request *
+tcp_to_ftp_data ( struct tcp_connection *conn ) {
+ return container_of ( conn, struct ftp_request, tcp_data );
+}
+
+static void ftp_complete ( struct ftp_request *ftp, int complete ) {
+ ftp->complete = complete;
+ tcp_close ( &ftp->tcp_data );
+ tcp_close ( &ftp->tcp );
+}
+
+
+static void ftp_aborted ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+
+ ftp_complete ( ftp, -ECONNABORTED );
+}
+
+static void ftp_timedout ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+
+ ftp_complete ( ftp, -ETIMEDOUT );
+}
+
+static void ftp_closed ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+
+ ftp_complete ( ftp, 1 );
+}
+
+static void ftp_connected ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+
+ /* Nothing to do */
+}
+
+static void ftp_acked ( struct tcp_connection *conn, size_t len ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+
+ ftp->already_sent += len;
+}
+
+static int ftp_open_passive ( struct ftp_request *ftp ) {
+ char *ptr = ftp->passive_text;
+ uint8_t *byte = ( uint8_t * ) ( &ftp->tcp_data.sin );
+ int i;
+
+ /* Parse the IP address and port from the PASV repsonse */
+ for ( i = 6 ; i ; i-- ) {
+ if ( ! *ptr )
+ return -EINVAL;
+ *(byte++) = strtoul ( ptr, &ptr, 10 );
+ if ( *ptr )
+ ptr++;
+ }
+ if ( *ptr )
+ return -EINVAL;
+
+ tcp_connect ( &ftp->tcp_data );
+ return 0;
+}
+
+static void ftp_reply ( struct ftp_request *ftp ) {
+ char status_major = ftp->status_text[0];
+ int success;
+
+ /* Ignore "intermediate" messages */
+ if ( status_major == '1' )
+ return;
+
+ /* Check for success */
+ success = ( status_major == '2' );
+
+ /* Special-case the "USER"-"PASS" sequence */
+ if ( ftp->state == FTP_USER ) {
+ if ( success ) {
+ /* No password was asked for; pretend we have
+ * already entered it
+ */
+ ftp->state = FTP_PASS;
+ } else if ( status_major == '3' ) {
+ /* Password requested, treat this as success
+ * for our purposes
+ */
+ success = 1;
+ }
+ }
+
+ /* Abort on failure */
+ if ( ! success )
+ goto err;
+
+ /* Open passive connection when we get "PASV" response */
+ if ( ftp->state == FTP_PASV ) {
+ if ( ftp_open_passive ( ftp ) != 0 )
+ goto err;
+ }
+
+ /* Move to next state */
+ if ( ftp->state < FTP_DONE )
+ ftp->state++;
+ ftp->already_sent = 0;
+ return;
+
+ err:
+ ftp->complete = -EPROTO;
+ tcp_close ( &ftp->tcp );
+}
+
+static void ftp_newdata ( struct tcp_connection *conn,
+ void *data, size_t len ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+ char c;
+
+ for ( ; len ; data++, len-- ) {
+ c = * ( ( char * ) data );
+ if ( ( c == '\r' ) || ( c == '\n' ) ) {
+ if ( ftp->recvsize == 0 )
+ ftp_reply ( ftp );
+ ftp->recvbuf = ftp->status_text;
+ ftp->recvsize = sizeof ( ftp->status_text ) - 1;
+ } else if ( c == '(' ) {
+ ftp->recvbuf = ftp->passive_text;
+ ftp->recvsize = sizeof ( ftp->passive_text ) - 1;
+ } else if ( c == ')' ) {
+ ftp->recvsize = 0;
+ } else if ( ftp->recvsize > 0 ) {
+ *(ftp->recvbuf++) = c;
+ ftp->recvsize--;
+ }
+ }
+}
+
+static void ftp_senddata ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp ( conn );
+ const char *format;
+ const char *data;
+ size_t len;
+
+ /* Select message format string and data */
+ format = ftp_strings[ftp->state];
+ switch ( ftp->state ) {
+ case FTP_RETR:
+ data = ftp->filename;
+ break;
+ default:
+ data = NULL;
+ break;
+ }
+ if ( ! data )
+ data = "";
+
+ /* Build message */
+ len = snprintf ( tcp_buffer, tcp_buflen, format, data );
+ tcp_send ( conn, tcp_buffer + ftp->already_sent,
+ len - ftp->already_sent );
+}
+
+static struct tcp_operations ftp_tcp_operations = {
+ .aborted = ftp_aborted,
+ .timedout = ftp_timedout,
+ .closed = ftp_closed,
+ .connected = ftp_connected,
+ .acked = ftp_acked,
+ .newdata = ftp_newdata,
+ .senddata = ftp_senddata,
+};
+
+static void ftp_data_aborted ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ ftp_complete ( ftp, -ECONNABORTED );
+}
+
+static void ftp_data_timedout ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ ftp_complete ( ftp, -ETIMEDOUT );
+}
+
+static void ftp_data_closed ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ /* Nothing to do */
+}
+
+static void ftp_data_connected ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ /* Nothing to do */
+}
+
+static void ftp_data_acked ( struct tcp_connection *conn, size_t len ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ /* Nothing to do */
+}
+
+static void ftp_data_newdata ( struct tcp_connection *conn,
+ void *data, size_t len ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ ftp->callback ( data, len );
+}
+
+static void ftp_data_senddata ( struct tcp_connection *conn ) {
+ struct ftp_request *ftp = tcp_to_ftp_data ( conn );
+
+ /* Nothing to do */
+}
+
+static struct tcp_operations ftp_data_tcp_operations = {
+ .aborted = ftp_data_aborted,
+ .timedout = ftp_data_timedout,
+ .closed = ftp_data_closed,
+ .connected = ftp_data_connected,
+ .acked = ftp_data_acked,
+ .newdata = ftp_data_newdata,
+ .senddata = ftp_data_senddata,
+};
+
+/**
+ * Initiate an FTP connection
+ *
+ * @v ftp FTP request
+ */
+void ftp_connect ( struct ftp_request *ftp ) {
+ ftp->tcp.tcp_op = &ftp_tcp_operations;
+ ftp->tcp_data.tcp_op = &ftp_data_tcp_operations;
+ ftp->recvbuf = ftp->status_text;
+ ftp->recvsize = sizeof ( ftp->status_text ) - 1;
+ tcp_connect ( &ftp->tcp );
+}