32d84ada85
- Backport FTP and Computer backend patches from master
362 lines
13 KiB
Diff
362 lines
13 KiB
Diff
From 2839922c259b848d7689d245a055c628754dc116 Mon Sep 17 00:00:00 2001
|
|
From: Benjamin Otte <otte@gnome.org>
|
|
Date: Mon, 15 Jun 2009 23:03:26 +0200
|
|
Subject: [PATCH 02/13] =?utf-8?q?[FTP]=20Bug=20516704=20=E2=80=93=20Be=20able=20to=20connect=20to=20an=20Active=20FTP=20Site?=
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=utf-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Add initial support for the PORT command. Support for EPRT and a
|
|
non-ugly API are still missing.
|
|
---
|
|
daemon/gvfsftpconnection.c | 157 +++++++++++++++++++++++++++++++++++++++++++-
|
|
daemon/gvfsftpconnection.h | 7 ++
|
|
daemon/gvfsftptask.c | 56 ++++++++++++++--
|
|
daemon/gvfsftptask.h | 1 +
|
|
4 files changed, 215 insertions(+), 6 deletions(-)
|
|
|
|
diff --git a/daemon/gvfsftpconnection.c b/daemon/gvfsftpconnection.c
|
|
index ac5418f..521664c 100644
|
|
--- a/daemon/gvfsftpconnection.c
|
|
+++ b/daemon/gvfsftpconnection.c
|
|
@@ -22,10 +22,12 @@
|
|
|
|
#include <config.h>
|
|
|
|
+#include "gvfsftpconnection.h"
|
|
+
|
|
#include <string.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
-#include "gvfsftpconnection.h"
|
|
+#include "gvfsbackendftp.h"
|
|
|
|
/* used for identifying the connection during debugging */
|
|
static volatile int debug_id = 0;
|
|
@@ -37,6 +39,7 @@ struct _GVfsFtpConnection
|
|
GIOStream * commands; /* ftp command stream */
|
|
GDataInputStream * commands_in; /* wrapper around in stream to allow line-wise reading */
|
|
|
|
+ GSocket * listen_socket; /* socket we are listening on for active FTP connections */
|
|
GIOStream * data; /* ftp data stream or NULL if not in use */
|
|
|
|
int debug_id; /* unique id for debugging purposes */
|
|
@@ -71,11 +74,22 @@ g_vfs_ftp_connection_new (GSocketConnectable *addr,
|
|
return conn;
|
|
}
|
|
|
|
+static void
|
|
+g_vfs_ftp_connection_stop_listening (GVfsFtpConnection *conn)
|
|
+{
|
|
+ if (conn->listen_socket)
|
|
+ {
|
|
+ g_object_unref (conn->listen_socket);
|
|
+ conn->listen_socket = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
void
|
|
g_vfs_ftp_connection_free (GVfsFtpConnection *conn)
|
|
{
|
|
g_return_if_fail (conn != NULL);
|
|
|
|
+ g_vfs_ftp_connection_stop_listening (conn);
|
|
if (conn->data)
|
|
g_vfs_ftp_connection_close_data_connection (conn);
|
|
|
|
@@ -218,6 +232,8 @@ g_vfs_ftp_connection_open_data_connection (GVfsFtpConnection *conn,
|
|
g_return_val_if_fail (conn != NULL, FALSE);
|
|
g_return_val_if_fail (conn->data == NULL, FALSE);
|
|
|
|
+ g_vfs_ftp_connection_stop_listening (conn);
|
|
+
|
|
conn->data = G_IO_STREAM (g_socket_client_connect (conn->client,
|
|
G_SOCKET_CONNECTABLE (addr),
|
|
cancellable,
|
|
@@ -226,6 +242,145 @@ g_vfs_ftp_connection_open_data_connection (GVfsFtpConnection *conn,
|
|
return conn->data != NULL;
|
|
}
|
|
|
|
+/**
|
|
+ * g_vfs_ftp_connection_listen_data_connection:
|
|
+ * @conn: a connection
|
|
+ * @error: %NULL or location to take potential errors
|
|
+ *
|
|
+ * Initiates a listening socket that the FTP server can connect to. To accept
|
|
+ * connections and initialize data transfers, use
|
|
+ * g_vfs_ftp_connection_accept_data_connection().
|
|
+ * This function supports what is known as "active FTP", while
|
|
+ * g_vfs_ftp_connection_open_data_connection() is to be used for "passive FTP".
|
|
+ *
|
|
+ * Returns: the actual address the socket is listening on or %NULL on error
|
|
+ **/
|
|
+GSocketAddress *
|
|
+g_vfs_ftp_connection_listen_data_connection (GVfsFtpConnection *conn,
|
|
+ GError ** error)
|
|
+{
|
|
+ GSocketAddress *local, *addr;
|
|
+
|
|
+ g_return_val_if_fail (conn != NULL, NULL);
|
|
+ g_return_val_if_fail (conn->data == NULL, FALSE);
|
|
+
|
|
+ g_vfs_ftp_connection_stop_listening (conn);
|
|
+
|
|
+ local = g_socket_connection_get_local_address (G_SOCKET_CONNECTION (conn->commands), error);
|
|
+ if (local == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ conn->listen_socket = g_socket_new (g_socket_address_get_family (local),
|
|
+ G_SOCKET_TYPE_STREAM,
|
|
+ G_SOCKET_PROTOCOL_TCP,
|
|
+ error);
|
|
+ if (conn->listen_socket == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ g_assert (G_IS_INET_SOCKET_ADDRESS (local));
|
|
+ addr = g_inet_socket_address_new (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (local)), 0);
|
|
+ g_object_unref (local);
|
|
+
|
|
+ if (!g_socket_bind (conn->listen_socket, addr, TRUE, error) ||
|
|
+ !g_socket_listen (conn->listen_socket, error) ||
|
|
+ !(local = g_socket_get_local_address (conn->listen_socket, error)))
|
|
+ {
|
|
+ g_object_unref (addr);
|
|
+ g_vfs_ftp_connection_stop_listening (conn);
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ g_object_unref (addr);
|
|
+ return local;
|
|
+}
|
|
+
|
|
+static void
|
|
+cancel_timer_cb (GCancellable *orig, GCancellable *to_cancel)
|
|
+{
|
|
+ g_cancellable_cancel (to_cancel);
|
|
+}
|
|
+
|
|
+static gboolean
|
|
+cancel_cancellable (gpointer cancellable)
|
|
+{
|
|
+ g_cancellable_cancel (cancellable);
|
|
+ return FALSE;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * g_vfs_ftp_connection_accept_data_connection:
|
|
+ * @conn: a listening connection
|
|
+ * @cancellable: cancellable to interrupt wait
|
|
+ * @error: %NULL or location to take a potential error
|
|
+ *
|
|
+ * Opens a data connection for @conn by accepting an incoming connection on the
|
|
+ * address it is listening on via g_vfs_ftp_connection_listen_data_connection(),
|
|
+ * which must have been called prior to this function.
|
|
+ * If this function succeeds, a data connection will have been opened, and calls
|
|
+ * to g_vfs_ftp_connection_get_data_stream() will work.
|
|
+ *
|
|
+ * Returns: %TRUE if a connection was successfully acquired
|
|
+ **/
|
|
+gboolean
|
|
+g_vfs_ftp_connection_accept_data_connection (GVfsFtpConnection *conn,
|
|
+ GCancellable * cancellable,
|
|
+ GError ** error)
|
|
+{
|
|
+ GSocket *accepted;
|
|
+ GCancellable *timer;
|
|
+ gulong cancel_cb_id;
|
|
+ GIOCondition condition;
|
|
+
|
|
+ g_return_val_if_fail (conn != NULL, FALSE);
|
|
+ g_return_val_if_fail (conn->data == NULL, FALSE);
|
|
+ g_return_val_if_fail (G_IS_SOCKET (conn->listen_socket), FALSE);
|
|
+
|
|
+ timer = g_cancellable_new ();
|
|
+ cancel_cb_id = g_cancellable_connect (cancellable,
|
|
+ G_CALLBACK (cancel_timer_cb),
|
|
+ timer,
|
|
+ NULL);
|
|
+ g_object_ref (timer);
|
|
+ g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
|
|
+ G_VFS_FTP_TIMEOUT_IN_SECONDS,
|
|
+ cancel_cancellable,
|
|
+ timer,
|
|
+ g_object_unref);
|
|
+
|
|
+ condition = g_socket_condition_wait (conn->listen_socket, G_IO_IN, timer, error);
|
|
+
|
|
+ g_cancellable_disconnect (cancellable, cancel_cb_id);
|
|
+ g_object_unref (timer);
|
|
+
|
|
+ if ((condition & G_IO_IN) == 0)
|
|
+ {
|
|
+ if (g_cancellable_is_cancelled (timer) &&
|
|
+ !g_cancellable_is_cancelled (cancellable))
|
|
+ {
|
|
+ g_clear_error (error);
|
|
+ g_set_error_literal (error,
|
|
+ G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
|
|
+ _("Failed to create active FTP connection. "
|
|
+ "Maybe your router does not support this?"));
|
|
+ }
|
|
+ else if (error && *error == NULL)
|
|
+ {
|
|
+ g_set_error_literal (error,
|
|
+ G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
|
|
+ _("Failed to create active FTP connection."));
|
|
+ }
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ accepted = g_socket_accept (conn->listen_socket, error);
|
|
+ if (accepted == NULL)
|
|
+ return FALSE;
|
|
+
|
|
+ conn->data = G_IO_STREAM (g_socket_connection_factory_create_connection (accepted));
|
|
+ g_object_unref (accepted);
|
|
+ return TRUE;
|
|
+}
|
|
+
|
|
void
|
|
g_vfs_ftp_connection_close_data_connection (GVfsFtpConnection *conn)
|
|
{
|
|
diff --git a/daemon/gvfsftpconnection.h b/daemon/gvfsftpconnection.h
|
|
index 3605f26..7d0c697 100644
|
|
--- a/daemon/gvfsftpconnection.h
|
|
+++ b/daemon/gvfsftpconnection.h
|
|
@@ -55,6 +55,13 @@ gboolean g_vfs_ftp_connection_open_data_connection
|
|
GSocketAddress * addr,
|
|
GCancellable * cancellable,
|
|
GError ** error);
|
|
+GSocketAddress * g_vfs_ftp_connection_listen_data_connection
|
|
+ (GVfsFtpConnection * conn,
|
|
+ GError ** error);
|
|
+gboolean g_vfs_ftp_connection_accept_data_connection
|
|
+ (GVfsFtpConnection * conn,
|
|
+ GCancellable * cancellable,
|
|
+ GError ** error);
|
|
void g_vfs_ftp_connection_close_data_connection
|
|
(GVfsFtpConnection * conn);
|
|
GIOStream * g_vfs_ftp_connection_get_data_stream (GVfsFtpConnection * conn);
|
|
diff --git a/daemon/gvfsftptask.c b/daemon/gvfsftptask.c
|
|
index 37f2b59..879b912 100644
|
|
--- a/daemon/gvfsftptask.c
|
|
+++ b/daemon/gvfsftptask.c
|
|
@@ -799,7 +799,6 @@ g_vfs_ftp_task_setup_data_connection_pasv (GVfsFtpTask *task, GVfsFtpMethod meth
|
|
guint status;
|
|
gboolean success;
|
|
|
|
- /* only binary transfers please */
|
|
status = g_vfs_ftp_task_send_and_check (task, 0, NULL, NULL, &reply, "PASV");
|
|
if (status == 0)
|
|
return G_VFS_FTP_METHOD_ANY;
|
|
@@ -872,6 +871,45 @@ g_vfs_ftp_task_setup_data_connection_pasv (GVfsFtpTask *task, GVfsFtpMethod meth
|
|
return G_VFS_FTP_METHOD_ANY;
|
|
}
|
|
|
|
+static GVfsFtpMethod
|
|
+g_vfs_ftp_task_open_data_connection_port (GVfsFtpTask *task, GVfsFtpMethod unused)
|
|
+{
|
|
+ GSocketAddress *addr;
|
|
+ guint status, i, port;
|
|
+ char *ip_string;
|
|
+
|
|
+ /* workaround for the task not having a connection yet */
|
|
+ if (task->conn == NULL &&
|
|
+ g_vfs_ftp_task_send (task, 0, "NOOP") == 0)
|
|
+ return G_VFS_FTP_METHOD_ANY;
|
|
+
|
|
+ addr = g_vfs_ftp_connection_listen_data_connection (task->conn, &task->error);
|
|
+ if (addr == NULL)
|
|
+ return G_VFS_FTP_METHOD_ANY;
|
|
+ /* the PORT command only supports IPv4 */
|
|
+ if (g_socket_address_get_family (addr) != G_SOCKET_FAMILY_IPV4)
|
|
+ {
|
|
+ g_object_unref (addr);
|
|
+ return G_VFS_FTP_METHOD_ANY;
|
|
+ }
|
|
+
|
|
+ ip_string = g_inet_address_to_string (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr)));
|
|
+ for (i = 0; ip_string[i]; i++)
|
|
+ {
|
|
+ if (ip_string[i] == '.')
|
|
+ ip_string[i] = ',';
|
|
+ }
|
|
+ port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));
|
|
+
|
|
+ status = g_vfs_ftp_task_send (task, 0, "PORT %s,%u,%u", ip_string, port >> 8, port & 0xFF);
|
|
+ g_free (ip_string);
|
|
+ g_object_unref (addr);
|
|
+ if (status == 0)
|
|
+ return G_VFS_FTP_METHOD_ANY;
|
|
+
|
|
+ return G_VFS_FTP_METHOD_PORT;
|
|
+}
|
|
+
|
|
typedef GVfsFtpMethod (* GVfsFtpOpenDataConnectionFunc) (GVfsFtpTask *task, GVfsFtpMethod method);
|
|
|
|
static GVfsFtpMethod
|
|
@@ -882,7 +920,8 @@ g_vfs_ftp_task_setup_data_connection_any (GVfsFtpTask *task, GVfsFtpMethod unuse
|
|
GVfsFtpOpenDataConnectionFunc func;
|
|
} funcs_ordered[] = {
|
|
{ G_VFS_FTP_FEATURE_EPSV, g_vfs_ftp_task_setup_data_connection_epsv },
|
|
- { 0, g_vfs_ftp_task_setup_data_connection_pasv }
|
|
+ { 0, g_vfs_ftp_task_setup_data_connection_pasv },
|
|
+ { 0, g_vfs_ftp_task_open_data_connection_port }
|
|
};
|
|
GVfsFtpMethod method;
|
|
guint i;
|
|
@@ -936,15 +975,15 @@ g_vfs_ftp_task_setup_data_connection (GVfsFtpTask *task)
|
|
[G_VFS_FTP_METHOD_PASV] = g_vfs_ftp_task_setup_data_connection_pasv,
|
|
[G_VFS_FTP_METHOD_PASV_ADDR] = g_vfs_ftp_task_setup_data_connection_pasv,
|
|
[G_VFS_FTP_METHOD_EPRT] = NULL,
|
|
- [G_VFS_FTP_METHOD_PORT] = NULL
|
|
+ [G_VFS_FTP_METHOD_PORT] = g_vfs_ftp_task_open_data_connection_port
|
|
};
|
|
GVfsFtpMethod method, result;
|
|
|
|
g_return_if_fail (task != NULL);
|
|
|
|
- /* FIXME: get the method from elsewhere */
|
|
+ task->method = G_VFS_FTP_METHOD_ANY;
|
|
+
|
|
method = g_atomic_int_get (&task->backend->method);
|
|
-
|
|
g_assert (method < G_N_ELEMENTS (connect_funcs) && connect_funcs[method]);
|
|
|
|
if (g_vfs_ftp_task_is_in_error (task))
|
|
@@ -973,6 +1012,7 @@ g_vfs_ftp_task_setup_data_connection (GVfsFtpTask *task)
|
|
g_debug ("# set default data connection method from %s to %s\n",
|
|
methods[method], methods[result]);
|
|
}
|
|
+ task->method = result;
|
|
}
|
|
|
|
/**
|
|
@@ -989,5 +1029,11 @@ g_vfs_ftp_task_open_data_connection (GVfsFtpTask *task)
|
|
|
|
if (g_vfs_ftp_task_is_in_error (task))
|
|
return;
|
|
+
|
|
+ if (task->method == G_VFS_FTP_METHOD_EPRT ||
|
|
+ task->method == G_VFS_FTP_METHOD_PORT)
|
|
+ g_vfs_ftp_connection_accept_data_connection (task->conn,
|
|
+ task->cancellable,
|
|
+ &task->error);
|
|
}
|
|
|
|
diff --git a/daemon/gvfsftptask.h b/daemon/gvfsftptask.h
|
|
index 8345535..ac0bd74 100644
|
|
--- a/daemon/gvfsftptask.h
|
|
+++ b/daemon/gvfsftptask.h
|
|
@@ -47,6 +47,7 @@ struct _GVfsFtpTask
|
|
|
|
GError * error; /* NULL or current error - will be propagated to task */
|
|
GVfsFtpConnection * conn; /* connection in use by this task or NULL if none */
|
|
+ GVfsFtpMethod method; /* method currently in use (only valid after call to _setup_data_connection() */
|
|
};
|
|
|
|
typedef void (* GVfsFtpErrorFunc) (GVfsFtpTask *task, gpointer data);
|
|
--
|
|
1.6.3.2
|
|
|