Merged in antmerlino/apps/ftp-ipv6 (pull request #122)

FTP: Adds support for IPv6 and fixes various transfer issues

* netutils/ftpd: Fix support for IPv6 FTP server

* netutils/ftpc:Adds support for IPv6 sockets

* ftpc: Must convert port received by EPSV reply to network order

* ftpc: Data socket address for passive connection should be same as server address

* ftpc: Must skip human readable string in EPSV response before scanning for port

* ftpc: Don't send PASS command if USER command was sufficient. ie no password required

* ftpc: Generically handle permanent negative completion in shard ftpc_cmd logic

* ftpc: Minor addresssing fix

* ftpc: Tweak logic for overriding network debugging output

* FTP: Adds option for setting stack size of various threads in FTPD and FTPC

* netutils/ftpd: Fixes build error setting IPv4 address. sin_addr => sin_addr.s_addr

* netutils/ftpd: Protects against partial write returns by looping in that case

Approved-by: Gregory Nutt <gnutt@nuttx.org>
This commit is contained in:
Anthony Merlino 2017-11-06 23:41:30 +00:00 committed by Gregory Nutt
parent efd1279025
commit f489bcd6f9
22 changed files with 630 additions and 195 deletions

View File

@ -10,4 +10,9 @@ config EXAMPLES_FTPC
Enable the FTP client example
if EXAMPLES_FTPC
config EXAMPLES_FTPC_STACKSIZE
int "FTPC stack size"
default 4096
endif

View File

@ -37,9 +37,11 @@
# FTPC Client Application
CONFIG_EXAMPLES_FTPC_STACKSIZE ?= 4096
APPNAME = ftpc
PRIORITY = SCHED_PRIORITY_DEFAULT
STACKSIZE = 4096
STACKSIZE = CONFIG_EXAMPLES_FTPC_STACKSIZE
ASRCS =
CSRCS = ftpc_cmds.c

View File

@ -56,6 +56,16 @@
#define FTPC_MAX_ARGUMENTS 4
/* If FTP is used and both IPv6 and IPv4 are enabled, then we need to
* pick one.
*/
#ifdef CONFIG_NET_IPv6
# define ADDR_FAMILY AF_INET6
#else
# define ADDR_FAMILY AF_INET
#endif
/****************************************************************************
* Public Types
****************************************************************************/
@ -361,39 +371,72 @@ int main(int argc, FAR char *argv[])
int ftpc_main(int argc, char **argv, char **envp)
#endif
{
struct ftpc_connect_s connect = {{0}, 0};
union ftpc_sockaddr_u server;
SESSION handle;
#if ADDR_FAMILY == AF_INET
FAR char *ptr;
#endif
#ifndef CONFIG_EXAMPLES_FTPC_FGETS
int ret;
#endif
memset(&server, 0, sizeof(union ftpc_sockaddr_u));
if (argc != 2)
{
#if ADDR_FAMILY == CONFIG_NET_IPv6
printf("Usage:\n");
printf(" %s xx:xx:xx:xx:xx:xx:xx:xx [pp]\n", argv[0]);
printf("Where\n");
printf(" xx:xx:xx:xx:xx:xx:xx:xx is the IP address of the FTP server\n");
printf(" pp is option port to use with the FTP server\n");
#else
printf("Usage:\n");
printf(" %s xx.xx.xx.xx[:pp]\n", argv[0]);
printf("Where\n");
printf(" xx.xx.xx.xx is the IP address of the FTP server\n");
printf(" pp is option port to use with the FTP server\n");
#endif
exit(1);
}
/* In any event, we can now extract the IP address from the comman-line */
#if ADDR_FAMILY == AF_INET6
server.in6.sin6_family = AF_INET6;
ret = inet_pton(AF_INET6, argv[1], &server.in6.sin6_addr);
if (ret < 0)
{
printf("Invalid IPv6 address\n");
exit(1);
}
if (argc > 2)
{
server.in6.sin6_port = atoi(argv[2]);
}
#else
/* Check if the argument includes a port number */
ptr = strchr(argv[1], ':');
if (ptr)
{
*ptr = '\0';
connect.port = atoi(ptr+1);
server.in4.sin_port = atoi(ptr+1);
}
/* In any event, we can now extract the IP address from the comman-line */
connect.addr.s_addr = inet_addr(argv[1]);
server.in4.sin_family = AF_INET;
ret = inet_pton(AF_INET, argv[1], &server.in4.sin_addr);
if (ret < 0)
{
printf("Invalid IP address\n");
exit(1);
}
#endif
/* Connect to the FTP server */
handle = ftpc_connect(&connect);
handle = ftpc_connect(&server);
if (!handle)
{
printf("Failed to connect to the server: %d\n", errno);

View File

@ -10,4 +10,9 @@ config EXAMPLES_FTPD
Enable the FTP server example
if EXAMPLES_FTPD
config EXAMPLES_FTPD_STACKSIZE
int "FTP Daemon Stack Size"
default 2048
endif

View File

@ -37,7 +37,7 @@
-include $(TOPDIR)/Make.defs
include $(APPDIR)/Make.defs
# Hello, World! Example
CONFIG_EXAMPLES_FTPD_STACKSIZE ?= 2048
ASRCS =
CSRCS =
@ -109,10 +109,10 @@ endif
ifeq ($(CONFIG_NSH_BUILTIN_APPS),y)
$(BUILTIN_REGISTRY)$(DELIM)ftpd_start.bdat: $(DEPCONFIG) Makefile
$(call REGISTER,ftpd_start,SCHED_PRIORITY_DEFAULT,2048,ftpd_main)
$(call REGISTER,ftpd_start,SCHED_PRIORITY_DEFAULT,$(CONFIG_EXAMPLES_FTPD_STACKSIZE),ftpd_main)
$(BUILTIN_REGISTRY)$(DELIM)ftpd_stop.bdat: $(DEPCONFIG) Makefile
$(call REGISTER,ftpd_stop,SCHED_PRIORITY_DEFAULT,2048,ftpd_stop)
$(call REGISTER,ftpd_stop,SCHED_PRIORITY_DEFAULT,$(CONFIG_EXAMPLES_FTPD_STACKSIZE),ftpd_stop)
context: $(BUILTIN_REGISTRY)$(DELIM)ftpd_start.bdat $(BUILTIN_REGISTRY)$(DELIM)ftpd_stop.bdat
else

View File

@ -51,6 +51,20 @@
#include "ftpd.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* If FTP is used and both IPv6 and IPv4 are enabled, then we need to
* pick one.
*/
#ifdef CONFIG_NET_IPv6
# define ADDR_FAMILY AF_INET6
#else
# define ADDR_FAMILY AF_INET
#endif
/****************************************************************************
* Private Data
****************************************************************************/
@ -167,7 +181,12 @@ int ftpd_daemon(int s_argc, char **s_argv)
/* Open FTPD */
handle = ftpd_open();
#if ADDR_FAMILY == AF_INET6
handle = ftpd_open(AF_INET6);
#else
handle = ftpd_open(AF_INET);
#endif
if (!handle)
{
printf("FTP daemon [%d] failed to open FTPD\n", g_ftpdglob.pid);

View File

@ -47,6 +47,7 @@
#include <signal.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
/****************************************************************************
@ -113,9 +114,9 @@
typedef FAR void *SESSION;
/* This structure provides information to connect to a host FTP server.
/* This union provides information to connect to a host FTP server.
*
* addr - The IPv4 address of the FTP server (or the proxy) for the FTP
* addr - The IPv4 or IPv6 address of the FTP server (or the proxy) for the FTP
* server.
* port - The port number on the FTP server to connect to (in host byte
* order). This is usually port 21 for FTP. You may set this
@ -123,10 +124,17 @@ typedef FAR void *SESSION;
* you (it will use CONFIG_FTP_DEFPORT).
*/
struct ftpc_connect_s
union ftpc_sockaddr_u
{
struct in_addr addr; /* Server/proxy IP address */
uint16_t port; /* Server/proxy port number (usually 21) in network order */
uint8_t raw[sizeof(struct sockaddr_storage)];
struct sockaddr_storage ss;
struct sockaddr sa;
#ifdef CONFIG_NET_IPv6
struct sockaddr_in6 in6;
#endif
#ifdef CONFIG_NET_IPv4
struct sockaddr_in in4;
#endif
};
/* This structure provides FTP login information */
@ -177,7 +185,7 @@ extern "C"
****************************************************************************/
/* Connection management ****************************************************/
SESSION ftpc_connect(FAR struct ftpc_connect_s *server);
SESSION ftpc_connect(FAR union ftpc_sockaddr_u *server);
void ftpc_disconnect(SESSION handle);
/* FTP commands *************************************************************/

View File

@ -135,7 +135,8 @@ extern "C"
* used to run the server.
*
* Input Parameters:
* None
* family - The type of INET family to use when opening the socket. AF_INET
* and AF_INET6 are supported.
*
* Returned Value:
* On success, a non-NULL handle is returned that can be used to reference
@ -143,7 +144,7 @@ extern "C"
*
****************************************************************************/
FTPD_SESSION ftpd_open(void);
FTPD_SESSION ftpd_open(sa_family_t family);
/****************************************************************************
* Name: ftpd_adduser

View File

@ -19,4 +19,29 @@ config DEBUG_FTPC
Enable debug support for the FTP client. This option simple forces
CONFIG_DEBUG_NET to be on, but only for the files within this directory.
config FTP_TMPDIR
string "TMP directory path for FTP"
default "/tmp"
---help---
The path to use for storing temporary files used in the transfer process
by the FTP client.
config FTPC_DISABLE_EPRT
bool "Disable EPRT and use PORT instead"
default n
---help---
FTP uses PORT and EPRT when in active mode. EPRT replaced PORT to allow
for IPv6 support. EPRT is supported in most FTP implementations now. However,
if you need to use PORT instead, use this option to disable EPRT and
fallback to using PORT.
config FTPC_DISABLE_EPSV
bool "Disable EPSV and use PASV instead"
default n
---help---
FTP uses EPSV or PASV when in passive mode. EPSV replaced PASV to allow
for IPv6 support. EPSV is supported in most FTP implementations now. However,
if you need to use PASV instead, use this option to disable EPSV and
fallback to using PASV.
endif

View File

@ -228,6 +228,17 @@ int ftpc_cmd(struct ftpc_session_s *session, const char *cmd, ...)
}
}
/* Error codes 5xx are Permanent Negative Completion. These can be
* handled generically for all commands. An example is error code 530
* which means 'not logged in'. The reply should include a string to
* display to the user
*/
else if (session->code > 500 && session->code < 600)
{
return ERROR;
}
/* The command was successfully sent */
return OK;

View File

@ -58,7 +58,14 @@
*/
#if !defined(CONFIG_DEBUG_NET) && defined(CONFIG_DEBUG_FTPC)
# undef CONFIG_DEBUG_NET
# define CONFIG_DEBUG_NET 1
# undef CONFIG_DEBUG_NET_ERROR
# define CONFIG_DEBUG_NET_ERROR 1
# undef CONFIG_DEBUG_NET_WARN
# define CONFIG_DEBUG_NET_WARN 1
# undef CONFIG_DEBUG_NET_INFO
# define CONFIG_DEBUG_NET_INFO 1
#endif
/****************************************************************************

View File

@ -63,7 +63,7 @@
*
****************************************************************************/
SESSION ftpc_connect(FAR struct ftpc_connect_s *server)
SESSION ftpc_connect(FAR union ftpc_sockaddr_u *server)
{
FAR struct ftpc_session_s *session;
int ret;
@ -80,7 +80,8 @@ SESSION ftpc_connect(FAR struct ftpc_connect_s *server)
/* Initialize the session structure with all non-zero and variable values */
session->addr.s_addr = server->addr.s_addr;
memcpy(&session->server, server, sizeof(union ftpc_sockaddr_u));
session->flags &= ~FTPC_FLAGS_CLEAR;
session->flags |= FTPC_FLAGS_SET;
session->replytimeo = CONFIG_FTP_DEFTIMEO * CLOCKS_PER_SEC;
@ -89,14 +90,24 @@ SESSION ftpc_connect(FAR struct ftpc_connect_s *server)
/* Use the default port if the user specified port number zero */
if (!server->port)
#ifdef CONFIG_NET_IPv6
if (session->server.sa.sa_family == AF_INET6)
{
session->port = HTONS(CONFIG_FTP_DEFPORT);
if (!session->server.in6.sin6_port)
{
session->server.in6.sin6_port = HTONS(CONFIG_FTP_DEFPORT);
}
}
else
#endif
#ifdef CONFIG_NET_IPv4
if (session->server.sa.sa_family == AF_INET)
{
session->port = htons(server->port);
if (!session->server.in4.sin_port)
{
session->server.in4.sin_port = HTONS(CONFIG_FTP_DEFPORT);
}
}
#endif
/* Get the local home directory, i.e., the value of the PWD environment
* variable at the time of the connection. We keep a local copy so that
@ -143,9 +154,8 @@ errout:
int ftpc_reconnect(FAR struct ftpc_session_s *session)
{
struct sockaddr_in addr;
#ifdef CONFIG_DEBUG_NET_ERROR
char *tmp;
char buffer[48];
#endif
int ret;
@ -166,7 +176,7 @@ int ftpc_reconnect(FAR struct ftpc_session_s *session)
/* Initialize a socket */
ret = ftpc_sockinit(&session->cmd);
ret = ftpc_sockinit(&session->cmd, session->server.sa.sa_family);
if (ret != OK)
{
nerr("ERROR: ftpc_sockinit() failed: %d\n", errno);
@ -176,16 +186,29 @@ int ftpc_reconnect(FAR struct ftpc_session_s *session)
/* Connect the socket to the server */
#ifdef CONFIG_DEBUG_NET_ERROR
tmp = inet_ntoa(session->addr);
ninfo("Connecting to server address %s:%d\n",
tmp, ntohs(session->port));
#endif
#ifdef CONFIG_NET_IPv6
if (session->server.sa.sa_family == AF_INET6)
{
if (inet_ntop(AF_INET6, &session->server.in6.sin6_addr, buffer, 48) != NULL)
{
ninfo("Connecting to server address %s:%d\n", buffer,
ntohs(session->server.in6.sin6_port));
}
}
#endif /* CONFIG_NET_IPv6 */
#ifdef CONFIG_NET_IPv4
if (session->server.sa.sa_family == AF_INET)
{
if (inet_ntop(AF_INET, &session->server.in4.sin_addr, buffer, 48) != NULL)
{
ninfo("Connecting to server address %s:%d\n", buffer,
ntohs(session->server.in4.sin_port));
}
}
#endif /* CONFIG_NET_IPv4 */
#endif /* CONFIG_DEBUG_NET_ERROR */
addr.sin_family = AF_INET;
addr.sin_port = session->port;
addr.sin_addr.s_addr = session->addr.s_addr;
ret = ftpc_sockconnect(&session->cmd, &addr);
ret = ftpc_sockconnect(&session->cmd, (FAR struct sockaddr *)&session->server);
if (ret != OK)
{
nerr("ERROR: ftpc_sockconnect() failed: %d\n", errno);
@ -224,11 +247,40 @@ int ftpc_reconnect(FAR struct ftpc_session_s *session)
#ifdef CONFIG_DEBUG_NET_ERROR
ninfo("Connected\n");
tmp = inet_ntoa(addr.sin_addr);
ninfo(" Remote address: %s:%d\n", tmp, ntohs(addr.sin_port));
tmp = inet_ntoa(session->cmd.laddr.sin_addr);
ninfo(" Local address: %s:%d\n", tmp, ntohs(session->cmd.laddr.sin_port));
#endif
#ifdef CONFIG_NET_IPv6
if (session->server.sa.sa_family == AF_INET6)
{
if (inet_ntop(AF_INET6, &session->server.in6.sin6_addr, buffer, 48) != NULL)
{
ninfo(" Remote address: %s:%d\n", buffer,
ntohs(session->server.in6.sin6_port));
}
if (inet_ntop(AF_INET6, &session->cmd.laddr.in6.sin6_addr, buffer, 48) != NULL)
{
ninfo(" Local address: %s:%d\n", buffer,
ntohs(session->cmd.laddr.in6.sin6_port));
}
}
#endif /* CONFIG_NET_IPv6 */
#ifdef CONFIG_NET_IPv4
if (session->server.sa.sa_family == AF_INET)
{
if (inet_ntop(AF_INET, &session->server.in4.sin_addr, buffer, 48) != NULL)
{
ninfo(" Remote address: %s:%d\n", buffer,
ntohs(session->server.in4.sin_port));
}
if (inet_ntop(AF_INET, &session->cmd.laddr.in4.sin_addr, buffer, 48) != NULL)
{
ninfo(" Local address: %s:%d\n", buffer,
ntohs(session->cmd.laddr.in4.sin_port));
}
}
#endif /* CONFIG_NET_IPv4 */
#endif /* CONFIG_DEBUG_NET_ERROR */
return OK;
errout_with_socket:

View File

@ -85,6 +85,7 @@ void ftpc_disconnect(SESSION handle)
/* Release sockets */
ftpc_sockclose(&session->data);
ftpc_sockclose(&session->dacceptor);
ftpc_sockclose(&session->cmd);
/* Free strings */

View File

@ -154,7 +154,7 @@ static int ftpc_recvinit(struct ftpc_session_s *session, FAR const char *path,
if (!FTPC_IS_PASSIVE(session))
{
ret = ftpc_sockaccept(&session->data);
ret = ftpc_sockaccept(&session->dacceptor, &session->data);
if (ret != OK)
{
nerr("ERROR: Data connection not accepted\n");

View File

@ -153,7 +153,7 @@ struct ftpc_socket_s
int sd; /* Socket descriptor */
FILE *instream; /* Incoming stream */
FILE *outstream; /* Outgoing stream */
struct sockaddr_in laddr; /* Local address */
union ftpc_sockaddr_u laddr; /* Local Address */
bool connected; /* True: socket is connected */
};
@ -161,9 +161,10 @@ struct ftpc_socket_s
struct ftpc_session_s
{
struct in_addr addr; /* Server/proxy IP address */
union ftpc_sockaddr_u server; /* Server/proxy socket address */
struct ftpc_socket_s cmd; /* FTP command channel */
struct ftpc_socket_s data; /* FTP data channel */
struct ftpc_socket_s dacceptor; /* FTP data listener (accepts data connection in active mode) */
WDOG_ID wdog; /* Timer */
FAR char *uname; /* Login uname */
FAR char *pwd; /* Login pwd */
@ -173,7 +174,6 @@ struct ftpc_session_s
FAR char *homeldir; /* Local home directory (PWD on startup) */
pid_t pid; /* Task ID of FTP client */
uint8_t xfrmode; /* Previous data transfer type (See FTPC_XFRMODE_* defines) */
uint16_t port; /* Server/proxy port number (probably 21) */
uint16_t flags; /* Connection flags (see FTPC_FLAGS_* defines) */
uint16_t code; /* Last 3-digit reply code */
uint32_t replytimeo; /* Server reply timeout (ticks) */
@ -250,13 +250,14 @@ EXTERN FAR char *ftpc_abslpath(FAR struct ftpc_session_s *session,
/* Socket helpers */
EXTERN int ftpc_sockinit(FAR struct ftpc_socket_s *sock);
EXTERN int ftpc_sockinit(FAR struct ftpc_socket_s *sock, sa_family_t family);
EXTERN void ftpc_sockclose(FAR struct ftpc_socket_s *sock);
EXTERN int ftpc_sockconnect(FAR struct ftpc_socket_s *sock,
FAR struct sockaddr_in *addr);
FAR struct sockaddr *addr);
EXTERN int ftpc_sockgetsockname(FAR struct ftpc_socket_s *sock,
FAR struct sockaddr_in *sa);
EXTERN int ftpc_sockaccept(FAR struct ftpc_socket_s *sock);
FAR union ftpc_sockaddr_u *addr);
EXTERN int ftpc_sockaccept(FAR struct ftpc_socket_s *acceptor,
FAR struct ftpc_socket_s *sock);
EXTERN int ftpc_socklisten(FAR struct ftpc_socket_s *sock);
EXTERN void ftpc_sockcopy(FAR struct ftpc_socket_s *dest,
FAR const struct ftpc_socket_s *src);

View File

@ -206,7 +206,7 @@ static int ftpc_recvdir(FAR struct ftpc_session_s *session,
if (!FTPC_IS_PASSIVE(session))
{
ret = ftpc_sockaccept(&session->data);
ret = ftpc_sockaccept(&session->dacceptor, &session->data);
if (ret != OK)
{
nerr("ERROR: ftpc_sockaccept() failed: %d\n", errno);

View File

@ -158,9 +158,6 @@ int ftpc_relogin(FAR struct ftpc_session_s *session)
* Or the server may reject USER with:
*
* - "530 Not logged in" meaning that the username is unacceptable.
*
* In practice, the server does not check the username until after a PASS
* request
*/
FTPC_CLR_LOGGEDIN(session);
@ -171,26 +168,29 @@ int ftpc_relogin(FAR struct ftpc_session_s *session)
return ERROR;
}
/* Send the PASS command with the passed. The server may accept PASS with:
*
* - "230 User logged in, proceed" meaning that the client has permission to
* access files under that username
* - "202 Command not implemented, superfluous at this site" meaning that
* permission was already granted in response to USER
* - "332 Need account for login" meaning that permission might be granted
* after an ACCT request.
*
* The server may reject PASS with:
*
* - "503 Bad sequence of commands" if the previous request was not USER
* - "530 - Not logged in" if this username and password are unacceptable.
*/
ret = ftpc_cmd(session, "PASS %s", session->pwd);
if (ret != OK)
if (session->code == 331)
{
nerr("ERROR: PASS %s cmd failed: %d\n", session->pwd, errno);
return ret;
/* Send the PASS command with the passed. The server may accept PASS with:
*
* - "230 User logged in, proceed" meaning that the client has permission to
* access files under that username
* - "202 Command not implemented, superfluous at this site" meaning that
* permission was already granted in response to USER
* - "332 Need account for login" meaning that permission might be granted
* after an ACCT request.
*
* The server may reject PASS with:
*
* - "503 Bad sequence of commands" if the previous request was not USER
* - "530 - Not logged in" if this username and password are unacceptable.
*/
ret = ftpc_cmd(session, "PASS %s", session->pwd);
if (ret != OK)
{
nerr("ERROR: PASS %s cmd failed: %d\n", session->pwd, errno);
return ret;
}
}
/* We are logged in.. the current working directory on login is our "home"

View File

@ -331,7 +331,7 @@ static int ftpc_sendfile(struct ftpc_session_s *session, const char *path,
if (!FTPC_IS_PASSIVE(session))
{
ret = ftpc_sockaccept(&session->data);
ret = ftpc_sockaccept(&session->dacceptor, &session->data);
if (ret != OK)
{
nerr("ERROR: Data connection not accepted\n");

View File

@ -81,15 +81,19 @@
*
****************************************************************************/
int ftpc_sockinit(FAR struct ftpc_socket_s *sock)
int ftpc_sockinit(FAR struct ftpc_socket_s *sock, sa_family_t family)
{
/* Initialize the socket structure */
memset(sock, 0, sizeof(struct ftpc_socket_s));
DEBUGASSERT(family == AF_INET || family == AF_INET6);
sock->laddr.sa.sa_family = family;
/* Create a socket descriptor */
sock->sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
sock->sd = socket(family, SOCK_STREAM, IPPROTO_TCP);
if (sock->sd < 0)
{
nerr("ERROR: socket() failed: %d\n", errno);
@ -142,7 +146,7 @@ errout:
*
****************************************************************************/
void ftpc_sockclose(struct ftpc_socket_s *sock)
void ftpc_sockclose(FAR struct ftpc_socket_s *sock)
{
/* Note that the same underlying socket descriptor is used for both streams.
* There should be harmless failures on the second fclose and the close.
@ -164,13 +168,31 @@ void ftpc_sockclose(struct ftpc_socket_s *sock)
*
****************************************************************************/
int ftpc_sockconnect(struct ftpc_socket_s *sock, struct sockaddr_in *addr)
int ftpc_sockconnect(FAR struct ftpc_socket_s *sock, FAR struct sockaddr *addr)
{
int ret;
/* Connect to the server */
ret = connect(sock->sd, (struct sockaddr *)addr, sizeof(struct sockaddr));
#ifdef CONFIG_NET_IPv6
if (addr->sa_family == AF_INET6)
{
ret = connect(sock->sd, (struct sockaddr *)addr, sizeof(struct sockaddr_in6));
}
else
#endif
#ifdef CONFIG_NET_IPv4
if (addr->sa_family == AF_INET)
{
ret = connect(sock->sd, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
}
else
#endif
{
nerr("ERROR: Unsupported address family\n");
return ERROR;
}
if (ret < 0)
{
nerr("ERROR: connect() failed: %d\n", errno);
@ -201,7 +223,7 @@ int ftpc_sockconnect(struct ftpc_socket_s *sock, struct sockaddr_in *addr)
void ftpc_sockcopy(FAR struct ftpc_socket_s *dest,
FAR const struct ftpc_socket_s *src)
{
memcpy(&dest->laddr, &src->laddr, sizeof(struct sockaddr_in));
memcpy(&dest->laddr, &src->laddr, sizeof(dest->laddr));
dest->connected = ftpc_sockconnected(src);
}
@ -209,7 +231,7 @@ void ftpc_sockcopy(FAR struct ftpc_socket_s *dest,
* Name: ftpc_sockaccept
*
* Description:
* Accept a connection on the data socket. This function is onlly used
* Accept a connection on the data socket. This function is only used
* in active mode.
*
* In active mode FTP the client connects from a random port (N>1023) to the
@ -230,9 +252,10 @@ void ftpc_sockcopy(FAR struct ftpc_socket_s *dest,
*
****************************************************************************/
int ftpc_sockaccept(FAR struct ftpc_socket_s *sock)
int ftpc_sockaccept(FAR struct ftpc_socket_s *acceptor,
FAR struct ftpc_socket_s *sock)
{
struct sockaddr addr;
union ftpc_sockaddr_u addr;
socklen_t addrlen;
/* Any previous socket should have been uninitialized (0) or explicitly
@ -247,14 +270,14 @@ int ftpc_sockaccept(FAR struct ftpc_socket_s *sock)
}
addrlen = sizeof(addr);
sock->sd = accept(sock->sd, &addr, &addrlen);
sock->sd = accept(acceptor->sd, (struct sockaddr *)&addr, &addrlen);
if (sock->sd == -1)
{
nerr("ERROR: accept() failed: %d\n", errno);
return ERROR;
}
memcpy(&sock->laddr, &addr, sizeof(sock->laddr));
memcpy(&sock->laddr, &addr, sizeof(union ftpc_sockaddr_u));
/* Create in/out C buffer I/O streams on the data channel. First,
* create the incoming buffered stream.
@ -299,15 +322,35 @@ errout_with_sd:
*
****************************************************************************/
int ftpc_socklisten(struct ftpc_socket_s *sock)
int ftpc_socklisten(FAR struct ftpc_socket_s *sock)
{
unsigned int addrlen = sizeof(sock->laddr);
int ret;
/* Bind the local socket to the local address */
sock->laddr.sin_port = 0;
ret = bind(sock->sd, (struct sockaddr *)&sock->laddr, addrlen);
#ifdef CONFIG_NET_IPv6
if (sock->laddr.sa.sa_family == AF_INET6)
{
sock->laddr.in6.sin6_port = 0;
ret = bind(sock->sd, (struct sockaddr *)&sock->laddr,
sizeof(struct sockaddr_in6));
}
else
#endif
#ifdef CONFIG_NET_IPv4
if (sock->laddr.sa.sa_family == AF_INET)
{
sock->laddr.in4.sin_port = 0;
ret = bind(sock->sd, (struct sockaddr *)&sock->laddr,
sizeof(struct sockaddr_in));
}
else
#endif
{
nerr("ERROR: unsupported family\n");
return ERROR;
}
if (ret < 0)
{
nerr("ERROR: bind() failed: %d\n", errno);
@ -335,7 +378,7 @@ int ftpc_socklisten(struct ftpc_socket_s *sock)
*
****************************************************************************/
int ftpc_sockprintf(struct ftpc_socket_s *sock, const char *fmt, ...)
int ftpc_sockprintf(FAR struct ftpc_socket_s *sock, FAR const char *fmt, ...)
{
va_list ap;
int r;
@ -355,11 +398,13 @@ int ftpc_sockprintf(struct ftpc_socket_s *sock, const char *fmt, ...)
****************************************************************************/
int ftpc_sockgetsockname(FAR struct ftpc_socket_s *sock,
FAR struct sockaddr_in *addr)
FAR union ftpc_sockaddr_u *addr)
{
socklen_t len = sizeof(struct sockaddr_in);
socklen_t len;
int ret;
len = sizeof(union ftpc_sockaddr_u);
ret = getsockname(sock->sd, (FAR struct sockaddr *)addr, &len);
if (ret < 0)
{

View File

@ -49,8 +49,10 @@
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <arpa/inet.h>
#include "netutils/ftpc.h"
#include "netutils/netlib.h"
#include "ftpc_internal.h"
@ -75,10 +77,10 @@
****************************************************************************/
/****************************************************************************
* Name: ftp_pasvmode
* Name: ftp_cmd_epsv
*
* Description:
* Enter passive mode.
* Enter passive mode using EPSV command.
*
* In active mode FTP the client connects from a random port (N>1023) to the
* FTP server's command port, port 21. Then, the client starts listening to
@ -98,22 +100,102 @@
*
****************************************************************************/
static int ftp_pasvmode(struct ftpc_session_s *session,
uint8_t addrport[6])
#ifndef CONFIG_FTPC_DISABLE_EPSV
static int ftp_cmd_epsv(FAR struct ftpc_session_s *session,
FAR union ftpc_sockaddr_u *addr)
{
char *ptr;
int nscan;
int ret;
uint16_t tmp;
/* Request passive mode. The server normally accepts EPSV with code 227.
* Its response is a single line showing the IP address of the server and
* the TCP port number where the server is accepting connections.
*/
ret = ftpc_cmd(session, "EPSV");
if (ret < 0 || !ftpc_connected(session))
{
return ERROR;
}
/* Skip over any leading stuff before important data begins */
ptr = session->reply + 4;
while (*ptr != '(')
{
ptr++;
if (ptr > (session->reply + sizeof(session->reply) - 1))
{
nwarn("WARNING: Error parsing EPSV reply: '%s'\n", session->reply);
return ERROR;
}
}
ptr++;
/* The response is then just the port number. None of the other fields
* are supplied.
*/
nscan = sscanf(ptr, "|||%u|", &tmp);
if (nscan != 1)
{
nwarn("WARNING: Error parsing EPSV reply: '%s'\n", session->reply);
return ERROR;
}
#ifdef CONFIG_NET_IPv4
if (addr->sa.sa_family == AF_INET)
{
addr->in4.sin_port = HTONS(tmp);
}
#endif
#ifdef CONFIG_NET_IPv6
if (addr->sa.sa_family == AF_INET6)
{
addr->in6.sin6_port = HTONS(tmp);
}
#endif
return OK;
}
#endif
/****************************************************************************
* Name: ftp_cmd pasv
*
* Description:
* Enter passive mode using PASV command.
*
* In active mode FTP the client connects from a random port (N>1023) to the
* FTP server's command port, port 21. Then, the client starts listening to
* port N+1 and sends the FTP command PORT N+1 to the FTP server. The server
* will then connect back to the client's specified data port from its local
* data port, which is port 20. In passive mode FTP the client initiates
* both connections to the server, solving the problem of firewalls filtering
* the incoming data port connection to the client from the server. When
* opening an FTP connection, the client opens two random ports locally
* (N>1023 and N+1). The first port contacts the server on port 21, but
* instead of then issuing a PORT command and allowing the server to connect
* back to its data port, the client will issue the PASV command. The result
* of this is that the server then opens a random unprivileged port (P >
* 1023) and sends the PORT P command back to the client. The client then
* initiates the connection from port N+1 to port P on the server to transfer
* data.
*
****************************************************************************/
#ifdef CONFIG_NET_IPv4
static int ftp_cmd_pasv(FAR struct ftpc_session_s *session,
FAR union ftpc_sockaddr_u *addr)
{
int tmpap[6];
char *ptr;
int nscan;
int ret;
int i;
/* Does this host support the PASV command */
if (!FTPC_HAS_PASV(session))
{
nwarn("WARNING: Host doesn't support passive mode\n");
return ERROR;
}
/* Request passive mode. The server normally accepts PASV with code 227.
* Its response is a single line showing the IP address of the server and
@ -126,7 +208,7 @@ static int ftp_pasvmode(struct ftpc_session_s *session,
return ERROR;
}
/* Skip over any leading stuff before address begins */
/* Skip over any leading stuff before important data begins */
ptr = session->reply + 4;
while (!isdigit((int)*ptr))
@ -149,14 +231,12 @@ static int ftp_pasvmode(struct ftpc_session_s *session,
/* Then copy the sscanf'ed values as bytes */
for (i = 0; i < 6; i++)
{
addrport[i] = (uint8_t)(tmpap[i] & 0xff);
}
memcpy(&addr->in4.sin_addr, tmpap, sizeof(addr->in4.sin_addr));
memcpy(&addr->in4.sin_port, &tmpap[4], sizeof(addr->in4.sin_port));
return OK;
}
#endif
/****************************************************************************
* Name: ftpc_abspath
@ -245,11 +325,14 @@ static FAR char *ftpc_abspath(FAR struct ftpc_session_s *session,
int ftpc_xfrinit(FAR struct ftpc_session_s *session)
{
struct sockaddr_in addr;
uint8_t addrport[6];
union ftpc_sockaddr_u addr;
int ret;
#ifdef CONFIG_FTPC_DISABLE_EPRT
uint8_t *paddr;
uint8_t *pport;
int ret;
#else
char ipstr[48];
#endif
/* We must be connected to initiate a transfer */
@ -259,41 +342,55 @@ int ftpc_xfrinit(FAR struct ftpc_session_s *session)
goto errout;
}
/* Initialize the data channel */
ret = ftpc_sockinit(&session->data);
if (ret != OK)
{
nerr("ERROR: ftpc_sockinit() failed: %d\n", errno);
goto errout;
}
/* Duplicate the address and connection information of the command channel */
ftpc_sockcopy(&session->data, &session->cmd);
/* Should we enter passive mode? */
if (FTPC_IS_PASSIVE(session))
{
/* Yes.. going passive. */
/* Initialize the data channel */
ret = ftp_pasvmode(session, addrport);
ret = ftpc_sockinit(&session->data, session->server.sa.sa_family);
if (ret != OK)
{
nerr("ERROR: ftp_pasvmode() failed: %d\n", errno);
goto errout_with_data;
nerr("ERROR: ftpc_sockinit() failed: %d\n", errno);
goto errout;
}
/* Configure the data socket */
/* Does this host support the PASV command */
ftpc_sockgetsockname(&session->cmd, &addr);
memcpy(&addr.sin_addr, addrport, 4);
memcpy(&addr.sin_port, addrport+4, 2);
if (!FTPC_HAS_PASV(session))
{
nerr("ERROR: Host doesn't support passive mode\n");
goto errout_with_data;
}
/* Configure the address to be the server address. If EPSV is used, the
* port will be populated by parsing the reply of the EPSV command. If the
* PASV command is used, the address and port will be overwritten.
*/
memcpy(&addr, &session->server, sizeof(union ftpc_sockaddr_u));
/* Yes.. going passive. */
#ifdef CONFIG_FTPC_DISABLE_EPSV
ret = ftp_cmd_pasv(session, &addr);
if (ret < 0)
{
nerr("ERROR: ftp_cmd_pasv() failed: %d\n", errno);
goto errout_with_data;
}
#else
ret = ftp_cmd_epsv(session, &addr);
if (ret < 0)
{
nerr("ERROR: ftp_cmd_epsv() failed: %d\n", errno);
goto errout_with_data;
}
#endif
/* Connect the data socket */
ret = ftpc_sockconnect(&session->data, &addr);
ret = ftpc_sockconnect(&session->data, (FAR struct sockaddr *)&addr);
if (ret < 0)
{
nerr("ERROR: ftpc_sockconnect() failed: %d\n", errno);
@ -302,18 +399,96 @@ int ftpc_xfrinit(FAR struct ftpc_session_s *session)
}
else
{
/* Initialize the data listener socket that allows us to accept new
* data connections from the server
*/
ret = ftpc_sockinit(&session->dacceptor, session->server.sa.sa_family);
if (ret != OK)
{
nerr("ERROR: ftpc_sockinit() failed: %d\n", errno);
goto errout;
}
/* Use the server IP address to find the network interface, and subsequent
* local IP address used to establish the active connection. We must send
* the IP and port to the server so that it knows how to connect.
*/
#ifdef CONFIG_NET_IPv6
if (session->server.sa.sa_family == AF_INET6)
{
ret = netlib_ipv6adaptor(&session->server.in6.sin6_addr,
&session->dacceptor.laddr.in6.sin6_addr);
if (ret < 0)
{
nerr("ERROR: netlib_ipv6adaptor() failed: %d\n", ret);
goto errout_with_data;
}
}
else
#endif
#ifdef CONFIG_NET_IPv4
if (session->server.sa.sa_family == AF_INET)
{
ret = netlib_ipv4adaptor(&session->server.in4.sin_addr,
&session->dacceptor.laddr.in4.sin_addr);
if (ret < 0)
{
nerr("ERROR: netlib_ipv4adaptor() failed: %d\n", ret);
goto errout_with_data;
}
}
else
#endif
{
nerr("ERROR: unsupported address family\n");
goto errout_with_data;
}
/* Wait for the connection to be established */
ftpc_socklisten(&session->data);
ftpc_socklisten(&session->dacceptor);
#ifdef CONFIG_FTPC_DISABLE_EPRT
/* Then send our local data channel address to the server */
paddr = (uint8_t *)&session->data.laddr.sin_addr;
pport = (uint8_t *)&session->data.laddr.sin_port;
paddr = (uint8_t *)&session->dacceptor.laddr.in4.sin_addr;
pport = (uint8_t *)&session->dacceptor.laddr.in4.sin_port;
ret = ftpc_cmd(session, "PORT %d,%d,%d,%d,%d,%d",
paddr[0], paddr[1], paddr[2],
paddr[3], pport[0], pport[1]);
#else
#ifdef CONFIG_NET_IPv6
if (session->dacceptor.laddr.sa.sa_family == AF_INET6)
{
if (!inet_ntop(AF_INET6, &session->dacceptor.laddr.in6.sin6_addr, ipstr, 48))
{
nerr("ERROR: inet_ntop failed: %d\n", errno);
goto errout_with_data;
}
ret = ftpc_cmd(session, "EPRT |2|%s|%d|", ipstr,
session->dacceptor.laddr.in6.sin6_port);
}
else
#endif /* CONFIG_NET_IPv6 */
#ifdef CONFIG_NET_IPv4
if (session->dacceptor.laddr.sa.sa_family == AF_INET)
{
if (!inet_ntop(AF_INET, &session->dacceptor.laddr.in4.sin_addr, ipstr, 48))
{
nerr("ERROR: inet_ntop failed: %d\n", errno);
goto errout_with_data;
}
ret = ftpc_cmd(session, "EPRT |1|%s|%d|", ipstr,
session->dacceptor.laddr.in4.sin_port);
}
else
#endif /* CONFIG_NET_IPv4 */
#endif /* CONFIG_FTPC_DISABLE_EPRT */
if (ret < 0)
{
nerr("ERROR: ftpc_cmd() failed: %d\n", errno);
@ -324,6 +499,7 @@ int ftpc_xfrinit(FAR struct ftpc_session_s *session)
errout_with_data:
ftpc_sockclose(&session->data);
ftpc_sockclose(&session->dacceptor);
errout:
return ERROR;
}

View File

@ -11,4 +11,9 @@ config NETUTILS_FTPD
Enable support for the FTP server.
if NETUTILS_FTPD
config FTPD_WORKERSTACKSIZE
int "FTPD client thread stack size"
default 2048
endif

View File

@ -113,7 +113,7 @@ static ssize_t ftpd_response(int sd, int timeout, FAR const char *fmt, ...);
static int ftpd_dataopen(FAR struct ftpd_session_s *session);
static int ftpd_dataclose(FAR struct ftpd_session_s *session);
static FAR struct ftpd_server_s *ftpd_openserver(int port);
static FAR struct ftpd_server_s *ftpd_openserver(int port, sa_family_t family);
/* Path helpers */
@ -1103,10 +1103,9 @@ static int ftpd_dataclose(FAR struct ftpd_session_s *session)
* Name: ftpd_openserver
****************************************************************************/
static FAR struct ftpd_server_s *ftpd_openserver(int port)
static FAR struct ftpd_server_s *ftpd_openserver(int port, sa_family_t family)
{
FAR struct ftpd_server_s *server;
sa_family_t family;
socklen_t addrlen;
FAR const void *addr;
#if defined(SOMAXCONN)
@ -1134,37 +1133,43 @@ static FAR struct ftpd_server_s *ftpd_openserver(int port)
/* Create the server listen socket */
#ifdef CONFIG_NET_IPv6
server->sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (server->sd < 0)
if (family == AF_INET6)
{
ftpd_close((FTPD_SESSION)server);
return NULL;
server->addr.in6.sin6_family = family;
server->addr.in6.sin6_addr = in6addr_any;
server->addr.in6.sin6_port = htons(port);
addrlen = (socklen_t)sizeof(server->addr.in6);
addr = (FAR void *)(&server->addr.in6);
server->sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
}
family = AF_INET6;
addrlen = (socklen_t)sizeof(server->addr.in6);
addr = (FAR void *)(&server->addr.in6);
server->addr.in6.sin6_family = family;
server->addr.in6.sin6_flowinfo = 0;
server->addr.in6.sin6_addr = in6addr_any;
server->addr.in6.sin6_port = htons(port);
#else
server->sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server->sd < 0)
{
ftpd_close((FTPD_SESSION)server);
return NULL;
}
family = AF_INET;
addrlen = (socklen_t)sizeof(server->addr.in4);
addr = (FAR void *)(&server->addr.in4);
server->addr.in4.sin_family = family;
server->addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
server->addr.in4.sin_port = htons(port);
else
#endif
#ifdef CONFIG_NET_IPv4
if (family == AF_INET)
{
server->addr.in4.sin_family = family;
server->addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
server->addr.in4.sin_port = htons(port);
addrlen = (socklen_t)sizeof(server->addr.in4);
addr = (FAR void *)(&server->addr.in4);
server->sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
}
else
#endif
{
nerr("ERROR: Unsupported family\n");
return NULL;
}
if (server->sd < 0)
{
ftpd_close((FTPD_SESSION)server);
return NULL;
}
#ifdef CONFIG_NET_HAVE_REUSEADDR
{
@ -1976,14 +1981,31 @@ static int ftpd_stream(FAR struct ftpd_session_s *session, int cmdtype)
}
else
{
int remaining;
int nwritten;
FAR char *next;
remaining = buflen;
next = buffer;
/* Write to the file */
wrbytes = write(session->fd, buffer, buflen);
if (wrbytes < 0)
do
{
errval = errno;
nerr("ERROR: write() failed: %d\n", errval);
nwritten = write(session->fd, next, remaining);
if (nwritten < 0)
{
errval = errno;
nerr("ERROR: write() failed: %d\n", errval);
break;
}
remaining -= nwritten;
next += nwritten;
}
while (remaining > 0);
wrbytes = next - buffer;
}
/* If the number of bytes returned by the write is not equal to the
@ -2646,6 +2668,7 @@ static int ftpd_command_noop(FAR struct ftpd_session_s *session)
static int ftpd_command_port(FAR struct ftpd_session_s *session)
{
#ifdef CONFIG_NET_IPv4
uint8_t value[6];
unsigned int utemp;
int temp;
@ -2749,6 +2772,11 @@ static int ftpd_command_port(FAR struct ftpd_session_s *session)
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 200, ' ',
"PORT command successful");
#else
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 502, ' ',
"PORT command not implemented");
#endif
}
/****************************************************************************
@ -3074,6 +3102,7 @@ static int ftpd_command_dele(FAR struct ftpd_session_s *session)
static int ftpd_command_pasv(FAR struct ftpd_session_s *session)
{
#ifdef CONFIG_NET_IPv4
unsigned int value[6];
unsigned int temp;
int ret;
@ -3118,22 +3147,7 @@ static int ftpd_command_pasv(FAR struct ftpd_session_s *session)
session->data.addr.in4.sin_addr.s_addr = in4addr.s_addr;
}
}
else
#endif
#ifndef CONFIG_NET_IPv6
if (session->data.addr.ss.ss_family == AF_INET)
{
/* Fixed to ipv4 */
memset((FAR void *)(&session->data.addr), 0, sizeof(session->data.addr));
session->data.addr.in4.sin_family = AF_INET;
session->data.addr.in4.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
#endif
{
nerr("ERROR: Unsupported family\n");
}
session->data.addr.in4.sin_port = 0;
ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr,
@ -3191,6 +3205,11 @@ static int ftpd_command_pasv(FAR struct ftpd_session_s *session)
}
return ret;
#else
return ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 502, ' ',
"PASV command not implemented");
#endif
}
/****************************************************************************
@ -3209,7 +3228,9 @@ static int ftpd_command_epsv(FAR struct ftpd_session_s *session)
session->data.sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (session->data.sd < 0)
{
#ifdef CONFIG_NET_IPv4
session->data.sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
}
else
{
@ -3245,13 +3266,21 @@ static int ftpd_command_epsv(FAR struct ftpd_session_s *session)
{
session->data.addr.in6.sin6_port = htons(0);
}
else if (session->data.addr.ss.ss_family == AF_INET)
else
#endif
#ifdef CONFIG_NET_IPv4
if (session->data.addr.ss.ss_family == AF_INET)
{
session->data.addr.in4.sin_port = htons(0);
}
#else
session->data.addr.in4.sin_port = htons(0);
else
#endif
{
ret = ftpd_response(session->cmd.sd, session->txtimeout,
g_respfmt1, 500, ' ', "EPSV family not supported!");
(void)ftpd_dataclose(session);
return ret;
}
ret = bind(session->data.sd, (FAR const struct sockaddr *)&session->data.addr,
session->data.addrlen);
@ -4155,14 +4184,14 @@ static FAR void *ftpd_worker(FAR void *arg)
*
****************************************************************************/
FTPD_SESSION ftpd_open(void)
FTPD_SESSION ftpd_open(sa_family_t family)
{
FAR struct ftpd_server_s *server;
server = ftpd_openserver(21);
server = ftpd_openserver(21, family);
if (!server)
{
server = ftpd_openserver(2211);
server = ftpd_openserver(2211, family);
}
return (FTPD_SESSION)server;