mirror of
https://github.com/xcat2/xNBA.git
synced 2024-11-26 11:19:17 +00:00
Proof-of-concept FTP implementation
This commit is contained in:
parent
a42092d2a0
commit
aec0e127d2
68
src/include/gpxe/ftp.h
Normal file
68
src/include/gpxe/ftp.h
Normal file
@ -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
|
263
src/net/tcp/ftp.c
Normal file
263
src/net/tcp/ftp.c
Normal file
@ -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 );
|
||||
}
|
Loading…
Reference in New Issue
Block a user