You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
517 lines
15 KiB
517 lines
15 KiB
/**************************************************************************** |
|
* apps/netutils/ftpc/ftpc_transfer.c |
|
* |
|
* Copyright (C) 2011 Gregory Nutt. All rights reserved. |
|
* Author: Gregory Nutt <spudmonkey@racsa.co.cr> |
|
* |
|
* 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 <apps/ftpc.h> |
|
|
|
#include "ftpc_internal.h" |
|
|
|
/**************************************************************************** |
|
* Pre-processor Definitions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Private Types |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Private Data |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Public Data |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Private Functions |
|
****************************************************************************/ |
|
|
|
/**************************************************************************** |
|
* Name: ftp_pasvmode |
|
* |
|
* Description: |
|
* Enter passive mode. |
|
* |
|
* 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. |
|
* |
|
****************************************************************************/ |
|
|
|
static int ftp_pasvmode(struct ftpc_session_s *session, |
|
uint8_t addrport[6]) |
|
{ |
|
int tmpap[6]; |
|
char *ptr; |
|
int nscan; |
|
int ret; |
|
int i; |
|
|
|
/* Does this host support the PASV command */ |
|
|
|
if (!FTPC_HAS_PASV(session)) |
|
{ |
|
ndbg("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 |
|
* 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 address begins */ |
|
|
|
ptr = session->reply + 4; |
|
while (!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) |
|
{ |
|
ndbg("Error parsing PASV reply: '%s'\n", session->reply); |
|
return ERROR; |
|
} |
|
|
|
/* Then copy the sscanf'ed values as bytes */ |
|
|
|
|
|
for (i = 0; i < 6; i++) |
|
{ |
|
addrport[i] = (uint8_t)(tmpap[i] & 0xff); |
|
} |
|
|
|
return OK; |
|
} |
|
|
|
/**************************************************************************** |
|
* 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; |
|
int ret = OK; |
|
|
|
/* 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] == '/') |
|
{ |
|
ret = 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) |
|
{ |
|
ret = asprintf(&ptr, "%s%s", curdir, relpath+1); |
|
} |
|
|
|
/* Check for an absolute path */ |
|
|
|
else if (relpath[0] == '/' && relpath[1] == ':' && relpath[2] == '\\') |
|
{ |
|
ptr = strdup(relpath); |
|
} |
|
|
|
/* Assume it a relative path */ |
|
|
|
else |
|
{ |
|
ret = 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) |
|
{ |
|
struct sockaddr_in addr; |
|
uint8_t addrport[6]; |
|
uint8_t *paddr; |
|
uint8_t *pport; |
|
int ret; |
|
|
|
/* We must be connected to initiate a transfer */ |
|
|
|
if (!ftpc_connected(session)) |
|
{ |
|
ndbg("Not connected\n"); |
|
goto errout; |
|
} |
|
|
|
/* Initialize the data channel */ |
|
|
|
ret = ftpc_sockinit(&session->data); |
|
if (ret != OK) |
|
{ |
|
ndbg("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. */ |
|
|
|
ret = ftp_pasvmode(session, addrport); |
|
if (ret != OK) |
|
{ |
|
ndbg("ftp_pasvmode() failed: %d\n", errno); |
|
goto errout_with_data; |
|
} |
|
|
|
/* Configure the data socket */ |
|
|
|
ftpc_sockgetsockname(&session->cmd, &addr); |
|
memcpy(&addr.sin_addr, addrport, 4); |
|
memcpy(&addr.sin_port, addrport+4, 2); |
|
|
|
/* Connect the data socket */ |
|
|
|
ret = ftpc_sockconnect(&session->data, &addr); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpc_sockconnect() failed: %d\n", errno); |
|
goto errout_with_data; |
|
} |
|
} |
|
else |
|
{ |
|
/* Wait for the connection to be established */ |
|
|
|
ftpc_socklisten(&session->data); |
|
|
|
/* 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; |
|
|
|
ret = ftpc_cmd(session, "PORT %d,%d,%d,%d,%d,%d", |
|
paddr[0], paddr[1], paddr[2], |
|
paddr[3], pport[0], pport[1]); |
|
if (ret < 0) |
|
{ |
|
ndbg("ftpc_cmd() failed: %d\n", errno); |
|
goto errout_with_data; |
|
} |
|
} |
|
return OK; |
|
|
|
errout_with_data: |
|
ftpc_sockclose(&session->data); |
|
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'); |
|
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 */ |
|
|
|
nvdbg("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> |
|
*/ |
|
|
|
nvdbg("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); |
|
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) |
|
{ |
|
nvdbg("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) |
|
{ |
|
nvdbg("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, uint32_t arg1, ...) |
|
{ |
|
FAR struct ftpc_session_s *session = (FAR struct ftpc_session_s *)arg1; |
|
|
|
nlldbg("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); |
|
nvdbg("%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); |
|
nvdbg("%s -> %s\n", relpath, abslpath); |
|
return abslpath; |
|
} |
|
|
|
|
|
|