NetworkManager/libnm-glib/nm-secret-agent.c

640 lines
20 KiB
C
Raw Normal View History

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* 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.
*
* (C) Copyright 2010 Red Hat, Inc.
*/
#include <config.h>
#include <ctype.h>
#include <string.h>
#include <NetworkManager.h>
#include <dbus/dbus-glib-lowlevel.h>
#include "nm-secret-agent.h"
#include "nm-marshal.h"
#include "NetworkManager.h"
static void impl_secret_agent_get_secrets (NMSecretAgent *self,
GHashTable *connection_hash,
const char *connection_path,
const char *setting_name,
const char **hints,
gboolean request_new,
DBusGMethodInvocation *context);
static void impl_secret_agent_save_secrets (NMSecretAgent *self,
GHashTable *connection_hash,
const char *connection_path,
DBusGMethodInvocation *context);
static void impl_secret_agent_delete_secrets (NMSecretAgent *self,
GHashTable *connection_hash,
const char *connection_path,
DBusGMethodInvocation *context);
#include "nm-secret-agent-glue.h"
G_DEFINE_TYPE (NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT)
#define NM_SECRET_AGENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
NM_TYPE_SECRET_AGENT, \
NMSecretAgentPrivate))
typedef struct {
gboolean registered;
DBusGConnection *bus;
DBusGProxy *dbus_proxy;
DBusGProxy *manager_proxy;
DBusGProxyCall *reg_call;
char *nm_owner;
char *identifier;
gboolean disposed;
} NMSecretAgentPrivate;
enum {
PROP_0,
PROP_IDENTIFIER,
LAST_PROP
};
enum {
REGISTRATION_RESULT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/********************************************************************/
GQuark
nm_secret_agent_error_quark (void)
{
static GQuark ret = 0;
if (G_UNLIKELY (ret == 0))
ret = g_quark_from_static_string ("nm-secret-agent-error");
return ret;
}
#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
GType
nm_secret_agent_error_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
/* Sender is not authorized to make this request */
ENUM_ENTRY (NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED, "NotAuthorized"),
/* Given connection details do not make a valid connection */
ENUM_ENTRY (NM_SECRET_AGENT_ERROR_INVALID_CONNECTION, "InvalidConnection"),
/* The request was canceled explicitly by the user */
ENUM_ENTRY (NM_SECRET_AGENT_ERROR_USER_CANCELED, "UserCanceled"),
/* The request was canceled, but not by the user */
ENUM_ENTRY (NM_SECRET_AGENT_ERROR_AGENT_CANCELED, "AgentCanceled"),
{ 0, 0, 0 }
};
etype = g_enum_register_static ("NMSecretAgentError", values);
}
return etype;
}
/*************************************************************/
static const char *
get_nm_owner (NMSecretAgent *self)
{
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
GError *error = NULL;
char *owner;
if (!priv->nm_owner) {
if (!dbus_g_proxy_call_with_timeout (priv->dbus_proxy,
"GetNameOwner", 2000, &error,
G_TYPE_STRING, NM_DBUS_SERVICE,
G_TYPE_INVALID,
G_TYPE_STRING, &owner,
G_TYPE_INVALID))
return NULL;
priv->nm_owner = g_strdup (owner);
g_free (owner);
}
return priv->nm_owner;
}
static void
name_owner_changed (DBusGProxy *proxy,
const char *name,
const char *old_owner,
const char *new_owner,
gpointer user_data)
{
NMSecretAgent *self = NM_SECRET_AGENT (user_data);
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
if (strcmp (name, NM_DBUS_SERVICE) == 0) {
g_free (priv->nm_owner);
priv->nm_owner = g_strdup (new_owner);
}
}
static NMConnection *
verify_request (NMSecretAgent *self,
DBusGMethodInvocation *context,
GHashTable *connection_hash,
GError **error)
{
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
NMConnection *connection = NULL;
DBusConnection *bus;
char *sender;
const char *nm_owner;
DBusError dbus_error;
uid_t sender_uid = G_MAXUINT;
GError *local = NULL;
g_return_val_if_fail (context != NULL, NULL);
g_return_val_if_fail (connection_hash != NULL, NULL);
/* Verify the sender's UID is 0, and that the sender is the same as
* NetworkManager's bus name owner.
*/
nm_owner = get_nm_owner (self);
if (!nm_owner) {
g_set_error_literal (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED,
"NetworkManager bus name owner unknown.");
return FALSE;
}
bus = dbus_g_connection_get_connection (priv->bus);
if (!bus) {
g_set_error_literal (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED,
"Failed to get DBus connection.");
return FALSE;
}
sender = dbus_g_method_get_sender (context);
if (!sender) {
g_set_error_literal (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED,
"Failed to get request sender.");
return FALSE;
}
/* Check that the sender matches the current NM bus name owner */
if (strcmp (sender, nm_owner) != 0) {
g_set_error_literal (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED,
"Request sender does not match NetworkManager bus name owner.");
goto out;
}
dbus_error_init (&dbus_error);
sender_uid = dbus_bus_get_unix_user (bus, sender, &dbus_error);
if (dbus_error_is_set (&dbus_error)) {
g_set_error (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED,
"Failed to get request unix user: (%s) %s.",
dbus_error.name, dbus_error.message);
dbus_error_free (&dbus_error);
goto out;
}
if (0 != sender_uid) {
g_set_error_literal (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_NOT_AUTHORIZED,
"Request sender is not root.");
goto out;
}
/* And make sure the connection is actually valid */
connection = nm_connection_new_from_hash (connection_hash, &local);
if (!connection) {
g_set_error (error,
NM_SECRET_AGENT_ERROR,
NM_SECRET_AGENT_ERROR_INVALID_CONNECTION,
"Invalid connection: (%d) %s",
local ? local->code : -1,
(local && local->message) ? local->message : "(unknown)");
g_clear_error (&local);
}
out:
g_free (sender);
return connection;
}
static void
impl_secret_agent_get_secrets (NMSecretAgent *self,
GHashTable *connection_hash,
const char *connection_path,
const char *setting_name,
const char **hints,
gboolean request_new,
DBusGMethodInvocation *context)
{
GError *error = NULL;
NMConnection *connection;
/* Make sure the request comes from NetworkManager and is valid */
connection = verify_request (self, context, connection_hash, &error);
if (!connection) {
dbus_g_method_return_error (context, error);
g_clear_error (&error);
return;
}
NM_SECRET_AGENT_GET_CLASS (self)->get_secrets (self,
connection,
connection_path,
setting_name,
hints,
request_new,
context);
g_object_unref (connection);
}
static void
impl_secret_agent_save_secrets (NMSecretAgent *self,
GHashTable *connection_hash,
const char *connection_path,
DBusGMethodInvocation *context)
{
GError *error = NULL;
NMConnection *connection;
/* Make sure the request comes from NetworkManager and is valid */
connection = verify_request (self, context, connection_hash, &error);
if (!connection) {
dbus_g_method_return_error (context, error);
g_clear_error (&error);
return;
}
NM_SECRET_AGENT_GET_CLASS (self)->save_secrets (self,
connection,
connection_path,
context);
g_object_unref (connection);
}
static void
impl_secret_agent_delete_secrets (NMSecretAgent *self,
GHashTable *connection_hash,
const char *connection_path,
DBusGMethodInvocation *context)
{
GError *error = NULL;
NMConnection *connection;
/* Make sure the request comes from NetworkManager and is valid */
connection = verify_request (self, context, connection_hash, &error);
if (!connection) {
dbus_g_method_return_error (context, error);
g_clear_error (&error);
return;
}
NM_SECRET_AGENT_GET_CLASS (self)->delete_secrets (self,
connection,
connection_path,
context);
g_object_unref (connection);
}
/**************************************************************/
static void
reg_request_cb (DBusGProxy *proxy,
DBusGProxyCall *call,
gpointer user_data)
{
NMSecretAgent *self = NM_SECRET_AGENT (user_data);
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
GError *error = NULL;
priv->reg_call = NULL;
if (dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID))
priv->registered = TRUE;
else {
/* If registration failed we shouldn't expose ourselves on the bus */
dbus_g_connection_unregister_g_object (priv->bus, G_OBJECT (self));
}
g_signal_emit (self, signals[REGISTRATION_RESULT], 0, error);
g_clear_error (&error);
}
/**
* nm_secret_agent_register:
*
* Registers the #NMSecretAgent with the NetworkManager secret manager,
* indicating to NetworkManager that the agent is able to provide and save
* secrets for connections on behalf of its user. Registration is an
* asynchronous operation and its success or failure is indicated via the
* 'registration-result' signal.
*
* Returns: a new %TRUE if registration was successfully requested (this does
* not mean registration itself was successful), %FALSE if registration was not
* successfully requested.
**/
gboolean
nm_secret_agent_register (NMSecretAgent *self)
{
NMSecretAgentPrivate *priv;
NMSecretAgentClass *class;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (NM_IS_SECRET_AGENT (self), FALSE);
priv = NM_SECRET_AGENT_GET_PRIVATE (self);
g_return_val_if_fail (priv->registered == FALSE, FALSE);
g_return_val_if_fail (priv->reg_call == NULL, FALSE);
g_return_val_if_fail (priv->bus != NULL, FALSE);
g_return_val_if_fail (priv->manager_proxy != NULL, FALSE);
/* Also make sure the subclass can actually respond to secrets requests */
class = NM_SECRET_AGENT_GET_CLASS (self);
g_return_val_if_fail (class->get_secrets != NULL, FALSE);
g_return_val_if_fail (class->save_secrets != NULL, FALSE);
g_return_val_if_fail (class->delete_secrets != NULL, FALSE);
/* Export our secret agent interface before registering with the manager */
dbus_g_connection_register_g_object (priv->bus,
NM_DBUS_PATH_SECRET_AGENT,
G_OBJECT (self));
priv->reg_call = dbus_g_proxy_begin_call_with_timeout (priv->manager_proxy,
"Register",
reg_request_cb,
self,
NULL,
G_USEC_PER_SEC * 5,
G_TYPE_STRING, priv->identifier,
G_TYPE_INVALID);
return TRUE;
}
/**
* nm_secret_agent_unregister:
*
* Unregisters the #NMSecretAgent with the NetworkManager secret manager,
* indicating to NetworkManager that the agent is will no longer provide or
* store secrets on behalf of this user.
*
* Returns: a new %TRUE if unregistration was successful, %FALSE if it was not.
**/
gboolean
nm_secret_agent_unregister (NMSecretAgent *self)
{
NMSecretAgentPrivate *priv;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (NM_IS_SECRET_AGENT (self), FALSE);
priv = NM_SECRET_AGENT_GET_PRIVATE (self);
g_return_val_if_fail (priv->registered == TRUE, FALSE);
g_return_val_if_fail (priv->bus != NULL, FALSE);
g_return_val_if_fail (priv->manager_proxy != NULL, FALSE);
dbus_g_proxy_call_no_reply (priv->manager_proxy, "Unregister", G_TYPE_INVALID);
return TRUE;
}
/**************************************************************/
static gboolean
validate_identifier (const char *identifier)
{
const char *p = identifier;
size_t id_len;
/* Length between 3 and 255 characters inclusive */
id_len = strlen (identifier);
if (id_len < 3 || id_len > 255)
return FALSE;
if ((identifier[0] == '.') || (identifier[id_len - 1] == '.'))
return FALSE;
/* FIXME: do complete validation here */
while (p && *p) {
if (!isalnum (*p) && (*p != '_') && (*p != '-') && (*p != '.'))
return FALSE;
if ((*p == '.') && (*(p + 1) == '.'))
return FALSE;
p++;
}
return TRUE;
}
static void
nm_secret_agent_init (NMSecretAgent *self)
{
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
GError *error = NULL;
priv->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
if (!priv->bus) {
g_warning ("Couldn't connect to system bus: %s", error->message);
g_error_free (error);
return;
}
priv->dbus_proxy = dbus_g_proxy_new_for_name (priv->bus,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS);
if (!priv->dbus_proxy) {
g_warning ("Couldn't create messagebus proxy.");
return;
}
dbus_g_object_register_marshaller (_nm_marshal_VOID__STRING_STRING_STRING,
G_TYPE_NONE,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_INVALID);
dbus_g_proxy_add_signal (priv->dbus_proxy, "NameOwnerChanged",
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_INVALID);
dbus_g_proxy_connect_signal (priv->dbus_proxy,
"NameOwnerChanged",
G_CALLBACK (name_owner_changed),
self, NULL);
priv->manager_proxy = dbus_g_proxy_new_for_name (priv->bus,
NM_DBUS_SERVICE,
NM_DBUS_PATH_AGENT_MANAGER,
NM_DBUS_INTERFACE_AGENT_MANAGER);
if (!priv->manager_proxy) {
g_warning ("Couldn't create NM agent manager proxy.");
return;
}
}
static void
get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (object);
switch (prop_id) {
case PROP_IDENTIFIER:
g_value_set_string (value, priv->identifier);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (object);
const char *identifier;
switch (prop_id) {
case PROP_IDENTIFIER:
identifier = g_value_get_string (value);
g_return_if_fail (validate_identifier (identifier));
g_free (priv->identifier);
priv->identifier = g_strdup (identifier);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
dispose (GObject *object)
{
NMSecretAgent *self = NM_SECRET_AGENT (object);
NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
if (!priv->disposed) {
priv->disposed = TRUE;
if (priv->registered)
nm_secret_agent_unregister (self);
g_free (priv->identifier);
g_free (priv->nm_owner);
if (priv->dbus_proxy)
g_object_unref (priv->dbus_proxy);
if (priv->manager_proxy)
g_object_unref (priv->manager_proxy);
if (priv->bus)
dbus_g_connection_unref (priv->bus);
}
G_OBJECT_CLASS (nm_secret_agent_parent_class)->dispose (object);
}
static void
nm_secret_agent_class_init (NMSecretAgentClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
g_type_class_add_private (class, sizeof (NMSecretAgentPrivate));
/* Virtual methods */
object_class->dispose = dispose;
object_class->get_property = get_property;
object_class->set_property = set_property;
/**
* NMSecretAgent:identifier:
*
* Identifies this agent; only one agent in each user session may use the
* same identifier. Identifier formatting follows the same rules as
* D-Bus bus names with the exception that the ':' character is not
* allowed. The valid set of characters is "[A-Z][a-z][0-9]_-." and the
* identifier is limited in length to 255 characters with a minimum
* of 3 characters. An example valid identifier is 'org.gnome.nm-applet'
* (without quotes).
**/
g_object_class_install_property
(object_class, PROP_IDENTIFIER,
g_param_spec_string (NM_SECRET_AGENT_IDENTIFIER,
"Identifier",
"Identifier",
NULL,
G_PARAM_READABLE | G_PARAM_CONSTRUCT_ONLY));
/**
* NMSecretAgent::registration-result:
* @agent: the agent that received the signal
* @error: the error, if any, that occured while registering
*
* Indicates the result of a registration request; if @error is NULL the
* request was successful.
**/
signals[REGISTRATION_RESULT] =
g_signal_new (REGISTRATION_RESULT,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1, G_TYPE_POINTER);
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (class),
&dbus_glib_nm_secret_agent_object_info);
dbus_g_error_domain_register (NM_SECRET_AGENT_ERROR,
NM_DBUS_INTERFACE_SECRET_AGENT,
NM_TYPE_SECRET_AGENT_ERROR);
}