nuttx-apps/netutils/ftpc/ftpc_transfer.c
Xiang Xiao 857158451b Unify the void cast usage
1.Remove void cast for function because many place ignore the returned value witout cast
2.Replace void cast for variable with UNUSED macro

Change-Id: Ie644129a563244a6397036789c4c3ea83c4e9b09
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-01-02 23:21:01 +08:00

694 lines
20 KiB
C

/****************************************************************************
* apps/netutils/ftpc/ftpc_transfer.c
*
* Copyright (C) 2011 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* 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"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* 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, "|||%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_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');
UNUSED(ret);
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 replys 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 replys are: or "225 Data connection open; no transfer in progress"
* "226 Closing data connection"
*/
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(int argc, wdparm_t arg1, ...)
{
FAR struct ftpc_session_s *session = (FAR struct ftpc_session_s *)arg1;
nerr("ERROR: Timeout!\n");
DEBUGASSERT(argc == 1 && 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;
}