/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
 * Copyright 2011 Red Hat, Inc.
 *           2011 Giovanni Campagna <scampa.giovanni@gmail.com>
 *           2017 Lubomir Rintel <lkundrak@v3.sk>
 *
 * 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.
 *
 */

#include "config.h"
#include <string.h>

#include <libsecret/secret.h>

#include "shell-network-agent.h"

enum
{
  PROP_0,

  PROP_FORCE_ALWAYS_ASK,

  N_PROPS
};

static GParamSpec *props[N_PROPS] = { NULL, };

enum {
  SIGNAL_NEW_REQUEST,
  SIGNAL_CANCEL_REQUEST,
  SIGNAL_LAST
};

static gint signals[SIGNAL_LAST];

typedef struct {
  GCancellable *                    cancellable;
  ShellNetworkAgent                *self;

  gchar                            *request_id;
  NMConnection                     *connection;
  gchar                            *setting_name;
  gchar                           **hints;
  NMSecretAgentGetSecretsFlags      flags;
  NMSecretAgentOldGetSecretsFunc    callback;
  gpointer                          callback_data;

  GVariantDict                     *entries;
  GVariantBuilder                   builder_vpn;
} ShellAgentRequest;

typedef struct _ShellNetworkAgent
{
  NMSecretAgentOld parent_instance;

  /* <gchar *request_id, ShellAgentRequest *request> */
  GHashTable *requests;
  gboolean force_always_ask;
} ShellNetworkAgent;

G_DEFINE_FINAL_TYPE (ShellNetworkAgent, shell_network_agent, NM_TYPE_SECRET_AGENT_OLD)

static const SecretSchema network_agent_schema = {
    "org.freedesktop.NetworkManager.Connection",
    SECRET_SCHEMA_DONT_MATCH_NAME,
    {
        { SHELL_KEYRING_UUID_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
        { SHELL_KEYRING_SN_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
        { SHELL_KEYRING_SK_TAG, SECRET_SCHEMA_ATTRIBUTE_STRING },
        { NULL, 0 },
    }
};

static void
shell_agent_request_free (gpointer data)
{
  ShellAgentRequest *request = data;

  g_cancellable_cancel (request->cancellable);
  g_object_unref (request->cancellable);
  g_object_unref (request->self);
  g_object_unref (request->connection);
  g_free (request->setting_name);
  g_strfreev (request->hints);
  g_clear_pointer (&request->entries, g_variant_dict_unref);
  g_variant_builder_clear (&request->builder_vpn);

  g_free (request);
}

static void
shell_agent_request_cancel (ShellAgentRequest *request)
{
  GError *error = NULL;
  ShellNetworkAgent *self;

  self = request->self;

  error = g_error_new (NM_SECRET_AGENT_ERROR,
                       NM_SECRET_AGENT_ERROR_AGENT_CANCELED,
                       "Canceled by NetworkManager");
  request->callback (NM_SECRET_AGENT_OLD (self), request->connection,
                     NULL, error, request->callback_data);

  g_signal_emit (self, signals[SIGNAL_CANCEL_REQUEST], 0, request->request_id);

  g_hash_table_remove (self->requests, request->request_id);
  g_error_free (error);
}

static void
shell_network_agent_init (ShellNetworkAgent *agent)
{
  agent->requests = g_hash_table_new_full (g_str_hash, g_str_equal,
					                                 g_free, shell_agent_request_free);
}

static void
shell_network_agent_finalize (GObject *object)
{
  ShellNetworkAgent *self = SHELL_NETWORK_AGENT (object);
  GError *error = NULL;
  GHashTableIter iter;
  gpointer key;
  gpointer value;

  error = g_error_new (NM_SECRET_AGENT_ERROR,
                       NM_SECRET_AGENT_ERROR_AGENT_CANCELED,
                       "The secret agent is going away");

  g_hash_table_iter_init (&iter, self->requests);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      ShellAgentRequest *request = value;

      request->callback (NM_SECRET_AGENT_OLD (object),
                         request->connection,
                         NULL, error,
                         request->callback_data);
    }

  g_hash_table_destroy (self->requests);
  g_error_free (error);

  G_OBJECT_CLASS (shell_network_agent_parent_class)->finalize (object);
}

static void
request_secrets_from_ui (ShellAgentRequest *request)
{
  g_signal_emit (request->self, signals[SIGNAL_NEW_REQUEST], 0,
                 request->request_id,
                 request->connection,
                 request->setting_name,
                 request->hints,
                 (int)request->flags);
}

static void
check_always_ask_cb (NMSetting    *setting,
                     const gchar  *key,
                     const GValue *value,
                     GParamFlags   flags,
                     gpointer      user_data)
{
  gboolean *always_ask = user_data;
  NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;

  if (flags & NM_SETTING_PARAM_SECRET)
    {
      if (nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
        {
          if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
            *always_ask = TRUE;
        }
    }
}

static gboolean
has_always_ask (NMSetting *setting)
{
  gboolean always_ask = FALSE;

  nm_setting_enumerate_values (setting, check_always_ask_cb, &always_ask);
  return always_ask;
}

static gboolean
is_connection_always_ask (NMConnection *connection)
{
  NMSettingConnection *s_con;
  const gchar *ctype;
  NMSetting *setting;

  /* For the given connection type, check if the secrets for that connection
   * are always-ask or not.
   */
  s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
  g_assert (s_con);
  ctype = nm_setting_connection_get_connection_type (s_con);

  setting = nm_connection_get_setting_by_name (connection, ctype);
  g_return_val_if_fail (setting != NULL, FALSE);

  if (has_always_ask (setting))
    return TRUE;

  /* Try type-specific settings too; be a bit paranoid and only consider
   * secrets from settings relevant to the connection type.
   */
  if (NM_IS_SETTING_WIRELESS (setting))
    {
      setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
      if (setting && has_always_ask (setting))
        return TRUE;
      setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X);
      if (setting && has_always_ask (setting))
        return TRUE;
	}
  else if (NM_IS_SETTING_WIRED (setting))
    {
      setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_PPPOE);
      if (setting && has_always_ask (setting))
        return TRUE;
      setting = nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X);
      if (setting && has_always_ask (setting))
        return TRUE;
    }

  return FALSE;
}

static void
get_secrets_keyring_cb (GObject            *source,
                        GAsyncResult       *result,
                        gpointer            user_data)
{
  ShellAgentRequest *closure;
  ShellNetworkAgent *self;
  GError *secret_error = NULL;
  GError *error = NULL;
  GList *items;
  GList *l;
  gboolean secrets_found = FALSE;
  GVariantBuilder builder_setting, builder_connection;
  g_autoptr (GVariant) setting = NULL;

  items = secret_service_search_finish (NULL, result, &secret_error);

  if (g_error_matches (secret_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    {
      g_error_free (secret_error);
      return;
    }

  closure = user_data;
  self = closure->self;

  if (secret_error != NULL)
    {
      g_set_error (&error,
                   NM_SECRET_AGENT_ERROR,
                   NM_SECRET_AGENT_ERROR_FAILED,
                   "Internal error while retrieving secrets from the keyring (%s)", secret_error->message);
      g_error_free (secret_error);
      closure->callback (NM_SECRET_AGENT_OLD (closure->self), closure->connection, NULL, error, closure->callback_data);

      goto out;
    }

  g_variant_builder_init (&builder_setting, NM_VARIANT_TYPE_SETTING);

  for (l = items; l; l = g_list_next (l))
    {
      SecretItem *item = l->data;
      GHashTable *attributes;
      GHashTableIter iter;
      const gchar *name, *attribute;
      SecretValue *secret = secret_item_get_secret (item);

      /* This can happen if the user denied a request to unlock */
      if (secret == NULL)
        continue;

      attributes = secret_item_get_attributes (item);
      g_hash_table_iter_init (&iter, attributes);
      while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&attribute))
        {
          if (g_strcmp0 (name, SHELL_KEYRING_SK_TAG) == 0)
            {
              g_variant_builder_add (&builder_setting, "{sv}", attribute,
                                     g_variant_new_string (secret_value_get (secret, NULL)));

              secrets_found = TRUE;

              break;
            }
        }

      g_hash_table_unref (attributes);
      secret_value_unref (secret);
    }

  g_list_free_full (items, g_object_unref);
  setting = g_variant_ref_sink (g_variant_builder_end (&builder_setting));

  /* All VPN requests get sent to the VPN's auth dialog, since it knows better
   * than the agent about what secrets are required.  Otherwise, if no secrets
   * were found and interaction is allowed the ask for some secrets, because
   * NetworkManager will fail the connection if not secrets are returned
   * instead of asking again with REQUEST_NEW.
   */
  if (strcmp(closure->setting_name, NM_SETTING_VPN_SETTING_NAME) == 0 ||
      (!secrets_found && (closure->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION)))
    {
      nm_connection_update_secrets (closure->connection, closure->setting_name,
                                    setting, NULL);

      closure->entries = g_variant_dict_new (setting);
      request_secrets_from_ui (closure);
      return;
    }

  g_variant_builder_init (&builder_connection, NM_VARIANT_TYPE_CONNECTION);
  g_variant_builder_add (&builder_connection, "{s@a{sv}}",
                         closure->setting_name, setting);

  closure->callback (NM_SECRET_AGENT_OLD (closure->self), closure->connection,
                     g_variant_builder_end (&builder_connection), NULL,
                     closure->callback_data);

 out:
  g_hash_table_remove (self->requests, closure->request_id);
  g_clear_error (&error);
}

static void
shell_network_agent_get_secrets (NMSecretAgentOld                 *agent,
				 NMConnection                     *connection,
				 const gchar                      *connection_path,
				 const gchar                      *setting_name,
				 const gchar                     **hints,
				 NMSecretAgentGetSecretsFlags      flags,
				 NMSecretAgentOldGetSecretsFunc    callback,
				 gpointer                          callback_data)
{
  ShellNetworkAgent *self = SHELL_NETWORK_AGENT (agent);
  ShellAgentRequest *request;
  GHashTable *attributes;
  char *request_id;

  request_id = g_strdup_printf ("%s/%s", connection_path, setting_name);
  if ((request = g_hash_table_lookup (self->requests, request_id)) != NULL)
    {
      /* We already have a request pending for this (connection, setting)
       * Cancel it before starting the new one.
       * This will also free the request structure and associated resources.
       */
      shell_agent_request_cancel (request);
    }

  request = g_new0 (ShellAgentRequest, 1);
  request->self = g_object_ref (self);
  request->cancellable = g_cancellable_new ();
  request->connection = g_object_ref (connection);
  request->setting_name = g_strdup (setting_name);
  request->hints = g_strdupv ((gchar **)hints);
  request->flags = flags;
  request->callback = callback;
  request->callback_data = callback_data;

  request->request_id = request_id;
  g_hash_table_replace (self->requests, request->request_id, request);

  g_variant_builder_init (&request->builder_vpn, G_VARIANT_TYPE ("a{ss}"));

  if ((flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW) ||
      ((flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION)
       && (self->force_always_ask || is_connection_always_ask (request->connection))))
    {
      request->entries = g_variant_dict_new (NULL);
      request_secrets_from_ui (request);
      return;
    }

  attributes = secret_attributes_build (&network_agent_schema,
                                        SHELL_KEYRING_UUID_TAG, nm_connection_get_uuid (connection),
                                        SHELL_KEYRING_SN_TAG, setting_name,
                                        NULL);

  secret_service_search (NULL, &network_agent_schema, attributes,
                         SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS,
                         request->cancellable, get_secrets_keyring_cb, request);

  g_hash_table_unref (attributes);
}

void
shell_network_agent_add_vpn_secret (ShellNetworkAgent *self,
                                    gchar             *request_id,
                                    gchar             *setting_key,
                                    gchar             *setting_value)
{
  ShellAgentRequest *request;

  g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));

  request = g_hash_table_lookup (self->requests, request_id);
  g_return_if_fail (request != NULL);

  g_variant_builder_add (&request->builder_vpn, "{ss}", setting_key, setting_value);
}

void
shell_network_agent_set_password (ShellNetworkAgent *self,
                                  gchar             *request_id,
                                  gchar             *setting_key,
                                  gchar             *setting_value)
{
  ShellAgentRequest *request;

  g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));

  request = g_hash_table_lookup (self->requests, request_id);
  g_return_if_fail (request != NULL);

  g_variant_dict_insert (request->entries, setting_key, "s", setting_value);
}

void
shell_network_agent_respond (ShellNetworkAgent         *self,
                             gchar                     *request_id,
                             ShellNetworkAgentResponse  response)
{
  ShellAgentRequest *request;
  GVariantBuilder builder_connection;
  GVariant *vpn_secrets, *setting;

  g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));

  request = g_hash_table_lookup (self->requests, request_id);
  g_return_if_fail (request != NULL);

  if (response == SHELL_NETWORK_AGENT_USER_CANCELED)
    {
      GError *error = g_error_new (NM_SECRET_AGENT_ERROR,
                                   NM_SECRET_AGENT_ERROR_USER_CANCELED,
                                   "Network dialog was canceled by the user");

      request->callback (NM_SECRET_AGENT_OLD (self), request->connection, NULL, error, request->callback_data);
      g_error_free (error);
      g_hash_table_remove (self->requests, request_id);
      return;
    }

  if (response == SHELL_NETWORK_AGENT_INTERNAL_ERROR)
    {
      GError *error = g_error_new (NM_SECRET_AGENT_ERROR,
                                   NM_SECRET_AGENT_ERROR_FAILED,
                                   "An internal error occurred while processing the request.");

      request->callback (NM_SECRET_AGENT_OLD (self), request->connection, NULL, error, request->callback_data);
      g_error_free (error);
      g_hash_table_remove (self->requests, request_id);
      return;
    }

  /* response == SHELL_NETWORK_AGENT_CONFIRMED */

  /* VPN secrets are stored as a hash of secrets in a single setting */
  vpn_secrets = g_variant_builder_end (&request->builder_vpn);
  if (g_variant_n_children (vpn_secrets))
    g_variant_dict_insert_value (request->entries, NM_SETTING_VPN_SECRETS, vpn_secrets);
  else
    g_variant_unref (vpn_secrets);

  setting = g_variant_dict_end (request->entries);

  /* Save any updated secrets */
  if ((request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION) ||
      (request->flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))
    {
      NMConnection *dup = nm_simple_connection_new_clone (request->connection);

      nm_connection_update_secrets (dup, request->setting_name, setting, NULL);
      nm_secret_agent_old_save_secrets (NM_SECRET_AGENT_OLD (self), dup, NULL, NULL);
      g_object_unref (dup);
    }

  g_variant_builder_init (&builder_connection, NM_VARIANT_TYPE_CONNECTION);
  g_variant_builder_add (&builder_connection, "{s@a{sv}}",
                         request->setting_name, setting);

  request->callback (NM_SECRET_AGENT_OLD (self), request->connection,
                     g_variant_builder_end (&builder_connection), NULL,
                     request->callback_data);

  g_hash_table_remove (self->requests, request_id);
}

static void
search_vpn_plugin (GTask        *task,
                   gpointer      object,
                   gpointer      task_data,
                   GCancellable *cancellable)
{
  NMVpnPluginInfo *info = NULL;
  char *service = task_data;

  info = nm_vpn_plugin_info_new_search_file (NULL, service);

  if (info)
    {
      g_task_return_pointer (task, info, g_object_unref);
    }
  else
    {
      g_task_return_new_error (task,
                               G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                               "No plugin for %s", service);
    }
}

void
shell_network_agent_search_vpn_plugin (ShellNetworkAgent   *self,
                                       const char          *service,
                                       GAsyncReadyCallback  callback,
                                       gpointer             user_data)
{
  g_autoptr(GTask) task = NULL;

  g_return_if_fail (SHELL_IS_NETWORK_AGENT (self));
  g_return_if_fail (service != NULL);

  task = g_task_new (self, NULL, callback, user_data);
  g_task_set_source_tag (task, shell_network_agent_search_vpn_plugin);
  g_task_set_task_data (task, g_strdup (service), g_free);

  g_task_run_in_thread (task, search_vpn_plugin);
}

/**
 * shell_network_agent_search_vpn_plugin_finish:
 *
 * Returns: (nullable) (transfer full): The found plugin or %NULL
 */
NMVpnPluginInfo *
shell_network_agent_search_vpn_plugin_finish (ShellNetworkAgent  *self,
                                              GAsyncResult       *result,
                                              GError            **error)
{
  g_return_val_if_fail (SHELL_IS_NETWORK_AGENT (self), NULL);
  g_return_val_if_fail (G_IS_TASK (result), NULL);

  return g_task_propagate_pointer (G_TASK (result), error);
}

static void
shell_network_agent_cancel_get_secrets (NMSecretAgentOld *agent,
                                        const gchar      *connection_path,
                                        const gchar      *setting_name)
{
  ShellNetworkAgent *self = SHELL_NETWORK_AGENT (agent);
  gchar *request_id;
  ShellAgentRequest *request;

  request_id = g_strdup_printf ("%s/%s", connection_path, setting_name);
  request = g_hash_table_lookup (self->requests, request_id);
  g_free (request_id);

  if (!request)
    {
      /* We've already sent the result, but the caller cancelled the
       * operation before receiving that result.
       */
      return;
    }

  shell_agent_request_cancel (request);
}

/************************* saving of secrets ****************************************/

static GHashTable *
create_keyring_add_attr_list (NMConnection *connection,
                              const gchar  *connection_uuid,
                              const gchar  *connection_id,
                              const gchar  *setting_name,
                              const gchar  *setting_key,
                              gchar       **out_display_name)
{
  NMSettingConnection *s_con;

  if (connection)
    {
      s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
      g_return_val_if_fail (s_con != NULL, NULL);
      connection_uuid = nm_setting_connection_get_uuid (s_con);
      connection_id = nm_setting_connection_get_id (s_con);
    }

  g_return_val_if_fail (connection_uuid != NULL, NULL);
  g_return_val_if_fail (connection_id != NULL, NULL);
  g_return_val_if_fail (setting_name != NULL, NULL);
  g_return_val_if_fail (setting_key != NULL, NULL);

  if (out_display_name)
    {
      *out_display_name = g_strdup_printf ("Network secret for %s/%s/%s",
                                           connection_id,
                                           setting_name,
                                           setting_key);
    }

  return secret_attributes_build (&network_agent_schema,
                                  SHELL_KEYRING_UUID_TAG, connection_uuid,
                                  SHELL_KEYRING_SN_TAG, setting_name,
                                  SHELL_KEYRING_SK_TAG, setting_key,
                                  NULL);
}

typedef struct
{
  /* Sort of ref count, indicates the number of secrets we still need to save */
  gint              n_secrets;

  NMSecretAgentOld *self;
  NMConnection     *connection;
  gpointer          callback;
  gpointer          callback_data;
} KeyringRequest;

static void
keyring_request_free (KeyringRequest *r)
{
  g_object_unref (r->self);
  g_object_unref (r->connection);

  g_free (r);
}

static void
save_secret_cb (GObject           *source,
                GAsyncResult      *result,
                gpointer           user_data)
{
  KeyringRequest *call = user_data;
  NMSecretAgentOldSaveSecretsFunc callback = call->callback;

  call->n_secrets--;

  if (call->n_secrets == 0)
    {
      if (callback)
        callback (call->self, call->connection, NULL, call->callback_data);
      keyring_request_free (call);
    }
}

static void
save_one_secret (KeyringRequest *r,
                 NMSetting      *setting,
                 const gchar    *key,
                 const gchar    *secret,
                 const gchar    *display_name)
{
  GHashTable *attrs;
  gchar *alt_display_name = NULL;
  const gchar *setting_name;
  NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;

  /* Only save agent-owned secrets (not system-owned or always-ask) */
  nm_setting_get_secret_flags (setting, key, &secret_flags, NULL);
  if (secret_flags != NM_SETTING_SECRET_FLAG_AGENT_OWNED)
    return;

  setting_name = nm_setting_get_name (setting);
  g_assert (setting_name);

  attrs = create_keyring_add_attr_list (r->connection, NULL, NULL,
                                        setting_name,
                                        key,
                                        display_name ? NULL : &alt_display_name);
  g_assert (attrs);
  r->n_secrets++;
  secret_password_storev (&network_agent_schema, attrs, SECRET_COLLECTION_DEFAULT,
                          display_name ? display_name : alt_display_name,
                          secret, NULL, save_secret_cb, r);

  g_hash_table_unref (attrs);
  g_free (alt_display_name);
}

static void
vpn_secret_iter_cb (const gchar *key,
                    const gchar *secret,
                    gpointer     user_data)
{
  KeyringRequest *r = user_data;
  NMSetting *setting;
  const gchar *service_name, *id;
  gchar *display_name;

  if (secret && strlen (secret))
    {
      setting = nm_connection_get_setting (r->connection, NM_TYPE_SETTING_VPN);
      g_assert (setting);
      service_name = nm_setting_vpn_get_service_type (NM_SETTING_VPN (setting));
      g_assert (service_name);
      id = nm_connection_get_id (r->connection);
      g_assert (id);

      display_name = g_strdup_printf ("VPN %s secret for %s/%s/" NM_SETTING_VPN_SETTING_NAME,
                                      key,
                                      id,
                                      service_name);
      save_one_secret (r, setting, key, secret, display_name);
      g_free (display_name);
    }
}

static void
write_one_secret_to_keyring (NMSetting    *setting,
                             const gchar  *key,
                             const GValue *value,
                             GParamFlags   flags,
                             gpointer      user_data)
{
  KeyringRequest *r = user_data;
  const gchar *secret;

  /* Non-secrets obviously don't get saved in the keyring */
  if (!(flags & NM_SETTING_PARAM_SECRET))
    return;

  if (NM_IS_SETTING_VPN (setting) && (g_strcmp0 (key, NM_SETTING_VPN_SECRETS) == 0))
    {
      /* Process VPN secrets specially since it's a hash of secrets, not just one */
      nm_setting_vpn_foreach_secret (NM_SETTING_VPN (setting),
                                     vpn_secret_iter_cb,
                                     r);
    }
  else
    {
      if (!G_VALUE_HOLDS_STRING (value))
        return;

      secret = g_value_get_string (value);
      if (secret && strlen (secret))
        save_one_secret (r, setting, key, secret, NULL);
  }
}

static void
save_delete_cb (NMSecretAgentOld *agent,
                NMConnection     *connection,
                GError           *error,
                gpointer          user_data)
{
  KeyringRequest *r = user_data;

  /* Ignore errors; now save all new secrets */
  nm_connection_for_each_setting_value (connection, write_one_secret_to_keyring, r);

  /* If no secrets actually got saved there may be nothing to do so
   * try to complete the request here. If there were secrets to save the
   * request will get completed when those keyring calls return (at the next
   * mainloop iteration).
   */
  if (r->n_secrets == 0)
    {
      if (r->callback)
        ((NMSecretAgentOldSaveSecretsFunc)r->callback) (agent, connection, NULL, r->callback_data);
      keyring_request_free (r);
    }
}

static void
shell_network_agent_save_secrets (NMSecretAgentOld                *agent,
                                  NMConnection                    *connection,
                                  const gchar                     *connection_path,
                                  NMSecretAgentOldSaveSecretsFunc  callback,
                                  gpointer                         callback_data)
{
  KeyringRequest *r;

  r = g_new (KeyringRequest, 1);
  r->n_secrets = 0;
  r->self = g_object_ref (agent);
  r->connection = g_object_ref (connection);
  r->callback = callback;
  r->callback_data = callback_data;

  /* First delete any existing items in the keyring */
  nm_secret_agent_old_delete_secrets (agent, connection, save_delete_cb, r);
}

static void
delete_items_cb (GObject *source,
                 GAsyncResult *result,
                 gpointer user_data)
{
  KeyringRequest *r = user_data;
  GError *secret_error = NULL;
  GError *error = NULL;
  NMSecretAgentOldDeleteSecretsFunc callback = r->callback;

  secret_password_clear_finish (result, &secret_error);
  if (secret_error != NULL)
    {
      error = g_error_new (NM_SECRET_AGENT_ERROR,
                           NM_SECRET_AGENT_ERROR_FAILED,
                           "The request could not be completed.  Keyring result: %s",
                           secret_error->message);
      g_error_free (secret_error);
    }

  callback (r->self, r->connection, error, r->callback_data);
  g_clear_error (&error);
  keyring_request_free (r);
}

static void
shell_network_agent_delete_secrets (NMSecretAgentOld                  *agent,
                                    NMConnection                      *connection,
                                    const gchar                       *connection_path,
                                    NMSecretAgentOldDeleteSecretsFunc  callback,
                                    gpointer                           callback_data)
{
  KeyringRequest *r;
  NMSettingConnection *s_con;
  const gchar *uuid;

  r = g_new (KeyringRequest, 1);
  r->n_secrets = 0; /* ignored by delete secrets calls */
  r->self = g_object_ref (agent);
  r->connection = g_object_ref (connection);
  r->callback = callback;
  r->callback_data = callback_data;

  s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
  g_assert (s_con);
  uuid = nm_setting_connection_get_uuid (s_con);
  g_assert (uuid);

  secret_password_clear (&network_agent_schema, NULL, delete_items_cb, r,
                         SHELL_KEYRING_UUID_TAG, uuid,
                         NULL);
}

static void
shell_network_agent_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  ShellNetworkAgent *self = SHELL_NETWORK_AGENT (object);

  switch (prop_id)
    {
    case PROP_FORCE_ALWAYS_ASK:
      self->force_always_ask = g_value_get_boolean (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
shell_network_agent_get_property (GObject    *object,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  ShellNetworkAgent *self = SHELL_NETWORK_AGENT (object);

  switch (prop_id)
    {
    case PROP_FORCE_ALWAYS_ASK:
      g_value_set_boolean (value, self->force_always_ask);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

void
shell_network_agent_class_init (ShellNetworkAgentClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  NMSecretAgentOldClass *agent_class = NM_SECRET_AGENT_OLD_CLASS (klass);

  gobject_class->set_property = shell_network_agent_set_property;
  gobject_class->get_property = shell_network_agent_get_property;
  gobject_class->finalize = shell_network_agent_finalize;

  agent_class->get_secrets = shell_network_agent_get_secrets;
  agent_class->cancel_get_secrets = shell_network_agent_cancel_get_secrets;
  agent_class->save_secrets = shell_network_agent_save_secrets;
  agent_class->delete_secrets = shell_network_agent_delete_secrets;

  props[PROP_FORCE_ALWAYS_ASK] =
     g_param_spec_boolean ("force-always-ask", NULL, NULL,
                           FALSE,
                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  g_object_class_install_properties (gobject_class, N_PROPS, props);

  signals[SIGNAL_NEW_REQUEST] = g_signal_new ("new-request",
					      G_TYPE_FROM_CLASS (klass),
					      0, /* flags */
					      0, /* class offset */
					      NULL, /* accumulator */
					      NULL, /* accu_data */
                                              NULL, /* marshaller */
					      G_TYPE_NONE, /* return */
					      5, /* n_params */
					      G_TYPE_STRING,
					      NM_TYPE_CONNECTION,
					      G_TYPE_STRING,
                                              G_TYPE_STRV,
                                              G_TYPE_INT);

  signals[SIGNAL_CANCEL_REQUEST] = g_signal_new ("cancel-request",
                                                 G_TYPE_FROM_CLASS (klass),
                                                 0, /* flags */
                                                 0, /* class offset */
                                                 NULL, /* accumulator */
                                                 NULL, /* accu_data */
                                                 NULL, /* marshaller */
                                                 G_TYPE_NONE,
                                                 1, /* n_params */
                                                 G_TYPE_STRING);
}
