changer/PROJECT/services/ftp_client.c

876 lines
24 KiB
C

#include <includes.h>
#include "app_serv.h"
#include "ftp_client.h"
#include <string.h>
#include "time.h"
#include "host_app.h"
/**
* @brief Removes all trailing whitespace from a string
* @param[in,out] s Pointer to a NULL-terminated character string
**/
void strRemoveTrailingSpace(char *s)
{
char *end;
// Search for the first whitespace to remove
// at the end of the string
for (end = NULL; *s != '\0'; s++)
{
if (*s != ' ')
end = NULL;
else if (!end)
end = s;
}
// Trim whitespace from the end
if (end) *end = '\0';
}
/**
* @brief Convert a binary IPv4 address to dot-decimal notation
* @param[in] ipAddr Binary representation of the IPv4 address
* @param[out] str NULL-terminated string representing the IPv4 address
* @return Pointer to the formatted string
**/
char *ipv4AddrToString(uint32_t ipAddr, char *str)
{
uint8_t *p;
static char buffer[16];
// The str parameter is optional
if (!str) str = buffer;
// Cast the address to byte array
p = (uint8_t *) &ipAddr;
// Format IPv4 address
sprintf(str, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
// Return a pointer to the formatted string
return str;
}
/**
* @brief Establish a connection with the specified FTP server
* @param[in] context Pointer to the FTP client context
* @param[in] interface Underlying network interface (optional parameter)
* @param[in] serverAddr IP address of the FTP server
* @param[in] serverPort Port number
* @param[in] flags Connection options
* @return Error code
**/
int ftpConnect(FtpClientContext *context, uint32_t *serverAddr, uint16_t serverPort, uint32_t flags)
{
int error;
uint32_t replyCode;
NET_ERR err;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
// Clear context
memset(context, 0, sizeof(FtpClientContext));
// Save the IP address of the FTP server
context->serverAddr = *serverAddr;
// Use passive mode?
if (flags & FTP_PASSIVE_MODE)
context->passiveMode = 1;
else
context->passiveMode = 0;
// Open control socket
context->controlSocket = HostConnectSocket(context->serverAddr, serverPort, FTP_CLIENT_DEFAULT_TIMEOUT, &err);
if ((err != NET_SOCK_ERR_NONE) || (context->controlSocket < 0))
{
return ERROR_OPEN_FAILED;
}
// Wait for the connection greeting reply
error = ftpSendCommand(context, NULL, &replyCode);
// Any communication error to report?
if (error >= 0)
{
// Check FTP response code
if (!FTP_REPLY_CODE_2YZ(replyCode))
error = ERROR_UNEXPECTED_RESPONSE;
}
// Any error to report?
if (error < 0)
{
// Clean up side effects
NetSock_Close(context->controlSocket, &err);
context->controlSocket = -1;
}
// Return status code
return error;
}
/**
* @brief Login to the FTP server using the provided username and password
* @param[in] context Pointer to the FTP client context
* @param[in] username The username to login under
* @param[in] password The password to use
* @param[in] account Account name
* @return Error code
**/
int ftpLogin(FtpClientContext *context, const char *username, const char *password, const char *account)
{
int error;
uint32_t replyCode;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
// Format the USER command
sprintf((char*)context->buffer, "USER %s\r\n", username);
// Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if (FTP_REPLY_CODE_2YZ(replyCode))
return 0;
else if(!FTP_REPLY_CODE_3YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Format the PASS command
sprintf((char*)context->buffer, "PASS %s\r\n", password);
// Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if(FTP_REPLY_CODE_2YZ(replyCode))
return 0;
else if(!FTP_REPLY_CODE_3YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Format the ACCT command
sprintf((char*)context->buffer, "ACCT %s\r\n", account);
// Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if (!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Successful processing
return 0;
}
/**
* @brief Set the port to be used in data connection
* @param[in] context Pointer to the FTP client context
* @param[in] ipAddr Host address
* @param[in] port Port number
* @return Error code
**/
int ftpSetPort(FtpClientContext *context, const uint32_t *ipAddr, uint16_t port)
{
int error;
uint32_t replyCode;
char *p;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
#if 1
// IPv4 FTP client?
if (1)//(ipAddr->length == sizeof(Ipv4Addr))
{
// Format the PORT command
strcpy((char*)context->buffer, "PORT ");
// Append host address
ipv4AddrToString((uint32_t)ipAddr, (char*)context->buffer + 5);
// Parse the resulting string
for (p = (char*)context->buffer; *p != '\0'; p++)
{
// Change dots to commas
if (*p == '.') *p = ',';
}
// Append port number
sprintf(p, "%d,%d\r\n", (port >> 8), (port & 0xFF));
}
else
#endif
// Invalid IP address?
{
// Report an error
return ERROR_INVALID_ADDRESS;
}
// Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if (!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Successful processing
return 0;
}
/**
* @brief Enter passive mode
* @param[in] context Pointer to the FTP client context
* @param[out] port The port number the server is listening on
* @return Error code
**/
int ftpSetPassiveMode(FtpClientContext *context, uint16_t *port)
{
int error;
uint32_t replyCode;
char delimiter;
char *p;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
#if 1 //(IPV4_SUPPORT == ENABLED)
// IPv4 FTP server?
if (1)//(context->serverAddr.length == sizeof(Ipv4Addr))
{
// Send the command to the server
error = ftpSendCommand(context, "PASV\r\n", &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if (!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Delimiter character
delimiter = ',';
// Retrieve the low byte of the port number
p = strrchr((char const*)context->buffer, delimiter);
// Failed to parse the response?
if (!p) return ERROR_INVALID_SYNTAX;
// Convert the resulting string
*port = atoi(p + 1);
// Split the string
*p = '\0';
// Retrieve the high byte of the port number
p = strrchr((char const*)context->buffer, delimiter);
// Failed to parse the response?
if (!p) return ERROR_INVALID_SYNTAX;
// Convert the resulting string
*port |= atoi(p + 1) << 8;
}
else
#endif
//Invalid IP address?
{
// Report an error
return ERROR_INVALID_ADDRESS;
}
// Successful processing
return FTP_NO_ERROR;
}
/**
* @brief Set representation type
* @param[in] context Pointer to the FTP client context
* @param[in] type Single character identifying the desired type
* @return Error code
**/
int ftpSetType(FtpClientContext *context, char type)
{
int error;
uint32_t replyCode;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
// Format the TYPE command
sprintf((char*)context->buffer, "TYPE %c\r\n", type);
// Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if(!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Successful processing
return FTP_NO_ERROR;
}
/**
* @brief Get the working directory from the FTP server
* @param[in] context Pointer to the FTP client context
* @param[out] path Output buffer where to store the current directory
* @param[in] size Size of the output buffer
* @return Error code
**/
int ftpGetWorkingDir(FtpClientContext *context, char *path, uint32_t size)
{
int error;
uint32_t length;
uint32_t replyCode;
char *p;
//Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
// Check parameters
if (path == NULL || size == 0)
return ERROR_INVALID_PARAMETER;
// Send the command to the server
error = ftpSendCommand(context, "PWD\r\n", &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if (!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Search for the last double quote
p = strrchr((char*)context->buffer, '\"');
// Failed to parse the response?
if (!p) return ERROR_INVALID_SYNTAX;
// Split the string
*p = '\0';
// Search for the first double quote
p = strchr((char const*)context->buffer, '\"');
// Failed to parse the response?
if (!p) return ERROR_INVALID_SYNTAX;
// Retrieve the length of the working directory
length = strlen(p + 1);
// Limit the number of characters to copy
if (length > size - 1)
{
length = size - 1;
}
// Copy the string
strncpy(path, p + 1, length);
// Properly terminate the string with a NULL character
path[length] = '\0';
// Successful processing
return 0;
}
/**
* @brief Open a file for reading, writing, or appending
* @param[in] context Pointer to the FTP client context
* @param[in] path Path to the file to be be opened
* @param[in] flags Access mode
* @return Error code
**/
int ftpOpenFile(FtpClientContext *context, const char *path, uint32_t flags)
{
int error;
uint32_t replyCode;
uint32_t ipAddr;
uint16_t port;
NET_ERR err;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
context->dataSocket = NetSock_Open(NET_SOCK_ADDR_FAMILY_IP_V4, NET_SOCK_TYPE_STREAM, NET_SOCK_PROTOCOL_TCP, &err);
if ((err != NET_SOCK_ERR_NONE) || (context->dataSocket < 0))
{
return ERROR_OPEN_FAILED;
}
// Start of exception handling block
do
{
//Set representation type
if (flags & FTP_TEXT_TYPE)
{
// Use ASCII type
error = ftpSetType(context, 'A');
// Any error to report?
if(error) break;
}
else
{
// Use image type
error = ftpSetType(context, 'I');
// Any error to report?
if(error) break;
}
// Check transfer mode
if (!context->passiveMode)
{
NET_SOCK_ADDR_IP server_sock_addr_ip;
Mem_Clr((void*)&server_sock_addr_ip, (CPU_SIZE_T)sizeof(server_sock_addr_ip));
server_sock_addr_ip.Family = NET_SOCK_ADDR_FAMILY_IP_V4;
server_sock_addr_ip.Addr = NET_UTIL_HOST_TO_NET_32(NET_SOCK_ADDR_IP_WILD_CARD);
server_sock_addr_ip.Port = NET_UTIL_HOST_TO_NET_16(FTP_DATA_PORT);
NetSock_Bind(context->dataSocket, (NET_SOCK_ADDR *)&server_sock_addr_ip, (NET_SOCK_ADDR_LEN)NET_SOCK_ADDR_SIZE, (NET_ERR *)&err);
if (err != NET_SOCK_ERR_NONE)
{
NetSock_Close(context->dataSocket, &err);
context->dataSocket = -1;
return -1;
}
NetSock_Listen(context->dataSocket, 1, &err);
if (err != NET_SOCK_ERR_NONE)
{
NetSock_Close(context->dataSocket, &err);
context->dataSocket = -1;
return -1;
}
ipAddr = server_sock_addr_ip.Addr;
port = FTP_DATA_PORT;
// Set the port to be used in data connection
error = ftpSetPort(context, &ipAddr, port);
// Any error to report?
if (error) break;
}
else
{
NET_SOCK_ADDR_IP server_sock_addr_ip;
CPU_INT32U time_stamp;
// Enter passive mode
error = ftpSetPassiveMode(context, &port);
// Any error to report?
if (error) break;
memset(&server_sock_addr_ip, 0, sizeof(server_sock_addr_ip));
server_sock_addr_ip.Family = NET_SOCK_ADDR_FAMILY_IP_V4;
server_sock_addr_ip.Addr = NET_UTIL_HOST_TO_NET_32(context->serverAddr);
server_sock_addr_ip.Port = NET_UTIL_HOST_TO_NET_16(port);
time_stamp = OSTimeGet();
NetSock_Conn((NET_SOCK_ID)context->dataSocket, (NET_SOCK_ADDR *)&server_sock_addr_ip, (NET_SOCK_ADDR_LEN)sizeof(server_sock_addr_ip), &err);
while (NetSock_IsConn((NET_SOCK_ID)context->dataSocket, &err) != DEF_YES)
{
if ((err != NET_SOCK_ERR_CONN_IN_PROGRESS) && (err != NET_SOCK_ERR_NONE))
{
break;
}
if (OSTimeGet() - time_stamp > FTP_CLIENT_DEFAULT_TIMEOUT)
{
error = -1;
break;
}
OSTimeDly(2);
}
if ((err != NET_SOCK_ERR_NONE) || (error))
{
break;
}
}
// Format the command
if (flags & FTP_FOR_WRITING)
sprintf((char*)context->buffer, "STOR %s\r\n", path);
else if(flags & FTP_FOR_APPENDING)
sprintf((char*)context->buffer, "APPE %s\r\n", path);
else
sprintf((char*)context->buffer, "RETR %s\r\n", path);
// Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
// Any error to report?
if (error) break;
// Check FTP response code
if (!FTP_REPLY_CODE_1YZ(replyCode))
{
// Report an error
error = ERROR_UNEXPECTED_RESPONSE;
break;
}
// Check transfer mode
if (!context->passiveMode)
{
NET_SOCK_ADDR_IP client_sock_addr_ip;
NET_SOCK_ADDR_LEN client_sock_addr_ip_size = sizeof(client_sock_addr_ip);
NET_SOCK_ID newSockID;
CPU_BOOLEAN attempt_conn;
CPU_INT32U time_stamp;
time_stamp = OSTimeGet();
do
{
if (NetNIC_ConnStatusGet() != DEF_ON)
{
err = NET_SOCK_ERR_NONE_AVAIL;
break;
}
if (OSTimeGet() - time_stamp > FTP_CLIENT_DEFAULT_TIMEOUT)
{
err = NET_SOCK_ERR_CONN_SIGNAL_TIMEOUT;
break;
}
OSTimeDly(2);
newSockID = NetSock_Accept((NET_SOCK_ID )context->dataSocket, (NET_SOCK_ADDR *)&client_sock_addr_ip, (NET_SOCK_ADDR_LEN *)&client_sock_addr_ip_size, (NET_ERR *)&err);
switch (err)
{
case NET_SOCK_ERR_NONE:
attempt_conn = DEF_NO;
break;
case NET_ERR_INIT_INCOMPLETE:
case NET_SOCK_ERR_NULL_PTR:
case NET_SOCK_ERR_NONE_AVAIL:
case NET_SOCK_ERR_CONN_ACCEPT_Q_NONE_AVAIL:
case NET_SOCK_ERR_CONN_SIGNAL_TIMEOUT:
case NET_OS_ERR_LOCK:
attempt_conn = DEF_YES;
break;
default:
attempt_conn = DEF_NO;
break;
}
} while (attempt_conn == DEF_YES);
// No connection request?
if ((newSockID < 0) || (err != NET_SOCK_ERR_NONE))
{
// Report an error
error = -1;
break;
}
// Close the listening socket
NetSock_Close(context->dataSocket, &err);
context->dataSocket = newSockID;
}
// End of exception handling block
} while(0);
// Any error to report?
if (error)
{
// Clean up side effects
NetSock_Close(context->dataSocket, &err);
context->dataSocket = -1;
}
// Return status code
return error;
}
/**
* @brief Write to a remote file
* @param[in] context Pointer to the FTP client context
* @param[in] data Pointer to a buffer containing the data to be written
* @param[in] length Number of data bytes to write
* @param[in] flags Set of flags that influences the behavior of this function
* @return Error code
**/
int ftpWriteFile(FtpClientContext *context, const void *data, uint32_t length, uint32_t flags)
{
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
OSTimeDly(10);
if (HostWriteDataTimeout(context->dataSocket, (char*)data, length, FTP_CLIENT_WRITE_TIMEOUT) != length)
{
return -1;
}
OSTimeDly(20);
// Transmit data to the FTP server
return FTP_NO_ERROR;
}
/**
* @brief Close file
* @param[in] context Pointer to the FTP client context
* @return Error code
**/
int ftpCloseFile(FtpClientContext *context)
{
NET_ERR err;
int error;
uint32_t replyCode;
// Invalid context?
if (context == NULL)
return ERROR_INVALID_PARAMETER;
// Close the data socket
NetSock_Close(context->dataSocket, &err);
context->dataSocket = -1;
// Check the transfer status
error = ftpSendCommand(context, NULL, &replyCode);
// Any error to report?
if (error) return error;
// Check FTP response code
if (!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
// Successful processing
return FTP_NO_ERROR;
}
/**
* @brief Delete a file
* @param[in] context Pointer to the FTP client context
* @param[in] path Path to the file to be be deleted
* @return Error code
**/
int ftpDeleteFile(FtpClientContext *context, const char *path)
{
int error;
uint32_t replyCode;
//Invalid context?
if(context == NULL)
return ERROR_INVALID_PARAMETER;
//Format the DELE command
sprintf((char*)context->buffer, "DELE %s\r\n", path);
//Send the command to the server
error = ftpSendCommand(context, (char const*)context->buffer, &replyCode);
//Any error to report?
if(error) return error;
//Check FTP response code
if(!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
//Successful processing
return 0;
}
/**
* @brief Close the connection with the FTP server
* @param[in] context Pointer to the FTP client context
* @return Error code
**/
int ftpClose(FtpClientContext *context)
{
NET_ERR err;
// Invalid context?
if (context == NULL)
{
return ERROR_INVALID_PARAMETER;
}
// Close data socket
if (context->dataSocket >= 0)
{
NetSock_Close(context->dataSocket, &err);
context->dataSocket = -1;
}
// Close control socket
if (context->controlSocket)
{
NetSock_Close(context->controlSocket, &err);
context->controlSocket = -1;
}
// Successful processing
return 0;
}
/**
* @brief Send FTP command and wait for a reply
* @param[in] context Pointer to the FTP client context
* @param[in] command Command line
* @param[out] replyCode Response code from the FTP server
* @return Error code
**/
int ftpSendCommand(FtpClientContext *context, const char *command, uint32_t *replyCode)
{
int length;
char *p;
// Any command line to send?
if (command)
{
if (HostWriteDataTimeout(context->controlSocket, (char*)command, strlen(command), FTP_CLIENT_WRITE_TIMEOUT) != strlen(command))
{
return -1;
}
}
// Multiline replies are allowed for any command
while (1)
{
// Wait for a response from the server
NET_ERR err;
length = HostReadData(context->controlSocket, (char *)context->buffer, FTP_CLIENT_BUFFER_SIZE - 1, FTP_CLIENT_DEFAULT_TIMEOUT, &err);
if (length <= 0)
{
return ERROR_EMPTY_RECEIVE;
}
// Point to the beginning of the buffer
p = (char*)context->buffer;
// Properly terminate the string with a NULL character
p[length] = '\0';
// Remove trailing whitespace from the response
strRemoveTrailingSpace(p);
// Check the length of the response
if (strlen(p) >= 3)
{
// All replies begin with a three digit numeric code
if (isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2]))
{
// A space character follows the response code for the last line
if (p[3] == ' ' || p[3] == '\0')
{
// Get the server response code
*replyCode = strtoul(p, NULL, 10);
// Exit immediately
break;
}
}
}
}
// Successful processing
return 0;
}
/**
* @brief Change the current working directory of the FTP session
* @param[in] context Pointer to the FTP client context
* @param[in] path The new current working directory
* @return Error code
**/
int ftpChangeWorkingDir(FtpClientContext *context, const char *path)
{
int error;
uint32_t replyCode;
//Invalid context?
if(context == NULL)
return ERROR_INVALID_PARAMETER;
//Format the CWD command
sprintf((char*)context->buffer, "CWD %s\r\n", path);
//Send the command to the server
error = ftpSendCommand(context, (char*)context->buffer, &replyCode);
//Any error to report?
if(error) return error;
//Check FTP response code
if(!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
//Successful processing
return 0;
}
/**
* @brief Create a new directory
* @param[in] context Pointer to the FTP client context
* @param[in] path The name of the new directory
* @return Error code
**/
int ftpMakeDir(FtpClientContext *context, const char *path)
{
int error;
uint32_t replyCode;
//Invalid context?
if(context == NULL)
return ERROR_INVALID_PARAMETER;
//Format the MKD command
sprintf((char*)context->buffer, "MKD %s\r\n", path);
//Send the command to the server
error = ftpSendCommand(context, (char*)context->buffer, &replyCode);
//Any error to report?
if(error) return error;
//Check FTP response code
if(!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
//Successful processing
return 0;
}
/**
* @brief Change the current working directory to the parent directory
* @param[in] context Pointer to the FTP client context
* @return Error code
**/
int ftpChangeToParentDir(FtpClientContext *context)
{
int error;
uint32_t replyCode;
//Invalid context?
if(context == NULL)
return ERROR_INVALID_PARAMETER;
//Send the command to the server
error = ftpSendCommand(context, "CDUP\r\n", &replyCode);
//Any error to report?
if(error) return error;
//Check FTP response code
if(!FTP_REPLY_CODE_2YZ(replyCode))
return ERROR_UNEXPECTED_RESPONSE;
//Successful processing
return 0;
}