From 2839922c259b848d7689d245a055c628754dc116 Mon Sep 17 00:00:00 2001 From: Benjamin Otte 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 +#include "gvfsftpconnection.h" + #include #include -#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