Skip to content

FTPS option in "Add FTP" is ignored by the native plugin – connections always use plain FTP #2979

Open

Description

@letkomar

Check for existing issues

  • Completed

Describe the bug / provide steps to reproduce it

Summary

The "Add FTP" dialog offers Security type: FTP / FTPS, and the selected value is correctly passed down to the native Android plugin. However, the native code never uses it and always creates a plain FTPClient. As a result, servers that require explicit FTPS (AUTH TLS) cannot be used at all: Acode sends USER/PASS over an unencrypted control channel and the server rejects the login.

This also seems to be the cause of the old issue #85 (SSLHandshakeFailed with FTPS / 530 Please login with USER and PASS with FTP).

Steps to reproduce

  1. Use an FTP server that requires explicit TLS on port 21 (AUTH TLS, e.g. vsftpd with force_local_logins_ssl=YES or ProFTPD with TLSRequired on).
  2. In Acode: File browser → + → FTP.
  3. Fill in host, port 21, username, password, select FTPS and confirm.

Expected: Acode negotiates TLS (AUTH TLS), logs in and lists the directory.
Actual: The connection is made without TLS and the server refuses the login (e.g. 530, or "TLS required on the control channel").

The same server works fine with other clients (WinSCP, FileZilla) using "FTP – TLS/SSL explicit encryption".

Root cause

  • src/lib/remoteStorage.js – the dialog sets security = "ftps".
  • src/fileSystem/ftp.js → src/plugins/ftp/www/ftp.js – securityType is passed to cordova.exec as argument index 5:
    [host, port, username, password, connectionMode, securityType, encoding]
  • src/plugins/ftp/src/android/com/foxdebug/ftp/Ftp.java – connect():
    • securityType is read (args.optString(5)) but never used,
    • the client is always created as new FTPClient(), so no TLS is ever negotiated.

Related bug (same method)

The argument indexes in Ftp.java do not match the order sent from ftp.js. Java reads connectionMode from index 6 (which is encoding) and encoding from index 8. Therefore active mode can never be enabled: connectionMode is never "active", so passive mode is always used.

Proposed fix

Only Ftp.java needs to change. commons-net:3.12.0 is already a dependency and FTPSClient is in the already imported org.apache.commons.net.ftp.* package, so no new dependency or JS change is needed.

@@ -78,6 +78,11 @@ public class Ftp extends CordovaPlugin {
     connect(args, callback, false);
   }
 
+  private FTPClient createClient(String securityType) {
+    // Explicit FTPS (AUTH TLS on the standard port), isImplicit = false
+    return "ftps".equals(securityType) ? new FTPSClient(false) : new FTPClient();
+  }
+
   public void connect(
     JSONArray args,
     CallbackContext callback,
@@ -93,11 +98,9 @@ public class Ftp extends CordovaPlugin {
             String host = args.optString(0);
             String username = args.optString(2);
             String password = args.optString(3);
-            String defaultPath = args.optString(4);
+            String connectionMode = args.optString(4);
             String securityType = args.optString(5);
-            String connectionMode = args.optString(6);
-            String encryption = args.optString(7);
-            String encoding = args.optString(8);
+            String encoding = args.optString(6);
             String ftpId = getFtpId(host, port, username);
             FTPClient ftp = null;
 
@@ -118,9 +121,11 @@ public class Ftp extends CordovaPlugin {
                 Log.d("FTP", "FTPClient (" + ftpId + ") is not connected");
                 ftp.disconnect();
                 Log.d("FTP", "FTPClient (" + ftpId + ") disconnecting...");
+                ftp = createClient(securityType);
+                ftpProfiles.put(ftpId, ftp);
               } else {
                 Log.d("FTP", "Creating new FTPClient (" + ftpId + ")");
-                ftp = new FTPClient();
+                ftp = createClient(securityType);
                 ftpProfiles.put(ftpId, ftp);
               }
 
@@ -149,6 +154,12 @@ public class Ftp extends CordovaPlugin {
                 return;
               }
 
+              if (ftp instanceof FTPSClient) {
+                // RFC 4217: protect the data channel (PBSZ 0 + PROT P) after AUTH TLS
+                ((FTPSClient) ftp).execPBSZ(0);
+                ((FTPSClient) ftp).execPROT("P");
+              }
+
               ftp.setListHiddenFiles(true);
               ftpProfiles.put(ftpId, ftp);
               Log.d("FTP", "FTPClient (" + ftpId + ") connected");

Notes

  • TLS session reuse: some servers (vsftpd with the default require_ssl_reuse=YES, ProFTPD without TLSOptions NoSessionReuseRequired) require the data connection to reuse the control connection's TLS session. Commons Net FTPSClient does not do this by default, so directory listings/transfers may fail on such servers (e.g. 522 SSL connection failed: session reuse required). This would need an additional workaround in _prepareDataSocket_().
  • Certificate validation: FTPSClient uses a permissive trust manager by default. It may be worth adding proper certificate/hostname validation or a "trust this certificate" prompt.
  • Implicit FTPS (port 990) is not covered. It could be added later as a separate option (new FTPSClient(true)).
  • The diff above has not been compiled/tested against a full Cordova build yet.

Environment

Device Information:
WebView Info:
Package Name: com.google.android.webview
Version: 155.0.8059.43

App Info:
Name: Acode
Package Name: com.foxdebug.acodefree
Version: 1.13.5
Version Code: 1011

Device Info:
Android Version: 17
Manufacturer: samsung
Model: SM-X840
Platform: Android
Cordova Version: 15.1.0

Screen Info:
Width: 1400
Height: 876
Color Depth: 24

Additional Info:
Language: cs-CZ
User Agent: Mozilla/5.0 (Linux; Android 17; SM-X840 Build/CP2A.260605.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/155.0.8059.43 Safari/537.36

If applicable, add mockups / screenshots regarding your vision

No response

If applicable, attach your Acode.log file to this issue.

Acode.log
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions