Skip to content

Commit

Permalink
FTP client trusts the host from PASV response by default
Browse files Browse the repository at this point in the history
Adapted patch from Jochen Wiedmann
  • Loading branch information
garydgregory committed Nov 7, 2022
1 parent 387ee3e commit b0bff89
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 19 deletions.
2 changes: 1 addition & 1 deletion checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ limitations under the License.
</module>

<module name="LineLength">
<property name="max" value="132"/>
<property name="max" value="160"/>
</module>

<module name="TreeWalker">
Expand Down
3 changes: 3 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Arturo Bernal">
Use Math.min and Math.max method instead of manual calculations. #104.
</action>
<action type="fix" dev="ggregory" due-to="Jochen Wiedmann, Gary Gregory">
FTP client trusts the host from PASV response by default.
</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary Gregory">
[FTP] Add FTPClient.mdtmInstant(String).
Expand Down
96 changes: 78 additions & 18 deletions src/main/java/org/apache/commons/net/ftp/FTPClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,10 @@
* @see FTPFileEntryParserFactory
* @see DefaultFTPFileEntryParserFactory
* @see FTPClientConfig
*
* @see org.apache.commons.net.MalformedServerReplyException
*/
public class FTPClient extends FTP implements Configurable {

// @since 3.0
private static class CSL implements CopyStreamListener {

Expand Down Expand Up @@ -437,6 +437,18 @@ private static class PropertiesSingleton {
*/
public static final String FTP_SYSTEM_TYPE_DEFAULT = "org.apache.commons.net.ftp.systemType.default";

/**
* The system property that defines the default for {@link #isIpAddressFromPasvResponse()}. This property, if present, configures the default for the
* following: If the client receives the servers response for a PASV request, then that response will contain an IP address. If this property is true, then
* the client will use that IP address, as requested by the server. This is compatible to version {@code 3.8.0}, and before. If this property is false, or
* absent, then the client will ignore that IP address, and instead use the remote address of the control connection.
*
* @see #isIpAddressFromPasvResponse()
* @see #setIpAddressFromPasvResponse(boolean)
* @since 3.9.0
*/
public static final String FTP_IP_ADDRESS_FROM_PASV_RESPONSE = "org.apache.commons.net.ftp.ipAddressFromPasvResponse";

/**
* The name of an optional systemType properties file ({@value}), which is loaded
* using {@link Class#getResourceAsStream(String)}.<br>
Expand Down Expand Up @@ -625,6 +637,8 @@ static String parsePathname(final String reply)
/** Map of FEAT responses. If null, has not been initialized. */
private HashMap<String, Set<String>> featuresMap;

private boolean ipAddressFromPasvResponse = Boolean.parseBoolean(System.getProperty(FTPClient.FTP_IP_ADDRESS_FROM_PASV_RESPONSE));

/**
* Default FTPClient constructor. Creates a new FTPClient instance
* with the data connection mode set to
Expand Down Expand Up @@ -928,35 +942,44 @@ protected void _parseExtendedPassiveModeReply(String reply) throws MalformedServ
protected void _parsePassiveModeReply(final String reply) throws MalformedServerReplyException {
final Matcher m = PARMS_PAT.matcher(reply);
if (!m.find()) {
throw new MalformedServerReplyException(
"Could not parse passive host information.\nServer Reply: " + reply);
throw new MalformedServerReplyException("Could not parse passive host information.\nServer Reply: " + reply);
}

this.passiveHost = "0,0,0,0".equals(m.group(1)) ? _socket_.getInetAddress().getHostAddress()
: m.group(1).replace(',', '.'); // Fix up to look like IP address
int pasvPort;
// Fix up to look like IP address
String pasvHost = "0,0,0,0".equals(m.group(1)) ? _socket_.getInetAddress().getHostAddress() : m.group(1).replace(',', '.');

try {
final int oct1 = Integer.parseInt(m.group(2));
final int oct2 = Integer.parseInt(m.group(3));
passivePort = (oct1 << 8) | oct2;
pasvPort = (oct1 << 8) | oct2;
} catch (final NumberFormatException e) {
throw new MalformedServerReplyException(
"Could not parse passive port information.\nServer Reply: " + reply);
throw new MalformedServerReplyException("Could not parse passive port information.\nServer Reply: " + reply);
}

if (passiveNatWorkaroundStrategy != null) {
try {
final String newPassiveHost = passiveNatWorkaroundStrategy.resolve(this.passiveHost);
if (!this.passiveHost.equals(newPassiveHost)) {
fireReplyReceived(0,
"[Replacing PASV mode reply address " + this.passiveHost + " with " + newPassiveHost + "]\n");
this.passiveHost = newPassiveHost;
if (isIpAddressFromPasvResponse()) {
// Pre-3.9.0 behavior
if (passiveNatWorkaroundStrategy != null) {
try {
final String newPassiveHost = passiveNatWorkaroundStrategy.resolve(pasvHost);
if (!pasvHost.equals(newPassiveHost)) {
fireReplyReceived(0, "[Replacing PASV mode reply address " + this.passiveHost + " with " + newPassiveHost + "]\n");
pasvHost = newPassiveHost;
}
} catch (final UnknownHostException e) { // Should not happen as we are passing in an IP address
throw new MalformedServerReplyException("Could not parse passive host information.\nServer Reply: " + reply);
}
} catch (final UnknownHostException e) { // Should not happen as we are passing in an IP address
throw new MalformedServerReplyException(
"Could not parse passive host information.\nServer Reply: " + reply);
}
} else {
// Post-3.8 behavior
if (_socket_ == null) {
pasvHost = null; // For unit testing.
} else {
pasvHost = _socket_.getInetAddress().getHostAddress();
}
}
this.passiveHost = pasvHost;
this.passivePort = pasvPort;
}

/**
Expand Down Expand Up @@ -4140,4 +4163,41 @@ public boolean structureMount(final String pathname) throws IOException
{
return FTPReply.isPositiveCompletion(smnt(pathname));
}

/**
* Returns, whether the IP address from the server's response should be used.
* Until 3.9.0, this has always been the case. Beginning with 3.9.0,
* that IP address will be silently ignored, and replaced with the remote
* IP address of the control connection, unless this configuration option is
* given, which restores the old behavior. To enable this by default, use
* the system property {@link FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE}.
* @return True, if the IP address from the server's response will be used
* (pre-3.9 compatible behavior), or false (ignore that IP address).
*
* @see FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE
* @see #setIpAddressFromPasvResponse(boolean)
* @since 3.9.0
*/
public boolean isIpAddressFromPasvResponse() {
return ipAddressFromPasvResponse;
}

/**
* Sets whether the IP address from the server's response should be used.
* Until 3.9.0, this has always been the case. Beginning with 3.9.0,
* that IP address will be silently ignored, and replaced with the remote
* IP address of the control connection, unless this configuration option is
* given, which restores the old behavior. To enable this by default, use
* the system property {@link FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE}.
*
* @param usingIpAddressFromPasvResponse True, if the IP address from the
* server's response should be used (pre-3.9.0 compatible behavior), or
* false (ignore that IP address).
* @see FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE
* @see #isIpAddressFromPasvResponse
* @since 3.9.0
*/
public void setIpAddressFromPasvResponse(boolean usingIpAddressFromPasvResponse) {
this.ipAddressFromPasvResponse = usingIpAddressFromPasvResponse;
}
}
29 changes: 29 additions & 0 deletions src/test/java/org/apache/commons/net/ftp/FTPClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ private static class LocalClient extends FTPClient {
public String getSystemType() throws IOException {
return systemType;
}

public void setSystemType(final String type) {
systemType = type;
}
Expand Down Expand Up @@ -98,52 +99,80 @@ public void testParseClient() {

public void testParsePassiveModeReplyForLocalAddressWithNatWorkaround() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertEquals("8.8.8.8", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertNull(client.getPassiveHost());
}

@SuppressWarnings("deprecation") // testing deprecated code
public void testParsePassiveModeReplyForLocalAddressWithNatWorkaroundDisabled() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setPassiveNatWorkaround(false);
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertEquals("172.16.204.138", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertNull(client.getPassiveHost());
}


public void testParsePassiveModeReplyForLocalAddressWithoutNatWorkaroundStrategy() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setPassiveNatWorkaroundStrategy(null);
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertEquals("172.16.204.138", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertNull(client.getPassiveHost());
}

public void testParsePassiveModeReplyForLocalAddressWithSimpleNatWorkaroundStrategy() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setPassiveNatWorkaroundStrategy(hostname -> "4.4.4.4");
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertEquals("4.4.4.4", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22).");
assertNull(client.getPassiveHost());
}

public void testParsePassiveModeReplyForNonLocalAddressWithNatWorkaround() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22).");
assertEquals("8.8.4.4", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22).");
assertNull(client.getPassiveHost());
}

@SuppressWarnings("deprecation") // testing deprecated code
public void testParsePassiveModeReplyForNonLocalAddressWithNatWorkaroundDisabled() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setPassiveNatWorkaround(false);
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22).");
assertEquals("8.8.4.4", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22).");
assertNull(client.getPassiveHost());
}

public void testParsePassiveModeReplyForNonLocalAddressWithoutNatWorkaroundStrategy() throws Exception {
final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8");
client.setPassiveNatWorkaroundStrategy(null);
client.setIpAddressFromPasvResponse(true);
client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22).");
assertEquals("8.8.4.4", client.getPassiveHost());
client.setIpAddressFromPasvResponse(false);
client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22).");
assertNull(client.getPassiveHost());
}

public void testParserCachingNullKey() throws Exception {
Expand Down

0 comments on commit b0bff89

Please sign in to comment.