Files
NetworkManager/libnm-glib/nm-settings.c
Dan Williams 63f2c0bfbe system-settings: protect system connection secrets with PolicyKit
So that normal users who have PolicyKit authorization to edit system connections
can read secrets, move system connection secrets logic into the system connection
service from libnm-glib, and protect it with PolicyKit checks.  Convert the
ifcfg-rh plugin over to using NMSysconfigConnection so that it can take advantage
of the new PolicyKit protection.
2009-04-04 11:42:48 -04:00

608 lines
18 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* libnm_glib -- Access network status & information from glib applications
*
* 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.
*
* Copyright (C) 2007 - 2008 Novell, Inc.
* Copyright (C) 2007 - 2008 Red Hat, Inc.
*/
#include <NetworkManager.h>
#include <nm-utils.h>
#include <nm-setting-connection.h>
#include "nm-settings.h"
#include "nm-dbus-glib-types.h"
#define NM_TYPE_SETTINGS_ERROR (nm_settings_error_get_type ())
/**
* nm_settings_error_quark:
*
* Setting error quark.
*
* Returns: the setting error quark
**/
GQuark
nm_settings_error_quark (void)
{
static GQuark quark;
if (G_UNLIKELY (!quark))
quark = g_quark_from_static_string ("nm-settings-error-quark");
return quark;
}
/* This should really be standard. */
#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
static GType
nm_settings_error_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
/* The connection was invalid. */
ENUM_ENTRY (NM_SETTINGS_ERROR_INVALID_CONNECTION, "InvalidConnection"),
/* The connection is read-only; modifications are not allowed. */
ENUM_ENTRY (NM_SETTINGS_ERROR_READ_ONLY_CONNECTION, "ReadOnlyConnection"),
/* A bug in the settings service caused the error. */
ENUM_ENTRY (NM_SETTINGS_ERROR_INTERNAL_ERROR, "InternalError"),
/* Retrieval or request of secrets failed. */
ENUM_ENTRY (NM_SETTINGS_ERROR_SECRETS_UNAVAILABLE, "SecretsUnavailable"),
/* The request for secrets was canceled. */
ENUM_ENTRY (NM_SETTINGS_ERROR_SECRETS_REQUEST_CANCELED, "SecretsRequestCanceled"),
/* The request could not be completed because permission was denied. */
ENUM_ENTRY (NM_SETTINGS_ERROR_PERMISSION_DENIED, "PermissionDenied"),
{ 0, 0, 0 },
};
etype = g_enum_register_static ("NMSettingsError", values);
}
return etype;
}
/*
* NMSettings implementation
*/
static gboolean impl_settings_list_connections (NMSettings *settings, GPtrArray **connections, GError **error);
#include "nm-settings-glue.h"
#define SETTINGS_CLASS(o) (NM_SETTINGS_CLASS (G_OBJECT_GET_CLASS (o)))
G_DEFINE_TYPE (NMSettings, nm_settings, G_TYPE_OBJECT)
enum {
S_NEW_CONNECTION,
S_LAST_SIGNAL
};
static guint settings_signals[S_LAST_SIGNAL] = { 0 };
static gboolean
impl_settings_list_connections (NMSettings *settings, GPtrArray **connections, GError **error)
{
GSList *list, *iter;
g_return_val_if_fail (NM_IS_SETTINGS (settings), FALSE);
list = nm_settings_list_connections (settings);
*connections = g_ptr_array_new ();
for (iter = list; iter; iter = iter->next) {
NMConnection *connection = nm_exported_connection_get_connection (NM_EXPORTED_CONNECTION (iter->data));
g_ptr_array_add (*connections, g_strdup (nm_connection_get_path (connection)));
}
g_slist_free (list);
return TRUE;
}
static void
nm_settings_init (NMSettings *settings)
{
}
static void
nm_settings_finalize (GObject *object)
{
G_OBJECT_CLASS (nm_settings_parent_class)->finalize (object);
}
static void
nm_settings_class_init (NMSettingsClass *settings_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (settings_class);
/* virtual methods */
object_class->finalize = nm_settings_finalize;
settings_class->list_connections = NULL;
/* signals */
/**
* NMSettings::new-connection:
* @setting: the setting that received the signal
* @connection: the new #NMExportedConnection
*
* Notifies that a new exported connection is added.
**/
settings_signals[S_NEW_CONNECTION] =
g_signal_new ("new-connection",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMSettingsClass, new_connection),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
G_TYPE_OBJECT);
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (settings_class),
&dbus_glib_nm_settings_object_info);
dbus_g_error_domain_register (NM_SETTINGS_ERROR, NULL, NM_TYPE_SETTINGS_ERROR);
}
/**
* nm_settings_list_connections:
* @settings:
*
* Lists all the available connections.
*
* Returns: the #GSList containing #NMExportedConnection<!-- -->s
**/
GSList *
nm_settings_list_connections (NMSettings *settings)
{
GSList *list = NULL;
g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
if (SETTINGS_CLASS (settings)->list_connections)
list = SETTINGS_CLASS (settings)->list_connections (settings);
else
g_warning ("Missing implementation for Settings::list_connections.");
return list;
}
void
nm_settings_signal_new_connection (NMSettings *settings, NMExportedConnection *connection)
{
g_return_if_fail (NM_IS_SETTINGS (settings));
g_return_if_fail (NM_IS_EXPORTED_CONNECTION (connection));
g_signal_emit (settings, settings_signals[S_NEW_CONNECTION], 0, connection);
}
/*
* NMExportedConnection implementation
*/
static gboolean impl_exported_connection_get_settings (NMExportedConnection *connection,
GHashTable **settings,
GError **error);
static gboolean impl_exported_connection_update (NMExportedConnection *connection,
GHashTable *new_settings,
DBusGMethodInvocation *context);
static gboolean impl_exported_connection_delete (NMExportedConnection *connection,
DBusGMethodInvocation *context);
static void impl_exported_connection_get_secrets (NMExportedConnection *connection,
const gchar *setting_name,
const gchar **hints,
gboolean request_new,
DBusGMethodInvocation *context);
#include "nm-exported-connection-glue.h"
#define EXPORTED_CONNECTION_CLASS(o) (NM_EXPORTED_CONNECTION_CLASS (G_OBJECT_GET_CLASS (o)))
G_DEFINE_TYPE (NMExportedConnection, nm_exported_connection, G_TYPE_OBJECT)
enum {
EC_UPDATED,
EC_REMOVED,
EC_LAST_SIGNAL
};
static guint connection_signals[EC_LAST_SIGNAL] = { 0 };
enum {
PROP_0,
PROP_CONNECTION,
LAST_PROP
};
typedef struct {
NMConnection *wrapped;
} NMExportedConnectionPrivate;
#define NM_EXPORTED_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
NM_TYPE_EXPORTED_CONNECTION, \
NMExportedConnectionPrivate))
NMExportedConnection *
nm_exported_connection_new (NMConnection *wrapped)
{
g_return_val_if_fail (NM_IS_CONNECTION (wrapped), NULL);
return (NMExportedConnection *) g_object_new (NM_TYPE_EXPORTED_CONNECTION,
NM_EXPORTED_CONNECTION_CONNECTION, wrapped,
NULL);
}
static GHashTable *
real_get_settings (NMExportedConnection *exported)
{
NMExportedConnectionPrivate *priv;
NMConnection *no_secrets;
GHashTable *hash;
g_return_val_if_fail (NM_IS_EXPORTED_CONNECTION (exported), NULL);
priv = NM_EXPORTED_CONNECTION_GET_PRIVATE (exported);
/* Secrets should *never* be returned by the GetSettings method, they
* get returned by the GetSecrets method which can be better
* protected against leakage of secrets to unprivileged callers.
*/
no_secrets = nm_connection_duplicate (priv->wrapped);
nm_connection_clear_secrets (no_secrets);
hash = nm_connection_to_hash (no_secrets);
g_object_unref (G_OBJECT (no_secrets));
return hash;
}
static gboolean
impl_exported_connection_get_settings (NMExportedConnection *connection,
GHashTable **settings,
GError **error)
{
NMExportedConnectionPrivate *priv;
g_return_val_if_fail (NM_IS_EXPORTED_CONNECTION (connection), FALSE);
priv = NM_EXPORTED_CONNECTION_GET_PRIVATE (connection);
if (!EXPORTED_CONNECTION_CLASS (connection)->get_settings)
*settings = real_get_settings (connection);
else
*settings = EXPORTED_CONNECTION_CLASS (connection)->get_settings (connection);
return TRUE;
}
static gboolean
impl_exported_connection_update (NMExportedConnection *connection,
GHashTable *new_settings,
DBusGMethodInvocation *context)
{
GError *err = NULL;
NMConnection *wrapped;
gboolean success = FALSE;
/* Read-only connections obviously cannot be changed */
wrapped = nm_exported_connection_get_connection (connection);
if (wrapped) {
NMSettingConnection *s_con;
s_con = (NMSettingConnection *) nm_connection_get_setting (wrapped, NM_TYPE_SETTING_CONNECTION);
if (s_con && nm_setting_connection_get_read_only (s_con)) {
g_set_error (&err, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_READ_ONLY_CONNECTION,
"%s.%d - Read-only connections may not be modified.",
__FILE__, __LINE__);
}
}
if (!err) {
/* A hack to share the DBusGMethodInvocation with the possible overriders of connection::update */
g_object_set_data (G_OBJECT (connection), NM_EXPORTED_CONNECTION_DBUS_METHOD_INVOCATION, context);
success = nm_exported_connection_update (connection, new_settings, &err);
g_object_set_data (G_OBJECT (connection), NM_EXPORTED_CONNECTION_DBUS_METHOD_INVOCATION, NULL);
}
if (success) {
dbus_g_method_return (context);
} else {
dbus_g_method_return_error (context, err);
g_error_free (err);
}
return success;
}
static gboolean
impl_exported_connection_delete (NMExportedConnection *connection,
DBusGMethodInvocation *context)
{
GError *err = NULL;
NMConnection *wrapped;
gboolean success = FALSE;
/* Read-only connections obviously cannot be changed */
wrapped = nm_exported_connection_get_connection (connection);
if (wrapped) {
NMSettingConnection *s_con;
s_con = (NMSettingConnection *) nm_connection_get_setting (wrapped, NM_TYPE_SETTING_CONNECTION);
if (s_con && nm_setting_connection_get_read_only (s_con)) {
g_set_error (&err, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_READ_ONLY_CONNECTION,
"%s.%d - Read-only connections may not be deleted.",
__FILE__, __LINE__);
}
}
if (!err) {
/* A hack to share the DBusGMethodInvocation with the possible overriders of connection::delete */
g_object_set_data (G_OBJECT (connection), NM_EXPORTED_CONNECTION_DBUS_METHOD_INVOCATION, context);
success = nm_exported_connection_delete (connection, &err);
g_object_set_data (G_OBJECT (connection), NM_EXPORTED_CONNECTION_DBUS_METHOD_INVOCATION, NULL);
}
if (success) {
dbus_g_method_return (context);
} else {
dbus_g_method_return_error (context, err);
g_error_free (err);
}
return success;
}
static void
impl_exported_connection_get_secrets (NMExportedConnection *connection,
const gchar *setting_name,
const gchar **hints,
gboolean request_new,
DBusGMethodInvocation *context)
{
GError *error = NULL;
if (!NM_IS_EXPORTED_CONNECTION (connection)) {
g_set_error (&error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"%s.%d - Invalid connection in ConnectionSettings::GetSecrets.",
__FILE__, __LINE__);
dbus_g_method_return_error (context, error);
g_error_free (error);
return;
}
if (!EXPORTED_CONNECTION_CLASS (connection)->service_get_secrets) {
g_set_error (&error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_SECRETS_UNAVAILABLE,
"%s.%d - Missing implementation for ConnectionSettings::GetSecrets.",
__FILE__, __LINE__);
dbus_g_method_return_error (context, error);
g_error_free (error);
return;
}
EXPORTED_CONNECTION_CLASS (connection)->service_get_secrets (connection, setting_name, hints, request_new, context);
}
static void
nm_exported_connection_init (NMExportedConnection *connection)
{
}
static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
GObject *connection;
NMExportedConnectionPrivate *priv = NM_EXPORTED_CONNECTION_GET_PRIVATE (object);
switch (prop_id) {
case PROP_CONNECTION:
if (priv->wrapped) {
g_object_unref (priv->wrapped);
priv->wrapped = NULL;
}
connection = g_value_dup_object (value);
if (connection)
priv->wrapped = NM_CONNECTION (connection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMExportedConnection *exported = NM_EXPORTED_CONNECTION (object);
switch (prop_id) {
case PROP_CONNECTION:
g_value_set_object (value, nm_exported_connection_get_connection (exported));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
nm_exported_connection_dispose (GObject *object)
{
NMExportedConnectionPrivate *priv = NM_EXPORTED_CONNECTION_GET_PRIVATE (object);
if (priv->wrapped) {
g_object_unref (priv->wrapped);
priv->wrapped = NULL;
}
G_OBJECT_CLASS (nm_exported_connection_parent_class)->dispose (object);
}
static void
nm_exported_connection_class_init (NMExportedConnectionClass *exported_connection_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (exported_connection_class);
g_type_class_add_private (object_class, sizeof (NMExportedConnectionPrivate));
/* virtual methods */
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->dispose = nm_exported_connection_dispose;
exported_connection_class->get_settings = real_get_settings;
/* Properties */
g_object_class_install_property
(object_class, PROP_CONNECTION,
g_param_spec_object (NM_EXPORTED_CONNECTION_CONNECTION,
"Connection",
"Wrapped connection",
NM_TYPE_CONNECTION,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/* signals */
connection_signals[EC_UPDATED] =
g_signal_new ("updated",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMExportedConnectionClass, updated),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT);
connection_signals[EC_REMOVED] =
g_signal_new ("removed",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMExportedConnectionClass, removed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (exported_connection_class),
&dbus_glib_nm_exported_connection_object_info);
}
NMConnection *
nm_exported_connection_get_connection (NMExportedConnection *connection)
{
g_return_val_if_fail (NM_IS_EXPORTED_CONNECTION (connection), NULL);
return NM_EXPORTED_CONNECTION_GET_PRIVATE (connection)->wrapped;
}
void
nm_exported_connection_register_object (NMExportedConnection *connection,
NMConnectionScope scope,
DBusGConnection *dbus_connection)
{
NMExportedConnectionPrivate *priv;
static guint32 ec_counter = 0;
char *path;
g_return_if_fail (NM_IS_EXPORTED_CONNECTION (connection));
g_return_if_fail (dbus_connection != NULL);
priv = NM_EXPORTED_CONNECTION_GET_PRIVATE (connection);
/* Don't allow the connection to be exported twice */
g_return_if_fail (nm_connection_get_path (priv->wrapped) == NULL);
path = g_strdup_printf ("%s/%u", NM_DBUS_PATH_SETTINGS, ec_counter++);
nm_connection_set_path (priv->wrapped, path);
nm_connection_set_scope (priv->wrapped, scope);
dbus_g_connection_register_g_object (dbus_connection,
path,
G_OBJECT (connection));
g_free (path);
}
gboolean
nm_exported_connection_update (NMExportedConnection *connection,
GHashTable *new_settings,
GError **err)
{
gboolean success = TRUE;
GError *error = NULL;
g_return_val_if_fail (NM_IS_EXPORTED_CONNECTION (connection), FALSE);
g_return_val_if_fail (new_settings != NULL, FALSE);
if (EXPORTED_CONNECTION_CLASS (connection)->update)
success = EXPORTED_CONNECTION_CLASS (connection)->update (connection, new_settings, err);
if (success) {
if (!nm_connection_replace_settings (NM_EXPORTED_CONNECTION_GET_PRIVATE (connection)->wrapped, new_settings, &error)) {
g_warning ("%s: '%s' / '%s' invalid: %d",
__func__,
error ? g_type_name (nm_connection_lookup_setting_type_by_quark (error->domain)) : "(none)",
(error && error->message) ? error->message : "(none)",
error ? error->code : -1);
g_clear_error (&error);
success = FALSE;
} else
nm_exported_connection_signal_updated (connection, new_settings);
}
return success;
}
gboolean
nm_exported_connection_delete (NMExportedConnection *connection, GError **err)
{
gboolean success = TRUE;
g_return_val_if_fail (NM_IS_EXPORTED_CONNECTION (connection), FALSE);
if (EXPORTED_CONNECTION_CLASS (connection)->do_delete)
success = EXPORTED_CONNECTION_CLASS (connection)->do_delete (connection, err);
if (success)
nm_exported_connection_signal_removed (connection);
return success;
}
void
nm_exported_connection_signal_updated (NMExportedConnection *connection, GHashTable *settings)
{
g_return_if_fail (NM_IS_EXPORTED_CONNECTION (connection));
g_signal_emit (connection, connection_signals[EC_UPDATED], 0, settings);
}
void
nm_exported_connection_signal_removed (NMExportedConnection *connection)
{
g_return_if_fail (NM_IS_EXPORTED_CONNECTION (connection));
g_signal_emit (connection, connection_signals[EC_REMOVED], 0);
}