/*
* gnio-util.c - Source for telepathy-glib GNIO utility functions
* Copyright (C) 2009 Collabora Ltd.
* @author Danielle Madeley
*
* 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.1 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* SECTION:gnio-util
* @title: GNIO Utilities
* @short_description: Telepathy/GNIO utility functions
*
* Utility functions for interacting between Telepathy and GNIO.
*
* Telepathy uses address variants stored in #GValue boxes for communicating
* network socket addresses over D-Bus to and from the Connection Manager
* (for instance when using the file transfer and stream tube APIs).
*
* This API provides translation between #GSocketAddress subtypes and a #GValue
* that can be used by telepathy-glib.
* #GInetSocketAddress is used for IPv4/IPv6 and #GUnixSocketAddress
* for UNIX sockets (only available on platforms with gio-unix).
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include
#ifdef __linux__
/* for getsockopt() and setsockopt() */
#include /* See NOTES */
#include
#include
#endif
#include
#ifdef HAVE_GIO_UNIX
#include
#include
#include
#endif /* HAVE_GIO_UNIX */
/**
* tp_g_socket_address_from_variant:
* @type: a Telepathy socket address type
* @variant: an initialised #GValue containing an address variant,
* as encoded by dbus-glib
* @error: return location for a #GError (or NULL)
*
* Converts an address variant stored in a #GValue into a #GSocketAddress that
* can be used to make a socket connection with GIO.
*
* Returns: a newly allocated #GSocketAddress for the given variant, or NULL
* on error
*/
GSocketAddress *
tp_g_socket_address_from_variant (TpSocketAddressType type,
const GValue *variant,
GError **error)
{
GSocketAddress *addr;
switch (type)
{
#ifdef HAVE_GIO_UNIX
case TP_SOCKET_ADDRESS_TYPE_UNIX:
if (!G_VALUE_HOLDS (variant, DBUS_TYPE_G_UCHAR_ARRAY))
{
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"variant is %s not DBUS_TYPE_G_UCHAR_ARRAY",
G_VALUE_TYPE_NAME (variant));
return NULL;
}
else
{
GArray *address = g_value_get_boxed (variant);
char path[address->len + 1];
strncpy (path, address->data, address->len);
path[address->len] = '\0';
addr = g_unix_socket_address_new (path);
}
break;
case TP_SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX:
if (!G_VALUE_HOLDS (variant, DBUS_TYPE_G_UCHAR_ARRAY))
{
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"variant is %s not DBUS_TYPE_G_UCHAR_ARRAY",
G_VALUE_TYPE_NAME (variant));
return NULL;
}
else
{
GArray *address = g_value_get_boxed (variant);
addr = g_unix_socket_address_new_with_type (
address->data, address->len, G_UNIX_SOCKET_ADDRESS_ABSTRACT);
}
break;
#endif /* HAVE_GIO_UNIX */
case TP_SOCKET_ADDRESS_TYPE_IPV4:
case TP_SOCKET_ADDRESS_TYPE_IPV6:
if (type == TP_SOCKET_ADDRESS_TYPE_IPV4 &&
!G_VALUE_HOLDS (variant, TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4))
{
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"variant is %s not TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4",
G_VALUE_TYPE_NAME (variant));
return NULL;
}
else if (type == TP_SOCKET_ADDRESS_TYPE_IPV6 &&
!G_VALUE_HOLDS (variant, TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV6))
{
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"variant is %s not TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV6",
G_VALUE_TYPE_NAME (variant));
return NULL;
}
else
{
GValueArray *array = g_value_get_boxed (variant);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
GValue *hostv = g_value_array_get_nth (array, 0);
GValue *portv = g_value_array_get_nth (array, 1);
G_GNUC_END_IGNORE_DEPRECATIONS
GInetAddress *address;
const char *host;
guint16 port;
g_return_val_if_fail (G_VALUE_HOLDS_STRING (hostv), NULL);
g_return_val_if_fail (G_VALUE_HOLDS_UINT (portv), NULL);
host = g_value_get_string (hostv);
port = g_value_get_uint (portv);
address = g_inet_address_new_from_string (host);
addr = g_inet_socket_address_new (address, port);
g_object_unref (address);
}
break;
default:
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"Unknown TpSocketAddressType (%i)",
type);
return NULL;
}
return addr;
}
/**
* tp_address_variant_from_g_socket_address:
* @address: a #GSocketAddress to convert
* @type: optional return of the Telepathy socket type (or NULL)
* @error: return location for a #GError (or NULL)
*
* Converts a #GSocketAddress to a #GValue address variant that can be used
* with Telepathy and dbus-glib.
*
* Returns: a newly allocated #GValue, free with tp_g_value_slice_free()
*/
GValue *
tp_address_variant_from_g_socket_address (GSocketAddress *address,
TpSocketAddressType *ret_type,
GError **error)
{
GValue *variant;
TpSocketAddressType type;
g_return_val_if_fail (G_IS_SOCKET_ADDRESS (address), NULL);
switch (g_socket_address_get_family (address))
{
#ifdef HAVE_GIO_UNIX
case G_SOCKET_FAMILY_UNIX:
{
GUnixSocketAddress *unixaddr = G_UNIX_SOCKET_ADDRESS (address);
GArray *array;
const char *path = g_unix_socket_address_get_path (unixaddr);
gsize len = g_unix_socket_address_get_path_len (unixaddr);
if (g_unix_socket_address_get_address_type (unixaddr) ==
G_UNIX_SOCKET_ADDRESS_ABSTRACT)
{
type = TP_SOCKET_ADDRESS_TYPE_ABSTRACT_UNIX;
}
else
{
type = TP_SOCKET_ADDRESS_TYPE_UNIX;
}
array = g_array_sized_new (TRUE, FALSE, sizeof (char), len);
array = g_array_append_vals (array, path, len);
variant = tp_g_value_slice_new (DBUS_TYPE_G_UCHAR_ARRAY);
g_value_take_boxed (variant, array);
}
break;
#endif /* HAVE_GIO_UNIX */
case G_SOCKET_FAMILY_IPV4:
case G_SOCKET_FAMILY_IPV6:
{
GInetAddress *addr = g_inet_socket_address_get_address (
G_INET_SOCKET_ADDRESS (address));
GValueArray *array;
char *address_str;
guint port;
switch (g_inet_address_get_family (addr))
{
case G_SOCKET_FAMILY_IPV4:
type = TP_SOCKET_ADDRESS_TYPE_IPV4;
variant = tp_g_value_slice_new (TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV4);
break;
case G_SOCKET_FAMILY_IPV6:
type = TP_SOCKET_ADDRESS_TYPE_IPV6;
variant = tp_g_value_slice_new (TP_STRUCT_TYPE_SOCKET_ADDRESS_IPV6);
break;
default:
g_assert_not_reached ();
}
address_str = g_inet_address_to_string (addr);
port = g_inet_socket_address_get_port (
G_INET_SOCKET_ADDRESS (address));
array = tp_value_array_build (2,
G_TYPE_STRING, address_str,
G_TYPE_UINT, port,
G_TYPE_INVALID);
g_free (address_str);
g_value_take_boxed (variant, array);
}
break;
default:
g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"Unknown GSocketAddressFamily %i",
g_socket_address_get_family (address));
return NULL;
}
if (ret_type != NULL)
*ret_type = type;
return variant;
}
/**
* tp_g_socket_address_from_g_variant:
* @type: a Telepathy socket address type
* @variant: a socket address as encoded by Telepathy according to @type
* @error: return location for a #GError (or %NULL)
*
* Converts an address variant stored in a #GVariant into a #GSocketAddress
* that can be used to make a socket connection with GIO.
*
* If @variant is a floating reference, this function takes ownership
* of it.
*
* Returns: a newly allocated #GSocketAddress for the given variant, or %NULL
* on error
*
* Since: 0.19.10
*/
GSocketAddress *
tp_g_socket_address_from_g_variant (TpSocketAddressType type,
GVariant *variant,
GError **error)
{
GValue value = G_VALUE_INIT;
GSocketAddress *ret;
g_variant_ref_sink (variant);
dbus_g_value_parse_g_variant (variant, &value);
g_variant_unref (variant);
ret = tp_g_socket_address_from_variant (type, &value, error);
g_value_unset (&value);
return ret;
}
/**
* tp_address_g_variant_from_g_socket_address:
* @address: a #GSocketAddress to convert
* @type: optional return of the Telepathy socket type (or NULL)
* @error: return location for a #GError (or NULL)
*
* Converts a #GSocketAddress to a #GVariant address variant that can be used
* with Telepathy.
*
* Returns: (transfer none): a new variant with a floating reference, or %NULL
*
* Since: 0.19.10
*/
GVariant *
tp_address_g_variant_from_g_socket_address (GSocketAddress *address,
TpSocketAddressType *type,
GError **error)
{
GValue *value = tp_address_variant_from_g_socket_address (address,
type, error);
GVariant *ret;
if (value == NULL)
return NULL;
ret = dbus_g_value_build_g_variant (value);
tp_g_value_slice_free (value);
return ret;
}
#ifdef HAVE_GIO_UNIX
static gboolean
_tp_unix_connection_send_credentials_with_byte (GUnixConnection *connection,
guchar byte,
GCancellable *cancellable,
GError **error)
{
/* There is not variant of g_unix_connection_send_credentials allowing us to
* choose the byte sent :( See bgo #629267
*
* This code has been copied from glib/gunixconnection.c
*
* Copyright © 2009 Codethink Limited
*/
GCredentials *credentials;
GSocketControlMessage *scm;
GSocket *_socket;
gboolean ret;
GOutputVector vector;
g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
ret = FALSE;
credentials = g_credentials_new ();
vector.buffer = &byte;
vector.size = 1;
scm = g_unix_credentials_message_new_with_credentials (credentials);
g_object_get (connection, "socket", &_socket, NULL);
if (g_socket_send_message (_socket,
NULL, /* address */
&vector,
1,
&scm,
1,
G_SOCKET_MSG_NONE,
cancellable,
error) != 1)
{
g_prefix_error (error, "Error sending credentials: ");
goto out;
}
ret = TRUE;
out:
g_object_unref (_socket);
g_object_unref (scm);
g_object_unref (credentials);
return ret;
}
#endif
/**
* tp_unix_connection_send_credentials_with_byte:
* @connection: a #GUnixConnection
* @byte: the byte to send with the credentials
* @cancellable: (allow-none): a #GCancellable, or %NULL
* @error: a #GError to fill
*
* A variant of g_unix_connection_send_credentials() allowing you to choose
* the byte which is send with the credentials
*
* Returns: %TRUE on success, %FALSE if error is set.
*
* Since: 0.13.2
*/
gboolean
tp_unix_connection_send_credentials_with_byte (GSocketConnection *connection,
guchar byte,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_GIO_UNIX
return _tp_unix_connection_send_credentials_with_byte (
G_UNIX_CONNECTION (connection), byte, cancellable, error);
#else
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unix sockets not supported");
return FALSE;
#endif
}
static void
send_credentials_with_byte_async_thread (GSimpleAsyncResult *res,
GObject *object,
GCancellable *cancellable)
{
guchar byte;
GError *error = NULL;
byte = GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (res));
if (!tp_unix_connection_send_credentials_with_byte (
(GSocketConnection *) object, byte, cancellable, &error))
{
g_simple_async_result_take_error (res, error);
}
}
/**
* tp_unix_connection_send_credentials_with_byte_async:
* @connection: A #GUnixConnection.
* @byte: the byte to send with the credentials
* @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
* @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: (closure): the data to pass to callback function
*
* Asynchronously send credentials.
*
* For more details, see tp_unix_connection_send_credentials_with_byte() which
* is the synchronous version of this call.
*
* When the operation is finished, @callback will be called. You can then call
* tp_unix_connection_send_credentials_with_byte_finish() to get the result of
* the operation.
*
* Since: 0.17.5
**/
void
tp_unix_connection_send_credentials_with_byte_async (
GSocketConnection *connection,
guchar byte,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
res = g_simple_async_result_new (G_OBJECT (connection), callback, user_data,
tp_unix_connection_send_credentials_with_byte_async);
/* Extra casting to guint to work around GNOME#661546 for GLib < 2.32 */
g_simple_async_result_set_op_res_gpointer (res,
GUINT_TO_POINTER ((guint) byte), NULL);
g_simple_async_result_run_in_thread (res,
send_credentials_with_byte_async_thread, G_PRIORITY_DEFAULT, cancellable);
g_object_unref (res);
}
/**
* tp_unix_connection_send_credentials_with_byte_finish:
* @connection: A #GUnixConnection.
* @result: a #GAsyncResult.
* @error: a #GError, or %NULL
*
* Finishes an asynchronous send credentials operation started with
* tp_unix_connection_send_credentials_with_byte_async().
*
* Returns: %TRUE if the operation was successful, otherwise %FALSE.
*
* Since: 0.17.5
**/
gboolean
tp_unix_connection_send_credentials_with_byte_finish (
GSocketConnection *connection,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (connection,
tp_unix_connection_send_credentials_with_byte_async);
}
#ifdef HAVE_GIO_UNIX
static GCredentials *
_tp_unix_connection_receive_credentials_with_byte (GUnixConnection *connection,
guchar *byte,
GCancellable *cancellable,
GError **error)
{
/* There is not variant of g_unix_connection_receive_credentials allowing us
* to choose the byte sent :( See bgo #629267
*
* This code has been copied from glib/gunixconnection.c
*
* Copyright © 2009 Codethink Limited
*/
GCredentials *ret;
GSocketControlMessage **scms;
gint nscm;
GSocket *_socket;
gint n;
gssize num_bytes_read;
#ifdef __linux__
gboolean turn_off_so_passcreds;
#endif
GInputVector vector;
guchar buffer[1];
g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
ret = NULL;
scms = NULL;
g_object_get (connection, "socket", &_socket, NULL);
/* On Linux, we need to turn on SO_PASSCRED if it isn't enabled
* already. We also need to turn it off when we're done. See
* #617483 for more discussion.
*/
#ifdef __linux__
{
gint opt_val;
socklen_t opt_len;
turn_off_so_passcreds = FALSE;
opt_val = 0;
opt_len = sizeof (gint);
if (getsockopt (g_socket_get_fd (_socket),
SOL_SOCKET,
SO_PASSCRED,
&opt_val,
&opt_len) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
"Error checking if SO_PASSCRED is enabled for socket: %s",
strerror (errno));
goto out;
}
if (opt_len != sizeof (gint))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Unexpected option length while checking if SO_PASSCRED is enabled for socket. "
"Expected %d bytes, got %d",
(gint) sizeof (gint), (gint) opt_len);
goto out;
}
if (opt_val == 0)
{
opt_val = 1;
if (setsockopt (g_socket_get_fd (_socket),
SOL_SOCKET,
SO_PASSCRED,
&opt_val,
sizeof opt_val) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
"Error enabling SO_PASSCRED: %s",
strerror (errno));
goto out;
}
turn_off_so_passcreds = TRUE;
}
}
#endif
vector.buffer = buffer;
vector.size = 1;
/* ensure the type of GUnixCredentialsMessage has been registered with the type system */
(void) (G_TYPE_UNIX_CREDENTIALS_MESSAGE);
num_bytes_read = g_socket_receive_message (_socket,
NULL, /* GSocketAddress **address */
&vector,
1,
&scms,
&nscm,
NULL,
cancellable,
error);
if (num_bytes_read != 1)
{
/* Handle situation where g_socket_receive_message() returns
* 0 bytes and not setting @error
*/
if (num_bytes_read == 0 && error != NULL && *error == NULL)
{
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Expecting to read a single byte for receiving credentials but read zero bytes");
}
goto out;
}
if (nscm != 1)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Expecting 1 control message, got %d",
nscm);
goto out;
}
if (!G_IS_UNIX_CREDENTIALS_MESSAGE (scms[0]))
{
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Unexpected type of ancillary data");
goto out;
}
if (byte != NULL)
{
*byte = buffer[0];
}
ret = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (scms[0]));
g_object_ref (ret);
out:
#ifdef __linux__
if (turn_off_so_passcreds)
{
gint opt_val;
opt_val = 0;
if (setsockopt (g_socket_get_fd (_socket),
SOL_SOCKET,
SO_PASSCRED,
&opt_val,
sizeof opt_val) != 0)
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errno),
"Error while disabling SO_PASSCRED: %s",
strerror (errno));
goto out;
}
}
#endif
if (scms != NULL)
{
for (n = 0; n < nscm; n++)
g_object_unref (scms[n]);
g_free (scms);
}
g_object_unref (_socket);
return ret;
}
#endif
/**
* tp_unix_connection_receive_credentials_with_byte
* @connection: a #GUnixConnection
* @byte: (out): if not %NULL, used to return the byte
* @cancellable: (allow-none): a #GCancellable, or %NULL
* @error: a #GError to fill
*
* A variant of g_unix_connection_receive_credentials() allowing you to get
* the byte which has been received with the credentials.
*
* Returns: (transfer full): Received credentials on success (free with
* g_object_unref()), %NULL if error is set.
*
* Since: 0.13.2
*/
GCredentials *
tp_unix_connection_receive_credentials_with_byte (GSocketConnection *connection,
guchar *byte,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_GIO_UNIX
return _tp_unix_connection_receive_credentials_with_byte (
G_UNIX_CONNECTION (connection), byte, cancellable, error);
#else
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unix sockets not supported");
return FALSE;
#endif
}
typedef struct
{
GCredentials *creds;
guchar byte;
} ReceiveCredentialsWithByteData;
static ReceiveCredentialsWithByteData *
receive_credentials_with_byte_data_new (GCredentials *creds,
guchar byte)
{
ReceiveCredentialsWithByteData *data;
data = g_slice_new0 (ReceiveCredentialsWithByteData);
data->creds = g_object_ref (creds);
data->byte = byte;
return data;
}
static void
receive_credentials_with_byte_data_free (ReceiveCredentialsWithByteData *data)
{
g_object_unref (data->creds);
g_slice_free (ReceiveCredentialsWithByteData, data);
}
static void
receive_credentials_with_byte_async_thread (GSimpleAsyncResult *res,
GObject *object,
GCancellable *cancellable)
{
guchar byte;
GCredentials *creds;
GError *error = NULL;
creds = tp_unix_connection_receive_credentials_with_byte (
(GSocketConnection *) object, &byte, cancellable, &error);
if (creds == NULL)
{
g_simple_async_result_take_error (res, error);
return;
}
g_simple_async_result_set_op_res_gpointer (res,
receive_credentials_with_byte_data_new (creds, byte),
(GDestroyNotify) receive_credentials_with_byte_data_free);
g_object_unref (creds);
}
/**
* tp_unix_connection_receive_credentials_with_byte_async:
* @connection: A #GUnixConnection.
* @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
* @callback: (scope async): a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: (closure): the data to pass to callback function
*
* Asynchronously receive credentials.
*
* For more details, see tp_unix_connection_receive_credentials_with_byte()
* which is the synchronous version of this call.
*
* When the operation is finished, @callback will be called. You can then call
* tp_unix_connection_receive_credentials_with_byte_finish() to get the result
* of the operation.
*
* Since: 0.17.5
**/
void
tp_unix_connection_receive_credentials_with_byte_async (
GSocketConnection *connection,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
res = g_simple_async_result_new (G_OBJECT (connection), callback, user_data,
tp_unix_connection_receive_credentials_with_byte_async);
g_simple_async_result_run_in_thread (res,
receive_credentials_with_byte_async_thread, G_PRIORITY_DEFAULT,
cancellable);
g_object_unref (res);
}
/**
* tp_unix_connection_receive_credentials_with_byte_finish:
* @connection: A #GUnixConnection.
* @result: a #GAsyncResult.
* @byte: (out): if not %NULL, used to return the byte
* @error: a #GError, or %NULL
*
* Finishes an asynchronous receive credentials operation started with
* tp_unix_connection_receive_credentials_with_byte_async().
*
* Returns: (transfer full): a #GCredentials, or %NULL on error.
* Free the returned object with g_object_unref().
*
* Since: 0.17.5
**/
GCredentials *
tp_unix_connection_receive_credentials_with_byte_finish (
GSocketConnection *connection,
GAsyncResult *result,
guchar *byte,
GError **error)
{
GSimpleAsyncResult *simple = (GSimpleAsyncResult *) result;
ReceiveCredentialsWithByteData *data;
g_return_val_if_fail (g_simple_async_result_is_valid (result,
G_OBJECT (connection),
tp_unix_connection_receive_credentials_with_byte_async),
NULL);
if (g_simple_async_result_propagate_error (simple, error))
return NULL;
data = g_simple_async_result_get_op_res_gpointer (simple);
if (byte != NULL)
*byte = data->byte;
return g_object_ref (data->creds);
}