1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
6 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU Lesser General Public
12 * License as published by the Free Software Foundation.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
26 * This looks a lot more complicated than it is, and than you'd think
27 * it would need to be. There is however, method to the madness.
29 * The code must cope with being called from any thread at any time,
30 * recursively from the main thread, and then serialising every
31 * request so that sane and correct values are always returned, and
32 * duplicate requests are never made.
34 * To this end, every call is marshalled and queued and a dispatch
35 * method invoked until that request is satisfied. If mainloop
36 * recursion occurs, then the sub-call will necessarily return out of
37 * order, but will not be processed out of order.
48 #include <glib/gi18n-lib.h>
50 /* XXX Yeah, yeah... */
51 #define SECRET_API_SUBJECT_TO_CHANGE
53 #include <libsecret/secret.h>
55 #include <libedataserver/libedataserver.h>
57 #include "e-passwords.h"
61 typedef struct _EPassMsg EPassMsg;
64 void (*dispatch) (EPassMsg *);
84 guint noreply:1; /* supress replies; when calling
85 * dispatch functions from others */
88 /* XXX probably want to share this with evalution-source-registry-migrate-sources.c */
89 static const SecretSchema e_passwords_schema = {
90 "org.gnome.Evolution.Password",
91 SECRET_SCHEMA_DONT_MATCH_NAME,
93 { "application", SECRET_SCHEMA_ATTRIBUTE_STRING, },
94 { "user", SECRET_SCHEMA_ATTRIBUTE_STRING, },
95 { "server", SECRET_SCHEMA_ATTRIBUTE_STRING, },
96 { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING, },
100 G_LOCK_DEFINE_STATIC (passwords);
101 static GThread *main_thread = NULL;
102 static GHashTable *password_cache = NULL;
103 static GtkDialog *password_dialog = NULL;
104 static GQueue message_queue = G_QUEUE_INIT;
106 static gint ep_online_state = TRUE;
109 ep_keyring_uri_new (const gchar *string,
114 uri = e_uri_new (string);
115 g_return_val_if_fail (uri != NULL, NULL);
117 /* LDAP URIs do not have usernames, so use the URI as the username. */
118 if (uri->user == NULL && uri->protocol != NULL &&
119 (strcmp (uri->protocol, "ldap") == 0|| strcmp (uri->protocol, "google") == 0))
120 uri->user = g_strdelimit (g_strdup (string), "/=", '_');
122 /* Make sure the URI has the required components. */
123 if (uri->user == NULL && uri->host == NULL) {
124 g_set_error_literal (
126 G_IO_ERROR_INVALID_ARGUMENT,
127 _("Keyring key is unusable: no user or host name"));
136 ep_idle_dispatch (gpointer data)
140 /* As soon as a password window is up we stop; it will
141 * re - invoke us when it has been closed down */
143 while (password_dialog == NULL && (msg = g_queue_pop_head (&message_queue)) != NULL) {
144 G_UNLOCK (passwords);
152 G_UNLOCK (passwords);
158 ep_msg_new (void (*dispatch) (EPassMsg *))
164 msg = g_malloc0 (sizeof (*msg));
165 msg->dispatch = dispatch;
166 msg->done = e_flag_new ();
167 msg->ismain = (g_thread_self () == main_thread);
173 ep_msg_free (EPassMsg *msg)
175 /* XXX We really should be passing this back to the caller, but
176 * doing so will require breaking the password API. */
177 if (msg->error != NULL) {
178 g_warning ("%s", msg->error->message);
179 g_error_free (msg->error);
182 e_flag_free (msg->done);
183 g_free (msg->password);
188 ep_msg_send (EPassMsg *msg)
193 g_queue_push_tail (&message_queue, msg);
196 idle_id = g_idle_add (ep_idle_dispatch, NULL);
200 G_UNLOCK (passwords);
204 ep_idle_dispatch (NULL);
205 while (!e_flag_is_set (msg->done))
206 g_main_context_iteration (NULL, TRUE);
208 e_flag_wait (msg->done);
211 /* the functions that actually do the work */
214 ep_clear_passwords (EPassMsg *msg)
216 GError *error = NULL;
218 /* Find all Evolution passwords and delete them. */
219 secret_password_clear_sync (
220 &e_passwords_schema, NULL, &error,
221 "application", "Evolution", NULL);
224 g_propagate_error (&msg->error, error);
227 e_flag_set (msg->done);
231 ep_remember_password (EPassMsg *msg)
235 GError *error = NULL;
237 password = g_hash_table_lookup (password_cache, msg->key);
238 if (password == NULL) {
239 g_warning ("Password for key \"%s\" not found", msg->key);
243 uri = ep_keyring_uri_new (msg->key, &msg->error);
247 secret_password_store_sync (
249 SECRET_COLLECTION_DEFAULT,
252 "application", "Evolution",
255 "protocol", uri->protocol,
258 /* Only remove the password from the session hash
259 * if the keyring insertion was successful. */
261 g_hash_table_remove (password_cache, msg->key);
263 g_propagate_error (&msg->error, error);
269 e_flag_set (msg->done);
273 ep_forget_password (EPassMsg *msg)
276 GError *error = NULL;
278 g_hash_table_remove (password_cache, msg->key);
280 uri = ep_keyring_uri_new (msg->key, &msg->error);
284 /* Find all Evolution passwords matching the URI and delete them.
286 * XXX We didn't always store protocols in the keyring, so for
287 * backward-compatibility we need to lookup passwords by user
288 * and host only (no protocol). But we do send the protocol
289 * to ep_keyring_delete_passwords(), which also knows about
290 * the backward-compatibility issue and will filter the list
292 secret_password_clear_sync (
293 &e_passwords_schema, NULL, &error,
294 "application", "Evolution",
300 g_propagate_error (&msg->error, error);
306 e_flag_set (msg->done);
310 ep_get_password (EPassMsg *msg)
314 GError *error = NULL;
316 /* Check the in-memory cache first. */
317 password = g_hash_table_lookup (password_cache, msg->key);
318 if (password != NULL) {
319 msg->password = g_strdup (password);
323 uri = ep_keyring_uri_new (msg->key, &msg->error);
327 msg->password = secret_password_lookup_sync (
328 &e_passwords_schema, NULL, &error,
329 "application", "Evolution",
332 "protocol", uri->protocol,
335 if (msg->password != NULL)
338 /* Clear the previous error, if there was one.
339 * It's likely to occur again. */
341 g_clear_error (&error);
343 /* XXX We didn't always store protocols in the keyring, so for
344 * backward-compatibility we also need to lookup passwords
345 * by user and host only (no protocol). */
346 msg->password = secret_password_lookup_sync (
347 &e_passwords_schema, NULL, &error,
348 "application", "Evolution",
355 g_propagate_error (&msg->error, error);
361 e_flag_set (msg->done);
365 ep_add_password (EPassMsg *msg)
367 g_hash_table_insert (
368 password_cache, g_strdup (msg->key),
369 g_strdup (msg->oldpass));
372 e_flag_set (msg->done);
375 static void ep_ask_password (EPassMsg *msg);
378 pass_response (GtkDialog *dialog,
382 EPassMsg *msg = data;
383 gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
384 GList *iter, *trash = NULL;
386 if (response == GTK_RESPONSE_OK) {
387 msg->password = g_strdup (gtk_entry_get_text ((GtkEntry *) msg->entry));
389 if (type != E_PASSWORDS_REMEMBER_NEVER) {
390 gint noreply = msg->noreply;
392 *msg->remember = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (msg->check));
396 if (*msg->remember || type == E_PASSWORDS_REMEMBER_FOREVER) {
397 msg->oldpass = msg->password;
398 ep_add_password (msg);
400 if (*msg->remember && type == E_PASSWORDS_REMEMBER_FOREVER)
401 ep_remember_password (msg);
403 msg->noreply = noreply;
407 gtk_widget_destroy ((GtkWidget *) dialog);
408 password_dialog = NULL;
410 /* ok, here things get interesting, we suck up any pending
411 * operations on this specific password, and return the same
412 * result or ignore other operations */
415 for (iter = g_queue_peek_head_link (&message_queue); iter != NULL; iter = iter->next) {
416 EPassMsg *pending = iter->data;
418 if ((pending->dispatch == ep_forget_password
419 || pending->dispatch == ep_get_password
420 || pending->dispatch == ep_ask_password)
421 && strcmp (pending->key, msg->key) == 0) {
423 /* Satisfy the pending operation. */
424 pending->password = g_strdup (msg->password);
425 e_flag_set (pending->done);
427 /* Mark the queue node for deletion. */
428 trash = g_list_prepend (trash, iter);
432 /* Expunge the message queue. */
433 for (iter = trash; iter != NULL; iter = iter->next)
434 g_queue_delete_link (&message_queue, iter->data);
437 G_UNLOCK (passwords);
440 e_flag_set (msg->done);
442 ep_idle_dispatch (NULL);
446 update_capslock_state (GtkDialog *dialog,
450 GdkModifierType mask = 0;
452 gchar *markup = NULL;
453 GdkDeviceManager *device_manager;
456 device_manager = gdk_display_get_device_manager (gtk_widget_get_display (label));
457 device = gdk_device_manager_get_client_pointer (device_manager);
458 window = gtk_widget_get_window (GTK_WIDGET (dialog));
459 gdk_window_get_device_position (window, device, NULL, NULL, &mask);
461 /* The space acts as a vertical placeholder. */
462 markup = g_markup_printf_escaped (
463 "<small>%s</small>", (mask & GDK_LOCK_MASK) ?
464 _("You have the Caps Lock key on.") : " ");
465 gtk_label_set_markup (GTK_LABEL (label), markup);
472 ep_ask_password (EPassMsg *msg)
475 GtkWidget *container;
476 GtkWidget *action_area;
477 GtkWidget *content_area;
478 gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
479 guint noreply = msg->noreply;
485 widget = gtk_dialog_new_with_buttons (
486 msg->title, msg->parent, 0,
487 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
488 GTK_STOCK_OK, GTK_RESPONSE_OK,
490 gtk_dialog_set_default_response (
491 GTK_DIALOG (widget), GTK_RESPONSE_OK);
492 gtk_window_set_resizable (GTK_WINDOW (widget), FALSE);
493 gtk_window_set_transient_for (GTK_WINDOW (widget), msg->parent);
494 gtk_window_set_position (GTK_WINDOW (widget), GTK_WIN_POS_CENTER_ON_PARENT);
495 gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
496 password_dialog = GTK_DIALOG (widget);
498 action_area = gtk_dialog_get_action_area (password_dialog);
499 content_area = gtk_dialog_get_content_area (password_dialog);
501 /* Override GtkDialog defaults */
502 gtk_box_set_spacing (GTK_BOX (action_area), 12);
503 gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
504 gtk_box_set_spacing (GTK_BOX (content_area), 12);
505 gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
508 container = gtk_grid_new ();
509 gtk_grid_set_column_spacing (GTK_GRID (container), 12);
510 gtk_grid_set_row_spacing (GTK_GRID (container), 6);
511 gtk_widget_show (container);
514 GTK_BOX (content_area), container, FALSE, TRUE, 0);
517 widget = gtk_image_new_from_icon_name (
518 "dialog-password", GTK_ICON_SIZE_DIALOG);
519 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
520 g_object_set (G_OBJECT (widget),
521 "halign", GTK_ALIGN_FILL,
523 "valign", GTK_ALIGN_FILL,
525 gtk_widget_show (widget);
527 gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 3);
530 widget = gtk_label_new (NULL);
531 gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
532 gtk_label_set_markup (GTK_LABEL (widget), msg->prompt);
533 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
534 g_object_set (G_OBJECT (widget),
536 "halign", GTK_ALIGN_FILL,
538 gtk_widget_show (widget);
540 gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
543 widget = gtk_entry_new ();
544 a11y = gtk_widget_get_accessible (widget);
545 visible = !(msg->flags & E_PASSWORDS_SECRET);
546 atk_object_set_description (a11y, msg->prompt);
547 gtk_entry_set_visibility (GTK_ENTRY (widget), visible);
548 gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
549 gtk_widget_grab_focus (widget);
550 g_object_set (G_OBJECT (widget),
552 "halign", GTK_ALIGN_FILL,
554 gtk_widget_show (widget);
557 if ((msg->flags & E_PASSWORDS_REPROMPT)) {
558 ep_get_password (msg);
559 if (msg->password != NULL) {
560 gtk_entry_set_text (GTK_ENTRY (widget), msg->password);
561 g_free (msg->password);
562 msg->password = NULL;
566 gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
568 /* Caps Lock Label */
569 widget = gtk_label_new (NULL);
570 g_object_set (G_OBJECT (widget),
572 "halign", GTK_ALIGN_FILL,
574 gtk_widget_show (widget);
576 gtk_grid_attach (GTK_GRID (container), widget, 1, 2, 1, 1);
579 password_dialog, "key-release-event",
580 G_CALLBACK (update_capslock_state), widget);
582 password_dialog, "focus-in-event",
583 G_CALLBACK (update_capslock_state), widget);
585 /* static password, shouldn't be remembered between sessions,
586 * but will be remembered within the session beyond our control */
587 if (type != E_PASSWORDS_REMEMBER_NEVER) {
588 if (msg->flags & E_PASSWORDS_PASSPHRASE) {
589 widget = gtk_check_button_new_with_mnemonic (
590 (type == E_PASSWORDS_REMEMBER_FOREVER)
591 ? _("_Remember this passphrase")
592 : _("_Remember this passphrase for"
593 " the remainder of this session"));
595 widget = gtk_check_button_new_with_mnemonic (
596 (type == E_PASSWORDS_REMEMBER_FOREVER)
597 ? _("_Remember this password")
598 : _("_Remember this password for"
599 " the remainder of this session"));
602 gtk_toggle_button_set_active (
603 GTK_TOGGLE_BUTTON (widget), *msg->remember);
604 if (msg->flags & E_PASSWORDS_DISABLE_REMEMBER)
605 gtk_widget_set_sensitive (widget, FALSE);
606 g_object_set (G_OBJECT (widget),
608 "halign", GTK_ALIGN_FILL,
609 "valign", GTK_ALIGN_FILL,
611 gtk_widget_show (widget);
614 gtk_grid_attach (GTK_GRID (container), widget, 1, 3, 1, 1);
617 msg->noreply = noreply;
620 password_dialog, "response",
621 G_CALLBACK (pass_response), msg);
624 gtk_dialog_run (GTK_DIALOG (password_dialog));
626 gtk_window_present (GTK_WINDOW (password_dialog));
627 /* workaround GTK+ bug (see Gnome's bugzilla bug #624229) */
628 gtk_grab_add (GTK_WIDGET (password_dialog));
635 * Initializes the e_passwords routines. Must be called before any other
636 * e_passwords_* function.
639 e_passwords_init (void)
643 if (password_cache == NULL) {
644 password_cache = g_hash_table_new_full (
645 g_str_hash, g_str_equal,
646 (GDestroyNotify) g_free,
647 (GDestroyNotify) g_free);
648 main_thread = g_thread_self ();
651 G_UNLOCK (passwords);
655 * e_passwords_cancel:
657 * Cancel any outstanding password operations and close any dialogues
658 * currently being shown.
661 e_passwords_cancel (void)
666 while ((msg = g_queue_pop_head (&message_queue)) != NULL)
667 e_flag_set (msg->done);
668 G_UNLOCK (passwords);
671 gtk_dialog_response (password_dialog, GTK_RESPONSE_CANCEL);
675 * e_passwords_shutdown:
677 * Cleanup routine to call before exiting.
680 e_passwords_shutdown (void)
686 while ((msg = g_queue_pop_head (&message_queue)) != NULL)
687 e_flag_set (msg->done);
689 if (password_cache != NULL) {
690 g_hash_table_destroy (password_cache);
691 password_cache = NULL;
694 G_UNLOCK (passwords);
696 if (password_dialog != NULL)
697 gtk_dialog_response (password_dialog, GTK_RESPONSE_CANCEL);
701 * e_passwords_set_online:
704 * Set the offline-state of the application. This is a work-around
705 * for having the backends fully offline aware, and returns a
706 * cancellation response instead of prompting for passwords.
708 * FIXME: This is not a permanent api, review post 2.0.
711 e_passwords_set_online (gint state)
713 ep_online_state = state;
714 /* TODO: we could check that a request is open and close it, or maybe who cares */
718 * e_passwords_forget_passwords:
720 * Forgets all cached passwords, in memory and on disk.
723 e_passwords_forget_passwords (void)
725 EPassMsg *msg = ep_msg_new (ep_clear_passwords);
732 * e_passwords_clear_passwords:
734 * Forgets all disk cached passwords for the component.
737 e_passwords_clear_passwords (const gchar *unused)
739 EPassMsg *msg = ep_msg_new (ep_clear_passwords);
746 * e_passwords_remember_password:
749 * Saves the password associated with @key to disk.
752 e_passwords_remember_password (const gchar *unused,
757 g_return_if_fail (key != NULL);
759 msg = ep_msg_new (ep_remember_password);
767 * e_passwords_forget_password:
770 * Forgets the password associated with @key, in memory and on disk.
773 e_passwords_forget_password (const gchar *unused,
778 g_return_if_fail (key != NULL);
780 msg = ep_msg_new (ep_forget_password);
788 * e_passwords_get_password:
791 * Returns: the password associated with @key, or %NULL. Caller
792 * must free the returned password.
795 e_passwords_get_password (const gchar *unused,
801 g_return_val_if_fail (key != NULL, NULL);
803 msg = ep_msg_new (ep_get_password);
808 passwd = msg->password;
809 msg->password = NULL;
816 * e_passwords_add_password:
818 * @passwd: the password for @key
820 * This stores the @key/@passwd pair in the current session's password
824 e_passwords_add_password (const gchar *key,
829 g_return_if_fail (key != NULL);
830 g_return_if_fail (passwd != NULL);
832 msg = ep_msg_new (ep_add_password);
834 msg->oldpass = passwd;
841 * e_passwords_ask_password:
842 * @title: title for the password dialog
843 * @unused: this argument is no longer used
844 * @key: key to store the password under
845 * @prompt: prompt string
846 * @remember_type: whether or not to offer to remember the password,
848 * @remember: on input, the default state of the remember checkbox.
849 * on output, the state of the checkbox when the dialog was closed.
850 * @parent: parent window of the dialog, or %NULL
852 * Asks the user for a password.
854 * Returns: the password, which the caller must free, or %NULL if
855 * the user cancelled the operation. *@remember will be set if the
856 * return value is non-%NULL and @remember_type is not
857 * E_PASSWORDS_DO_NOT_REMEMBER.
860 e_passwords_ask_password (const gchar *title,
864 EPasswordsRememberType remember_type,
871 g_return_val_if_fail (key != NULL, NULL);
873 if ((remember_type & E_PASSWORDS_ONLINE) && !ep_online_state)
876 msg = ep_msg_new (ep_ask_password);
879 msg->prompt = prompt;
880 msg->flags = remember_type;
881 msg->remember = remember;
882 msg->parent = parent;
885 passwd = msg->password;
886 msg->password = NULL;