[gvfs] ftp: Add implicit TLS mode



commit 708fd040f8848e9afdf30a91a2c0c87e49f13ba9
Author: Mantas Mikulėnas <grawity gmail com>
Date:   Sat Jun 9 16:58:32 2018 +0300

    ftp: Add implicit TLS mode
    
    Although specs discourage it, ftps:// is generally accepted to mean
    "implicit" TLS on a separate port (in the same style as HTTPS); some
    servers still use this method and it's regaining popularity within IANA.
    
    gvfs couldn't connect to such servers, as it only implemented "explicit"
    TLS (STARTTLS) mode.
    
    This patch implements "implicit" TLS mode and adds the 'ftpis' URI
    scheme for it.
    
    https://gitlab.gnome.org/GNOME/gvfs/issues/4

 daemon/ftpis.mount.in      |  7 ++++
 daemon/gvfsbackendftp.c    | 69 ++++++++++++++++++++++++---------------
 daemon/gvfsbackendftp.h    |  8 ++++-
 daemon/gvfsftpconnection.c |  3 +-
 daemon/gvfsftpconnection.h |  1 +
 daemon/gvfsftptask.c       | 80 +++++++++++++++++++++++++++++++++-------------
 daemon/gvfsftptask.h       |  2 +-
 daemon/meson.build         |  4 +--
 8 files changed, 120 insertions(+), 54 deletions(-)
---
diff --git a/daemon/ftpis.mount.in b/daemon/ftpis.mount.in
new file mode 100644
index 00000000..7863b3e8
--- /dev/null
+++ b/daemon/ftpis.mount.in
@@ -0,0 +1,7 @@
+[Mount]
+Type=ftpis
+Exec=@libexecdir@/gvfsd-ftp
+AutoMount=false
+Scheme=ftpis
+DefaultPort=990
+HostnameIsInetAddress=true
diff --git a/daemon/gvfsbackendftp.c b/daemon/gvfsbackendftp.c
index d5540250..8fad4e41 100644
--- a/daemon/gvfsbackendftp.c
+++ b/daemon/gvfsbackendftp.c
@@ -407,6 +407,12 @@ g_vfs_backend_ftp_init (GVfsBackendFtp *ftp)
   g_cond_init (&ftp->cond);
 }
 
+static guint
+g_vfs_backend_ftp_default_port (GVfsBackendFtp *ftp)
+{
+  return (ftp->tls_mode == G_VFS_FTP_TLS_MODE_IMPLICIT) ? 990 : 21;
+}
+
 /* If the initial connection has a verification error, display the certificate
  * to the user and ask whether to proceed. */
 static gboolean
@@ -440,7 +446,7 @@ do_mount (GVfsBackend *backend,
   gboolean aborted, anonymous, break_on_fail;
   GPasswordSave password_save = G_PASSWORD_SAVE_NEVER;
   GNetworkAddress *addr;
-  guint port;
+  guint port, default_port;
 
 restart:
   task.conn = g_vfs_ftp_connection_new (ftp->addr, task.cancellable, &task.error);
@@ -453,14 +459,11 @@ restart:
       return;
     }
 
-  /* send pre-login commands */
-  g_vfs_ftp_task_receive (&task, 0, NULL);
-
-  /* Secure the initial connection if necessary. This may result in a prompt
-   * for the user. */
+  /* Receive the greeting, and secure the initial connection if necessary. This
+   * may result in a prompt for the user.
+   */
   ftp->mount_source = mount_source;
-  if (ftp->use_tls &&
-      !g_vfs_ftp_task_enable_tls (&task, initial_certificate_cb, ftp))
+  if (!g_vfs_ftp_task_initial_handshake (&task, initial_certificate_cb, ftp))
     {
       ftp->mount_source = NULL;
       g_vfs_ftp_task_done (&task);
@@ -484,6 +487,7 @@ restart:
   addr = G_NETWORK_ADDRESS (ftp->addr);
   g_object_ref (addr);
   port = g_network_address_get_port (addr);
+  default_port = g_vfs_backend_ftp_default_port (ftp);
   username = NULL;
   password = NULL;
   break_on_fail = FALSE;
@@ -494,17 +498,17 @@ restart:
       break_on_fail = TRUE;
       goto try_login;
     }
- 
+
   if (g_vfs_keyring_lookup_password (ftp->user,
-                                    g_network_address_get_hostname (addr),
-                                    NULL,
-                                    "ftp",
-                                    NULL,
-                                    NULL,
-                                    port == 21 ? 0 : port,
-                                    &username,
-                                    NULL,
-                                    &password) &&
+                                     g_network_address_get_hostname (addr),
+                                     NULL,
+                                     "ftp",
+                                     NULL,
+                                     NULL,
+                                     (port == default_port) ? 0 : port,
+                                     &username,
+                                     NULL,
+                                     &password) &&
       username != NULL &&
       password != NULL)
     {
@@ -646,15 +650,20 @@ try_login:
                                    "ftp",
                                    NULL,
                                    NULL,
-                                   port == 21 ? 0 : port,
+                                   (port == default_port) ? 0 : port,
                                    ftp->password,
                                    password_save);
       g_free (prompt);
     }
 
-  mount_spec = g_mount_spec_new (ftp->use_tls ? "ftps" : "ftp");
+  if (ftp->tls_mode == G_VFS_FTP_TLS_MODE_EXPLICIT)
+    mount_spec = g_mount_spec_new ("ftps");
+  else if (ftp->tls_mode == G_VFS_FTP_TLS_MODE_IMPLICIT)
+    mount_spec = g_mount_spec_new ("ftpis");
+  else
+    mount_spec = g_mount_spec_new ("ftp");
   g_mount_spec_set (mount_spec, "host", g_network_address_get_hostname (addr));
-  if (port != 21)
+  if (port != default_port)
     {
       char *port_str = g_strdup_printf ("%u", port);
       g_mount_spec_set (mount_spec, "port", port_str);
@@ -695,9 +704,17 @@ try_mount (GVfsBackend *backend,
           gboolean is_automount)
 {
   GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
-  const char *host, *port_str;
+  const char *type, *host, *port_str;
   guint port;
 
+  type = g_mount_spec_get_type (mount_spec);
+  if (g_strcmp0 (type, "ftps") == 0)
+    ftp->tls_mode = G_VFS_FTP_TLS_MODE_EXPLICIT;
+  else if (g_strcmp0 (type, "ftpis") == 0)
+    ftp->tls_mode = G_VFS_FTP_TLS_MODE_IMPLICIT;
+  else
+    ftp->tls_mode = G_VFS_FTP_TLS_MODE_NONE;
+
   host = g_mount_spec_get (mount_spec, "host");
   if (host == NULL)
     {
@@ -709,7 +726,7 @@ try_mount (GVfsBackend *backend,
   port_str = g_mount_spec_get (mount_spec, "port");
   if (port_str == NULL)
     {
-      port = 21;
+      port = g_vfs_backend_ftp_default_port (ftp);
     }
   else
     {
@@ -720,12 +737,12 @@ try_mount (GVfsBackend *backend,
   ftp->addr = g_network_address_new (host, port);
   ftp->user = g_strdup (g_mount_spec_get (mount_spec, "user"));
   ftp->has_initial_user = ftp->user != NULL;
-  if (port == 21)
+  if (port == g_vfs_backend_ftp_default_port (ftp))
     ftp->host_display_name = g_strdup (host);
   else
     ftp->host_display_name = g_strdup_printf ("%s:%u", host, port);
-  ftp->use_tls = strcmp (g_mount_spec_get_type (mount_spec), "ftps") == 0;
-  if (ftp->use_tls)
+
+  if (ftp->tls_mode != G_VFS_FTP_TLS_MODE_NONE)
     ftp->server_identity = g_object_ref (ftp->addr);
 
   return FALSE;
diff --git a/daemon/gvfsbackendftp.h b/daemon/gvfsbackendftp.h
index bd98a79f..e4c03cfb 100644
--- a/daemon/gvfsbackendftp.h
+++ b/daemon/gvfsbackendftp.h
@@ -31,6 +31,12 @@ G_BEGIN_DECLS
 
 #define G_VFS_FTP_TIMEOUT_IN_SECONDS 30
 
+typedef enum {
+  G_VFS_FTP_TLS_MODE_NONE,              /* plaintext */
+  G_VFS_FTP_TLS_MODE_IMPLICIT,          /* port 990 */
+  G_VFS_FTP_TLS_MODE_EXPLICIT,          /* STARTTLS (RFC 4217) */
+} GVfsFtpTlsMode;
+
 typedef enum {
   G_VFS_FTP_FEATURE_MDTM,
   G_VFS_FTP_FEATURE_SIZE,
@@ -92,7 +98,7 @@ struct _GVfsBackendFtp
   char *                host_display_name;
 
   /* ftps support */
-  gboolean              use_tls;
+  GVfsFtpTlsMode        tls_mode;
   GSocketConnectable *  server_identity;        /* Server identity used for verification */
   GTlsCertificate *     certificate;            /* Initial server certificate */
   GTlsCertificateFlags  certificate_errors;     /* Errors received during TLS handshake */
diff --git a/daemon/gvfsftpconnection.c b/daemon/gvfsftpconnection.c
index 2db6f51c..f5f5c76f 100644
--- a/daemon/gvfsftpconnection.c
+++ b/daemon/gvfsftpconnection.c
@@ -599,6 +599,7 @@ g_vfs_ftp_connection_is_usable (GVfsFtpConnection *conn)
 gboolean
 g_vfs_ftp_connection_enable_tls (GVfsFtpConnection * conn,
                                  GSocketConnectable *server_identity,
+                                 gboolean            implicit_tls,
                                  CertificateCallback cb,
                                  gpointer            user_data,
                                  GCancellable *      cancellable,
@@ -608,7 +609,7 @@ g_vfs_ftp_connection_enable_tls (GVfsFtpConnection * conn,
 
   g_return_val_if_fail (conn != NULL, FALSE);
   g_return_val_if_fail (conn->data == NULL, FALSE);
-  g_return_val_if_fail (!conn->waiting_for_reply, FALSE);
+  g_return_val_if_fail (implicit_tls || !conn->waiting_for_reply, FALSE);
   g_return_val_if_fail (g_buffered_input_stream_get_available (G_BUFFERED_INPUT_STREAM (conn->commands_in)) 
== 0, FALSE);
 
   secure = g_tls_client_connection_new (conn->commands,
diff --git a/daemon/gvfsftpconnection.h b/daemon/gvfsftpconnection.h
index f1ddab34..db34797c 100644
--- a/daemon/gvfsftpconnection.h
+++ b/daemon/gvfsftpconnection.h
@@ -73,6 +73,7 @@ GIOStream *             g_vfs_ftp_connection_get_data_stream  (GVfsFtpConnection
 
 gboolean                g_vfs_ftp_connection_enable_tls       (GVfsFtpConnection *      conn,
                                                                GSocketConnectable *     server_identity,
+                                                               gboolean                 implicit_tls,
                                                                CertificateCallback      cb,
                                                                gpointer                 user_data,
                                                                GCancellable *           cancellable,
diff --git a/daemon/gvfsftptask.c b/daemon/gvfsftptask.c
index 2209c2b1..5da8e549 100644
--- a/daemon/gvfsftptask.c
+++ b/daemon/gvfsftptask.c
@@ -250,9 +250,7 @@ g_vfs_ftp_task_acquire_connection (GVfsFtpTask *task)
           task->conn = g_vfs_ftp_connection_new (ftp->addr, task->cancellable, &task->error);
           if (G_LIKELY (task->conn != NULL))
             {
-              g_vfs_ftp_task_receive (task, 0, NULL);
-              if (ftp->use_tls)
-                g_vfs_ftp_task_enable_tls (task, reconnect_certificate_cb, ftp);
+              g_vfs_ftp_task_initial_handshake (task, reconnect_certificate_cb, ftp);
               g_vfs_ftp_task_login (task, ftp->user, ftp->password);
               g_vfs_ftp_task_setup_connection (task);
               if (G_LIKELY (!g_vfs_ftp_task_is_in_error (task)))
@@ -1188,7 +1186,7 @@ g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task)
   if (g_vfs_ftp_task_is_in_error (task))
     return;
 
-  if (task->backend->use_tls)
+  if (task->backend->tls_mode != G_VFS_FTP_TLS_MODE_NONE)
     g_vfs_ftp_connection_data_connection_enable_tls (task->conn,
                                                      task->backend->server_identity,
                                                      reconnect_certificate_cb,
@@ -1198,38 +1196,74 @@ g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task)
 }
 
 /**
- * g_vfs_ftp_task_enable_tls:
+ * g_vfs_ftp_task_initial_handshake:
  * @task: a task
  * @cb: callback called if there's a verification error
  * @user_data: user data passed to @cb
  *
- * Tries to enable tls on the control connection to an ftp server.
- * If the operation fails, @task will be set into an error state.
+ * Performs the initial handshake with an FTP server, including reading the
+ * greeting and activating TLS.  If the operation fails, @task will be set into
+ * an error state.
  **/
 gboolean
-g_vfs_ftp_task_enable_tls (GVfsFtpTask *task,
-                           CertificateCallback cb,
-                           gpointer user_data)
+g_vfs_ftp_task_initial_handshake (GVfsFtpTask *task,
+                                  CertificateCallback cb,
+                                  gpointer user_data)
 {
   if (g_vfs_ftp_task_is_in_error (task))
     return FALSE;
 
-  if (!g_vfs_ftp_task_send (task, 0, "AUTH TLS"))
-    return FALSE;
+  /* In implicit mode, we do the TLS handshake first, then receive the FTP
+   * greeting. Explicit mode is practically the opposite.
+   */
 
-  if (!g_vfs_ftp_connection_enable_tls (task->conn,
-                                        task->backend->server_identity,
-                                        cb,
-                                        user_data,
-                                        task->cancellable,
-                                        &task->error))
-    return FALSE;
+  if (task->backend->tls_mode == G_VFS_FTP_TLS_MODE_IMPLICIT)
+    {
+      if (!g_vfs_ftp_connection_enable_tls (task->conn,
+                                            task->backend->server_identity,
+                                            TRUE,
+                                            cb,
+                                            user_data,
+                                            task->cancellable,
+                                            &task->error))
+        return FALSE;
+
+      if (!g_vfs_ftp_task_receive (task, 0, NULL))
+        return FALSE;
+    }
+  else if (task->backend->tls_mode == G_VFS_FTP_TLS_MODE_EXPLICIT)
+    {
+      if (!g_vfs_ftp_task_receive (task, 0, NULL))
+        return FALSE;
+
+      if (!g_vfs_ftp_task_send (task, 0, "AUTH TLS"))
+        return FALSE;
+
+      if (!g_vfs_ftp_connection_enable_tls (task->conn,
+                                            task->backend->server_identity,
+                                            FALSE,
+                                            cb,
+                                            user_data,
+                                            task->cancellable,
+                                            &task->error))
+        return FALSE;
+    }
+  else
+    {
+      if (!g_vfs_ftp_task_receive (task, 0, NULL))
+        return FALSE;
+    }
 
-  if (!g_vfs_ftp_task_send (task, 0, "PBSZ 0"))
-    return FALSE;
+  /* Request TLS also for the data channels. */
 
-  if (!g_vfs_ftp_task_send (task, 0, "PROT P"))
-    return FALSE;
+  if (task->backend->tls_mode != G_VFS_FTP_TLS_MODE_NONE)
+    {
+      if (!g_vfs_ftp_task_send (task, 0, "PBSZ 0"))
+        return FALSE;
+
+      if (!g_vfs_ftp_task_send (task, 0, "PROT P"))
+        return FALSE;
+    }
 
   return TRUE;
 }
diff --git a/daemon/gvfsftptask.h b/daemon/gvfsftptask.h
index f2626baf..744169b3 100644
--- a/daemon/gvfsftptask.h
+++ b/daemon/gvfsftptask.h
@@ -93,7 +93,7 @@ gboolean                g_vfs_ftp_task_login                    (GVfsFtpTask *
                                                                  const char *           password);
 void                    g_vfs_ftp_task_setup_connection         (GVfsFtpTask *          task);
 
-gboolean                g_vfs_ftp_task_enable_tls               (GVfsFtpTask *          task,
+gboolean                g_vfs_ftp_task_initial_handshake        (GVfsFtpTask *          task,
                                                                  CertificateCallback    cb,
                                                                  gpointer               user_data);
 
diff --git a/daemon/meson.build b/daemon/meson.build
index e4cc9735..7f2608bd 100644
--- a/daemon/meson.build
+++ b/daemon/meson.build
@@ -179,12 +179,12 @@ sources = files(
 cflags = [
   '-DBACKEND_HEADER=gvfsbackendftp.h',
   '-DDEFAULT_BACKEND_TYPE=ftp',
-  '-DBACKEND_TYPES="ftp", G_VFS_TYPE_BACKEND_FTP, "ftps", G_VFS_TYPE_BACKEND_FTP,',
+  '-DBACKEND_TYPES="ftp", G_VFS_TYPE_BACKEND_FTP, "ftps", G_VFS_TYPE_BACKEND_FTP, "ftpis", 
G_VFS_TYPE_BACKEND_FTP,',
   '-DMAX_JOB_THREADS=10'
 ]
 
 programs += [['gvfsd-ftp', sources, [], cflags]]
-mounts += ['ftp', 'ftps']
+mounts += ['ftp', 'ftps', 'ftpis']
 
 sources = files('gvfsbackendtrash.c')
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]