Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance CURLOPT_FTP_SKIP_PASV_IP #1470

Closed
wants to merge 21 commits into from
23 changes: 15 additions & 8 deletions docs/libcurl/opts/CURLOPT_FTP_SKIP_PASV_IP.3
Expand Up @@ -26,20 +26,27 @@ CURLOPT_FTP_SKIP_PASV_IP \- ignore the IP address in the PASV response
.SH SYNOPSIS
#include <curl/curl.h>

CURLcode curl_easy_setopt(CURL *handle, CURLOPT_FTP_SKIP_PASV_IP, long skip);
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_FTP_SKIP_PASV_IP, long rule);
.SH DESCRIPTION
Pass a long. If \fIskip\fP is set to 1, it instructs libcurl to not use the IP
address the server suggests in its 227-response to libcurl's PASV command when
libcurl connects the data connection. Instead libcurl will re-use the same IP
address it already uses for the control connection. But it will use the port
number from the 227-response.
URL_FTP_SKIP_PASV_IP_ALWAYS instructs libcurl to ignore the IP address the
server suggests in its 227-response to libcurl's PASV command when libcurl connects
the data connection. Instead libcurl will re-use the same IP address it already uses
for the control connection. But it will use the port number from the 227-response.
The allowed values are:
.IP CURL_FTP_SKIP_PASV_IP_NEVER
Always use the IP returned by the server.
.IP CURL_FTP_SKIP_PASV_IP_ALWAYS
Always ignore the IP returned by the server.
.IP CURL_FTP_SKIP_PASV_IP_IF_NOT_ROUTABLE
Only ignore the returned IP if it is within the private address space (see RFC1918),
which is a common server configuration error.

This option thus allows libcurl to work around broken server installations
that due to NATs, firewalls or incompetence report the wrong IP address back.

This option has no effect if PORT, EPRT or EPSV is used instead of PASV.
.SH DEFAULT
0
CURL_FTP_SKIP_PASV_IP_NEVER
.SH PROTOCOLS
FTP
.SH EXAMPLE
Expand All @@ -49,7 +56,7 @@ if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/file.txt");

/* please ignore the IP in the PASV response */
curl_easy_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, 1L);
curl_easy_setopt(curl, CURLOPT_FTP_SKIP_PASV_IP, CURL_FTP_SKIP_PASV_IP_ALWAYS);
ret = curl_easy_perform(curl);

curl_easy_cleanup(curl);
Expand Down
3 changes: 3 additions & 0 deletions docs/libcurl/symbols-in-versions
Expand Up @@ -710,6 +710,9 @@ CURL_FORMADD_NULL 7.9.8
CURL_FORMADD_OK 7.9.8
CURL_FORMADD_OPTION_TWICE 7.9.8
CURL_FORMADD_UNKNOWN_OPTION 7.9.8
CURL_FTP_SKIP_PASV_IP_ALWAYS 7.55.0
CURL_FTP_SKIP_PASV_IP_IF_NOT_ROUTABLE 7.55.0
CURL_FTP_SKIP_PASV_IP_NEVER 7.55.0
CURL_GLOBAL_ACK_EINTR 7.30.0
CURL_GLOBAL_ALL 7.8
CURL_GLOBAL_DEFAULT 7.8
Expand Down
6 changes: 6 additions & 0 deletions include/curl/curl.h
Expand Up @@ -1824,6 +1824,12 @@ typedef enum {
/* three convenient "aliases" that follow the name scheme better */
#define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER

/*parameters for the CURLOPT_FTP_SKIP_PASV_IP option */
#define CURL_FTP_SKIP_PASV_IP_NEVER 0L
#define CURL_FTP_SKIP_PASV_IP_ALWAYS 1L
#define CURL_FTP_SKIP_PASV_IP_IF_NOT_ROUTABLE 2L


/* These enums are for use with the CURLOPT_HTTP_VERSION option. */
enum {
CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd
Expand Down
30 changes: 27 additions & 3 deletions lib/ftp.c
Expand Up @@ -1867,6 +1867,18 @@ static char *control_address(struct connectdata *conn)
return conn->ip_addr_str;
}

static bool is_private_ip_v4(int ip[4])
{
if(ip[0] == 127 || /*127.0.0.0/8 (localhost)*/
ip[0] == 10 || /*10.0.0.0/8 (private)*/
(ip[0] == 192 && ip[1] == 168) || /*192.168.0.0/16 (private)*/
(ip[0] == 169 && ip[1] == 254) || /*169.254.0.0/16 (link-local)*/
(ip[0] == 172 && ip[1] / 16 == 1)) /*172.16.0.0/12 (private)*/
return false;
return true;
}


static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
int ftpcode)
{
Expand Down Expand Up @@ -1930,6 +1942,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
/* positive PASV response */
int ip[4];
int port[2];
bool skipIp;

/*
* Scan for a sequence of six comma-separated numbers and use them as
Expand All @@ -1954,9 +1967,20 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn,
}

/* we got OK from server */
if(data->set.ftp_skip_ip) {
/* told to ignore the remotely given IP but instead use the host we used
for the control connection */
skipIp = data->set.ftp_pasvp_ip_rule == CURL_FTP_SKIP_PASV_IP_ALWAYS;

if(data->set.ftp_pasvp_ip_rule == CURL_FTP_SKIP_PASV_IP_IF_NOT_ROUTABLE &&
!is_private_ip_v4(ip)) {
int ip_ctrl[4];
if(4 != sscanf(control_address(conn), "%d.%d.%d.%d",
&ip_ctrl[0], &ip_ctrl[1], &ip_ctrl[2], &ip_ctrl[3]) ||
is_private_ip_v4(ip_ctrl))
skipIp = true;
}

if(skipIp) {
/* told to ignore the remotely given IP but instead use the host we used
for the control connection */
infof(data, "Skip %d.%d.%d.%d for data connection, re-use %s instead\n",
ip[0], ip[1], ip[2], ip[3],
conn->host.name);
Expand Down
9 changes: 4 additions & 5 deletions lib/url.c
Expand Up @@ -1604,11 +1604,10 @@ CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option,
break;

case CURLOPT_FTP_SKIP_PASV_IP:
/*
* Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the
* bypass of the IP address in PASV responses.
*/
data->set.ftp_skip_ip = (0 != va_arg(param, long)) ? TRUE : FALSE;
/*
* Set up how to handle the IP that is returned by the server for PASV
*/
data->set.ftp_pasvp_ip_rule = va_arg(param, long);
break;

case CURLOPT_READDATA:
Expand Down
4 changes: 2 additions & 2 deletions lib/urldata.h
Expand Up @@ -1724,8 +1724,8 @@ struct UserDefined {
bool global_dns_cache; /* subject for future removal */
bool tcp_nodelay; /* whether to enable TCP_NODELAY or not */
bool ignorecl; /* ignore content length */
bool ftp_skip_ip; /* skip the IP address the FTP server passes on to
us */
long ftp_pasvp_ip_rule; /* how to handle the IP address the FTP server
passes on to us */
bool connect_only; /* make connection, let application use the socket */
long ssh_auth_types; /* allowed SSH auth types */
bool http_te_skip; /* pass the raw body data to the user, even when
Expand Down