mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-14 05:16:18 +01:00
211ed1775d
Add g_socket_get_option() and g_socket_set_option(), wrapping getsockopt/setsockopt for the case of integer-valued options. Update code to use these instead of the underlying calls. https://bugzilla.gnome.org/show_bug.cgi?id=623187
685 lines
22 KiB
C
685 lines
22 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
|
*
|
|
* Copyright © 2009 Codethink Limited
|
|
*
|
|
* This program 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 licence or (at
|
|
* your option) any later version.
|
|
*
|
|
* See the included COPYING file for more information.
|
|
*
|
|
* Authors: Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gunixconnection.h"
|
|
#include "gnetworking.h"
|
|
#include "gsocket.h"
|
|
#include "gsocketcontrolmessage.h"
|
|
#include "gunixcredentialsmessage.h"
|
|
#include "gunixfdmessage.h"
|
|
#include "glibintl.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
/**
|
|
* SECTION:gunixconnection
|
|
* @title: GUnixConnection
|
|
* @short_description: A UNIX domain GSocketConnection
|
|
* @include: gio/gunixconnection.h
|
|
* @see_also: #GSocketConnection.
|
|
*
|
|
* This is the subclass of #GSocketConnection that is created
|
|
* for UNIX domain sockets.
|
|
*
|
|
* It contains functions to do some of the UNIX socket specific
|
|
* functionality like passing file descriptors.
|
|
*
|
|
* Note that <filename><gio/gunixconnection.h></filename> belongs to
|
|
* the UNIX-specific GIO interfaces, thus you have to use the
|
|
* <filename>gio-unix-2.0.pc</filename> pkg-config file when using it.
|
|
*
|
|
* Since: 2.22
|
|
*/
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GUnixConnection, g_unix_connection,
|
|
G_TYPE_SOCKET_CONNECTION,
|
|
g_socket_connection_factory_register_type (g_define_type_id,
|
|
G_SOCKET_FAMILY_UNIX,
|
|
G_SOCKET_TYPE_STREAM,
|
|
G_SOCKET_PROTOCOL_DEFAULT);
|
|
);
|
|
|
|
/**
|
|
* g_unix_connection_send_fd:
|
|
* @connection: a #GUnixConnection
|
|
* @fd: a file descriptor
|
|
* @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
|
|
* @error: (allow-none): #GError for error reporting, or %NULL to ignore.
|
|
*
|
|
* Passes a file descriptor to the receiving side of the
|
|
* connection. The receiving end has to call g_unix_connection_receive_fd()
|
|
* to accept the file descriptor.
|
|
*
|
|
* As well as sending the fd this also writes a single byte to the
|
|
* stream, as this is required for fd passing to work on some
|
|
* implementations.
|
|
*
|
|
* Returns: a %TRUE on success, %NULL on error.
|
|
*
|
|
* Since: 2.22
|
|
*/
|
|
gboolean
|
|
g_unix_connection_send_fd (GUnixConnection *connection,
|
|
gint fd,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GSocketControlMessage *scm;
|
|
GSocket *socket;
|
|
|
|
g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), FALSE);
|
|
g_return_val_if_fail (fd >= 0, FALSE);
|
|
|
|
scm = g_unix_fd_message_new ();
|
|
|
|
if (!g_unix_fd_message_append_fd (G_UNIX_FD_MESSAGE (scm), fd, error))
|
|
{
|
|
g_object_unref (scm);
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_get (connection, "socket", &socket, NULL);
|
|
if (g_socket_send_message (socket, NULL, NULL, 0, &scm, 1, 0, cancellable, error) != 1)
|
|
/* XXX could it 'fail' with zero? */
|
|
{
|
|
g_object_unref (socket);
|
|
g_object_unref (scm);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_unref (socket);
|
|
g_object_unref (scm);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* g_unix_connection_receive_fd:
|
|
* @connection: a #GUnixConnection
|
|
* @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore
|
|
* @error: (allow-none): #GError for error reporting, or %NULL to ignore
|
|
*
|
|
* Receives a file descriptor from the sending end of the connection.
|
|
* The sending end has to call g_unix_connection_send_fd() for this
|
|
* to work.
|
|
*
|
|
* As well as reading the fd this also reads a single byte from the
|
|
* stream, as this is required for fd passing to work on some
|
|
* implementations.
|
|
*
|
|
* Returns: a file descriptor on success, -1 on error.
|
|
*
|
|
* Since: 2.22
|
|
**/
|
|
gint
|
|
g_unix_connection_receive_fd (GUnixConnection *connection,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GSocketControlMessage **scms;
|
|
gint *fds, nfd, fd, nscm;
|
|
GUnixFDMessage *fdmsg;
|
|
GSocket *socket;
|
|
|
|
g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), -1);
|
|
|
|
g_object_get (connection, "socket", &socket, NULL);
|
|
if (g_socket_receive_message (socket, NULL, NULL, 0,
|
|
&scms, &nscm, NULL, cancellable, error) != 1)
|
|
/* XXX it _could_ 'fail' with zero. */
|
|
{
|
|
g_object_unref (socket);
|
|
|
|
return -1;
|
|
}
|
|
|
|
g_object_unref (socket);
|
|
|
|
if (nscm != 1)
|
|
{
|
|
gint i;
|
|
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Expecting 1 control message, got %d"), nscm);
|
|
|
|
for (i = 0; i < nscm; i++)
|
|
g_object_unref (scms[i]);
|
|
|
|
g_free (scms);
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (!G_IS_UNIX_FD_MESSAGE (scms[0]))
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Unexpected type of ancillary data"));
|
|
g_object_unref (scms[0]);
|
|
g_free (scms);
|
|
|
|
return -1;
|
|
}
|
|
|
|
fdmsg = G_UNIX_FD_MESSAGE (scms[0]);
|
|
g_free (scms);
|
|
|
|
fds = g_unix_fd_message_steal_fds (fdmsg, &nfd);
|
|
g_object_unref (fdmsg);
|
|
|
|
if (nfd != 1)
|
|
{
|
|
gint i;
|
|
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Expecting one fd, but got %d\n"), nfd);
|
|
|
|
for (i = 0; i < nfd; i++)
|
|
close (fds[i]);
|
|
|
|
g_free (fds);
|
|
|
|
return -1;
|
|
}
|
|
|
|
fd = *fds;
|
|
g_free (fds);
|
|
|
|
if (fd < 0)
|
|
{
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Received invalid fd"));
|
|
fd = -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void
|
|
g_unix_connection_init (GUnixConnection *connection)
|
|
{
|
|
}
|
|
|
|
static void
|
|
g_unix_connection_class_init (GUnixConnectionClass *class)
|
|
{
|
|
}
|
|
|
|
/* TODO: Other stuff we might want to add are:
|
|
void g_unix_connection_send_fd_async (GUnixConnection *connection,
|
|
gint fd,
|
|
gboolean close,
|
|
gint io_priority,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data);
|
|
gboolean g_unix_connection_send_fd_finish (GUnixConnection *connection,
|
|
GError **error);
|
|
|
|
gboolean g_unix_connection_send_fds (GUnixConnection *connection,
|
|
gint *fds,
|
|
gint nfds,
|
|
GError **error);
|
|
void g_unix_connection_send_fds_async (GUnixConnection *connection,
|
|
gint *fds,
|
|
gint nfds,
|
|
gint io_priority,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data);
|
|
gboolean g_unix_connection_send_fds_finish (GUnixConnection *connection,
|
|
GError **error);
|
|
|
|
void g_unix_connection_receive_fd_async (GUnixConnection *connection,
|
|
gint io_priority,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data);
|
|
gint g_unix_connection_receive_fd_finish (GUnixConnection *connection,
|
|
GError **error);
|
|
|
|
|
|
gboolean g_unix_connection_send_fake_credentials (GUnixConnection *connection,
|
|
guint64 pid,
|
|
guint64 uid,
|
|
guint64 gid,
|
|
GError **error);
|
|
void g_unix_connection_send_fake_credentials_async (GUnixConnection *connection,
|
|
guint64 pid,
|
|
guint64 uid,
|
|
guint64 gid,
|
|
gint io_priority,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data);
|
|
gboolean g_unix_connection_send_fake_credentials_finish (GUnixConnection *connection,
|
|
GError **error);
|
|
|
|
gboolean g_unix_connection_create_pair (GUnixConnection **one,
|
|
GUnixConnection **two,
|
|
GError **error);
|
|
*/
|
|
|
|
|
|
/**
|
|
* g_unix_connection_send_credentials:
|
|
* @connection: A #GUnixConnection.
|
|
* @cancellable: (allow-none): A #GCancellable or %NULL.
|
|
* @error: Return location for error or %NULL.
|
|
*
|
|
* Passes the credentials of the current user the receiving side
|
|
* of the connection. The receiving end has to call
|
|
* g_unix_connection_receive_credentials() (or similar) to accept the
|
|
* credentials.
|
|
*
|
|
* As well as sending the credentials this also writes a single NUL
|
|
* byte to the stream, as this is required for credentials passing to
|
|
* work on some implementations.
|
|
*
|
|
* Other ways to exchange credentials with a foreign peer includes the
|
|
* #GUnixCredentialsMessage type and g_socket_get_credentials() function.
|
|
*
|
|
* Returns: %TRUE on success, %FALSE if @error is set.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
gboolean
|
|
g_unix_connection_send_credentials (GUnixConnection *connection,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GCredentials *credentials;
|
|
GSocketControlMessage *scm;
|
|
GSocket *socket;
|
|
gboolean ret;
|
|
GOutputVector vector;
|
|
guchar nul_byte[1] = {'\0'};
|
|
gint num_messages;
|
|
|
|
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 = &nul_byte;
|
|
vector.size = 1;
|
|
|
|
if (g_unix_credentials_message_is_supported ())
|
|
{
|
|
scm = g_unix_credentials_message_new_with_credentials (credentials);
|
|
num_messages = 1;
|
|
}
|
|
else
|
|
{
|
|
scm = NULL;
|
|
num_messages = 0;
|
|
}
|
|
|
|
g_object_get (connection, "socket", &socket, NULL);
|
|
if (g_socket_send_message (socket,
|
|
NULL, /* address */
|
|
&vector,
|
|
1,
|
|
&scm,
|
|
num_messages,
|
|
G_SOCKET_MSG_NONE,
|
|
cancellable,
|
|
error) != 1)
|
|
{
|
|
g_prefix_error (error, _("Error sending credentials: "));
|
|
goto out;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
g_object_unref (socket);
|
|
if (scm != NULL)
|
|
g_object_unref (scm);
|
|
g_object_unref (credentials);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
send_credentials_async_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (g_unix_connection_send_credentials (G_UNIX_CONNECTION (source_object),
|
|
cancellable,
|
|
&error))
|
|
g_task_return_boolean (task, TRUE);
|
|
else
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/**
|
|
* g_unix_connection_send_credentials_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 send credentials.
|
|
*
|
|
* For more details, see g_unix_connection_send_credentials() which is
|
|
* the synchronous version of this call.
|
|
*
|
|
* When the operation is finished, @callback will be called. You can then call
|
|
* g_unix_connection_send_credentials_finish() to get the result of the operation.
|
|
*
|
|
* Since: 2.32
|
|
**/
|
|
void
|
|
g_unix_connection_send_credentials_async (GUnixConnection *connection,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (connection, cancellable, callback, user_data);
|
|
|
|
g_task_run_in_thread (task, send_credentials_async_thread);
|
|
}
|
|
|
|
/**
|
|
* g_unix_connection_send_credentials_finish:
|
|
* @connection: A #GUnixConnection.
|
|
* @result: a #GAsyncResult.
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Finishes an asynchronous send credentials operation started with
|
|
* g_unix_connection_send_credentials_async().
|
|
*
|
|
* Returns: %TRUE if the operation was successful, otherwise %FALSE.
|
|
*
|
|
* Since: 2.32
|
|
**/
|
|
gboolean
|
|
g_unix_connection_send_credentials_finish (GUnixConnection *connection,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, connection), FALSE);
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
|
|
/**
|
|
* g_unix_connection_receive_credentials:
|
|
* @connection: A #GUnixConnection.
|
|
* @cancellable: (allow-none): A #GCancellable or %NULL.
|
|
* @error: Return location for error or %NULL.
|
|
*
|
|
* Receives credentials from the sending end of the connection. The
|
|
* sending end has to call g_unix_connection_send_credentials() (or
|
|
* similar) for this to work.
|
|
*
|
|
* As well as reading the credentials this also reads (and discards) a
|
|
* single byte from the stream, as this is required for credentials
|
|
* passing to work on some implementations.
|
|
*
|
|
* Other ways to exchange credentials with a foreign peer includes the
|
|
* #GUnixCredentialsMessage type and g_socket_get_credentials() function.
|
|
*
|
|
* Returns: (transfer full): Received credentials on success (free with
|
|
* g_object_unref()), %NULL if @error is set.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
GCredentials *
|
|
g_unix_connection_receive_credentials (GUnixConnection *connection,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GCredentials *ret;
|
|
GSocketControlMessage **scms;
|
|
gint nscm;
|
|
GSocket *socket;
|
|
gint n;
|
|
gssize num_bytes_read;
|
|
#ifdef __linux__
|
|
gboolean turn_off_so_passcreds;
|
|
#endif
|
|
|
|
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;
|
|
|
|
turn_off_so_passcreds = FALSE;
|
|
opt_val = 0;
|
|
if (!g_socket_get_option (socket,
|
|
SOL_SOCKET,
|
|
SO_PASSCRED,
|
|
&opt_val,
|
|
NULL))
|
|
{
|
|
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_val == 0)
|
|
{
|
|
if (!g_socket_set_option (socket,
|
|
SOL_SOCKET,
|
|
SO_PASSCRED,
|
|
TRUE,
|
|
NULL))
|
|
{
|
|
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
|
|
|
|
g_type_ensure (G_TYPE_UNIX_CREDENTIALS_MESSAGE);
|
|
num_bytes_read = g_socket_receive_message (socket,
|
|
NULL, /* GSocketAddress **address */
|
|
NULL,
|
|
0,
|
|
&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 (g_unix_credentials_message_is_supported () &&
|
|
/* Fall back on get_credentials if the other side didn't send the credentials */
|
|
nscm > 0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
ret = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (scms[0]));
|
|
g_object_ref (ret);
|
|
}
|
|
else
|
|
{
|
|
if (nscm != 0)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
_("Not expecting control message, but got %d"),
|
|
nscm);
|
|
goto out;
|
|
}
|
|
else
|
|
{
|
|
ret = g_socket_get_credentials (socket, error);
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
#ifdef __linux__
|
|
if (turn_off_so_passcreds)
|
|
{
|
|
if (!g_socket_set_option (socket,
|
|
SOL_SOCKET,
|
|
SO_PASSCRED,
|
|
FALSE,
|
|
NULL))
|
|
{
|
|
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;
|
|
}
|
|
|
|
static void
|
|
receive_credentials_async_thread (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
GCredentials *creds;
|
|
GError *error = NULL;
|
|
|
|
creds = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (source_object),
|
|
cancellable,
|
|
&error);
|
|
if (creds)
|
|
g_task_return_pointer (task, creds, g_object_unref);
|
|
else
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/**
|
|
* g_unix_connection_receive_credentials_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 g_unix_connection_receive_credentials() which is
|
|
* the synchronous version of this call.
|
|
*
|
|
* When the operation is finished, @callback will be called. You can then call
|
|
* g_unix_connection_receive_credentials_finish() to get the result of the operation.
|
|
*
|
|
* Since: 2.32
|
|
**/
|
|
void
|
|
g_unix_connection_receive_credentials_async (GUnixConnection *connection,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (connection, cancellable, callback, user_data);
|
|
|
|
g_task_run_in_thread (task, receive_credentials_async_thread);
|
|
}
|
|
|
|
/**
|
|
* g_unix_connection_receive_credentials_finish:
|
|
* @connection: A #GUnixConnection.
|
|
* @result: a #GAsyncResult.
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Finishes an asynchronous receive credentials operation started with
|
|
* g_unix_connection_receive_credentials_async().
|
|
*
|
|
* Returns: (transfer full): a #GCredentials, or %NULL on error.
|
|
* Free the returned object with g_object_unref().
|
|
*
|
|
* Since: 2.32
|
|
**/
|
|
GCredentials *
|
|
g_unix_connection_receive_credentials_finish (GUnixConnection *connection,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (g_task_is_valid (result, connection), NULL);
|
|
|
|
return g_task_propagate_pointer (G_TASK (result), error);
|
|
}
|