Commit b3ca1e56 authored by Dan Williams's avatar Dan Williams

Merge remote-tracking branch 'origin/agent-secrets'

parents 0fe8c80f 66281b4e
......@@ -69,7 +69,7 @@ libnm_util_la_LIBADD = $(GLIB_LIBS) $(DBUS_LIBS) $(UUID_LIBS)
SYMBOL_VIS_FILE=$(srcdir)/libnm-util.ver
libnm_util_la_LDFLAGS = -Wl,--version-script=$(SYMBOL_VIS_FILE) \
-version-info "2:0:0"
-version-info "3:0:1"
if WITH_GNUTLS
libnm_util_la_SOURCES += crypto_gnutls.c
......
......@@ -2,6 +2,7 @@
global:
nm_connection_add_setting;
nm_connection_clear_secrets;
nm_connection_clear_secrets_with_flags;
nm_connection_compare;
nm_connection_create_setting;
nm_connection_diff;
......@@ -174,6 +175,7 @@ global:
nm_setting_cdma_get_username;
nm_setting_cdma_new;
nm_setting_clear_secrets;
nm_setting_clear_secrets_with_flags;
nm_setting_compare;
nm_setting_connection_add_permission;
nm_setting_connection_error_get_type;
......
......@@ -127,7 +127,7 @@ enum {
enum {
SECRETS_UPDATED,
SECRETS_CLEARED,
LAST_SIGNAL
};
......@@ -950,12 +950,6 @@ nm_connection_need_secrets (NMConnection *connection,
return name;
}
static void
clear_setting_secrets (gpointer key, gpointer data, gpointer user_data)
{
nm_setting_clear_secrets (NM_SETTING (data));
}
/**
* nm_connection_clear_secrets:
* @connection: the #NMConnection
......@@ -966,12 +960,42 @@ clear_setting_secrets (gpointer key, gpointer data, gpointer user_data)
void
nm_connection_clear_secrets (NMConnection *connection)
{
NMConnectionPrivate *priv;
GHashTableIter iter;
NMSetting *setting;
g_return_if_fail (NM_IS_CONNECTION (connection));
priv = NM_CONNECTION_GET_PRIVATE (connection);
g_hash_table_foreach (priv->settings, clear_setting_secrets, NULL);
g_hash_table_iter_init (&iter, NM_CONNECTION_GET_PRIVATE (connection)->settings);
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &setting))
nm_setting_clear_secrets (setting);
g_signal_emit (connection, signals[SECRETS_CLEARED], 0);
}
/**
* nm_connection_clear_secrets_with_flags:
* @connection: the #NMConnection
* @func: function to be called to determine whether a specific secret should be
* cleared or not
* @user_data: caller-supplied data passed to @func
*
* Clears and frees secrets determined by @func.
**/
void
nm_connection_clear_secrets_with_flags (NMConnection *connection,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
GHashTableIter iter;
NMSetting *setting;
g_return_if_fail (NM_IS_CONNECTION (connection));
g_hash_table_iter_init (&iter, NM_CONNECTION_GET_PRIVATE (connection)->settings);
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &setting))
nm_setting_clear_secrets_with_flags (setting, func, user_data);
g_signal_emit (connection, signals[SECRETS_CLEARED], 0);
}
/**
......@@ -1609,5 +1633,20 @@ nm_connection_class_init (NMConnectionClass *klass)
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
/**
* NMConnection::secrets-cleared:
* @connection: the object on which the signal is emitted
*
* The ::secrets-cleared signal is emitted when the secrets of a connection
* are cleared.
*/
signals[SECRETS_CLEARED] =
g_signal_new ("secrets-cleared",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
......@@ -147,6 +147,10 @@ const char * nm_connection_need_secrets (NMConnection *connection,
void nm_connection_clear_secrets (NMConnection *connection);
void nm_connection_clear_secrets_with_flags (NMConnection *connection,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data);
gboolean nm_connection_update_secrets (NMConnection *connection,
const char *setting_name,
GHashTable *secrets,
......
......@@ -611,6 +611,30 @@ compare_property (NMSetting *setting,
return same;
}
static void
clear_secrets_with_flags (NMSetting *setting,
GParamSpec *pspec,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
NMSettingVPNPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
GHashTableIter iter;
const char *secret;
if (priv->secrets == NULL)
return;
/* Iterate through secrets hash and check each entry */
g_hash_table_iter_init (&iter, priv->secrets);
while (g_hash_table_iter_next (&iter, (gpointer) &secret, NULL)) {
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
nm_setting_get_secret_flags (setting, secret, &flags, NULL);
if (func (setting, pspec->name, flags, user_data) == TRUE)
g_hash_table_iter_remove (&iter);
}
}
static void
destroy_one_secret (gpointer data)
{
......@@ -733,6 +757,7 @@ nm_setting_vpn_class_init (NMSettingVPNClass *setting_class)
parent_class->set_secret_flags = set_secret_flags;
parent_class->need_secrets = need_secrets;
parent_class->compare_property = compare_property;
parent_class->clear_secrets_with_flags = clear_secrets_with_flags;
/* Properties */
/**
......
......@@ -648,6 +648,60 @@ nm_setting_clear_secrets (NMSetting *setting)
g_free (property_specs);
}
static void
clear_secrets_with_flags (NMSetting *setting,
GParamSpec *pspec,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
GValue value = { 0 };
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
/* Clear the secret if the user function says to do so */
nm_setting_get_secret_flags (setting, pspec->name, &flags, NULL);
if (func (setting, pspec->name, flags, user_data) == TRUE) {
g_value_init (&value, pspec->value_type);
g_param_value_set_default (pspec, &value);
g_object_set_property (G_OBJECT (setting), pspec->name, &value);
g_value_unset (&value);
}
}
/**
* nm_setting_clear_secrets_with_flags:
* @setting: the #NMSetting
* @func: function to be called to determine whether a specific secret should be
* cleared or not
* @user_data: caller-supplied data passed to @func
*
* Clears and frees secrets determined by @func.
**/
void
nm_setting_clear_secrets_with_flags (NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
GParamSpec **property_specs;
guint n_property_specs;
guint i;
g_return_if_fail (setting);
g_return_if_fail (NM_IS_SETTING (setting));
g_return_if_fail (func != NULL);
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (setting), &n_property_specs);
for (i = 0; i < n_property_specs; i++) {
if (property_specs[i]->flags & NM_SETTING_PARAM_SECRET) {
NM_SETTING_GET_CLASS (setting)->clear_secrets_with_flags (setting,
property_specs[i],
func,
user_data);
}
}
g_free (property_specs);
}
/**
* nm_setting_need_secrets:
* @setting: the #NMSetting
......@@ -1029,6 +1083,7 @@ nm_setting_class_init (NMSettingClass *setting_class)
setting_class->get_secret_flags = get_secret_flags;
setting_class->set_secret_flags = set_secret_flags;
setting_class->compare_property = compare_property;
setting_class->clear_secrets_with_flags = clear_secrets_with_flags;
/* Properties */
......
......@@ -150,6 +150,21 @@ typedef struct {
GObject parent;
} NMSetting;
/**
* NMSettingClearSecretsWithFlagsFn:
* @setting: The setting for which secrets are being iterated
* @secret: The secret's name
* @flags: The secret's flags, eg %NM_SETTING_SECRET_FLAG_AGENT_OWNED
* @user_data: User data passed to nm_connection_clear_secrets_with_flags()
*
* Returns: %TRUE to clear the secret, %FALSE to not clear the secret
*/
typedef gboolean (*NMSettingClearSecretsWithFlagsFn) (NMSetting *setting,
const char *secret,
NMSettingSecretFlags flags,
gpointer user_data);
typedef struct {
GObjectClass parent;
......@@ -183,10 +198,14 @@ typedef struct {
const GParamSpec *prop_spec,
NMSettingCompareFlags flags);
void (*clear_secrets_with_flags) (NMSetting *setting,
GParamSpec *pspec,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data);
/* Padding for future expansion */
void (*_reserved1) (void);
void (*_reserved2) (void);
void (*_reserved3) (void);
} NMSettingClass;
/**
......@@ -269,6 +288,11 @@ char *nm_setting_to_string (NMSetting *setting);
/* Secrets */
void nm_setting_clear_secrets (NMSetting *setting);
void nm_setting_clear_secrets_with_flags (NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data);
GPtrArray *nm_setting_need_secrets (NMSetting *setting);
gboolean nm_setting_update_secrets (NMSetting *setting,
GHashTable *secrets,
......
......@@ -84,15 +84,27 @@ typedef struct {
NMDBusManager *dbus_mgr;
NMAgentManager *agent_mgr;
NMSessionMonitor *session_monitor;
guint session_changed_id;
GSList *pending_auths; /* List of pending authentication requests */
NMConnection *secrets;
gboolean visible; /* Is this connection is visible by some session? */
GSList *reqs; /* in-progress secrets requests */
NMSessionMonitor *session_monitor;
guint session_changed_id;
/* Caches secrets from on-disk connections; were they not cached any
* call to nm_connection_clear_secrets() wipes them out and we'd have
* to re-read them from disk which defeats the purpose of having the
* connection in-memory at all.
*/
NMConnection *system_secrets;
/* Caches secrets from agents during the activation process; if new system
* secrets are returned from an agent, they get written out to disk,
* triggering a re-read of the connection, which reads only system
* secrets, and would wipe out any agent-owned or not-saved secrets the
* agent also returned.
*/
NMConnection *agent_secrets;
guint64 timestamp; /* Up-to-date timestamp of connection use */
GHashTable *seen_bssids; /* Up-to-date BSSIDs that's been seen for the connection */
......@@ -294,57 +306,67 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
/**************************************************************/
static void
only_system_secrets_cb (NMSetting *setting,
const char *key,
const GValue *value,
GParamFlags flags,
gpointer user_data)
static gboolean
secrets_filter_cb (NMSetting *setting,
const char *secret,
NMSettingSecretFlags flags,
gpointer user_data)
{
if (flags & NM_SETTING_PARAM_SECRET) {
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
/* VPNs are special; need to handle each secret separately */
if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
GHashTableIter iter;
const char *secret_name = NULL;
/* Returns TRUE to remove the secret */
g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
while (g_hash_table_iter_next (&iter, (gpointer *) &secret_name, NULL)) {
secret_flags = NM_SETTING_SECRET_FLAG_NONE;
nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL);
if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
nm_setting_vpn_remove_secret (NM_SETTING_VPN (setting), secret_name);
}
} else {
nm_setting_get_secret_flags (setting, key, &secret_flags, NULL);
if (secret_flags != NM_SETTING_SECRET_FLAG_NONE)
g_object_set (G_OBJECT (setting), key, NULL, NULL);
}
}
/* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */
if ( (flags == NM_SETTING_SECRET_FLAG_NONE)
&& (filter_flags == NM_SETTING_SECRET_FLAG_NONE))
return FALSE;
/* Otherwise if the secret has at least one of the desired flags keep it */
return (flags & filter_flags) ? FALSE : TRUE;
}
static void
update_secrets_cache (NMSettingsConnection *self)
update_system_secrets_cache (NMSettingsConnection *self)
{
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
if (priv->secrets)
g_object_unref (priv->secrets);
priv->secrets = nm_connection_duplicate (NM_CONNECTION (self));
if (priv->system_secrets)
g_object_unref (priv->system_secrets);
priv->system_secrets = nm_connection_duplicate (NM_CONNECTION (self));
/* Clear out non-system-owned and not-saved secrets */
nm_connection_for_each_setting_value (priv->secrets, only_system_secrets_cb, NULL);
nm_connection_clear_secrets_with_flags (priv->system_secrets,
secrets_filter_cb,
GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_NONE));
}
static gboolean
clear_system_secrets (GHashTableIter *iter,
NMSettingSecretFlags flags,
gpointer user_data)
static void
update_agent_secrets_cache (NMSettingsConnection *self, NMConnection *new)
{
if (flags == NM_SETTING_SECRET_FLAG_NONE)
g_hash_table_iter_remove (iter);
return TRUE;
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMSettingSecretFlags filter_flags = NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_AGENT_OWNED;
if (priv->agent_secrets)
g_object_unref (priv->agent_secrets);
priv->agent_secrets = nm_connection_duplicate (new ? new : NM_CONNECTION (self));
/* Clear out non-system-owned secrets */
nm_connection_clear_secrets_with_flags (priv->agent_secrets,
secrets_filter_cb,
GUINT_TO_POINTER (filter_flags));
}
static void
secrets_cleared_cb (NMSettingsConnection *self)
{
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
/* Clear agent secrets when connection's secrets are cleared since agent
* secrets are transient.
*/
if (priv->agent_secrets)
g_object_unref (priv->agent_secrets);
priv->agent_secrets = NULL;
}
/* Update the settings of this connection to match that of 'new', taking care to
......@@ -356,7 +378,7 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self,
GError **error)
{
NMSettingsConnectionPrivate *priv;
GHashTable *new_settings, *transient_secrets;
GHashTable *new_settings, *hash = NULL;
gboolean success = FALSE;
g_return_val_if_fail (self != NULL, FALSE);
......@@ -366,37 +388,29 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self,
priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
/* Replacing the settings might replace transient secrets, such as when
* a user agent returns secrets, which might trigger the connection to be
* written out, which triggers an inotify event to re-read and update the
* connection, which, if we're not careful, could wipe out the transient
* secrets the user agent just sent us. Basically, only
* nm_connection_clear_secrets() should wipe out transient secrets but
* re-reading a connection from on-disk and updating our in-memory copy
* should not. Thus we preserve non-system-owned secrets here.
*/
transient_secrets = nm_connection_to_hash (NM_CONNECTION (self), NM_SETTING_HASH_FLAG_ONLY_SECRETS);
if (transient_secrets)
for_each_secret (NM_CONNECTION (self), transient_secrets, clear_system_secrets, NULL);
new_settings = nm_connection_to_hash (new, NM_SETTING_HASH_FLAG_ALL);
g_assert (new_settings);
if (nm_connection_replace_settings (NM_CONNECTION (self), new_settings, error)) {
/* Copy the connection to keep its secrets around even if NM
* calls nm_connection_clear_secrets().
/* Cache the just-updated system secrets in case something calls
* nm_connection_clear_secrets() and clears them.
*/
update_secrets_cache (self);
update_system_secrets_cache (self);
success = TRUE;
/* And add the transient secrets back */
if (transient_secrets)
nm_connection_update_secrets (NM_CONNECTION (self), NULL, transient_secrets, NULL);
/* Add agent and always-ask secrets back; they won't necessarily be
* in the replacement connection data if it was eg reread from disk.
*/
if (priv->agent_secrets) {
hash = nm_connection_to_hash (priv->agent_secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
if (hash) {
success = nm_connection_update_secrets (NM_CONNECTION (self), NULL, hash, error);
g_hash_table_destroy (hash);
}
}
nm_settings_connection_recheck_visibility (self);
success = TRUE;
}
g_hash_table_destroy (new_settings);
if (transient_secrets)
g_hash_table_destroy (transient_secrets);
return success;
}
......@@ -719,7 +733,7 @@ agent_secrets_done_cb (NMAgentManager *manager,
/* Update the connection with our existing secrets from backing storage */
nm_connection_clear_secrets (NM_CONNECTION (self));
hash = nm_connection_to_hash (priv->secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
hash = nm_connection_to_hash (priv->system_secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
if (!hash || nm_connection_update_secrets (NM_CONNECTION (self), setting_name, hash, &local)) {
/* Update the connection with the agent's secrets; by this point if any
* system-owned secrets exist in 'secrets' the agent that provided them
......@@ -730,7 +744,8 @@ agent_secrets_done_cb (NMAgentManager *manager,
/* Now that all secrets are updated, copy and cache new secrets,
* then save them to backing storage.
*/
update_secrets_cache (self);
update_system_secrets_cache (self);
update_agent_secrets_cache (self, NULL);
/* Only save secrets to backing storage if the agent returned any
* new system secrets. If it didn't, then the secrets are agent-
......@@ -807,11 +822,9 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
guint32 call_id = 0;
/* Use priv->secrets to work around the fact that nm_connection_clear_secrets()
* will clear secrets on this object's settings. priv->secrets should be
* a complete copy of this object and kept in sync by
* nm_settings_connection_replace_settings().
* will clear secrets on this object's settings.
*/
if (!priv->secrets) {
if (!priv->system_secrets) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"%s.%d - Internal error; secrets cache invalid.",
__FILE__, __LINE__);
......@@ -826,7 +839,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
return 0;
}
existing_secrets = nm_connection_to_hash (priv->secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
existing_secrets = nm_connection_to_hash (priv->system_secrets, NM_SETTING_HASH_FLAG_ONLY_SECRETS);
call_id = nm_agent_manager_get_secrets (priv->agent_mgr,
NM_CONNECTION (self),
filter_by_uid,
......@@ -1084,47 +1097,40 @@ impl_settings_connection_get_settings (NMSettingsConnection *self,
auth_start (self, context, NULL, get_settings_auth_cb, NULL);
}
typedef struct {
DBusGMethodInvocation *context;
NMAgentManager *agent_mgr;
gulong sender_uid;
} UpdateInfo;
static void
con_update_cb (NMSettingsConnection *connection,
con_update_cb (NMSettingsConnection *self,
GError *error,
gpointer user_data)
{
DBusGMethodInvocation *context = user_data;
UpdateInfo *info = user_data;
NMConnection *for_agent;
if (error)
dbus_g_method_return_error (context, error);
else
dbus_g_method_return (context);
}
static void
secrets_filter_cb (NMSetting *setting,
const char *key,
const GValue *value,
GParamFlags flags,
gpointer user_data)
{
NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
const char *secret_name = NULL;
GHashTableIter iter;
dbus_g_method_return_error (info->context, error);
else {
/* Dupe the connection so we can clear out non-agent-owned secrets,
* as agent-owned secrets are the only ones we send back be saved.
* Only send secrets to agents of the same UID that called update too.
*/
for_agent = nm_connection_duplicate (NM_CONNECTION (self));
nm_connection_clear_secrets_with_flags (for_agent,
secrets_filter_cb,
GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
nm_agent_manager_save_secrets (info->agent_mgr, for_agent, TRUE, info->sender_uid);
g_object_unref (for_agent);
if (flags & NM_SETTING_PARAM_SECRET) {
if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
/* VPNs are special; need to handle each secret separately */
g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
while (g_hash_table_iter_next (&iter, (gpointer) &secret_name, NULL)) {
secret_flags = NM_SETTING_SECRET_FLAG_NONE;
nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL);
if (!(secret_flags & filter_flags))
nm_setting_vpn_remove_secret (NM_SETTING_VPN (setting), secret_name);
}
} else {
nm_setting_get_secret_flags (setting, key, &secret_flags, NULL);
if (!(secret_flags & filter_flags))
g_object_set (G_OBJECT (setting), key, NULL, NULL);
}
dbus_g_method_return (info->context);
}
g_object_unref (info->agent_mgr);
memset (info, 0, sizeof (*info));
g_free (info);
}
static void
......@@ -1136,54 +1142,27 @@ update_auth_cb (NMSettingsConnection *self,
{
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
NMConnection *new_settings = data;
NMConnection *for_agent, *dup;
NMSettingSecretFlags filter_flags;
GHashTable *hash;
GError *local = NULL;
UpdateInfo *info;
if (error)
dbus_g_method_return_error (context, error);
else {
/* Cache the new secrets since they may get overwritten by the replace
* when transient secrets are copied back.
info = g_malloc0 (sizeof (*info));
info->context = context;
info->agent_mgr = g_object_ref (priv->agent_mgr);
info->sender_uid = sender_uid;
/* Cache the new secrets from the agent, as stuff like inotify-triggered
* changes to connection's backing config files will blow them away if
* they're in the main connection.
*/
dup = nm_connection_duplicate (new_settings);
update_agent_secrets_cache (self, new_settings);