mirror of https://gitee.com/openkylin/gvfs.git
638 lines
21 KiB
C
638 lines
21 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
|
*
|
|
* Copyright (C) 2009 Benjamin Otte <otte@gnome.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General
|
|
* Public License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
* Author: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "gvfsftpconnection.h"
|
|
|
|
#include <string.h>
|
|
#include <glib/gi18n.h>
|
|
#include <gio/gnetworking.h>
|
|
|
|
#include "gvfsbackendftp.h"
|
|
|
|
/* used for identifying the connection during debugging */
|
|
static volatile int debug_id = 0;
|
|
|
|
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 */
|
|
|
|
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 */
|
|
};
|
|
|
|
static void
|
|
enable_keepalive (GSocketConnection *conn)
|
|
{
|
|
/* We enable keepalive on the socket, because data connections can be
|
|
* idle for a long time while data is transferred using the data
|
|
* connection. And there are still buggy routers in existance that purge
|
|
* idle connections from time to time.
|
|
* To work around this problem, we set the keep alive flag here. It's the
|
|
* user's responsibility to configure his kernel properly so that the
|
|
* keepalive packets are sent before the buggy router disconnects the
|
|
* TCP connection. If a user asks, a howto is at
|
|
* http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
|
|
*/
|
|
g_socket_set_keepalive (g_socket_connection_get_socket (conn), TRUE);
|
|
}
|
|
|
|
/* Set TCP_NODELAY on the connection to avoid a bad interaction between Nagle's
|
|
* algorithm and delayed acks when doing a write-write-read. */
|
|
static void
|
|
enable_nodelay (GSocketConnection *conn)
|
|
{
|
|
GError *error = NULL;
|
|
GSocket *socket = g_socket_connection_get_socket (conn);
|
|
|
|
if (!g_socket_set_option (socket, IPPROTO_TCP, TCP_NODELAY, TRUE, &error))
|
|
{
|
|
g_warning ("Could not set TCP_NODELAY: %s\n", error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
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,
|
|
GError ** error)
|
|
{
|
|
GVfsFtpConnection *conn;
|
|
|
|
g_return_val_if_fail (G_IS_SOCKET_CONNECTABLE (addr), NULL);
|
|
|
|
conn = g_slice_new0 (GVfsFtpConnection);
|
|
conn->client = g_socket_client_new ();
|
|
conn->debug_id = g_atomic_int_add (&debug_id, 1);
|
|
conn->commands = G_IO_STREAM (g_socket_client_connect (conn->client,
|
|
addr,
|
|
cancellable,
|
|
error));
|
|
if (conn->commands == NULL)
|
|
{
|
|
g_object_unref (conn->client);
|
|
g_slice_free (GVfsFtpConnection, conn);
|
|
return NULL;
|
|
}
|
|
|
|
conn->connection = G_SOCKET_CONNECTION (conn->commands);
|
|
enable_nodelay (conn->connection);
|
|
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;
|
|
|
|
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);
|
|
g_vfs_ftp_connection_close_data_connection (conn);
|
|
|
|
g_object_unref (conn->commands_in);
|
|
g_object_unref (conn->commands);
|
|
g_object_unref (conn->client);
|
|
g_slice_free (GVfsFtpConnection, conn);
|
|
}
|
|
|
|
gboolean
|
|
g_vfs_ftp_connection_send (GVfsFtpConnection *conn,
|
|
const char * command,
|
|
int len,
|
|
GCancellable * cancellable,
|
|
GError ** error)
|
|
{
|
|
g_return_val_if_fail (conn != NULL, FALSE);
|
|
g_return_val_if_fail (!conn->waiting_for_reply, FALSE);
|
|
g_return_val_if_fail (command != NULL, FALSE);
|
|
g_return_val_if_fail (len >= -1, FALSE);
|
|
if (len < 0)
|
|
len = strlen (command);
|
|
g_return_val_if_fail (command[len-2] == '\r' && command[len-1] == '\n', FALSE);
|
|
|
|
if (g_str_has_prefix (command, "PASS"))
|
|
g_debug ("--%2d -> PASS ***\r\n", conn->debug_id);
|
|
else
|
|
g_debug ("--%2d -> %s", conn->debug_id, command);
|
|
|
|
conn->waiting_for_reply = TRUE;
|
|
return g_output_stream_write_all (g_io_stream_get_output_stream (conn->commands),
|
|
command,
|
|
len,
|
|
NULL,
|
|
cancellable,
|
|
error);
|
|
}
|
|
|
|
guint
|
|
g_vfs_ftp_connection_receive (GVfsFtpConnection *conn,
|
|
char *** reply,
|
|
GCancellable * cancellable,
|
|
GError ** error)
|
|
{
|
|
char *line;
|
|
enum {
|
|
FIRST_LINE,
|
|
MULTILINE,
|
|
DONE
|
|
} reply_state = FIRST_LINE;
|
|
GPtrArray *lines;
|
|
guint response = 0;
|
|
|
|
g_return_val_if_fail (conn != NULL, 0);
|
|
g_return_val_if_fail (conn->waiting_for_reply, 0);
|
|
|
|
if (reply)
|
|
lines = g_ptr_array_new_with_free_func (g_free);
|
|
else
|
|
lines = NULL;
|
|
|
|
while (reply_state != DONE)
|
|
{
|
|
line = g_data_input_stream_read_line (conn->commands_in, NULL, cancellable, error);
|
|
if (line == NULL)
|
|
{
|
|
if (error && *error == NULL)
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
|
|
_("Unexpected end of stream"));
|
|
|
|
goto fail;
|
|
}
|
|
|
|
g_debug ("<-%2d -- %s\r\n", conn->debug_id, line);
|
|
if (lines)
|
|
g_ptr_array_add (lines, line);
|
|
|
|
if (reply_state == FIRST_LINE)
|
|
{
|
|
if (line[0] <= '0' || line[0] > '5' ||
|
|
line[1] < '0' || line[1] > '9' ||
|
|
line[2] < '0' || line[2] > '9')
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Invalid reply"));
|
|
goto fail;
|
|
}
|
|
response = 100 * (line[0] - '0') +
|
|
10 * (line[1] - '0') +
|
|
(line[2] - '0');
|
|
if (line[3] == ' ')
|
|
reply_state = DONE;
|
|
else if (line[3] == '-')
|
|
reply_state = MULTILINE;
|
|
else
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Invalid reply"));
|
|
goto fail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (line[0] - '0' == response / 100 &&
|
|
line[1] - '0' == (response / 10) % 10 &&
|
|
line[2] - '0' == response % 10 &&
|
|
line[3] == ' ')
|
|
reply_state = DONE;
|
|
}
|
|
if (!lines)
|
|
g_free (line);
|
|
}
|
|
|
|
if (lines)
|
|
{
|
|
g_ptr_array_add (lines, NULL);
|
|
*reply = (char **) g_ptr_array_free (lines, FALSE);
|
|
}
|
|
|
|
/* 1xx commands are intermediate commands and require a further
|
|
* message from the server to complete
|
|
*/
|
|
if (response >= 200)
|
|
conn->waiting_for_reply = FALSE;
|
|
|
|
return response;
|
|
|
|
fail:
|
|
if (lines)
|
|
g_ptr_array_free (lines, TRUE);
|
|
else
|
|
g_free (line);
|
|
return 0;
|
|
}
|
|
|
|
GSocketAddress *
|
|
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 (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
|
|
g_vfs_ftp_connection_open_data_connection (GVfsFtpConnection *conn,
|
|
GSocketAddress * addr,
|
|
GCancellable * cancellable,
|
|
GError ** error)
|
|
{
|
|
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,
|
|
error));
|
|
if (conn->data)
|
|
enable_nodelay (G_SOCKET_CONNECTION (conn->data));
|
|
|
|
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, NULL);
|
|
|
|
g_vfs_ftp_connection_stop_listening (conn);
|
|
|
|
local = g_socket_connection_get_local_address (conn->connection, 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, cancellable, error);
|
|
if (accepted == NULL)
|
|
return FALSE;
|
|
|
|
conn->data = G_IO_STREAM (g_socket_connection_factory_create_connection (accepted));
|
|
g_object_unref (accepted);
|
|
enable_nodelay (G_SOCKET_CONNECTION (conn->data));
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
g_vfs_ftp_connection_close_data_connection (GVfsFtpConnection *conn)
|
|
{
|
|
g_return_if_fail (conn != NULL);
|
|
|
|
if (conn->data == NULL)
|
|
return;
|
|
|
|
g_object_unref (conn->data);
|
|
conn->data = NULL;
|
|
}
|
|
|
|
/**
|
|
* g_vfs_ftp_connection_get_debug_id:
|
|
* @conn: the connection
|
|
*
|
|
* Gets an ID that uniquely identifies this connection. Intended for use in
|
|
* debug print statements.
|
|
*
|
|
* Returns: the ID to use for referring to this connection in debug messages
|
|
**/
|
|
guint
|
|
g_vfs_ftp_connection_get_debug_id (GVfsFtpConnection *conn)
|
|
{
|
|
g_return_val_if_fail (conn != NULL, 0);
|
|
|
|
return conn->debug_id;
|
|
}
|
|
|
|
/**
|
|
* g_vfs_ftp_connection_get_data_stream:
|
|
* @conn: a connection
|
|
*
|
|
* Gets the data stream in use by @conn. It is an error to call this function
|
|
* when no data stream exists. Be sure to check the return value of
|
|
* g_vfs_ftp_connection_open_data_connection().
|
|
*
|
|
* Returns: the data stream of @conn
|
|
**/
|
|
GIOStream *
|
|
g_vfs_ftp_connection_get_data_stream (GVfsFtpConnection *conn)
|
|
{
|
|
g_return_val_if_fail (conn != NULL, NULL);
|
|
g_return_val_if_fail (conn->data != NULL, NULL);
|
|
|
|
return conn->data;
|
|
}
|
|
|
|
/**
|
|
* g_vfs_ftp_connection_is_usable:
|
|
* @conn: a connection
|
|
*
|
|
* Checks if this connection can be used to send new commands. For
|
|
* example, if the connection is in the process of a command sequence or
|
|
* if the connection was closed, this is not possible and this function
|
|
* will return %FALSE.
|
|
*
|
|
* Returns: %TRUE if the connection is still usable
|
|
**/
|
|
gboolean
|
|
g_vfs_ftp_connection_is_usable (GVfsFtpConnection *conn)
|
|
{
|
|
GIOCondition cond;
|
|
|
|
g_return_val_if_fail (conn != NULL, FALSE);
|
|
|
|
if (conn->waiting_for_reply)
|
|
return FALSE;
|
|
|
|
cond = G_IO_ERR | G_IO_HUP | G_IO_IN;
|
|
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,
|
|
cond & G_IO_IN ? "IN " : "",
|
|
cond & G_IO_HUP ? "HUP " : "",
|
|
cond & G_IO_ERR ? "ERR " : "");
|
|
return FALSE;
|
|
}
|
|
|
|
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,
|
|
gboolean implicit_tls,
|
|
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 (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,
|
|
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;
|
|
}
|