1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * e-passwords.c
5 *
6 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7 */
8
9 /*
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 * for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, see <http://www.gnu.org/licenses/>.
21 */
22
23 /*
24 * This looks a lot more complicated than it is, and than you'd think
25 * it would need to be. There is however, method to the madness.
26 *
27 * The code must cope with being called from any thread at any time,
28 * recursively from the main thread, and then serialising every
29 * request so that sane and correct values are always returned, and
30 * duplicate requests are never made.
31 *
32 * To this end, every call is marshalled and queued and a dispatch
33 * method invoked until that request is satisfied. If mainloop
34 * recursion occurs, then the sub-call will necessarily return out of
35 * order, but will not be processed out of order.
36 */
37
38 #include "evolution-config.h"
39
40 #include <limits.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <gtk/gtk.h>
44 #include <glib/gi18n-lib.h>
45
46 #include <libsoup/soup.h>
47
48 /* XXX Yeah, yeah... */
49 #define SECRET_API_SUBJECT_TO_CHANGE
50
51 #include <libsecret/secret.h>
52
53 #include <libedataserver/libedataserver.h>
54
55 #include "e-passwords.h"
56
57 #define d(x)
58
59 typedef struct _EPassMsg EPassMsg;
60
61 struct _EPassMsg {
62 void (*dispatch) (EPassMsg *);
63 EFlag *done;
64
65 /* input */
66 GtkWindow *parent;
67 const gchar *key;
68 const gchar *title;
69 const gchar *prompt;
70 const gchar *oldpass;
71 guint32 flags;
72
73 /* output */
74 gboolean *remember;
75 gchar *password;
76 GError *error;
77
78 /* work variables */
79 GtkWidget *entry;
80 GtkWidget *check;
81 guint ismain : 1;
82 guint noreply:1; /* supress replies; when calling
83 * dispatch functions from others */
84 };
85
86 /* XXX probably want to share this with evalution-source-registry-migrate-sources.c */
87 static const SecretSchema e_passwords_schema = {
88 "org.gnome.Evolution.Password",
89 SECRET_SCHEMA_DONT_MATCH_NAME,
90 {
91 { "application", SECRET_SCHEMA_ATTRIBUTE_STRING, },
92 { "user", SECRET_SCHEMA_ATTRIBUTE_STRING, },
93 { "server", SECRET_SCHEMA_ATTRIBUTE_STRING, },
94 { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING, },
95 }
96 };
97
98 G_LOCK_DEFINE_STATIC (passwords);
99 static GThread *main_thread = NULL;
100 static GHashTable *password_cache = NULL;
101 static GtkDialog *password_dialog = NULL;
102 static GQueue message_queue = G_QUEUE_INIT;
103 static gint idle_id;
104 static gint ep_online_state = TRUE;
105
106 static SoupURI *
ep_keyring_uri_new(const gchar * string,GError ** error)107 ep_keyring_uri_new (const gchar *string,
108 GError **error)
109 {
110 SoupURI *uri;
111
112 uri = soup_uri_new (string);
113 g_return_val_if_fail (uri != NULL, NULL);
114
115 /* LDAP URIs do not have usernames, so use the URI as the username. */
116 if (uri->user == NULL && uri->scheme != NULL &&
117 (strcmp (uri->scheme, "ldap") == 0|| strcmp (uri->scheme, "google") == 0))
118 uri->user = g_strdelimit (g_strdup (string), "/=", '_');
119
120 /* Make sure the URI has the required components. */
121 if (uri->user == NULL && uri->host == NULL) {
122 g_set_error_literal (
123 error, G_IO_ERROR,
124 G_IO_ERROR_INVALID_ARGUMENT,
125 _("Keyring key is unusable: no user or host name"));
126 soup_uri_free (uri);
127 uri = NULL;
128 }
129
130 return uri;
131 }
132
133 static gboolean
ep_idle_dispatch(gpointer data)134 ep_idle_dispatch (gpointer data)
135 {
136 EPassMsg *msg;
137
138 /* As soon as a password window is up we stop; it will
139 * re - invoke us when it has been closed down */
140 G_LOCK (passwords);
141 while (password_dialog == NULL && (msg = g_queue_pop_head (&message_queue)) != NULL) {
142 G_UNLOCK (passwords);
143
144 msg->dispatch (msg);
145
146 G_LOCK (passwords);
147 }
148
149 idle_id = 0;
150 G_UNLOCK (passwords);
151
152 return FALSE;
153 }
154
155 static EPassMsg *
ep_msg_new(void (* dispatch)(EPassMsg *))156 ep_msg_new (void (*dispatch) (EPassMsg *))
157 {
158 EPassMsg *msg;
159
160 e_passwords_init ();
161
162 msg = g_malloc0 (sizeof (*msg));
163 msg->dispatch = dispatch;
164 msg->done = e_flag_new ();
165 msg->ismain = (g_thread_self () == main_thread);
166
167 return msg;
168 }
169
170 static void
ep_msg_free(EPassMsg * msg)171 ep_msg_free (EPassMsg *msg)
172 {
173 /* XXX We really should be passing this back to the caller, but
174 * doing so will require breaking the password API. */
175 if (msg->error != NULL) {
176 g_warning ("%s", msg->error->message);
177 g_error_free (msg->error);
178 }
179
180 e_flag_free (msg->done);
181 g_free (msg->password);
182 g_free (msg);
183 }
184
185 static void
ep_msg_send(EPassMsg * msg)186 ep_msg_send (EPassMsg *msg)
187 {
188 gint needidle = 0;
189
190 G_LOCK (passwords);
191 g_queue_push_tail (&message_queue, msg);
192 if (!idle_id) {
193 if (!msg->ismain)
194 idle_id = g_idle_add (ep_idle_dispatch, NULL);
195 else
196 needidle = 1;
197 }
198 G_UNLOCK (passwords);
199
200 if (msg->ismain) {
201 if (needidle)
202 ep_idle_dispatch (NULL);
203 while (!e_flag_is_set (msg->done))
204 g_main_context_iteration (NULL, TRUE);
205 } else
206 e_flag_wait (msg->done);
207 }
208
209 /* the functions that actually do the work */
210
211 static void
ep_remember_password(EPassMsg * msg)212 ep_remember_password (EPassMsg *msg)
213 {
214 gchar *password;
215 SoupURI *uri;
216 GError *error = NULL;
217
218 password = g_hash_table_lookup (password_cache, msg->key);
219 if (password == NULL) {
220 g_warning ("Password for key \"%s\" not found", msg->key);
221 goto exit;
222 }
223
224 uri = ep_keyring_uri_new (msg->key, &msg->error);
225 if (uri == NULL)
226 goto exit;
227
228 secret_password_store_sync (
229 &e_passwords_schema,
230 SECRET_COLLECTION_DEFAULT,
231 msg->key, password,
232 NULL, &error,
233 "application", "Evolution",
234 "user", uri->user,
235 "server", uri->host,
236 "protocol", uri->scheme,
237 NULL);
238
239 /* Only remove the password from the session hash
240 * if the keyring insertion was successful. */
241 if (error == NULL)
242 g_hash_table_remove (password_cache, msg->key);
243 else
244 g_propagate_error (&msg->error, error);
245
246 soup_uri_free (uri);
247
248 exit:
249 if (!msg->noreply)
250 e_flag_set (msg->done);
251 }
252
253 static void
ep_forget_password(EPassMsg * msg)254 ep_forget_password (EPassMsg *msg)
255 {
256 SoupURI *uri;
257 GError *error = NULL;
258
259 g_hash_table_remove (password_cache, msg->key);
260
261 uri = ep_keyring_uri_new (msg->key, &msg->error);
262 if (uri == NULL)
263 goto exit;
264
265 /* Find all Evolution passwords matching the URI and delete them.
266 *
267 * XXX We didn't always store protocols in the keyring, so for
268 * backward-compatibility we need to lookup passwords by user
269 * and host only (no protocol). But we do send the protocol
270 * to ep_keyring_delete_passwords(), which also knows about
271 * the backward-compatibility issue and will filter the list
272 * appropriately. */
273 secret_password_clear_sync (
274 &e_passwords_schema, NULL, &error,
275 "application", "Evolution",
276 "user", uri->user,
277 "server", uri->host,
278 NULL);
279
280 if (error != NULL)
281 g_propagate_error (&msg->error, error);
282
283 soup_uri_free (uri);
284
285 exit:
286 if (!msg->noreply)
287 e_flag_set (msg->done);
288 }
289
290 static void
ep_get_password(EPassMsg * msg)291 ep_get_password (EPassMsg *msg)
292 {
293 SoupURI *uri;
294 gchar *password;
295 GError *error = NULL;
296
297 /* Check the in-memory cache first. */
298 password = g_hash_table_lookup (password_cache, msg->key);
299 if (password != NULL) {
300 msg->password = g_strdup (password);
301 goto exit;
302 }
303
304 uri = ep_keyring_uri_new (msg->key, &msg->error);
305 if (uri == NULL)
306 goto exit;
307
308 msg->password = secret_password_lookup_sync (
309 &e_passwords_schema, NULL, &error,
310 "application", "Evolution",
311 "user", uri->user,
312 "server", uri->host,
313 "protocol", uri->scheme,
314 NULL);
315
316 if (msg->password != NULL)
317 goto done;
318
319 /* Clear the previous error, if there was one.
320 * It's likely to occur again. */
321 if (error != NULL)
322 g_clear_error (&error);
323
324 /* XXX We didn't always store protocols in the keyring, so for
325 * backward-compatibility we also need to lookup passwords
326 * by user and host only (no protocol). */
327 msg->password = secret_password_lookup_sync (
328 &e_passwords_schema, NULL, &error,
329 "application", "Evolution",
330 "user", uri->user,
331 "server", uri->host,
332 NULL);
333
334 done:
335 if (error != NULL)
336 g_propagate_error (&msg->error, error);
337
338 soup_uri_free (uri);
339
340 exit:
341 if (!msg->noreply)
342 e_flag_set (msg->done);
343 }
344
345 static void
ep_add_password(EPassMsg * msg)346 ep_add_password (EPassMsg *msg)
347 {
348 g_hash_table_insert (
349 password_cache, g_strdup (msg->key),
350 g_strdup (msg->oldpass));
351
352 if (!msg->noreply)
353 e_flag_set (msg->done);
354 }
355
356 static void ep_ask_password (EPassMsg *msg);
357
358 static void
pass_response(GtkDialog * dialog,gint response,gpointer data)359 pass_response (GtkDialog *dialog,
360 gint response,
361 gpointer data)
362 {
363 EPassMsg *msg = data;
364 gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
365 GList *iter, *trash = NULL;
366
367 if (response == GTK_RESPONSE_OK) {
368 msg->password = g_strdup (gtk_entry_get_text ((GtkEntry *) msg->entry));
369
370 if (type != E_PASSWORDS_REMEMBER_NEVER) {
371 gint noreply = msg->noreply;
372
373 *msg->remember = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (msg->check));
374
375 msg->noreply = 1;
376
377 if (*msg->remember || type == E_PASSWORDS_REMEMBER_FOREVER) {
378 msg->oldpass = msg->password;
379 ep_add_password (msg);
380 }
381 if (*msg->remember && type == E_PASSWORDS_REMEMBER_FOREVER)
382 ep_remember_password (msg);
383
384 msg->noreply = noreply;
385 }
386 }
387
388 gtk_widget_destroy ((GtkWidget *) dialog);
389 password_dialog = NULL;
390
391 /* ok, here things get interesting, we suck up any pending
392 * operations on this specific password, and return the same
393 * result or ignore other operations */
394
395 G_LOCK (passwords);
396 for (iter = g_queue_peek_head_link (&message_queue); iter != NULL; iter = iter->next) {
397 EPassMsg *pending = iter->data;
398
399 if ((pending->dispatch == ep_forget_password
400 || pending->dispatch == ep_get_password
401 || pending->dispatch == ep_ask_password)
402 && strcmp (pending->key, msg->key) == 0) {
403
404 /* Satisfy the pending operation. */
405 pending->password = g_strdup (msg->password);
406 e_flag_set (pending->done);
407
408 /* Mark the queue node for deletion. */
409 trash = g_list_prepend (trash, iter);
410 }
411 }
412
413 /* Expunge the message queue. */
414 for (iter = trash; iter != NULL; iter = iter->next)
415 g_queue_delete_link (&message_queue, iter->data);
416 g_list_free (trash);
417
418 G_UNLOCK (passwords);
419
420 if (!msg->noreply)
421 e_flag_set (msg->done);
422
423 ep_idle_dispatch (NULL);
424 }
425
426 static gboolean
update_capslock_state(GtkDialog * dialog,GdkEvent * event,GtkWidget * label)427 update_capslock_state (GtkDialog *dialog,
428 GdkEvent *event,
429 GtkWidget *label)
430 {
431 GdkModifierType mask = 0;
432 GdkWindow *window;
433 gchar *markup = NULL;
434 GdkDeviceManager *device_manager;
435 GdkDevice *device;
436
437 device_manager = gdk_display_get_device_manager (gtk_widget_get_display (label));
438 device = gdk_device_manager_get_client_pointer (device_manager);
439 window = gtk_widget_get_window (GTK_WIDGET (dialog));
440 gdk_window_get_device_position (window, device, NULL, NULL, &mask);
441
442 /* The space acts as a vertical placeholder. */
443 markup = g_markup_printf_escaped (
444 "<small>%s</small>", (mask & GDK_LOCK_MASK) ?
445 _("You have the Caps Lock key on.") : " ");
446 gtk_label_set_markup (GTK_LABEL (label), markup);
447 g_free (markup);
448
449 return FALSE;
450 }
451
452 static void
ep_ask_password(EPassMsg * msg)453 ep_ask_password (EPassMsg *msg)
454 {
455 GtkWidget *widget;
456 GtkWidget *container;
457 GtkWidget *action_area;
458 GtkWidget *content_area;
459 gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
460 guint noreply = msg->noreply;
461 gboolean visible;
462 AtkObject *a11y;
463
464 msg->noreply = 1;
465
466 widget = gtk_dialog_new_with_buttons (
467 msg->title, msg->parent, 0,
468 _("_Cancel"), GTK_RESPONSE_CANCEL,
469 _("_OK"), GTK_RESPONSE_OK,
470 NULL);
471 gtk_dialog_set_default_response (
472 GTK_DIALOG (widget), GTK_RESPONSE_OK);
473 gtk_window_set_resizable (GTK_WINDOW (widget), FALSE);
474 gtk_window_set_transient_for (GTK_WINDOW (widget), msg->parent);
475 gtk_window_set_position (GTK_WINDOW (widget), GTK_WIN_POS_CENTER_ON_PARENT);
476 gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
477 password_dialog = GTK_DIALOG (widget);
478
479 action_area = gtk_dialog_get_action_area (password_dialog);
480 content_area = gtk_dialog_get_content_area (password_dialog);
481
482 /* Override GtkDialog defaults */
483 gtk_box_set_spacing (GTK_BOX (action_area), 12);
484 gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
485 gtk_box_set_spacing (GTK_BOX (content_area), 12);
486 gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
487
488 /* Grid */
489 container = gtk_grid_new ();
490 gtk_grid_set_column_spacing (GTK_GRID (container), 12);
491 gtk_grid_set_row_spacing (GTK_GRID (container), 6);
492 gtk_widget_show (container);
493
494 gtk_box_pack_start (
495 GTK_BOX (content_area), container, FALSE, TRUE, 0);
496
497 /* Password Image */
498 widget = gtk_image_new_from_icon_name (
499 "dialog-password", GTK_ICON_SIZE_DIALOG);
500 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
501 g_object_set (
502 G_OBJECT (widget),
503 "halign", GTK_ALIGN_FILL,
504 "vexpand", TRUE,
505 "valign", GTK_ALIGN_FILL,
506 NULL);
507 gtk_widget_show (widget);
508
509 gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 3);
510
511 /* Password Label */
512 widget = gtk_label_new (NULL);
513 gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
514 gtk_label_set_width_chars (GTK_LABEL (widget), 20);
515 gtk_label_set_markup (GTK_LABEL (widget), msg->prompt);
516 gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
517 g_object_set (
518 G_OBJECT (widget),
519 "hexpand", TRUE,
520 "halign", GTK_ALIGN_FILL,
521 NULL);
522 gtk_widget_show (widget);
523
524 gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
525
526 /* Password Entry */
527 widget = gtk_entry_new ();
528 a11y = gtk_widget_get_accessible (widget);
529 visible = !(msg->flags & E_PASSWORDS_SECRET);
530 atk_object_set_description (a11y, msg->prompt);
531 gtk_entry_set_visibility (GTK_ENTRY (widget), visible);
532 gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
533 gtk_widget_grab_focus (widget);
534 g_object_set (
535 G_OBJECT (widget),
536 "hexpand", TRUE,
537 "halign", GTK_ALIGN_FILL,
538 NULL);
539 gtk_widget_show (widget);
540 msg->entry = widget;
541
542 if ((msg->flags & E_PASSWORDS_REPROMPT)) {
543 ep_get_password (msg);
544 if (msg->password != NULL) {
545 gtk_entry_set_text (GTK_ENTRY (widget), msg->password);
546 g_free (msg->password);
547 msg->password = NULL;
548 }
549 }
550
551 gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
552
553 /* Caps Lock Label */
554 widget = gtk_label_new (NULL);
555 g_object_set (
556 G_OBJECT (widget),
557 "hexpand", TRUE,
558 "halign", GTK_ALIGN_FILL,
559 NULL);
560 gtk_widget_show (widget);
561
562 gtk_grid_attach (GTK_GRID (container), widget, 1, 2, 1, 1);
563
564 g_signal_connect (
565 password_dialog, "key-release-event",
566 G_CALLBACK (update_capslock_state), widget);
567 g_signal_connect (
568 password_dialog, "focus-in-event",
569 G_CALLBACK (update_capslock_state), widget);
570
571 /* static password, shouldn't be remembered between sessions,
572 * but will be remembered within the session beyond our control */
573 if (type != E_PASSWORDS_REMEMBER_NEVER) {
574 if (msg->flags & E_PASSWORDS_PASSPHRASE) {
575 widget = gtk_check_button_new_with_mnemonic (
576 (type == E_PASSWORDS_REMEMBER_FOREVER)
577 ? _("_Remember this passphrase")
578 : _("_Remember this passphrase for"
579 " the remainder of this session"));
580 } else {
581 widget = gtk_check_button_new_with_mnemonic (
582 (type == E_PASSWORDS_REMEMBER_FOREVER)
583 ? _("_Remember this password")
584 : _("_Remember this password for"
585 " the remainder of this session"));
586 }
587
588 gtk_toggle_button_set_active (
589 GTK_TOGGLE_BUTTON (widget), *msg->remember);
590 if (msg->flags & E_PASSWORDS_DISABLE_REMEMBER)
591 gtk_widget_set_sensitive (widget, FALSE);
592 g_object_set (
593 G_OBJECT (widget),
594 "hexpand", TRUE,
595 "halign", GTK_ALIGN_FILL,
596 "valign", GTK_ALIGN_FILL,
597 NULL);
598 gtk_widget_show (widget);
599 msg->check = widget;
600
601 gtk_grid_attach (GTK_GRID (container), widget, 1, 3, 1, 1);
602 }
603
604 msg->noreply = noreply;
605
606 g_signal_connect (
607 password_dialog, "response",
608 G_CALLBACK (pass_response), msg);
609
610 if (msg->parent) {
611 gtk_dialog_run (GTK_DIALOG (password_dialog));
612 } else {
613 gtk_window_present (GTK_WINDOW (password_dialog));
614 /* workaround GTK+ bug (see Gnome's bugzilla bug #624229) */
615 gtk_grab_add (GTK_WIDGET (password_dialog));
616 }
617 }
618
619 /**
620 * e_passwords_init:
621 *
622 * Initializes the e_passwords routines. Must be called before any other
623 * e_passwords_* function.
624 **/
625 void
e_passwords_init(void)626 e_passwords_init (void)
627 {
628 G_LOCK (passwords);
629
630 if (password_cache == NULL) {
631 password_cache = g_hash_table_new_full (
632 g_str_hash, g_str_equal,
633 (GDestroyNotify) g_free,
634 (GDestroyNotify) g_free);
635 main_thread = g_thread_self ();
636 }
637
638 G_UNLOCK (passwords);
639 }
640
641 /**
642 * e_passwords_set_online:
643 * @state:
644 *
645 * Set the offline-state of the application. This is a work-around
646 * for having the backends fully offline aware, and returns a
647 * cancellation response instead of prompting for passwords.
648 *
649 * FIXME: This is not a permanent api, review post 2.0.
650 **/
651 void
e_passwords_set_online(gint state)652 e_passwords_set_online (gint state)
653 {
654 ep_online_state = state;
655 /* TODO: we could check that a request is open and close it, or maybe who cares */
656 }
657
658 /**
659 * e_passwords_remember_password:
660 * @key: the key
661 *
662 * Saves the password associated with @key to disk.
663 **/
664 void
e_passwords_remember_password(const gchar * key)665 e_passwords_remember_password (const gchar *key)
666 {
667 EPassMsg *msg;
668
669 g_return_if_fail (key != NULL);
670
671 msg = ep_msg_new (ep_remember_password);
672 msg->key = key;
673
674 ep_msg_send (msg);
675 ep_msg_free (msg);
676 }
677
678 /**
679 * e_passwords_forget_password:
680 * @key: the key
681 *
682 * Forgets the password associated with @key, in memory and on disk.
683 **/
684 void
e_passwords_forget_password(const gchar * key)685 e_passwords_forget_password (const gchar *key)
686 {
687 EPassMsg *msg;
688
689 g_return_if_fail (key != NULL);
690
691 msg = ep_msg_new (ep_forget_password);
692 msg->key = key;
693
694 ep_msg_send (msg);
695 ep_msg_free (msg);
696 }
697
698 /**
699 * e_passwords_get_password:
700 * @key: the key
701 *
702 * Returns: the password associated with @key, or %NULL. Caller
703 * must free the returned password.
704 **/
705 gchar *
e_passwords_get_password(const gchar * key)706 e_passwords_get_password (const gchar *key)
707 {
708 EPassMsg *msg;
709 gchar *passwd;
710
711 g_return_val_if_fail (key != NULL, NULL);
712
713 msg = ep_msg_new (ep_get_password);
714 msg->key = key;
715
716 ep_msg_send (msg);
717
718 passwd = msg->password;
719 msg->password = NULL;
720 ep_msg_free (msg);
721
722 return passwd;
723 }
724
725 /**
726 * e_passwords_add_password:
727 * @key: a key
728 * @passwd: the password for @key
729 *
730 * This stores the @key/@passwd pair in the current session's password
731 * hash.
732 **/
733 void
e_passwords_add_password(const gchar * key,const gchar * passwd)734 e_passwords_add_password (const gchar *key,
735 const gchar *passwd)
736 {
737 EPassMsg *msg;
738
739 g_return_if_fail (key != NULL);
740 g_return_if_fail (passwd != NULL);
741
742 msg = ep_msg_new (ep_add_password);
743 msg->key = key;
744 msg->oldpass = passwd;
745
746 ep_msg_send (msg);
747 ep_msg_free (msg);
748 }
749
750 /**
751 * e_passwords_ask_password:
752 * @title: title for the password dialog
753 * @key: key to store the password under
754 * @prompt: prompt string
755 * @remember_type: whether or not to offer to remember the password,
756 * and for how long.
757 * @remember: on input, the default state of the remember checkbox.
758 * on output, the state of the checkbox when the dialog was closed.
759 * @parent: parent window of the dialog, or %NULL
760 *
761 * Asks the user for a password.
762 *
763 * Returns: the password, which the caller must free, or %NULL if
764 * the user cancelled the operation. *@remember will be set if the
765 * return value is non-%NULL and @remember_type is not
766 * E_PASSWORDS_DO_NOT_REMEMBER.
767 **/
768 gchar *
e_passwords_ask_password(const gchar * title,const gchar * key,const gchar * prompt,EPasswordsRememberType remember_type,gboolean * remember,GtkWindow * parent)769 e_passwords_ask_password (const gchar *title,
770 const gchar *key,
771 const gchar *prompt,
772 EPasswordsRememberType remember_type,
773 gboolean *remember,
774 GtkWindow *parent)
775 {
776 gchar *passwd;
777 EPassMsg *msg;
778
779 g_return_val_if_fail (key != NULL, NULL);
780
781 if ((remember_type & E_PASSWORDS_ONLINE) && !ep_online_state)
782 return NULL;
783
784 msg = ep_msg_new (ep_ask_password);
785 msg->title = title;
786 msg->key = key;
787 msg->prompt = prompt;
788 msg->flags = remember_type;
789 msg->remember = remember;
790 msg->parent = parent;
791
792 ep_msg_send (msg);
793 passwd = msg->password;
794 msg->password = NULL;
795 ep_msg_free (msg);
796
797 return passwd;
798 }
799