[gvfs] ftp: Implement TLS support
- From: Ross Lagerwall <rossl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gvfs] ftp: Implement TLS support
- Date: Thu, 9 Apr 2015 21:15:41 +0000 (UTC)
commit dccf4aeea3f94251062836f2c6f5f37219c64054
Author: Ross Lagerwall <rosslagerwall gmail com>
Date: Thu Feb 26 22:30:44 2015 +0000
ftp: Implement TLS support
Implement TLS support (aka explicit ftps). This is done by using a
different URL scheme, ftps, so that it is only used if explicitly
specified.
Although the protocol allows transparently upgrading a normal
connection to a secure one, there are several problems with this.
FEAT is needed to determine support for it but some servers do not allow
this before login. Some servers are configured to allow AUTH TLS but
have firewalls that block data connections because they can't inspect
the traffic. Servers may disallow TLS on the data connection, making it
unclear to the user how secure the connection is. Finally, there may be
verification errors which need to be presented to the user, and these
are unexpected because they did not choose to use ftps.
Making secure ftp opt-in as a separate URL scheme side-steps most of
these issues as well as ensuring there are no regressions for normal
ftp. When using ftps, we assume that the server implements AUTH TLS so
the connection is secured before login. It is also assumed that TLS for
data connections is allowed, so both control and data connection are
secure before any information is transferred.
Verification errors are presented during mounting. If the identity
changes on subsequent reconnections, the connection is aborted.
While presenting verification errors to the user in this way is perhaps
not the best way of handling security, it is fairly standard.
The implementation has been successfully tested on vsftpd, ProFTPD,
Pure-FTPd, IIS, and FileZilla Server.
Based on a patch by Benjamin Otte.
https://bugzilla.gnome.org/show_bug.cgi?id=526582
configure.ac | 2 +-
daemon/Makefile.am | 6 +-
daemon/ftps.mount.in | 7 ++
daemon/gvfsbackendftp.c | 38 ++++++++++++-
daemon/gvfsbackendftp.h | 8 +++
daemon/gvfsftpconnection.c | 133 ++++++++++++++++++++++++++++++++++++++++++--
daemon/gvfsftpconnection.h | 18 ++++++
daemon/gvfsftptask.c | 75 ++++++++++++++++++++++++-
daemon/gvfsftptask.h | 4 +
9 files changed, 278 insertions(+), 13 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d219aba..9a03466 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,7 +61,7 @@ GTK_DOC_CHECK
DISTCHECK_CONFIGURE_FLAGS="--enable-gtk-doc"
AC_SUBST(DISTCHECK_CONFIGURE_FLAGS)
-PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.43.2 gobject-2.0 gmodule-no-export-2.0 gio-unix-2.0 gio-2.0 )
+PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.45.0 gobject-2.0 gmodule-no-export-2.0 gio-unix-2.0 gio-2.0 )
PKG_CHECK_MODULES(DBUS, dbus-1)
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 384f18c..2d52f0a 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -45,8 +45,8 @@ service_DATA = gvfs-daemon.service
libexec_PROGRAMS=gvfsd gvfsd-sftp gvfsd-trash gvfsd-computer gvfsd-burn gvfsd-localtest gvfsd-ftp
gvfsd-network
-mount_in_files = sftp.mount.in ftp.mount.in trash.mount.in computer.mount.in burn.mount.in
localtest.mount.in network.mount.in
-mount_DATA = sftp.mount ftp.mount trash.mount computer.mount burn.mount localtest.mount network.mount
+mount_in_files = sftp.mount.in ftp.mount.in ftps.mount.in trash.mount.in computer.mount.in burn.mount.in
localtest.mount.in network.mount.in
+mount_DATA = sftp.mount ftp.mount ftps.mount trash.mount computer.mount burn.mount localtest.mount
network.mount
mount_in_files +=recent.mount.in
if USE_GTK
@@ -276,7 +276,7 @@ gvfsd_ftp_CPPFLAGS = \
-DBACKEND_HEADER=gvfsbackendftp.h \
-DDEFAULT_BACKEND_TYPE=ftp \
-DMAX_JOB_THREADS=10 \
- -DBACKEND_TYPES='"ftp", G_VFS_TYPE_BACKEND_FTP,'
+ -DBACKEND_TYPES='"ftp", G_VFS_TYPE_BACKEND_FTP, "ftps", G_VFS_TYPE_BACKEND_FTP,'
gvfsd_ftp_LDADD = $(libraries)
diff --git a/daemon/ftps.mount.in b/daemon/ftps.mount.in
new file mode 100644
index 0000000..06ae835
--- /dev/null
+++ b/daemon/ftps.mount.in
@@ -0,0 +1,7 @@
+[Mount]
+Type=ftps
+Exec= libexecdir@/gvfsd-ftp
+AutoMount=false
+Scheme=ftps
+DefaultPort=21
+HostnameIsInetAddress=true
diff --git a/daemon/gvfsbackendftp.c b/daemon/gvfsbackendftp.c
index 5fe056b..388c4c8 100644
--- a/daemon/gvfsbackendftp.c
+++ b/daemon/gvfsbackendftp.c
@@ -393,6 +393,9 @@ g_vfs_backend_ftp_finalize (GObject *object)
g_free (ftp->user);
g_free (ftp->password);
+ g_clear_object (&ftp->server_identity);
+ g_clear_object (&ftp->certificate);
+
if (G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize)
(*G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize) (object);
}
@@ -404,6 +407,23 @@ g_vfs_backend_ftp_init (GVfsBackendFtp *ftp)
g_cond_init (&ftp->cond);
}
+/* If the initial connection has a verification error, display the certificate
+ * to the user and ask whether to proceed. */
+static gboolean
+initial_certificate_cb (GTlsConnection *conn,
+ GTlsCertificate *certificate,
+ GTlsCertificateFlags errors,
+ gpointer user_data)
+{
+ GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (user_data);
+
+ /* Save the certificate and result for reconnections. */
+ ftp->certificate = g_object_ref (certificate);
+ ftp->certificate_errors = errors;
+
+ return gvfs_accept_certificate (ftp->mount_source, certificate, errors);
+}
+
static void
do_mount (GVfsBackend *backend,
GVfsJobMount *job,
@@ -435,6 +455,19 @@ restart:
/* 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. */
+ ftp->mount_source = mount_source;
+ if (ftp->use_tls &&
+ !g_vfs_ftp_task_enable_tls (&task, initial_certificate_cb, ftp))
+ {
+ ftp->mount_source = NULL;
+ g_vfs_ftp_task_done (&task);
+ return;
+ }
+ ftp->mount_source = NULL;
+
if (!g_vfs_backend_ftp_uses_workaround (ftp, G_VFS_FTP_WORKAROUND_FEAT_AFTER_LOGIN) &&
!gvfs_backend_ftp_determine_features (&task))
{
@@ -619,7 +652,7 @@ try_login:
g_free (prompt);
}
- mount_spec = g_mount_spec_new ("ftp");
+ mount_spec = g_mount_spec_new (ftp->use_tls ? "ftps" : "ftp");
g_mount_spec_set (mount_spec, "host", g_network_address_get_hostname (addr));
if (port != 21)
{
@@ -691,6 +724,9 @@ try_mount (GVfsBackend *backend,
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)
+ ftp->server_identity = g_object_ref (ftp->addr);
return FALSE;
}
diff --git a/daemon/gvfsbackendftp.h b/daemon/gvfsbackendftp.h
index f24d413..bd98a79 100644
--- a/daemon/gvfsbackendftp.h
+++ b/daemon/gvfsbackendftp.h
@@ -25,6 +25,7 @@
#include <gvfsbackend.h>
#include <gmountspec.h>
+#include <gio/gio.h>
G_BEGIN_DECLS
@@ -90,6 +91,13 @@ struct _GVfsBackendFtp
char * password; /* password or NULL for anonymous */
char * host_display_name;
+ /* ftps support */
+ gboolean use_tls;
+ GSocketConnectable * server_identity; /* Server identity used for verification */
+ GTlsCertificate * certificate; /* Initial server certificate */
+ GTlsCertificateFlags certificate_errors; /* Errors received during TLS handshake */
+ GMountSource * mount_source; /* Only used during do_mount */
+
GVfsFtpSystem system; /* the system from the SYST response */
int features; /* GVfsFtpFeatures that are supported */
int workarounds; /* GVfsFtpWorkarounds in use - int because it's atomic */
diff --git a/daemon/gvfsftpconnection.c b/daemon/gvfsftpconnection.c
index edfffac..217d798 100644
--- a/daemon/gvfsftpconnection.c
+++ b/daemon/gvfsftpconnection.c
@@ -37,6 +37,7 @@ struct _GVfsFtpConnection
GSocketClient * client; /* socket client used for opening connections */
GIOStream * commands; /* ftp command stream */
+ GSocketConnection * connection; /* original connection */
GDataInputStream * commands_in; /* wrapper around in stream to allow line-wise reading */
gboolean waiting_for_reply; /* TRUE if a command was sent but no reply received yet
*/
@@ -62,6 +63,19 @@ enable_keepalive (GSocketConnection *conn)
g_socket_set_keepalive (g_socket_connection_get_socket (conn), TRUE);
}
+static void
+create_input_stream (GVfsFtpConnection *conn)
+{
+ if (conn->commands_in)
+ {
+ g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (conn->commands_in), FALSE);
+ g_object_unref (conn->commands_in);
+ }
+
+ conn->commands_in = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream
(conn->commands)));
+ g_data_input_stream_set_newline_type (conn->commands_in, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+}
+
GVfsFtpConnection *
g_vfs_ftp_connection_new (GSocketConnectable *addr,
GCancellable * cancellable,
@@ -85,9 +99,9 @@ g_vfs_ftp_connection_new (GSocketConnectable *addr,
return NULL;
}
- enable_keepalive (G_SOCKET_CONNECTION (conn->commands));
- conn->commands_in = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream
(conn->commands)));
- g_data_input_stream_set_newline_type (conn->commands_in, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+ conn->connection = G_SOCKET_CONNECTION (conn->commands);
+ enable_keepalive (conn->connection);
+ create_input_stream (conn);
/* The first thing that needs to happen is receiving the welcome message */
conn->waiting_for_reply = TRUE;
@@ -249,7 +263,60 @@ g_vfs_ftp_connection_get_address (GVfsFtpConnection *conn, GError **error)
{
g_return_val_if_fail (conn != NULL, NULL);
- return g_socket_connection_get_remote_address (G_SOCKET_CONNECTION (conn->commands), error);
+ return g_socket_connection_get_remote_address (conn->connection, error);
+}
+
+/**
+ * g_vfs_ftp_connection_data_connection_enable_tls:
+ * @conn: a connection with an active control connection
+ * @server_identity: address of the server used to verify the certificate
+ * @cb: callback called if there's a verification error
+ * @user_data: user data passed to @cb
+ * @cancellable: cancellable to interrupt wait
+ * @error: %NULL or location to take a potential error
+ *
+ * Tries to enable TLS on the given @connection's data connection. If setting
+ * up TLS fails, %FALSE will be returned and @error will be set.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ **/
+gboolean
+g_vfs_ftp_connection_data_connection_enable_tls (GVfsFtpConnection *conn,
+ GSocketConnectable *server_identity,
+ CertificateCallback cb,
+ gpointer user_data,
+ GCancellable * cancellable,
+ GError ** error)
+{
+ GIOStream *secure;
+
+ g_return_val_if_fail (conn != NULL, FALSE);
+ g_return_val_if_fail (conn->commands != NULL, FALSE);
+
+ secure = g_tls_client_connection_new (conn->data,
+ server_identity,
+ error);
+ if (secure == NULL)
+ return FALSE;
+
+ g_object_unref (conn->data);
+ conn->data = secure;
+
+ g_tls_client_connection_copy_session_state (G_TLS_CLIENT_CONNECTION (secure),
+ G_TLS_CLIENT_CONNECTION (conn->commands));
+
+ g_signal_connect (secure, "accept-certificate", G_CALLBACK (cb), user_data);
+
+ if (!g_tls_connection_handshake (G_TLS_CONNECTION (secure),
+ cancellable,
+ error))
+ {
+ /* Close here to be sure it won't get used anymore */
+ g_io_stream_close (secure, cancellable, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
}
gboolean
@@ -295,7 +362,7 @@ g_vfs_ftp_connection_listen_data_connection (GVfsFtpConnection *conn,
g_vfs_ftp_connection_stop_listening (conn);
- local = g_socket_connection_get_local_address (G_SOCKET_CONNECTION (conn->commands), error);
+ local = g_socket_connection_get_local_address (conn->connection, error);
if (local == NULL)
return NULL;
@@ -480,7 +547,7 @@ g_vfs_ftp_connection_is_usable (GVfsFtpConnection *conn)
return FALSE;
cond = G_IO_ERR | G_IO_HUP | G_IO_IN;
- cond = g_socket_condition_check (g_socket_connection_get_socket (G_SOCKET_CONNECTION (conn->commands)),
cond);
+ cond = g_socket_condition_check (g_socket_connection_get_socket (conn->connection), cond);
if (cond)
{
g_debug ("##%2d ## connection unusuable: %s%s%s\r\n", conn->debug_id,
@@ -493,3 +560,57 @@ g_vfs_ftp_connection_is_usable (GVfsFtpConnection *conn)
return TRUE;
}
+/**
+ * g_vfs_ftp_connection_enable_tls:
+ * @conn: a connection without an active data connection
+ * @server_identity: address of the server used to verify the certificate
+ * @cb: callback called if there's a verification error
+ * @user_data: user data passed to @cb
+ * @cancellable: cancellable to interrupt wait
+ * @error: %NULL or location to take a potential error
+ *
+ * Tries to enable TLS on the given @connection. If setting up TLS fails,
+ * %FALSE will be returned and @error will be set. When this function fails,
+ * you need to check if the connection is still usable. It might have been
+ * closed.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ **/
+gboolean
+g_vfs_ftp_connection_enable_tls (GVfsFtpConnection * conn,
+ GSocketConnectable *server_identity,
+ CertificateCallback cb,
+ gpointer user_data,
+ GCancellable * cancellable,
+ GError ** error)
+{
+ GIOStream *secure;
+
+ 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 (g_buffered_input_stream_get_available (G_BUFFERED_INPUT_STREAM (conn->commands_in))
== 0, FALSE);
+
+ secure = g_tls_client_connection_new (conn->commands,
+ server_identity,
+ error);
+ if (secure == NULL)
+ return FALSE;
+
+ g_object_unref (conn->commands);
+ conn->commands = secure;
+ create_input_stream (conn);
+
+ g_signal_connect (secure, "accept-certificate", G_CALLBACK (cb), user_data);
+
+ if (!g_tls_connection_handshake (G_TLS_CONNECTION (secure),
+ cancellable,
+ error))
+ {
+ /* Close here to be sure it won't get used anymore */
+ g_io_stream_close (secure, cancellable, NULL);
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/daemon/gvfsftpconnection.h b/daemon/gvfsftpconnection.h
index a413959..f1ddab3 100644
--- a/daemon/gvfsftpconnection.h
+++ b/daemon/gvfsftpconnection.h
@@ -30,6 +30,11 @@ G_BEGIN_DECLS
typedef struct _GVfsFtpConnection GVfsFtpConnection;
+typedef gboolean (*CertificateCallback) (GTlsConnection * conn,
+ GTlsCertificate * peer_cert,
+ GTlsCertificateFlags errors,
+ gpointer user_data);
+
GVfsFtpConnection * g_vfs_ftp_connection_new (GSocketConnectable * addr,
GCancellable * cancellable,
GError ** error);
@@ -66,6 +71,19 @@ void g_vfs_ftp_connection_close_data_connection
(GVfsFtpConnection * conn);
GIOStream * g_vfs_ftp_connection_get_data_stream (GVfsFtpConnection * conn);
+gboolean g_vfs_ftp_connection_enable_tls (GVfsFtpConnection * conn,
+ GSocketConnectable * server_identity,
+ CertificateCallback cb,
+ gpointer user_data,
+ GCancellable * cancellable,
+ GError ** error);
+gboolean g_vfs_ftp_connection_data_connection_enable_tls (GVfsFtpConnection *conn,
+ GSocketConnectable *server_identity,
+ CertificateCallback cb,
+ gpointer user_data,
+ GCancellable * cancellable,
+ GError ** error);
+
G_END_DECLS
diff --git a/daemon/gvfsftptask.c b/daemon/gvfsftptask.c
index c70262b..2209c2b 100644
--- a/daemon/gvfsftptask.c
+++ b/daemon/gvfsftptask.c
@@ -161,6 +161,28 @@ do_broadcast (GCancellable *cancellable, GCond *cond)
g_cond_broadcast (cond);
}
+/* Decide whether to allow verification errors for control and data connections
+ * after the initial connection. The connection is only allowed if the
+ * identity is the same as for the initial connection. */
+static gboolean
+reconnect_certificate_cb (GTlsConnection *conn,
+ GTlsCertificate *certificate,
+ GTlsCertificateFlags errors,
+ gpointer user_data)
+{
+ GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (user_data);
+
+ /* If the verification result has changed in some way, abort the
+ * connection. */
+ if (errors != ftp->certificate_errors)
+ return FALSE;
+
+ /* Only allow the connection if the certificate presented is the same as for
+ * the initial connection which the user accepted. */
+ return ftp->certificate &&
+ g_tls_certificate_is_same (certificate, ftp->certificate);
+}
+
/**
* g_vfs_ftp_task_acquire_connection:
* @task: a task without an associated connection
@@ -229,6 +251,8 @@ g_vfs_ftp_task_acquire_connection (GVfsFtpTask *task)
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_login (task, ftp->user, ftp->password);
g_vfs_ftp_task_setup_connection (task);
if (G_LIKELY (!g_vfs_ftp_task_is_in_error (task)))
@@ -1144,8 +1168,8 @@ g_vfs_ftp_task_setup_data_connection (GVfsFtpTask *task)
* g_vfs_ftp_task_open_data_connection:
* @task: a task
*
- * Tries to open a data connection to the ftp server. If the operation fails,
- * @task will be set into an error state.
+ * Tries to open a data connection to the ftp server and secures it if
+ * necessary. If the operation fails, @task will be set into an error state.
**/
void
g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task)
@@ -1160,5 +1184,52 @@ g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task)
g_vfs_ftp_connection_accept_data_connection (task->conn,
task->cancellable,
&task->error);
+
+ if (g_vfs_ftp_task_is_in_error (task))
+ return;
+
+ if (task->backend->use_tls)
+ g_vfs_ftp_connection_data_connection_enable_tls (task->conn,
+ task->backend->server_identity,
+ reconnect_certificate_cb,
+ task->backend,
+ task->cancellable,
+ &task->error);
}
+/**
+ * g_vfs_ftp_task_enable_tls:
+ * @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.
+ **/
+gboolean
+g_vfs_ftp_task_enable_tls (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;
+
+ if (!g_vfs_ftp_connection_enable_tls (task->conn,
+ task->backend->server_identity,
+ cb,
+ user_data,
+ task->cancellable,
+ &task->error))
+ return FALSE;
+
+ 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 442b467..f2626ba 100644
--- a/daemon/gvfsftptask.h
+++ b/daemon/gvfsftptask.h
@@ -93,6 +93,10 @@ 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,
+ CertificateCallback cb,
+ gpointer user_data);
+
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]