emFTP Client (Sample)
Jump to navigation
Jump to search
| IP_FTP_Client_Sample.c | |
|---|---|
| Components required |
|
| Requires modifications | Yes |
| Download | IP_FTP_Client_Sample.c |
This Sample demonstrates how to use the emFTP client in different configurations. Requires an FTP Server (e.g. emFTP Server embedded Sample, SEGGER FTPServer PC Application, or Filezilla FTP Server).
Some (necessary) configurations:
APP_USE_SSL : Enable/Disable SSL support.
FTP_HOST / FTP_PORT : Set FTP server address and Port.
FTP_USER / FTP_PASS : Set username and password for login.
Code
/*********************************************************************
* (c) SEGGER Microcontroller GmbH *
* The Embedded Experts *
* www.segger.com *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
Purpose : Demonstrates use of the emFTP client in different configurations.
Notes:
(1) The following requirements need to be met when including this
sample into another C-module:
- BSP_Init() for LED routines and IP_Init() for emNet are
called before starting APP_MainTask() .
- The optional IP_Connect() is called and a link state hook
is installed if necessary for the emNet interface used
before starting APP_MainTask() .
(2) The following application defines can be overwritten when
including this sample into another C-module:
- APP_MAIN_STACK_SIZE
Stack size in bytes to use for APP_MainTask() .
- APP_TASK_STACK_OVERHEAD
Additional task stack size [multiples of sizeof(int)] if some
underlying processes require more task stack than default.
- APP_USE_SSL
Enables/disables secure connection support.
(3) The following symbols can be used and renamed via preprocessor
if necessary when including this sample into another C-module:
- MainTask
Main starting point of the sample when used as a
standalone sample. Can be renamed to anything else to
avoid multiple MainTask symbols and to skip common
initializing steps done in every sample.
- APP_MainTask
Functional starting point of the sample itself. Typically
called by the MainTask in this sample. Should be renamed
via preprocessor to a more application specific name to
avoid having multiple APP_MainTask symbols in linker map
files for a better overview when including multiple samples
this way.
- APP_MainTCB
Task-Control-Block used for the APP_MainTask when started
as a task from the MainTask in this sample. Can/should be
renamed via preprocessor to a more application specific name.
- APP_MainStack
Task stack used for the APP_MainTask when started as a task
from the MainTask in this sample. Can/should be renamed via
preprocessor to a more application specific name.
Additional information:
The sample can easily be configured to start the emFTP Client
with or without SSL support, and to upload a specific file
when running a non-read-only filesystem on a storage medium.
Preparations:
Enable/disable the features that you want to use. The available
binary configuration switches are:
- APP_USE_SSL : Helper switch that enables/disables all SSL switches below.
- USE_FS_RO : Helper switch that enables/disables all read only filesystem switches below.
Additionally the following configuration defines are available:
- SERVER_ROOT_DIR : Specify the user's root directory on the server (with trailing slash).
- SERVER_TEST_DIR_NOSL : Specify the test directory to create/delete on the server (without trailing slash).
- FTP_HOST : Specify the host the FTP server is running on.
- FTP_PORT : Specify the port and thus whether to use explicit or implicit mode.
- FTP_USER : Specify the username to use to log into the server.
- FTP_PASS : Specify the password to use to log into the server.
Expected behavior:
This sample starts an FTP client, which connects to a
specified FTP Server and tries to perform various actions
like creating a directory, uploading a file,
and listing the current working directory.
Example output:
...
4:072 FTPClient - APP: Connect to server "ftptest@192.168.11.231:21".
4:073 FTPClient - APP: Connected to 192.168.11.231, port 21.
4:075 FTPClient - FTP command: FEAT
4:077 FTPClient - FTP command: USER ftptest
4:078 FTPClient - FTP command: PASS Secret123
4:088 FTPClient - FTP command: SYST
4:088 FTPClient - FTP command: PWD
4:089 FTPClient - FTP command: TYPE A
4:090 FTPClient - APP: Creating directory "Test".
4:090 FTPClient - FTP command: MKD Test
4:091 FTPClient - APP: --- Test with old API that needs to CWD into the directory to operate within.
4:091 FTPClient - APP: Changing to directory "/home/ftptest/Test/".
4:091 FTPClient - FTP command: CWD /home/ftptest/Test/
4:092 FTPClient - APP: List directory (before upload):
4:092 FTPClient - FTP command: LIST
4:092 FTPClient - FTP command: PASV
4:094 FTPClient - APP: Connected to 192.168.11.231, port 34763.
4:095 FTPClient -
4:095 FTPClient - APP: Uploading "index.htm".
4:096 FTPClient - FTP command: TYPE I
4:096 FTPClient - FTP command: PASV
4:097 FTPClient - APP: Connected to 192.168.11.231, port 39997.
4:097 FTPClient - FTP command: STOR index.htm
4:099 FTPClient - APP: List directory (after upload):
4:099 FTPClient - FTP command: LIST
4:099 FTPClient - FTP command: PASV
4:100 FTPClient - APP: Connected to 192.168.11.231, port 44117.
4:102 FTPClient - -rw-r--r-- 1 ftptest ftptest 629 May 12 09:16 index.htm
4:102 FTPClient - APP: Appending 45 bytes: "This has been added using the APPEnd command.".
4:103 FTPClient - FTP command: TYPE I
4:103 FTPClient - FTP command: PASV
4:105 FTPClient - APP: Connected to 192.168.11.231, port 37429.
4:105 FTPClient - FTP command: APPE index.htm
4:106 FTPClient - APP: List directory (after append):
4:106 FTPClient - FTP command: LIST
4:107 FTPClient - FTP command: PASV
4:108 FTPClient - APP: Connected to 192.168.11.231, port 40449.
4:109 FTPClient - -rw-r--r-- 1 ftptest ftptest 674 May 12 09:16 index.htm
4:109 FTPClient - APP: Deleting "index.htm".
4:110 FTPClient - FTP command: DELE index.htm
4:110 FTPClient - APP: List directory (after delete):
4:110 FTPClient - FTP command: LIST
4:111 FTPClient - FTP command: PASV
4:112 FTPClient - APP: Connected to 192.168.11.231, port 41055.
4:113 FTPClient -
4:113 FTPClient - APP: Changing to parent directory.
4:114 FTPClient - FTP command: CDUP
4:114 FTPClient - APP: --- Test with new API using IP_FTPC_CMD_CONFIG that can operate regardless of current directory.
4:114 FTPClient - APP: List directory (before upload, old API with path parameter):
4:114 FTPClient - FTP command: LIST /home/ftptest/Test/
4:115 FTPClient - FTP command: PASV
4:116 FTPClient - APP: Connected to 192.168.11.231, port 34945.
4:117 FTPClient -
4:117 FTPClient - APP: Uploading "index.htm" to "/home/ftptest/Test/Test.txt" (new API).
4:118 FTPClient - FTP command: TYPE I
4:118 FTPClient - FTP command: PASV
4:119 FTPClient - APP: Connected to 192.168.11.231, port 36859.
4:120 FTPClient - FTP command: STOR /home/ftptest/Test/Test.txt
4:121 FTPClient - APP: List directory (after upload, new API):
4:121 FTPClient - FTP command: LIST /home/ftptest/Test/
4:122 FTPClient - FTP command: PASV
4:123 FTPClient - APP: Connected to 192.168.11.231, port 46065.
4:125 FTPClient - -rw-r--r-- 1 ftptest ftptest 629 May 12 09:16 Test.txt
4:125 FTPClient - APP: Deleting "/home/ftptest/Test/Test.txt" (new API).
4:125 FTPClient - FTP command: DELE /home/ftptest/Test/Test.txt
4:126 FTPClient - APP: List directory (after delete, new API):
4:126 FTPClient - FTP command: LIST /home/ftptest/Test/
4:126 FTPClient - FTP command: PASV
4:127 FTPClient - APP: Connected to 192.168.11.231, port 44047.
4:129 FTPClient -
4:129 FTPClient - APP: Delete directory "/home/ftptest/Test/" (new API).
4:129 FTPClient - FTP command: RMD /home/ftptest/Test/
4:130 FTPClient - APP: Done.
*/
#include "RTOS.h"
#include "FS.h"
#include "BSP.h"
#include "IP.h"
#include "TaskPrio.h"
#include "IP_FTPC.h"
#include "SEGGER.h"
/*********************************************************************
*
* Defines, configurable
*
**********************************************************************
*/
#ifndef FTPC_STRLEN
#define FTPC_STRLEN strlen
#endif
#ifndef FTPC_MEMSET
#define FTPC_MEMSET memset
#endif
#ifndef USE_RX_TASK
#define USE_RX_TASK 0 // 0: Packets are read in ISR, 1: Packets are read in a task of its own.
#endif
//
// Sample features enable/disable
//
#ifndef APP_USE_SSL
#define APP_USE_SSL (0) // Use SSL to enable support for secure connections.
#endif
#define SERVER_ROOT_DIR "/" // Specify the user's root directory on the server (with trailing slash). For most users this will be "/".
#define SERVER_TEST_DIR_NOSL "Test" // Specify the test directory to create/delete in the user's root directory on the server (without trailing slash!!!).
#define SERVER_TEST_DIR SERVER_TEST_DIR_NOSL "/" // The test directory name is used with the old API as the name alone (without trailing SLash).
#define USE_FS_RO 1 // Use a read-only filesystem. This runs only commands in this sample that do not require writing to a local FS.
#if (USE_FS_RO == 0)
#define UPLOAD_FS IP_FS_FS
#define UPLOAD_FILENAME_OLD_API "Readme.txt" // Filename present in the root folder on the (real) FS, used with USE_FS_RO disabled.
#define UPLOAD_PATH_LOCAL "Readme.txt" // Local filename/path on the (real) FS, used with USE_FS_RO disabled.
#define UPLOAD_PATH_REMOTE SERVER_ROOT_DIR SERVER_TEST_DIR "Test.txt" // Remote filename/path to upload to , used with USE_FS_RO disabled.
#else
#define UPLOAD_FS IP_FS_ReadOnly
#define UPLOAD_FILENAME_OLD_API "index.htm" // Filename present in the root folder on the read-only FS, used with USE_FS_RO enabled.
#define UPLOAD_PATH_LOCAL "index.htm" // Local filename/path on the read-only FS, used with USE_FS_RO enabled.
#define UPLOAD_PATH_REMOTE SERVER_ROOT_DIR SERVER_TEST_DIR "Test.txt" // Remote filename/path to upload to , used with USE_FS_RO enabled.
#endif
#if APP_USE_SSL
#include "SSL.h"
#endif
#define FTP_HOST "192.168.11.122"
//
// Choose between FTPS implicit mode server using port 990
// or FTPS explicit mode and plain mode server using port 21.
//
#define FTP_PORT 21 // FTP plain / FTPS explicit.
//#define FTP_PORT 990 // FTP implicit
#define FTP_USER "Admin"
#define FTP_PASS "Secret"
//
// Task stack sizes that might not fit for some interfaces (multiples of sizeof(int)).
//
#ifndef APP_MAIN_STACK_SIZE
#if (APP_USE_SSL != 0)
#define APP_MAIN_STACK_SIZE (6144)
#else
#define APP_MAIN_STACK_SIZE (4096)
#endif
#endif
#ifndef APP_TASK_STACK_OVERHEAD
#define APP_TASK_STACK_OVERHEAD (0)
#endif
#ifndef FTPC_STACK_SIZE
#define FTPC_STACK_SIZE (APP_MAIN_STACK_SIZE + APP_TASK_STACK_OVERHEAD)
#endif
/*********************************************************************
*
* Data types
*
**********************************************************************
*/
typedef struct {
unsigned PlainSocket;
#if APP_USE_SSL
unsigned IsSecure;
unsigned MustResume;
SSL_SESSION Session;
SSL_SESSION_RESUME_PARAS ResumeParas;
const char* sName;
#endif
} FTPS_APP_CONTEXT;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
#ifdef __cplusplus
extern "C" { /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif
/*********************************************************************
*
* static data
*
**********************************************************************
*/
static const char* sAppendTest = "This has been added using the APPEnd command.";
static FTPS_APP_CONTEXT _aContext[3]; // Connecting to one server requires two connections/sessions (command and data).
// If active mode is used, an additional socket which waits for a connection is required.
// If only passive mode is used, the number of available contexts can be set to 2.
#if APP_USE_SSL
//
// SSL transport API.
//
static const SSL_TRANSPORT_API _IP_Transport = {
send,
recv,
NULL, // Don't verify the time. Otherwise a function that returns the unix time is needed.
NULL
};
#endif
static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;
//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int APP_MainStack[FTPC_STACK_SIZE / sizeof(int)]; // Stack of the starting point of this sample.
static OS_TASK APP_MainTCB; // Task-Control-Block of the IP_Task.
static OS_STACKPTR int _IPStack[TASK_STACK_SIZE_IP_TASK/sizeof(int)]; // Stack of the IP_Task.
static OS_TASK _IPTCB; // Task-Control-Block of the IP_Task.
#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)]; // Stack of the IP_RxTask.
static OS_TASK _IPRxTCB; // Task-Control-Block of the IP_RxTask.
#endif
/*********************************************************************
*
* static code
*
**********************************************************************
*/
/*********************************************************************
*
* _OnStateChange()
*
* Function description
* Callback that will be notified once the state of an interface
* changes.
*
* Parameters
* IFaceId : Zero-based interface index.
* AdminState: Is this interface enabled ?
* HWState : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
//
// Check if this is a disconnect from the peer or a link down.
// In this case call IP_Disconnect() to get into a known state.
//
if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) || // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
((AdminState == IP_ADMIN_STATE_UP) && (HWState == 0))) { // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
IP_Disconnect(IFaceId); // Disconnect the interface to a clean state.
}
}
/*********************************************************************
*
* _IsIPAddress()
*
* Function description
* Checks if string is a dot-decimal IP address, for example 192.168.1.1
*/
static unsigned _IsIPAddress(const char * sIPAddr) {
unsigned NumDots;
unsigned i;
char c;
NumDots = 0;
i = 0;
while (1) {
c = *(sIPAddr + i);
if ((c >= '0') && (c <= '9')) {
goto Loop;
}
if (c == '.') {
NumDots++;
goto Loop;
}
if (c == '\0') {
if ((NumDots < 3) || (i > 15)) { // Error, every dot-decimal IP address includes 3 '.' and is not longer than 15 characters.
Error:
return 0;
}
return 1;
} else {
goto Error;
}
Loop:
i++;
}
}
/*********************************************************************
*
* _ParseIPAddr()
*
* Function description
* Parses a string for a dot-decimal IP address and returns the
* IP as 32-bit number in host endianess.
*/
static long _ParseIPAddr(const char * sIPAddr) {
long IPAddr;
unsigned Value;
unsigned NumDots;
unsigned i;
unsigned j;
char acDigits[4];
char c;
IPAddr = 0;
//
// Check if string is a valid IP address
//
Value = _IsIPAddress(sIPAddr);
if (Value) {
//
// Parse the IP address
//
NumDots = 3;
i = 0;
j = 0;
while (1) {
c = *(sIPAddr + i);
if (c == '\0') {
//
// Add the last byte of the IP address.
//
acDigits[j] = '\0';
Value = SEGGER_atoi(acDigits);
if (Value < 255) {
IPAddr |= Value;
}
return IPAddr; // O.K., string completely parsed. Returning IP address.
}
//
// Parse the first three byte of the IP address.
//
if (c != '.') {
acDigits[j] = c;
j++;
} else {
acDigits[j] = '\0';
Value = SEGGER_atoi(acDigits);
if (Value <= 255) {
IPAddr |= (Value << (NumDots * 8));
NumDots--;
j = 0;
} else {
return -1; // Error, illegal number in IP address.
}
}
i++;
}
}
return -1;
}
/*********************************************************************
*
* _AllocContext()
*
* Function description
* Retrieves the next free context memory block to use.
*
* Parameters
* hSock: Socket handle.
*
* Return value
* != NULL: Free context found, pointer to context.
* == NULL: Error, no more free entries.
*/
static FTPS_APP_CONTEXT* _AllocContext(long hSock) {
FTPS_APP_CONTEXT* pContext;
FTPS_APP_CONTEXT* pRunner;
unsigned i;
pContext = NULL;
i = 0;
OS_DI();
pRunner = &_aContext[0];
do {
if (pRunner->PlainSocket == 0) {
#if APP_USE_SSL
if (pRunner->Session.Socket == 0)
#endif
{
FTPC_MEMSET(pRunner, 0, sizeof(FTPS_APP_CONTEXT));
pRunner->PlainSocket = hSock; // Mark the entry to be in use.
pContext = pRunner;
break;
}
}
pRunner++;
i++;
} while (i < SEGGER_COUNTOF(_aContext));
OS_EI();
return pContext;
}
/*********************************************************************
*
* _FreeContext()
*
* Function description
* Frees a context memory block.
*
* Parameters
* pContext: Connection context.
*/
static void _FreeContext(FTPS_APP_CONTEXT* pContext) {
FTPC_MEMSET(pContext, 0, sizeof(*pContext));
}
#if APP_USE_SSL
/*********************************************************************
*
* _UpgradeOnDemand()
*
* Function description
* Upgrades a connection to a secured one.
*
* Parameters
* pContext: Connection context.
*
* Return value
* == 0: Success.
* < 0: Error.
*/
static int _UpgradeOnDemand(FTPS_APP_CONTEXT* pContext) {
int Status;
Status = 0;
if ((pContext->IsSecure != 0) && (pContext->PlainSocket != 0) && (pContext->Session.Socket == 0)) {
SSL_SESSION_Prepare(&pContext->Session, pContext->PlainSocket, &_IP_Transport);
if (pContext->MustResume != 0) {
SSL_SESSION_SetResumeParas(&pContext->Session, &pContext->ResumeParas); // Bind data connection to control connection
}
Status = SSL_SESSION_Connect(&pContext->Session, pContext->sName);
if (Status < 0) {
closesocket(pContext->PlainSocket);
FTPC_APP_LOG(("APP: Could not upgrade to secure."));
} else {
if (pContext->MustResume) {
if (SSL_SESSION_QueryFlags(&pContext->Session) & SSL_SESSION_FLAG_RESUME_GRANTED) {
FTPC_APP_LOG(("APP: Session was successfully resumed"));
} else {
FTPC_APP_LOG(("APP: Session was not resumed -- new session ID provided by server"));
}
}
FTPC_APP_LOG(("APP: Secured using %s.", SSL_SUITE_GetIANASuiteName(SSL_SUITE_GetID(SSL_SESSION_GetSuite(&pContext->Session)))));
}
pContext->PlainSocket = 0;
}
return Status;
}
#endif
/*********************************************************************
*
* _Connect
*
* Function description
* Creates a socket and opens a TCP connection to the FTP host.
*/
static FTPC_SOCKET _Connect(const char * sSrvAddr, unsigned SrvPort) {
struct sockaddr_in sin;
FTPS_APP_CONTEXT* pContext;
U32 Ip;
long Sock;
int Error;
int r;
if (_IsIPAddress(sSrvAddr)) {
Ip = _ParseIPAddr(sSrvAddr);
Ip = htonl(Ip);
} else {
//
// Convert host into IP address
//
r = IP_ResolveHost(sSrvAddr, &Ip, 10000); // Resolve host name.
if (r != 0) {
IP_Logf_Application("Could not resolve host addr. for \"%s\"", sSrvAddr);
return NULL;
}
}
//
// Create socket and connect to the FTP server
//
Sock = socket(AF_INET, SOCK_STREAM, 0);
if(Sock == -1) {
FTPC_APP_LOG(("APP: Could not create socket!" ));
return NULL;
}
FTPC_MEMSET(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SrvPort);
sin.sin_addr.s_addr = Ip;
r = connect(Sock, (struct sockaddr*)&sin, sizeof(sin));
if(r == SOCKET_ERROR) {
getsockopt((long)Sock, AF_INET, SO_ERROR, &Error, sizeof(Error));
closesocket(Sock);
FTPC_APP_LOG(("APP: \nSocket error: %s", IP_Err2Str(Error)));
return NULL;
}
//
// Alloc context.
//
pContext = _AllocContext(Sock);
if (pContext == NULL) {
closesocket(Sock);
FTPC_APP_LOG(("APP: Could not allocate context!" ));
return NULL;
}
#if APP_USE_SSL
pContext->sName = sSrvAddr;
#endif
FTPC_APP_LOG(("APP: Connected to %y, port %d.", Ip, SrvPort));
return (FTPC_SOCKET)pContext;
}
/*********************************************************************
*
* _Disconnect
*
* Function description
* Closes a socket.
*/
static void _Disconnect(FTPC_SOCKET Socket) {
FTPS_APP_CONTEXT* pContext;
pContext = (FTPS_APP_CONTEXT*)Socket;
if (pContext != NULL) {
#if APP_USE_SSL
//
// Check if the connection is marked as a secured connection and check if it has been used so far.
// If the connection was meant to be used secured but has not been used at all (no prepare and connect),
// simply disconnect the plain socket.
//
if ((pContext->IsSecure != 0) && (SSL_SESSION_GetSuite(&pContext->Session) != NULL)) {
SSL_SESSION_Disconnect(&pContext->Session);
closesocket(pContext->Session.Socket);
}
else
#endif
{
if (pContext->PlainSocket != 0) {
closesocket(pContext->PlainSocket);
}
}
_FreeContext(pContext);
}
}
/*********************************************************************
*
* _Send
*
* Function description
* Sends data via socket interface.
*/
static int _Send(const char * buf, int len, void * pConnectionInfo) {
FTPS_APP_CONTEXT* pContext;
int Status;
pContext = (FTPS_APP_CONTEXT*)pConnectionInfo;
#if APP_USE_SSL
_UpgradeOnDemand(pContext);
if (pContext->IsSecure != 0) {
Status = SSL_SESSION_Send(&pContext->Session, buf, len);
}
else
#endif
{
Status = send(pContext->PlainSocket, buf, len, 0);
}
//
return Status;
}
/*********************************************************************
*
* _Recv
*
* Function description
* Receives data via socket interface.
*/
static int _Recv(char * buf, int len, void * pConnectionInfo) {
FTPS_APP_CONTEXT* pContext;
int Status;
pContext = (FTPS_APP_CONTEXT*)pConnectionInfo;
#if APP_USE_SSL
_UpgradeOnDemand(pContext);
if (pContext->IsSecure != 0) {
do {
Status = SSL_SESSION_Receive(&pContext->Session, buf, len);
} while (Status == 0); // Receiving 0 bytes means something different on a plain socket.
//
// Translate EOF into "connection closed",
// the FTP Server expects same return values as with recv().
//
if (Status == SSL_ERROR_EOF) {
Status = 0;
}
}
else
#endif
{
Status = recv(pContext->PlainSocket, buf, len, 0);
}
//
return Status;
}
#if APP_USE_SSL
/*********************************************************************
*
* _SetSecure
*
* Function description
* Configure the socket as secured.
*/
static int _SetSecure(FTPC_SOCKET Socket, FTPC_SOCKET Clone) {
FTPS_APP_CONTEXT* pSocket;
FTPS_APP_CONTEXT* pClone;
//
pSocket = (FTPS_APP_CONTEXT*)Socket;
//
// Check if we have a valid socket.
// The FTP client prior V3.50.5 calls this routine even
// if the connect to the server was not successful.
//
if (pSocket != NULL) {
pClone = (FTPS_APP_CONTEXT*)Clone;
//
pSocket->IsSecure = 1;
if (pClone != NULL) {
pSocket->MustResume = 1;
} else {
pSocket->MustResume = 0;
}
if (pClone != NULL) {
SSL_SESSION_GetResumeParas(&pClone->Session, &pSocket->ResumeParas);
}
}
//
return 0;
}
#endif
/*********************************************************************
*
* _Listen()
*
* Function description
* This function is called from the FTP client module if active
* FTP should be used. It creates a socket and starts listening
* on the port.
*
* If active FTP should not be used, this function can be removed.
*
* Parameter
* CtrlSocket - Control socket
* pPort - [Out] Port number of the data connection.
* pIPAddr - [Out] IP address of the client.
*
* Return value
* > 0 Socket descriptor
* NULL Error
*/
static FTPC_SOCKET _Listen(FTPC_SOCKET CtrlSocket, U8* pIPAddr, U16* pPort) {
FTPS_APP_CONTEXT* pCtrlContext;
FTPS_APP_CONTEXT* pDataContext;
struct sockaddr_in CtrlSockAddrIn;
struct sockaddr_in DataSockAddrIn;
long DataSock;
long CtrlSock;
U32 IPAddr;
int AddrSize;
int r;
pDataContext = NULL;
pCtrlContext = (FTPS_APP_CONTEXT*)CtrlSocket;
//
// Create listening socket for the data channel.
//
if (pCtrlContext != NULL) {
#if APP_USE_SSL
//
// SSL
//
if (pCtrlContext->IsSecure != 0) {
CtrlSock = pCtrlContext->Session.Socket;
} else
#endif
{
CtrlSock = pCtrlContext->PlainSocket;
}
FTPC_MEMSET(&CtrlSockAddrIn, 0, sizeof(CtrlSockAddrIn));
AddrSize = sizeof(CtrlSockAddrIn);
r = getsockname(CtrlSock, (struct sockaddr*)&CtrlSockAddrIn, &AddrSize);
if (r != SOCKET_ERROR) {
DataSock = socket(AF_INET, SOCK_STREAM, 0); // Create a new socket for data connection to the client
if(DataSock != SOCKET_ERROR) { // Socket created ?
//
// Bind a socket to the data port (data port: port number of the control socket+1)
// and set into listening mode.
//
FTPC_MEMSET(&DataSockAddrIn, 0, sizeof(DataSockAddrIn));
DataSockAddrIn.sin_family = AF_INET;
DataSockAddrIn.sin_port = 0; // Let Stack find a free port.
DataSockAddrIn.sin_addr.s_addr = INADDR_ANY;
bind(DataSock, (struct sockaddr*)&DataSockAddrIn, sizeof(DataSockAddrIn));
listen(DataSock, 1);
//
// Allocate a FTP context.
//
pDataContext = _AllocContext(DataSock);
if (pDataContext == NULL) {
closesocket(DataSock);
} else {
FTPC_MEMSET(&DataSockAddrIn, 0, sizeof(DataSockAddrIn));
AddrSize = sizeof(DataSockAddrIn);
getsockname(DataSock , (struct sockaddr*)&DataSockAddrIn, &AddrSize);
*pPort = ntohs(DataSockAddrIn.sin_port);
IPAddr = ntohl(CtrlSockAddrIn.sin_addr.s_addr); // Load to host endianess.
SEGGER_WrU32BE(pIPAddr, IPAddr); // Save from host endianess to network endiness.
}
}
}
}
return (FTPC_SOCKET)pDataContext;
}
/*********************************************************************
*
* _Accept()
*
* Function description
* This function is called from the FTP client module if active
* FTP should be used. It waits for the connection of the server
* to the data port.
*
* If active FTP should not be used, this function can be removed.
*
* Parameter
* CtrlSocket - Control socket
* ListenSock - Listen socket to use for the data channel operations.
* pPort - [Out] Port used by data connection.
*
* Return value
* > 0 Socket descriptor
* NULL Error
*/
static FTPC_SOCKET _Accept(FTPC_SOCKET CtrlSocket, FTPC_SOCKET ListenSock, U16* pPort) {
FTPS_APP_CONTEXT* pDataListenContext;
FTPS_APP_CONTEXT* pDataContext;
struct sockaddr_in DataSockAddrIn;
long hSockListen;
long DataSock;
int SoError;
int t0;
int t;
struct sockaddr Addr;
int AddrSize;
int Opt;
int r;
(void)CtrlSocket;
r = 0;
AddrSize = sizeof(Addr);
pDataListenContext = (FTPS_APP_CONTEXT*)ListenSock;
#if APP_USE_SSL
if (pDataListenContext->PlainSocket == 0) {
hSockListen = pDataListenContext->Session.Socket;
}
else
#endif
{
hSockListen = pDataListenContext->PlainSocket;
}
//
// Set command socket non-blocking.
//
Opt = 1;
setsockopt(hSockListen, SOL_SOCKET, SO_NONBLOCK, &Opt, sizeof(Opt));
t0 = IP_OS_GET_TIME();
do {
DataSock = accept(hSockListen, &Addr, &AddrSize);
if ((DataSock != SOCKET_ERROR) && (DataSock != 0)) {
//
// Connection accepted. Close listening socket.
//
closesocket(hSockListen);
_FreeContext(pDataListenContext);
//
// Set data socket blocking. The data socket inherits the blocking
// mode from the socket that was used as parameter for accept().
// Therefore, we have to set it blocking after creation.
//
Opt = 0;
setsockopt(DataSock, SOL_SOCKET, SO_NONBLOCK, &Opt, sizeof(Opt));
//
// SO_KEEPALIVE is required to guarantee that the socket will be
// closed even if the client has lost the connection to the server
// before it closes the connection.
//
Opt = 1;
setsockopt(DataSock, SOL_SOCKET, SO_KEEPALIVE, &Opt, sizeof(Opt));
//
// Allocate a FTP context.
//
pDataContext = _AllocContext(DataSock);
if (pDataContext == NULL) {
closesocket(DataSock);
} else {
//
// Get connection information.
//
FTPC_MEMSET(&DataSockAddrIn, 0, sizeof(DataSockAddrIn));
r = getsockname(DataSock , (struct sockaddr*)&DataSockAddrIn, &AddrSize);
if (r != SOCKET_ERROR) {
FTPC_APP_LOG(("APP: Data connection established on local port: %lu\r\n", ntohs(DataSockAddrIn.sin_port)));
}
*pPort = ntohs(DataSockAddrIn.sin_port);
}
return (FTPC_SOCKET)pDataContext; // Successfully connected
}
//
// Handle socket error.
//
getsockopt(hSockListen, SOL_SOCKET, SO_ERROR, &SoError, sizeof(SoError));
if (SoError != IP_ERR_WOULD_BLOCK) {
closesocket(hSockListen);
_FreeContext(pDataListenContext);
FTPC_APP_LOG(("APP: \nSocket error: %s", IP_Err2Str(SoError)));
return NULL; // Not in progress and not successful, error...
}
//
// Wait for connection (max. 1 second)
//
t = IP_OS_GET_TIME() - t0;
if (t >= 1000) {
closesocket(hSockListen);
_FreeContext(pDataListenContext);
return NULL;
}
OS_Delay(1); // Give lower prior tasks some time
} while (1);
}
//
// IP API.
//
static const IP_FTPC_API _IP_Api = {
_Connect,
_Disconnect,
_Send,
_Recv,
#if APP_USE_SSL
_SetSecure,
#else
NULL,
#endif
_Listen,
_Accept
};
#if (USE_FS_RO == 0)
/*********************************************************************
*
* _FSTest
*
* Function description
* Initializes the file system and creates a test file on storage medium
*/
static void _FSTest(void) {
FS_FILE* pFile;
unsigned Len;
const char* sInfo = "SEGGER emFTP client.\r\nFor further information please visit: www.segger.com\r\n";
FS_Init();
Len = FTPC_STRLEN(sInfo);
if (FS_IsLLFormatted("") == 0) {
FTPC_APP_LOG(("Low level formatting"));
FS_FormatLow(""); // Erase & Low-level format the volume
}
if (FS_IsHLFormatted("") == 0) {
FTPC_APP_LOG(("High level formatting\n"));
FS_Format("", NULL); // High-level format the volume
}
pFile = FS_FOpen(UPLOAD_FILENAME_OLD_API, "w");
FS_Write(pFile, sInfo, Len);
FS_FClose(pFile);
FS_Unmount("");
}
#endif
/*********************************************************************
*
* _FTPClientTask
*
*/
static void _FTPClientTask(void) {
IP_FTPC_CMD_CONFIG Config;
IP_FTPC_CONTEXT FTPConnection;
U32 acCtrlIn[FTPC_CTRL_BUFFER_SIZE / sizeof(U32)]; // U32 to make sure we have a word alignment.
U32 acDataIn[FTPC_BUFFER_SIZE / sizeof(U32)]; // U32 to make sure we have a word alignment.
U32 acDataOut[FTPC_BUFFER_SIZE / sizeof(U32)]; // U32 to make sure we have a word alignment.
unsigned Mode;
int r;
FTPC_MEMSET(&Config, 0, sizeof(Config));
//
// Initialize FTP client context
//
FTPC_MEMSET(&FTPConnection, 0, sizeof(FTPConnection));
//
// Initialize the FTP client
//
IP_FTPC_Init(&FTPConnection, &_IP_Api, &UPLOAD_FS, (U8*)acCtrlIn, sizeof(acCtrlIn), (U8*)acDataIn, sizeof(acDataIn), (U8*)acDataOut, sizeof(acDataOut));
//
// Connect to the FTP server
//
Mode = FTPC_MODE_PASSIVE;
// Mode = FTPC_MODE_ACTIVE;
#if APP_USE_SSL
#if FTP_PORT == 990
Mode |= FTPC_MODE_IMPLICIT_TLS_REQUIRED;
#else
Mode |= FTPC_MODE_EXPLICIT_TLS_REQUIRED;
#endif
#endif
FTPC_APP_LOG(("APP: Connect to server \"%s@%s:%u\".", FTP_USER, FTP_HOST, FTP_PORT));
r = IP_FTPC_Connect(&FTPConnection, FTP_HOST, FTP_USER, FTP_PASS, FTP_PORT, Mode);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not connect to FTP server."));
goto Disconnect;
}
//
// Create the test directory
//
FTPC_APP_LOG(("APP: Creating directory \"%s\".", SERVER_TEST_DIR_NOSL));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_MKD, SERVER_TEST_DIR_NOSL);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not create directory."));
goto Disconnect;
}
FTPC_APP_LOG(("APP: --- Test with old API that needs to CWD into the directory to operate within."));
//
// Change from root directory into test directory
//
FTPC_APP_LOG(("APP: Changing to directory \"%s\".", SERVER_ROOT_DIR SERVER_TEST_DIR));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_CWD, SERVER_ROOT_DIR SERVER_TEST_DIR);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not change working directory."));
goto Disconnect;
}
//
// List directory content (before upload)
//
FTPC_APP_LOG(("APP: List directory (before upload):"));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_LIST, NULL);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Upload a file
//
FTPC_APP_LOG(("APP: Uploading \"%s\".", UPLOAD_FILENAME_OLD_API));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_STOR, UPLOAD_FILENAME_OLD_API);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not upload data file."));
goto Disconnect;
}
//
// List directory content (after upload)
//
FTPC_APP_LOG(("APP: List directory (after upload):"));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_LIST, NULL);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Try to append something to the end of the file using
// input from a buffer instead of a file. Can be used to
// append log data to a remote file.
//
Config.sPara = UPLOAD_FILENAME_OLD_API;
Config.pData = (U8*)sAppendTest;
Config.NumBytes = FTPC_STRLEN(sAppendTest);
FTPC_APP_LOG(("APP: Appending %u bytes: \"%s\".", Config.NumBytes, sAppendTest));
r = IP_FTPC_ExecCmdEx(&FTPConnection, FTPC_CMD_APPE, &Config);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not append to data file."));
goto Disconnect;
}
Config.pData = NULL; // No need to memset the whole structure but this would override further uploads from FS in this sample.
//
// List directory content (after append)
//
FTPC_APP_LOG(("APP: List directory (after append):"));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_LIST, NULL);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Delete the file
//
FTPC_APP_LOG(("APP: Deleting \"%s\".", UPLOAD_FILENAME_OLD_API));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_DELE, UPLOAD_FILENAME_OLD_API);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not delete data file."));
goto Disconnect;
}
//
// List directory content
//
FTPC_APP_LOG(("APP: List directory (after delete):"));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_LIST, NULL);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Change back to root directory.
//
FTPC_APP_LOG(("APP: Changing to parent directory."));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_CDUP, NULL);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Change to parent directory failed."));
goto Disconnect;
}
FTPC_APP_LOG(("APP: --- Test with new API using IP_FTPC_CMD_CONFIG that can operate regardless of current directory."));
//
// List directory content (before upload)
//
FTPC_APP_LOG(("APP: List directory (before upload, old API with path parameter):"));
r = IP_FTPC_ExecCmd(&FTPConnection, FTPC_CMD_LIST, SERVER_ROOT_DIR SERVER_TEST_DIR);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Upload a file
//
FTPC_APP_LOG(("APP: Uploading \"%s\" to \"%s\" (new API).", UPLOAD_PATH_LOCAL, UPLOAD_PATH_REMOTE));
Config.sLocalPath = UPLOAD_PATH_LOCAL;
Config.sRemotePath = UPLOAD_PATH_REMOTE;
r = IP_FTPC_ExecCmdEx(&FTPConnection, FTPC_CMD_STOR, &Config);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not upload data file."));
goto Disconnect;
}
//
// List directory content (after upload)
//
FTPC_APP_LOG(("APP: List directory (after upload, new API):"));
Config.sRemotePath = SERVER_ROOT_DIR SERVER_TEST_DIR;
r = IP_FTPC_ExecCmdEx(&FTPConnection, FTPC_CMD_LIST, &Config);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Delete the file
//
FTPC_APP_LOG(("APP: Deleting \"%s\" (new API).", UPLOAD_PATH_REMOTE));
Config.sRemotePath = UPLOAD_PATH_REMOTE;
r = IP_FTPC_ExecCmdEx(&FTPConnection, FTPC_CMD_DELE, &Config);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not delete data file."));
goto Disconnect;
}
//
// List directory content
//
FTPC_APP_LOG(("APP: List directory (after delete, new API):"));
Config.sRemotePath = SERVER_ROOT_DIR SERVER_TEST_DIR;
r = IP_FTPC_ExecCmdEx(&FTPConnection, FTPC_CMD_LIST, &Config);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not list directory."));
goto Disconnect;
}
FTPC_APP_LOG(("%s", acDataIn));
//
// Delete the test directory
//
FTPC_APP_LOG(("APP: Delete directory \"%s\" (new API).", SERVER_ROOT_DIR SERVER_TEST_DIR));
Config.sRemotePath = SERVER_ROOT_DIR SERVER_TEST_DIR;
r = IP_FTPC_ExecCmdEx(&FTPConnection, FTPC_CMD_RMD, &Config);
if (r == FTPC_ERROR) {
FTPC_APP_LOG(("APP: Could not delete directory."));
goto Disconnect;
}
//
// Disconnect.
//
Disconnect:
IP_FTPC_Disconnect(&FTPConnection);
FTPC_APP_LOG(("APP: Done."));
//
while (1) {
OS_Delay(500);
}
}
/*********************************************************************
*
* APP_MainTask()
*
* Function description
* Sample starting point.
*/
static void APP_MainTask(void) {
OS_SetTaskName(OS_GetTaskID(), "FTP client");
#if (APP_USE_SSL != 0)
//
// Initialize SSL.
//
SSL_Init();
#endif
_FTPClientTask();
}
/*********************************************************************
*
* Global functions
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Main sample starting point when running one individual sample.
*
* Additional information
* Runs the sample in a task of its own to decouple it from not
* knowing the required task stack size of this sample when
* starting from main() . This task typically is terminated once
* it has fulfilled its purpose.
*
* This routine initializes everything that might be common with
* other samples of the same kind. These initializations can then
* be skipped by not starting from MainTask() but APP_MainTask()
* instead.
*/
void MainTask(void) {
int IFaceId;
#if (USE_FS_RO == 0)
//
// Create a test file on the storage medium
//
_FSTest();
#endif
//
// Initialize the IP stack
//
IP_Init();
//
// Start TCP/IP task
//
OS_CREATETASK(&_IPTCB, "IP_Task", IP_Task, APP_TASK_PRIO_IP_TASK , _IPStack);
#if USE_RX_TASK
OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, APP_TASK_PRIO_IP_RXTASK, _IPRxStack); // Start the IP_RxTask, optional.
#endif
IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange); // Register hook to be notified on disconnects.
IFaceId = IP_INFO_GetNumInterfaces() - 1; // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
IP_Connect(IFaceId); // Connect the interface if necessary.
//
// IPv4 address configured ?
//
while (IP_IFaceIsReadyEx(IFaceId) == 0) {
BSP_ToggleLED(0);
OS_Delay(200);
}
BSP_ClrLED(0);
//
// Start the sample itself.
//
OS_CREATETASK(&APP_MainTCB, "APP_MainTask", APP_MainTask, APP_TASK_PRIO_MAINTASK, APP_MainStack);
OS_TASK_Terminate(NULL);
}
/*************************** End of file ****************************/