/* * gnome-keyring * * Copyright (C) 2018 Red Hat, Inc. * * This program 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.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * . * * Author: Daiki Ueno */ #include "config.h" #include "gkd-login-interaction.h" #include "gkd-login-password.h" #include #include "gkd-login.h" #include "egg/egg-secure-memory.h" #include #include static const gchar *XDG_SCHEMA = "xdg:schema"; static const gchar *GENERIC_SCHEMA_VALUE = "org.freedesktop.Secret.Generic"; enum { PROP_0, PROP_BASE, PROP_SESSION, PROP_LABEL, PROP_FIELDS }; struct _GkdLoginInteraction { GTlsInteraction interaction; GTlsInteraction *base; GckSession *session; gchar *label; GHashTable *lookup_fields; GHashTable *store_fields; gboolean login_available; gboolean login_checked; }; G_DEFINE_TYPE (GkdLoginInteraction, gkd_login_interaction, G_TYPE_TLS_INTERACTION); EGG_SECURE_DECLARE (gkd_login_interaction); static void gkd_login_interaction_init (GkdLoginInteraction *self) { } static void gkd_login_interaction_constructed (GObject *object) { GkdLoginInteraction *self = GKD_LOGIN_INTERACTION (object); self->login_available = gkd_login_available (self->session); if (g_hash_table_contains (self->lookup_fields, (gpointer) XDG_SCHEMA)) self->store_fields = g_hash_table_ref (self->lookup_fields); else { GHashTableIter iter; gpointer key, value; self->store_fields = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_iter_init (&iter, self->lookup_fields); while (g_hash_table_iter_next (&iter, &key, &value)) g_hash_table_insert (self->store_fields, key, value); g_hash_table_insert (self->store_fields, (gpointer) XDG_SCHEMA, (gpointer) GENERIC_SCHEMA_VALUE); } G_OBJECT_CLASS (gkd_login_interaction_parent_class)->constructed (object); } static GkdLoginPassword * wrap_password (GkdLoginInteraction *self, GTlsPassword *password) { GkdLoginPassword *wrapped; wrapped = g_object_new (GKD_TYPE_LOGIN_PASSWORD, "base", password, "login-available", self->login_available, NULL); g_tls_password_set_description (G_TLS_PASSWORD (wrapped), self->label); return wrapped; } static void on_ask_password_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); GkdLoginInteraction *self = g_task_get_source_object (task); GTlsInteractionResult result; GError *error = NULL; result = g_tls_interaction_ask_password_finish (self->base, res, &error); if (result == G_TLS_INTERACTION_FAILED && error != NULL) g_task_return_error (task, error); else g_task_return_int (task, result); g_object_unref (task); } static void gkd_login_interaction_ask_password_async (GTlsInteraction *interaction, GTlsPassword *password, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GkdLoginInteraction *self = GKD_LOGIN_INTERACTION (interaction); GkdLoginPassword *login_password; GTask *task; login_password = wrap_password (self, password); task = g_task_new (interaction, cancellable, callback, user_data); g_task_set_task_data (task, g_object_ref (login_password), g_object_unref); /* If the login keyring is available, look for the password there */ if (self->login_available) { if (self->login_checked) g_message ("already attempted to use password from login keyring"); else { gchar *value = gkd_login_lookup_passwordv (self->session, self->lookup_fields); self->login_checked = TRUE; if (value) { g_tls_password_set_value_full (G_TLS_PASSWORD (login_password), (guchar *)value, strlen (value), (GDestroyNotify)egg_secure_free); g_object_unref (login_password); g_task_return_int (task, G_TLS_INTERACTION_HANDLED); g_object_unref (task); return; } } } /* Otherwise, call out to the base interaction */ g_tls_interaction_ask_password_async (self->base, G_TLS_PASSWORD (login_password), cancellable, on_ask_password_ready, task); g_object_unref (login_password); } static GTlsInteractionResult gkd_login_interaction_ask_password_finish (GTlsInteraction *interaction, GAsyncResult *res, GError **error) { GkdLoginInteraction *self = GKD_LOGIN_INTERACTION (interaction); GTask *task = G_TASK (res); GkdLoginPassword *login_password = g_task_get_task_data (task); GTlsInteractionResult result; result = g_task_propagate_int (task, error); if (result == -1) result = G_TLS_INTERACTION_FAILED; if (self->login_available && result == G_TLS_INTERACTION_HANDLED && gkd_login_password_get_store_password (login_password)) { const guchar *value; gsize length; gchar *password; gchar *label; value = g_tls_password_get_value (G_TLS_PASSWORD (login_password), &length); password = egg_secure_strndup ((const gchar *)value, length); label = g_strdup_printf (_("Unlock password for: %s"), self->label); gkd_login_store_passwordv (self->session, password, label, GCR_UNLOCK_OPTION_ALWAYS, -1, self->store_fields); egg_secure_free (password); g_free (label); } return result; } static void gkd_login_interaction_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GkdLoginInteraction *self = GKD_LOGIN_INTERACTION (object); switch (prop_id) { case PROP_BASE: self->base = g_value_dup_object (value); break; case PROP_SESSION: self->session = g_value_dup_object (value); break; case PROP_LABEL: self->label = g_value_dup_string (value); break; case PROP_FIELDS: self->lookup_fields = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gkd_login_interaction_dispose (GObject *object) { GkdLoginInteraction *self = GKD_LOGIN_INTERACTION (object); g_clear_object (&self->base); g_clear_object (&self->session); G_OBJECT_CLASS (gkd_login_interaction_parent_class)->dispose (object); } static void gkd_login_interaction_finalize (GObject *object) { GkdLoginInteraction *self = GKD_LOGIN_INTERACTION (object); g_free (self->label); g_hash_table_unref (self->lookup_fields); g_hash_table_unref (self->store_fields); G_OBJECT_CLASS (gkd_login_interaction_parent_class)->finalize (object); } static void gkd_login_interaction_class_init (GkdLoginInteractionClass *klass) { GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); interaction_class->ask_password_async = gkd_login_interaction_ask_password_async; interaction_class->ask_password_finish = gkd_login_interaction_ask_password_finish; gobject_class->constructed = gkd_login_interaction_constructed; gobject_class->set_property = gkd_login_interaction_set_property; gobject_class->dispose = gkd_login_interaction_dispose; gobject_class->finalize = gkd_login_interaction_finalize; g_object_class_install_property (gobject_class, PROP_BASE, g_param_spec_object ("base", "Base", "Base", G_TYPE_TLS_INTERACTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); g_object_class_install_property (gobject_class, PROP_SESSION, g_param_spec_object ("session", "Session", "Session", GCK_TYPE_SESSION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); g_object_class_install_property (gobject_class, PROP_LABEL, g_param_spec_string ("label", "Label", "Label", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); g_object_class_install_property (gobject_class, PROP_FIELDS, g_param_spec_boxed ("fields", "Fields", "Fields", G_TYPE_HASH_TABLE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); } GTlsInteraction * gkd_login_interaction_new (GTlsInteraction *base, GckSession *session, const gchar *label, GHashTable *fields) { return g_object_new (GKD_TYPE_LOGIN_INTERACTION, "base", base, "session", session, "label", label, "fields", fields, NULL); }