0a6b1f55ab
Gregory Nutt is has submitted the SGA Sebastien Lorquet has submitted the ICLA as a result we can migrate the licenses to Apache. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com>
677 lines
19 KiB
C
677 lines
19 KiB
C
/****************************************************************************
|
|
* apps/netutils/ftpc/ftpc_transfer.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <poll.h>
|
|
#include <ctype.h>
|
|
#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"
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: ftp_cmd_epsv
|
|
*
|
|
* Description:
|
|
* 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 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#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, "|||%hu|", &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
|
|
* of this is that the server then opens a random unprivileged port (P >
|
|
* command. The result 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_FTPC_DISABLE_EPSV
|
|
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;
|
|
|
|
/* 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
|
|
* the TCP port number where the server is accepting connections.
|
|
*/
|
|
|
|
ret = ftpc_cmd(session, "PASV");
|
|
if (ret < 0 || !ftpc_connected(session))
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
/* Skip over any leading stuff before important data begins */
|
|
|
|
ptr = session->reply + 4;
|
|
while (*ptr != '\0' && !isdigit((int)*ptr))
|
|
{
|
|
ptr++;
|
|
}
|
|
|
|
/* The response is then 6 integer values: four representing the
|
|
* IP address and two representing the port number.
|
|
*/
|
|
|
|
nscan = sscanf(ptr, "%d,%d,%d,%d,%d,%d",
|
|
&tmpap[0], &tmpap[1], &tmpap[2],
|
|
&tmpap[3], &tmpap[4], &tmpap[5]);
|
|
if (nscan != 6)
|
|
{
|
|
nwarn("WARNING: Error parsing PASV reply: '%s'\n", session->reply);
|
|
return ERROR;
|
|
}
|
|
|
|
/* Then copy the sscanf'ed values as bytes */
|
|
|
|
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
|
|
*
|
|
* Description:
|
|
* Get the absolute path to a file, handling tilde expansion.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static FAR char *ftpc_abspath(FAR struct ftpc_session_s *session,
|
|
FAR const char *relpath,
|
|
FAR const char *homedir,
|
|
FAR const char *curdir)
|
|
{
|
|
FAR char *ptr = NULL;
|
|
|
|
/* If no relative path was provide,
|
|
* then use the current working directory
|
|
*/
|
|
|
|
if (!relpath)
|
|
{
|
|
return strdup(curdir);
|
|
}
|
|
|
|
/* Handle tilde expansion */
|
|
|
|
if (relpath[0] == '~')
|
|
{
|
|
/* Is the relative path only '~' */
|
|
|
|
if (relpath[1] == '\0')
|
|
{
|
|
return strdup(homedir);
|
|
}
|
|
|
|
/* No... then a '/' better follow the tilde */
|
|
|
|
else if (relpath[1] == '/')
|
|
{
|
|
asprintf(&ptr, "%s%s", homedir, &relpath[1]);
|
|
}
|
|
|
|
/* Hmmm... this pretty much guaranteed to fail */
|
|
|
|
else
|
|
{
|
|
ptr = strdup(relpath);
|
|
}
|
|
}
|
|
|
|
/* No tilde expansion. Check for a path relative to the current
|
|
* directory.
|
|
*/
|
|
|
|
else if (strncmp(relpath, "./", 2) == 0)
|
|
{
|
|
asprintf(&ptr, "%s%s", curdir, relpath + 1);
|
|
}
|
|
|
|
/* Check for an absolute path */
|
|
|
|
else if (relpath[0] == '/')
|
|
{
|
|
ptr = strdup(relpath);
|
|
}
|
|
|
|
/* Assume it a relative path */
|
|
|
|
else
|
|
{
|
|
asprintf(&ptr, "%s/%s", curdir, relpath);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_xfrinit
|
|
*
|
|
* Description:
|
|
* Perform common transfer setup logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int ftpc_xfrinit(FAR struct ftpc_session_s *session)
|
|
{
|
|
union ftpc_sockaddr_u addr;
|
|
int ret;
|
|
#ifdef CONFIG_FTPC_DISABLE_EPRT
|
|
uint8_t *paddr;
|
|
uint8_t *pport;
|
|
#else
|
|
char ipstr[48];
|
|
#endif
|
|
|
|
/* We must be connected to initiate a transfer */
|
|
|
|
if (!ftpc_connected(session))
|
|
{
|
|
nerr("ERROR: Not connected\n");
|
|
goto errout;
|
|
}
|
|
|
|
/* Should we enter passive mode? */
|
|
|
|
if (FTPC_IS_PASSIVE(session))
|
|
{
|
|
/* Initialize the data channel */
|
|
|
|
ret = ftpc_sockinit(&session->data, session->server.sa.sa_family);
|
|
if (ret != OK)
|
|
{
|
|
nerr("ERROR: ftpc_sockinit() failed: %d\n", errno);
|
|
goto errout;
|
|
}
|
|
|
|
/* Does this host support the PASV command */
|
|
|
|
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, (FAR struct sockaddr *)&addr);
|
|
if (ret < 0)
|
|
{
|
|
nerr("ERROR: ftpc_sockconnect() failed: %d\n", errno);
|
|
goto errout_with_data;
|
|
}
|
|
}
|
|
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.s_addr,
|
|
&session->dacceptor.laddr.in4.sin_addr.s_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->dacceptor);
|
|
|
|
#ifdef CONFIG_FTPC_DISABLE_EPRT
|
|
/* Then send our local data channel address to the server */
|
|
|
|
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);
|
|
goto errout_with_data;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
|
|
errout_with_data:
|
|
ftpc_sockclose(&session->data);
|
|
ftpc_sockclose(&session->dacceptor);
|
|
errout:
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_xfrreset
|
|
*
|
|
* Description:
|
|
* Reset transfer variables
|
|
*
|
|
****************************************************************************/
|
|
|
|
void ftpc_xfrreset(struct ftpc_session_s *session)
|
|
{
|
|
session->size = 0;
|
|
session->flags &= ~FTPC_XFER_FLAGS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_xfrmode
|
|
*
|
|
* Description:
|
|
* Select ASCII or Binary transfer mode
|
|
*
|
|
****************************************************************************/
|
|
|
|
int ftpc_xfrmode(struct ftpc_session_s *session, uint8_t xfrmode)
|
|
{
|
|
int ret;
|
|
|
|
/* Check if we have already selected the requested mode */
|
|
|
|
DEBUGASSERT(xfrmode != FTPC_XFRMODE_UNKNOWN);
|
|
if (session->xfrmode != xfrmode)
|
|
{
|
|
/* Send the TYPE request to control the binary flag.
|
|
* Parameters for the TYPE request include:
|
|
*
|
|
* A: Turn the binary flag off.
|
|
* A N: Turn the binary flag off.
|
|
* I: Turn the binary flag on.
|
|
* L 8: Turn the binary flag on.
|
|
*
|
|
* The server accepts the TYPE request with code 200.
|
|
*/
|
|
|
|
ret = ftpc_cmd(session, "TYPE %c",
|
|
xfrmode == FTPC_XFRMODE_ASCII ? 'A' : 'I');
|
|
if (ret < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
session->xfrmode = xfrmode;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_xfrabort
|
|
*
|
|
* Description:
|
|
* Abort a transfer in progress
|
|
*
|
|
****************************************************************************/
|
|
|
|
int ftpc_xfrabort(FAR struct ftpc_session_s *session, FAR FILE *stream)
|
|
{
|
|
FAR struct pollfd fds;
|
|
int ret;
|
|
|
|
/* Make sure that we are still connected */
|
|
|
|
if (!ftpc_connected(session))
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
/* Check if there is data waiting to be read from the cmd channel */
|
|
|
|
fds.fd = session->cmd.sd;
|
|
fds.events = POLLIN;
|
|
ret = poll(&fds, 1, 0);
|
|
if (ret > 0)
|
|
{
|
|
/* Read data from command channel */
|
|
|
|
ninfo("Flush cmd channel data\n");
|
|
while (stream &&
|
|
fread(session->buffer, 1, CONFIG_FTP_BUFSIZE, stream) > 0);
|
|
return OK;
|
|
}
|
|
|
|
FTPC_SET_INTERRUPT(session);
|
|
|
|
/* Send the Telnet interrupt sequence to abort the transfer:
|
|
* <IAC IP><IAC DM>ABORT<CR><LF>
|
|
*/
|
|
|
|
ninfo("Telnet ABORt sequence\n");
|
|
ftpc_sockprintf(&session->cmd, "%c%c", TELNET_IAC, TELNET_IP); /* Interrupt process */
|
|
ftpc_sockprintf(&session->cmd, "%c%c", TELNET_IAC, TELNET_DM); /* Telnet synch signal */
|
|
ftpc_sockprintf(&session->cmd, "ABOR\r\n"); /* Abort */
|
|
ftpc_sockflush(&session->cmd);
|
|
|
|
/* Read remaining bytes from connection */
|
|
|
|
while (stream &&
|
|
fread(session->buffer, 1, CONFIG_FTP_BUFSIZE, stream) > 0);
|
|
|
|
/* Get the ABORt reply */
|
|
|
|
fptc_getreply(session);
|
|
|
|
/* Expected replies are: "226 Closing data connection" or
|
|
* "426 Connection closed; transfer aborted"
|
|
*/
|
|
|
|
if (session->code != 226 && session->code != 426)
|
|
{
|
|
ninfo("Expected 226 or 426 reply\n");
|
|
}
|
|
else
|
|
{
|
|
/* Get the next reply */
|
|
|
|
fptc_getreply(session);
|
|
|
|
/* Expected replies are: "226 Closing data connection" or
|
|
* "225 Data connection open; no transfer in progress"
|
|
*/
|
|
|
|
if (session->code != 226 && session->code != 225)
|
|
{
|
|
ninfo("Expected 225 or 226 reply\n");
|
|
}
|
|
}
|
|
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_timeout
|
|
*
|
|
* Description:
|
|
* A timeout occurred -- either on a specific command or while waiting
|
|
* for a reply.
|
|
*
|
|
* NOTE:
|
|
* This function executes in the context of a timer interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void ftpc_timeout(wdparm_t arg)
|
|
{
|
|
FAR struct ftpc_session_s *session = (FAR struct ftpc_session_s *)arg;
|
|
|
|
nerr("ERROR: Timeout!\n");
|
|
DEBUGASSERT(session);
|
|
kill(session->pid, CONFIG_FTP_SIGNAL);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_absrpath
|
|
*
|
|
* Description:
|
|
* Get the absolute path to a remote file, handling tilde expansion.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR char *ftpc_absrpath(FAR struct ftpc_session_s *session,
|
|
FAR const char *relpath)
|
|
{
|
|
FAR char *absrpath = ftpc_abspath(session, relpath,
|
|
session->homerdir, session->currdir);
|
|
ninfo("%s -> %s\n", relpath, absrpath);
|
|
return absrpath;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ftpc_abslpath
|
|
*
|
|
* Description:
|
|
* Get the absolute path to a local file, handling tilde expansion.
|
|
*
|
|
****************************************************************************/
|
|
|
|
FAR char *ftpc_abslpath(FAR struct ftpc_session_s *session,
|
|
FAR const char *relpath)
|
|
{
|
|
FAR char *abslpath = ftpc_abspath(session, relpath,
|
|
session->homeldir, session->curldir);
|
|
ninfo("%s -> %s\n", relpath, abslpath);
|
|
return abslpath;
|
|
}
|