
GError codes are only unique per domain, so logging the code without also indicating the domain is not helpful. And anyway, if the error messages are not distinctive enough to tell the whole story then we should fix the error messages. Based-on-patch-by: Dan Winship <danw@gnome.org>
496 lines
13 KiB
C
496 lines
13 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager -- Network link manager
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright (C) 2010 Red Hat, Inc.
|
|
*/
|
|
|
|
#include "nm-default.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "nm-setting-connection.h"
|
|
#include "nm-auth-utils.h"
|
|
#include "nm-auth-subject.h"
|
|
#include "nm-auth-manager.h"
|
|
#include "nm-session-monitor.h"
|
|
|
|
struct NMAuthChain {
|
|
guint32 refcount;
|
|
GSList *calls;
|
|
GHashTable *data;
|
|
|
|
GDBusMethodInvocation *context;
|
|
NMAuthSubject *subject;
|
|
GError *error;
|
|
|
|
guint idle_id;
|
|
gboolean done;
|
|
|
|
NMAuthChainResultFunc done_func;
|
|
gpointer user_data;
|
|
};
|
|
|
|
typedef struct {
|
|
NMAuthChain *chain;
|
|
GCancellable *cancellable;
|
|
char *permission;
|
|
guint call_idle_id;
|
|
} AuthCall;
|
|
|
|
typedef struct {
|
|
gpointer data;
|
|
GDestroyNotify destroy;
|
|
} ChainData;
|
|
|
|
static ChainData *
|
|
chain_data_new (gpointer data, GDestroyNotify destroy)
|
|
{
|
|
ChainData *tmp;
|
|
|
|
tmp = g_slice_new (ChainData);
|
|
tmp->data = data;
|
|
tmp->destroy = destroy;
|
|
return tmp;
|
|
}
|
|
|
|
static void
|
|
chain_data_free (gpointer data)
|
|
{
|
|
ChainData *tmp = data;
|
|
|
|
if (tmp->destroy)
|
|
tmp->destroy (tmp->data);
|
|
memset (tmp, 0, sizeof (ChainData));
|
|
g_slice_free (ChainData, tmp);
|
|
}
|
|
|
|
static gboolean
|
|
auth_chain_finish (gpointer user_data)
|
|
{
|
|
NMAuthChain *self = user_data;
|
|
|
|
self->idle_id = 0;
|
|
self->done = TRUE;
|
|
|
|
/* Ensure we stay alive across the callback */
|
|
self->refcount++;
|
|
self->done_func (self, self->error, self->context, self->user_data);
|
|
nm_auth_chain_unref (self);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Creates the NMAuthSubject automatically */
|
|
NMAuthChain *
|
|
nm_auth_chain_new_context (GDBusMethodInvocation *context,
|
|
NMAuthChainResultFunc done_func,
|
|
gpointer user_data)
|
|
{
|
|
NMAuthSubject *subject;
|
|
NMAuthChain *chain;
|
|
|
|
g_return_val_if_fail (context != NULL, NULL);
|
|
|
|
subject = nm_auth_subject_new_unix_process_from_context (context);
|
|
if (!subject)
|
|
return NULL;
|
|
|
|
chain = nm_auth_chain_new_subject (subject,
|
|
context,
|
|
done_func,
|
|
user_data);
|
|
g_object_unref (subject);
|
|
return chain;
|
|
}
|
|
|
|
/* Requires an NMAuthSubject */
|
|
NMAuthChain *
|
|
nm_auth_chain_new_subject (NMAuthSubject *subject,
|
|
GDBusMethodInvocation *context,
|
|
NMAuthChainResultFunc done_func,
|
|
gpointer user_data)
|
|
{
|
|
NMAuthChain *self;
|
|
|
|
g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
|
|
g_return_val_if_fail (nm_auth_subject_is_unix_process (subject) || nm_auth_subject_is_internal (subject), NULL);
|
|
|
|
self = g_slice_new0 (NMAuthChain);
|
|
self->refcount = 1;
|
|
self->data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, chain_data_free);
|
|
self->done_func = done_func;
|
|
self->user_data = user_data;
|
|
self->context = context ? g_object_ref (context) : NULL;
|
|
self->subject = g_object_ref (subject);
|
|
|
|
return self;
|
|
}
|
|
|
|
static gpointer
|
|
_get_data (NMAuthChain *self, const char *tag)
|
|
{
|
|
ChainData *tmp;
|
|
|
|
tmp = g_hash_table_lookup (self->data, tag);
|
|
return tmp ? tmp->data : NULL;
|
|
}
|
|
|
|
gpointer
|
|
nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (tag != NULL, NULL);
|
|
|
|
return _get_data (self, tag);
|
|
}
|
|
|
|
/**
|
|
* nm_auth_chain_steal_data:
|
|
* @self: A #NMAuthChain.
|
|
* @tag: A "tag" uniquely identifying the data to steal.
|
|
*
|
|
* Removes the datum assocated with @tag from the chain's data associations,
|
|
* without invoking the association's destroy handler. The caller assumes
|
|
* ownership over the returned value.
|
|
*
|
|
* Returns: the datum originally associated with @tag
|
|
*/
|
|
gpointer
|
|
nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
|
|
{
|
|
ChainData *tmp;
|
|
gpointer value = NULL;
|
|
void *orig_key;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (tag != NULL, NULL);
|
|
|
|
if (g_hash_table_lookup_extended (self->data, tag, &orig_key, (gpointer)&tmp)) {
|
|
g_hash_table_steal (self->data, tag);
|
|
value = tmp->data;
|
|
/* Make sure the destroy handler isn't called when freeing */
|
|
tmp->destroy = NULL;
|
|
chain_data_free (tmp);
|
|
g_free (orig_key);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void
|
|
nm_auth_chain_set_data (NMAuthChain *self,
|
|
const char *tag,
|
|
gpointer data,
|
|
GDestroyNotify data_destroy)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (tag != NULL);
|
|
|
|
if (data == NULL)
|
|
g_hash_table_remove (self->data, tag);
|
|
else {
|
|
g_hash_table_insert (self->data,
|
|
g_strdup (tag),
|
|
chain_data_new (data, data_destroy));
|
|
}
|
|
}
|
|
|
|
gulong
|
|
nm_auth_chain_get_data_ulong (NMAuthChain *self, const char *tag)
|
|
{
|
|
gulong *data;
|
|
|
|
g_return_val_if_fail (self != NULL, 0);
|
|
g_return_val_if_fail (tag != NULL, 0);
|
|
|
|
data = _get_data (self, tag);
|
|
return data ? *data : 0ul;
|
|
}
|
|
|
|
void
|
|
nm_auth_chain_set_data_ulong (NMAuthChain *self,
|
|
const char *tag,
|
|
gulong data)
|
|
{
|
|
gulong *ptr;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (tag != NULL);
|
|
|
|
ptr = g_malloc (sizeof (*ptr));
|
|
*ptr = data;
|
|
nm_auth_chain_set_data (self, tag, ptr, g_free);
|
|
}
|
|
|
|
NMAuthSubject *
|
|
nm_auth_chain_get_subject (NMAuthChain *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return self->subject;
|
|
}
|
|
|
|
NMAuthCallResult
|
|
nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
|
|
{
|
|
gpointer data;
|
|
|
|
g_return_val_if_fail (self != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
|
|
g_return_val_if_fail (permission != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
|
|
|
|
data = _get_data (self, permission);
|
|
return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
|
|
}
|
|
|
|
static AuthCall *
|
|
auth_call_new (NMAuthChain *chain, const char *permission)
|
|
{
|
|
AuthCall *call;
|
|
|
|
call = g_slice_new0 (AuthCall);
|
|
call->chain = chain;
|
|
call->permission = g_strdup (permission);
|
|
return call;
|
|
}
|
|
|
|
static void
|
|
auth_call_free (AuthCall *call)
|
|
{
|
|
g_free (call->permission);
|
|
g_clear_object (&call->cancellable);
|
|
g_slice_free (AuthCall, call);
|
|
}
|
|
|
|
static gboolean
|
|
auth_call_complete (AuthCall *call)
|
|
{
|
|
NMAuthChain *self;
|
|
|
|
g_return_val_if_fail (call, G_SOURCE_REMOVE);
|
|
|
|
self = call->chain;
|
|
|
|
g_return_val_if_fail (self, G_SOURCE_REMOVE);
|
|
g_return_val_if_fail (g_slist_find (self->calls, call), G_SOURCE_REMOVE);
|
|
|
|
self->calls = g_slist_remove (self->calls, call);
|
|
|
|
if (!self->calls) {
|
|
g_assert (!self->idle_id && !self->done);
|
|
self->idle_id = g_idle_add (auth_chain_finish, self);
|
|
}
|
|
auth_call_free (call);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
auth_call_cancel (gpointer user_data)
|
|
{
|
|
AuthCall *call = user_data;
|
|
|
|
if (call->cancellable) {
|
|
/* we don't free call immediately. Instead we cancel the async operation
|
|
* and set cancellable to NULL. pk_call_cb() will check for this and
|
|
* do the final cleanup. */
|
|
g_cancellable_cancel (call->cancellable);
|
|
g_clear_object (&call->cancellable);
|
|
} else {
|
|
g_source_remove (call->call_idle_id);
|
|
auth_call_free (call);
|
|
}
|
|
}
|
|
|
|
#if WITH_POLKIT
|
|
static void
|
|
pk_call_cb (GObject *object, GAsyncResult *result, gpointer user_data)
|
|
{
|
|
AuthCall *call = user_data;
|
|
GError *error = NULL;
|
|
gboolean is_authorized, is_challenge;
|
|
|
|
nm_auth_manager_polkit_authority_check_authorization_finish (NM_AUTH_MANAGER (object),
|
|
result,
|
|
&is_authorized,
|
|
&is_challenge,
|
|
&error);
|
|
|
|
/* If the call is already canceled do nothing */
|
|
if (!call->cancellable) {
|
|
nm_log_dbg (LOGD_CORE, "callback already cancelled");
|
|
g_clear_error (&error);
|
|
auth_call_free (call);
|
|
return;
|
|
}
|
|
|
|
if (error) {
|
|
nm_log_warn (LOGD_CORE, "error requesting auth for %s: %s",
|
|
call->permission, error->message);
|
|
|
|
if (!call->chain->error) {
|
|
call->chain->error = error;
|
|
error = NULL;
|
|
} else
|
|
g_clear_error (&error);
|
|
} else {
|
|
guint call_result = NM_AUTH_CALL_RESULT_UNKNOWN;
|
|
|
|
if (is_authorized) {
|
|
/* Caller has the permission */
|
|
call_result = NM_AUTH_CALL_RESULT_YES;
|
|
} else if (is_challenge) {
|
|
/* Caller could authenticate to get the permission */
|
|
call_result = NM_AUTH_CALL_RESULT_AUTH;
|
|
} else
|
|
call_result = NM_AUTH_CALL_RESULT_NO;
|
|
|
|
nm_auth_chain_set_data (call->chain, call->permission, GUINT_TO_POINTER (call_result), NULL);
|
|
}
|
|
|
|
auth_call_complete (call);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
nm_auth_chain_add_call (NMAuthChain *self,
|
|
const char *permission,
|
|
gboolean allow_interaction)
|
|
{
|
|
AuthCall *call;
|
|
NMAuthManager *auth_manager = nm_auth_manager_get ();
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (permission && *permission);
|
|
g_return_if_fail (self->subject);
|
|
g_return_if_fail (nm_auth_subject_is_unix_process (self->subject) || nm_auth_subject_is_internal (self->subject));
|
|
g_return_if_fail (!self->idle_id && !self->done);
|
|
|
|
call = auth_call_new (self, permission);
|
|
self->calls = g_slist_append (self->calls, call);
|
|
|
|
if ( nm_auth_subject_is_internal (self->subject)
|
|
|| nm_auth_subject_get_unix_process_uid (self->subject) == 0
|
|
|| !nm_auth_manager_get_polkit_enabled (auth_manager)) {
|
|
/* Root user or non-polkit always gets the permission */
|
|
nm_auth_chain_set_data (self, permission, GUINT_TO_POINTER (NM_AUTH_CALL_RESULT_YES), NULL);
|
|
call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
|
|
} else {
|
|
/* Non-root always gets authenticated when using polkit */
|
|
#if WITH_POLKIT
|
|
call->cancellable = g_cancellable_new ();
|
|
nm_auth_manager_polkit_authority_check_authorization (auth_manager,
|
|
self->subject,
|
|
permission,
|
|
allow_interaction,
|
|
call->cancellable,
|
|
pk_call_cb,
|
|
call);
|
|
#else
|
|
if (!call->chain->error) {
|
|
call->chain->error = g_error_new_literal (DBUS_GERROR,
|
|
DBUS_GERROR_FAILED,
|
|
"Polkit support is disabled at compile time");
|
|
}
|
|
call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nm_auth_chain_unref:
|
|
* @self: the auth-chain
|
|
*
|
|
* Unrefs the auth-chain. By unrefing the auth-chain, you also cancel
|
|
* the receipt of the done-callback. IOW, the callback will not be invoked.
|
|
*
|
|
* The only exception is, if you call nm_auth_chain_unref() from inside
|
|
* the callback. In this case, @self stays alive until the callback returns.
|
|
*/
|
|
void
|
|
nm_auth_chain_unref (NMAuthChain *self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (self->refcount > 0);
|
|
|
|
self->refcount--;
|
|
if (self->refcount > 0)
|
|
return;
|
|
|
|
if (self->idle_id)
|
|
g_source_remove (self->idle_id);
|
|
|
|
g_object_unref (self->subject);
|
|
|
|
if (self->context)
|
|
g_object_unref (self->context);
|
|
|
|
g_slist_free_full (self->calls, auth_call_cancel);
|
|
|
|
g_clear_error (&self->error);
|
|
g_hash_table_destroy (self->data);
|
|
|
|
memset (self, 0, sizeof (NMAuthChain));
|
|
g_slice_free (NMAuthChain, self);
|
|
}
|
|
|
|
/************ utils **************/
|
|
|
|
gboolean
|
|
nm_auth_is_subject_in_acl (NMConnection *connection,
|
|
NMAuthSubject *subject,
|
|
char **out_error_desc)
|
|
{
|
|
NMSettingConnection *s_con;
|
|
const char *user = NULL;
|
|
gulong uid;
|
|
|
|
g_return_val_if_fail (connection != NULL, FALSE);
|
|
g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE);
|
|
g_return_val_if_fail (nm_auth_subject_is_internal (subject) || nm_auth_subject_is_unix_process (subject), FALSE);
|
|
|
|
if (nm_auth_subject_is_internal (subject))
|
|
return TRUE;
|
|
|
|
uid = nm_auth_subject_get_unix_process_uid (subject);
|
|
|
|
/* Root gets a free pass */
|
|
if (0 == uid)
|
|
return TRUE;
|
|
|
|
if (!nm_session_monitor_uid_to_user (uid, &user)) {
|
|
if (out_error_desc)
|
|
*out_error_desc = g_strdup_printf ("Could not determine username for uid %lu", uid);
|
|
return FALSE;
|
|
}
|
|
|
|
s_con = nm_connection_get_setting_connection (connection);
|
|
if (!s_con) {
|
|
/* This can only happen when called from AddAndActivate, so we know
|
|
* the user will be authorized when the connection is completed.
|
|
*/
|
|
return TRUE;
|
|
}
|
|
|
|
/* Match the username returned by the session check to a user in the ACL */
|
|
if (!nm_setting_connection_permissions_user_allowed (s_con, user)) {
|
|
if (out_error_desc)
|
|
*out_error_desc = g_strdup_printf ("uid %lu has no permission to perform this operation", uid);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|